You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
549 lines
25 KiB
549 lines
25 KiB
#include "MultiComponent.h" |
|
#include <iostream> |
|
|
|
#include "DocumentComponent.h" |
|
#include "TabbedComponent.h" |
|
#include "ButtonComponent.h" |
|
#include "InputComponent.h" |
|
#include "ImageComponent.h" |
|
|
|
#include "../graphical/renderers/glfw/Shader.h" |
|
#include "../../app/browser/browser.h" |
|
#include <ctime> |
|
|
|
extern std::unique_ptr<Browser> browser; |
|
|
|
MultiComponent::MultiComponent(const float rawX, const float rawY, const float rawWidth, const float rawHeight, const int passedWindowWidth, const int passedWindowHeight) { |
|
|
|
// take our space (for parent picking) |
|
x = rawX; |
|
y = rawY; |
|
width = rawWidth; |
|
height = rawHeight; |
|
|
|
// not really needed but nothing else sets this |
|
this->windowWidth = passedWindowWidth; |
|
this->windowHeight = passedWindowHeight; |
|
|
|
this->onResize=[this](int w, int h) { |
|
this->resize(w, h); |
|
}; |
|
|
|
// we need a mouseout to mouseout our hovercomponent |
|
this->onMouseout=[this]() { |
|
if (this->hoverComponent && this->hoverComponent->onMouseout) { |
|
this->hoverComponent->onMouseout(); |
|
} |
|
// select nothing |
|
this->hoverComponent = nullptr; |
|
}; |
|
|
|
this->onMousemove=[this](int passedX, int passedY) { |
|
//std::cout << "MultiComponent::MultiComponent:onMousemove - at " << passedX << "," << passedY << std::endl; |
|
if (this->cursorX == passedX && this->cursorY == passedY) { |
|
return; |
|
} |
|
this->cursorX = passedX; |
|
this->cursorY = passedY; |
|
//std::cout << "MultiComponent::MultiComponent:onMousemove - size " << this->windowWidth << "," << this->windowHeight << std::endl; |
|
this->updateMouse(); |
|
}; |
|
this->onMousedown=[this](int passedX, int passedY) { |
|
//std::cout << "MultiComponent left press" << std::endl; |
|
if (this->hoverComponent) { |
|
if (this->focusedComponent != this->hoverComponent) { |
|
// blur old component |
|
if (this->focusedComponent) { |
|
if (this->focusedComponent->onBlur) { |
|
this->focusedComponent->onBlur(); |
|
} |
|
} |
|
// focus new component |
|
if (this->hoverComponent->onFocus) { |
|
this->hoverComponent->onFocus(); |
|
} |
|
} |
|
this->focusedComponent = this->hoverComponent; |
|
if (this->focusedComponent->onMousedown) { |
|
//std::cout << "click event" << std::endl; |
|
this->focusedComponent->onMousedown(passedX, passedY); |
|
} |
|
} |
|
}; |
|
this->onMouseup=[this](int passedX, int passedY) { |
|
//std::cout << "MultiComponent left release" << std::endl; |
|
if (this->hoverComponent) { |
|
//std::cout << "DocumentComponent::DocumentComponent:onMouseup - hovering over " << typeOfComponent(this->hoverComponent) << " component" << std::endl; |
|
if (this->focusedComponent != this->hoverComponent) { |
|
// blur old component |
|
if (this->focusedComponent) { |
|
if (this->focusedComponent->onBlur) { |
|
this->focusedComponent->onBlur(); |
|
} |
|
} |
|
// focus new component |
|
if (this->hoverComponent->onFocus) { |
|
this->hoverComponent->onFocus(); |
|
} |
|
} |
|
|
|
this->focusedComponent = this->hoverComponent; |
|
if (this->focusedComponent && this->focusedComponent->onMouseup) { |
|
//std::cout << "click event" << std::endl; |
|
//std::cout << "unloaded1 " << this->parent->unloaded << std::endl; |
|
this->focusedComponent->onMouseup(passedX, passedY); |
|
// ok we can't communicate through the component |
|
//std::cout << "unloaded2 " << this->parent->unloaded << std::endl; |
|
// we can through the window global |
|
//std::cout << "window unloaded focus: " << window->focusedComponent << std::endl; |
|
//std::cout << "window unloaded hover: " << window->hoverComponent << std::endl; |
|
//std::cout << "win unloaded focus: " << this->win->focusedComponent << std::endl; |
|
//std::cout << "win unloaded hover: " << this->win->hoverComponent << std::endl; |
|
} |
|
// make sure we weren't unloaded by last click and check for additional events |
|
// FIXME: test, double click link to see if it crashes and how we can prevent that |
|
// this->win->focusedComponent != nullptr && |
|
if (this->focusedComponent->onClick) { |
|
//std::cout << "click event" << std::endl; |
|
this->focusedComponent->onClick(); |
|
} |
|
} |
|
}; |
|
this->onWheel=[this](int passedX, int passedY) { |
|
//std::cout << "MultiComponent::MultiComponent:onWheel " << passedX << "," << passedY << std::endl; |
|
|
|
// if we're hovering over somethign |
|
if (this->hoverComponent) { |
|
// and it receives these messages |
|
if (this->hoverComponent->onWheel) { |
|
// send the event down |
|
this->hoverComponent->onWheel(passedX, passedY); |
|
} |
|
} |
|
//renderDirty = true; |
|
// should we mark win->renderDirty = true? |
|
this->win->renderDirty = true; |
|
}; |
|
this->onKeyUp=[this](int key, int scancode, int action, int mods) { |
|
//std::cout << "MultiComponent::MultiComponent:onKeyup - focus: " << typeOfComponent(this->focusedComponent) << " key: " << key << " scancode: " << scancode << " mods: " << mods << std::endl; |
|
/* |
|
TabbedComponent *tabComponent = dynamic_cast<TabbedComponent*>(this->focusedComponent.get()); |
|
if (tabComponent) { |
|
if (action == 0) { |
|
if (tabComponent->onKeyUp) { |
|
tabComponent->onKeyUp(key, scancode, action, mods); |
|
} |
|
} |
|
return; |
|
} |
|
DocumentComponent *docComponent = dynamic_cast<DocumentComponent*>(this->focusedComponent.get()); |
|
if (docComponent) { |
|
if (action == 0) { |
|
if (docComponent->onKeyUp) { |
|
docComponent->onKeyUp(key, scancode, action, mods); |
|
} |
|
} |
|
return; |
|
} |
|
*/ |
|
|
|
// allow address bar to hijack from relayKeyboardComponent |
|
InputComponent *inputComponent = dynamic_cast<InputComponent*>(this->focusedComponent.get()); |
|
if (inputComponent) { |
|
//std::cout << "inputComponent is focused, key pressed " << key << " action: " <<action << std::endl; |
|
// action 1 is down, 0 is up, 2 is a repeat |
|
if (action == 0 || action == 2) { |
|
// key up |
|
// it's always uppercase... |
|
if (key == 259) { |
|
inputComponent->backSpace(); |
|
} else if (key == 257) { |
|
//std::cout << "MultiComponent enter!" << std::endl; |
|
if (inputComponent->multiLine) { |
|
// harfbuzz or freetype2 (something?) doesn't like \n //focusedInputComponent->value += "\r"; |
|
inputComponent->addChar('\r'); |
|
} else { |
|
//std::cout << "should submit form!" << std::endl; |
|
if (inputComponent->onEnter) { |
|
inputComponent->onEnter(inputComponent->getValue()); |
|
} |
|
return; |
|
} |
|
} else { |
|
if (key < 256) { |
|
if (mods & GLFW_MOD_SHIFT) { |
|
// SHIFT |
|
if (key == GLFW_KEY_SLASH) key='?'; |
|
if (key == GLFW_KEY_APOSTROPHE) key='"'; |
|
if (key == GLFW_KEY_COMMA) key='<'; |
|
if (key == GLFW_KEY_MINUS) key='_'; |
|
if (key == GLFW_KEY_PERIOD) key='>'; |
|
if (key == GLFW_KEY_SEMICOLON) key=':'; |
|
if (key == GLFW_KEY_EQUAL) key='+'; |
|
if (key == GLFW_KEY_LEFT_BRACKET) key='{'; |
|
if (key == GLFW_KEY_BACKSLASH) key='|'; |
|
if (key == GLFW_KEY_RIGHT_BRACKET) key='}'; |
|
if (key == GLFW_KEY_GRAVE_ACCENT) key='~'; |
|
|
|
} else { |
|
// no shift or caplocks |
|
// basically: when SHIFT isn't pressed but key is in A-Z range, add ascii offset to make it lower case |
|
if (key >= 'A' && key <= 'Z') { |
|
key += 'a' - 'A'; |
|
} |
|
} |
|
inputComponent->addChar(key); |
|
} // otherwise I think it's some weird control char |
|
} |
|
} |
|
} |
|
/* |
|
// pass it to the doc if we're hovering over it |
|
docComponent = dynamic_cast<DocumentComponent*>(this->hoverComponent.get()); |
|
if (docComponent) { |
|
if (action == 0) { |
|
if (docComponent->onKeyUp) { |
|
docComponent->onKeyUp(key, scancode, action, mods); |
|
} |
|
} |
|
return; |
|
} |
|
*/ |
|
if (relayKeyboardComponent) { |
|
//std::cout << "relayKeyboardComponent" << std::endl; |
|
// needs to be cast |
|
TabbedComponent *tabComponent = dynamic_cast<TabbedComponent*>(this->relayKeyboardComponent.get()); |
|
if (tabComponent) { |
|
//std::cout << "relayKeyboardComponent - tab" << std::endl; |
|
if (tabComponent->documentComponent && tabComponent->documentComponent->onKeyUp) { |
|
tabComponent->documentComponent->onKeyUp(key, scancode, action, mods); |
|
return; |
|
} |
|
} |
|
DocumentComponent *docComponent = dynamic_cast<DocumentComponent*>(this->relayKeyboardComponent.get()); |
|
if (docComponent) { |
|
//std::cout << "relayKeyboardComponent - doc" << std::endl; |
|
if (docComponent->onKeyUp) { |
|
docComponent->onKeyUp(key, scancode, action, mods); |
|
return; |
|
} |
|
} |
|
if (key == GLFW_KEY_T && action == GLFW_RELEASE) { |
|
browser->NextTheme(); |
|
} |
|
if (relayKeyboardComponent->onKeyUp) { |
|
relayKeyboardComponent->onKeyUp(key, scancode, action, mods); |
|
return; |
|
} |
|
} |
|
// probably should recurse over all components and check for onKeyUp |
|
}; |
|
} |
|
|
|
// update component hover (need to call on component change) |
|
void MultiComponent::updateMouse() { |
|
// do we need to make pX/pY relative to this component? no, we just made the picking system take mouse coordinates |
|
std::shared_ptr<Component> newHover = nullptr; |
|
// iterator backwards here (top layer to back layer) |
|
for(auto layer = this->layers.rbegin(); layer != this->layers.rend(); ++layer) { |
|
std::shared_ptr<Component> found = this->searchComponentTree(*layer, this->cursorX, this->cursorY); |
|
if (found) { |
|
newHover = found; |
|
break; |
|
} |
|
} |
|
if (newHover != this->hoverComponent) { |
|
if (this->hoverComponent && this->hoverComponent->onMouseout) { |
|
this->hoverComponent->onMouseout(); |
|
} |
|
if (newHover && newHover->onMouseover) { |
|
newHover->onMouseover(); |
|
} |
|
this->hoverComponent = newHover; |
|
} |
|
if (this->hoverComponent) { |
|
//std::cout << "MultiComponent::updateMouse - hovering over " << typeOfComponent(this->hoverComponent) << " component" << std::endl; |
|
if (this->hoverComponent->onMousemove) { |
|
// this could communicate the cursor to use |
|
this->hoverComponent->onMousemove(this->cursorX, this->cursorY); |
|
} else { |
|
if (this->hoverComponent->onClick) { |
|
glfwSetCursor(this->win->window, this->win->cursorHand); |
|
} else { |
|
TextComponent *text=dynamic_cast<TextComponent *>(this->hoverComponent.get()); |
|
InputComponent *ab=dynamic_cast<InputComponent *>(this->hoverComponent.get()); |
|
if (text || ab) { |
|
glfwSetCursor(this->win->window, this->win->cursorIbeam); |
|
} else { |
|
glfwSetCursor(this->win->window, this->win->cursorArrow); |
|
} |
|
} |
|
} |
|
} else { |
|
glfwSetCursor(this->win->window, this->win->cursorArrow); |
|
} |
|
} |
|
|
|
void MultiComponent::resize(const int passedWindowWidth, const int passedWindowHeight) { |
|
// can't get this to work |
|
// , my type: " << typeOfComponent(std::make_shared<Component>(this)) |
|
//std::cout << "MultiComponent::resize - relaying out. Name: " << name << std::endl; |
|
windowWidth = passedWindowWidth; |
|
windowHeight = passedWindowHeight; |
|
|
|
const std::clock_t begin = clock(); |
|
// for each layer |
|
for(auto layer: this->layers) { |
|
//Component::printComponentTree(rootComponent, 0); |
|
// update size |
|
layer->windowWidth = passedWindowWidth; |
|
layer->windowHeight = passedWindowHeight; |
|
// relayout it out with new sizes |
|
layer->layout(); |
|
// in the case of Document, we need to re-assess the scroll limits |
|
/* |
|
// recalculate scroll max by calculating how many screens are in the rootComponent's Height |
|
if (transformMatrix[13]>std::max((rootComponent->height)/(windowHeight)*2.0f, 2.0f)) { |
|
transformMatrix[13]=std::max((rootComponent->height)/(windowHeight)*2.0f, 2.0f); |
|
transformMatrixDirty = true; |
|
} |
|
*/ |
|
//Component::printComponentTree(rootComponent, 0); |
|
} |
|
const std::clock_t end = clock(); |
|
std::cout << "MultiComponent::resize - resized layers in: " << std::fixed << ((static_cast<double>(end - begin)) / CLOCKS_PER_SEC) << std::scientific << " seconds" << std::endl; |
|
|
|
//renderDirty = true; |
|
// should we mark win->renderDirty = true? |
|
if (this->win) { |
|
this->win->renderDirty = true; |
|
} |
|
} |
|
|
|
void MultiComponent::render() { |
|
//std::cout << "MultiComponent::render" << std::endl; |
|
//fontShader->bind(); |
|
//renderDocumentComponents(rootComponent); |
|
//fontShader->release(); |
|
GLenum glErr=glGetError(); |
|
if(glErr != GL_NO_ERROR) { |
|
std::cout << "MultiComponent::render - start - not ok: " << glErr << std::endl; |
|
} |
|
|
|
Shader *textureShader = this->win->shaderLoader.getShader(VertexShader("TextureShader.vert"), |
|
FragmentShader("TextureShader.frag")); |
|
Shader *fontShader = this->win->shaderLoader.getShader(VertexShader("FontShader.vert"), |
|
FragmentShader("FontShader.frag")); |
|
GLint transformLocation = textureShader->uniform("transform"); |
|
GLint transformLocation2 = fontShader->uniform("transform"); |
|
|
|
for(auto layer: this->layers) { |
|
|
|
fontShader->bind(); |
|
this->renderComponentType("doc", layer); |
|
|
|
textureShader->bind(); |
|
// reset textureShader scroll |
|
glUniformMatrix4fv(transformLocation, 1, GL_FALSE, this->win->textureTransformMatrix); |
|
glErr=glGetError(); |
|
if(glErr != GL_NO_ERROR) { |
|
std::cout << "MultiComponent::render - glUniformMatrix4fv - not ok: " << glErr << std::endl; |
|
} |
|
//std::cout << "MultiComponent::render - start Box components" << std::endl; |
|
//renderBoxComponents(rootComponent); |
|
this->renderComponentType("image", layer); |
|
// may need to reset transformMatrix for shader |
|
this->renderComponentType("box", layer); |
|
this->renderComponentType("input", layer); |
|
this->renderComponentType("button", layer); |
|
|
|
this->renderComponentType("tab", layer); |
|
textureShader->bind(); |
|
|
|
//std::cout << "MultiComponent::render - end Box components" << std::endl; |
|
//textureShader->release(); // select no shader |
|
|
|
// if we move text earlier, we can't put tab labels on top of the tab |
|
// but we have layers now |
|
fontShader->bind(); |
|
if (!boundToPage) { |
|
glUniformMatrix4fv(transformLocation2, 1, GL_FALSE, win->transformMatrix); |
|
glErr=glGetError(); |
|
if(glErr != GL_NO_ERROR) { |
|
std::cout << "MultiComponent::render - glUniformMatrix4fv not ok: " << glErr << std::endl; |
|
} |
|
} |
|
//std::cout << "MultiComponent::render - start text components" << std::endl; |
|
//renderComponents(rootComponent); |
|
this->renderComponentType("text", layer); |
|
fontShader->release(); // select no shader |
|
//std::cout << "MultiComponent::render - end text components" << std::endl; |
|
} |
|
} |
|
|
|
/* |
|
// draw this component and all it's children |
|
void MultiComponent::renderComponents(std::shared_ptr<Component> component) { |
|
if (!component) { |
|
std::cout << "DocumentComponent::renderComponents - got null passed" << std::endl; |
|
return; |
|
} |
|
//DocumentComponent *docComponent = dynamic_cast<DocumentComponent*>(component.get()); |
|
//if (docComponent) { |
|
//docComponent->render(); |
|
//} |
|
TextComponent *textComponent = dynamic_cast<TextComponent*>(component.get()); |
|
if (textComponent) { |
|
textComponent->render(); |
|
} |
|
// is this needed? |
|
if (component->children.empty()) { |
|
return; |
|
} |
|
for (std::shared_ptr<Component> &child : component->children) { |
|
renderComponents(child); |
|
} |
|
} |
|
|
|
void MultiComponent::renderDocumentComponents(std::shared_ptr<Component> component) { |
|
if (!component) { |
|
std::cout << "MultiComponent::renderBoxComponents - got null passed" << std::endl; |
|
return; |
|
} |
|
//std::cout << "MultiComponent::renderBoxComponents - renderering: " << component->name << std::endl; |
|
// render non-text components too |
|
DocumentComponent *docComponent = dynamic_cast<DocumentComponent*>(component.get()); |
|
if (docComponent) { |
|
docComponent->render(); |
|
} |
|
// is this needed? |
|
if (component->children.empty()) { |
|
return; |
|
} |
|
for (std::shared_ptr<Component> &child : component->children) { |
|
this->renderDocumentComponents(child); |
|
} |
|
} |
|
|
|
void MultiComponent::renderBoxComponents(std::shared_ptr<Component> component) { |
|
if (!component) { |
|
std::cout << "MultiComponent::renderBoxComponents - got null passed" << std::endl; |
|
return; |
|
} |
|
//std::cout << "MultiComponent::renderBoxComponents - renderering: " << component->name << std::endl; |
|
// render non-text components too |
|
BoxComponent *boxComponent = dynamic_cast<BoxComponent*>(component.get()); |
|
if (boxComponent) { |
|
boxComponent->render(); |
|
} |
|
// is this needed? |
|
if (component->children.empty()) { |
|
return; |
|
} |
|
for (std::shared_ptr<Component> &child : component->children) { |
|
this->renderBoxComponents(child); |
|
} |
|
} |
|
*/ |
|
|
|
void MultiComponent::renderComponentType(std::string str, std::shared_ptr<Component> component) { |
|
if (!component) { |
|
std::cout << "MultiComponent::renderComponentType - got null passed" << std::endl; |
|
return; |
|
} |
|
if (typeOfComponent(component) == str) { |
|
// how slow is this? |
|
if (str == "doc") { |
|
DocumentComponent *docComponent = dynamic_cast<DocumentComponent*>(component.get()); |
|
docComponent->render(); |
|
} else if (str =="tab") { |
|
TabbedComponent *pTabComponent = dynamic_cast<TabbedComponent*>(component.get()); |
|
pTabComponent->render(); |
|
} else if (str =="button") { |
|
ButtonComponent *butComponent = dynamic_cast<ButtonComponent*>(component.get()); |
|
butComponent->render(); |
|
} else if (str =="text") { |
|
TextComponent *textComponent = dynamic_cast<TextComponent*>(component.get()); |
|
textComponent->render(); |
|
} else if (str =="input") { |
|
InputComponent *inputComponent = dynamic_cast<InputComponent*>(component.get()); |
|
inputComponent->render(); |
|
} else if (str =="image") { |
|
ImageComponent *imageComponent = dynamic_cast<ImageComponent*>(component.get()); |
|
imageComponent->render(); |
|
} else if (str =="box") { |
|
//AnimeComponent *animeComponent = dynamic_cast<AnimeComponent*>(component.get()); |
|
//if (!animeComponent) { |
|
BoxComponent *boxComponent = dynamic_cast<BoxComponent*>(component.get()); |
|
boxComponent->render(); |
|
//} |
|
} else { |
|
std::cout << "Unknown type " << str << std::endl; |
|
} |
|
//} else { |
|
//std::cout << "type: " << typeOfComponent(component) << "!=" << str << std::endl; |
|
} |
|
// is this needed? |
|
/* |
|
if (component->children.empty()) { |
|
return; |
|
} |
|
*/ |
|
for (std::shared_ptr<Component> &child : component->children) { |
|
this->renderComponentType(str, child); |
|
} |
|
} |
|
|
|
|
|
// used for picking |
|
std::shared_ptr<Component> MultiComponent::searchComponentTree(const std::shared_ptr<Component> &component, const int passedX, const int passedY) { |
|
if (component->children.empty()) { |
|
//std::cout << "MultiComponent::searchComponentTree - [Tabbed mode] component at " << static_cast<int>(component->x) << "," << static_cast<int>(component->y) << " size " << static_cast<int>(component->width) << "," << static_cast<int>(component->height) << std::endl; |
|
// if calling from a extended tabbed control |
|
if (this->tabbed) { |
|
//std::cout << "MultiComponent::searchComponentTree:Tabbed - y search: " << static_cast<int>(component->windowHeight - component->y - component->height) << "<" << static_cast<int>(passedY) << "<" << static_cast<int>(component->windowHeight - component->y) << std::endl; |
|
if (component->windowHeight - component->y - component->height < passedY && component->windowHeight - component->y > passedY) { |
|
//std::cout << "DocumentComponent::searchComponentTree:Tabbed - x search: " << static_cast<int>(component->x) << "<" << static_cast<int>(passedX) << "<" << static_cast<int>(component->x + component->width) << std::endl; |
|
if (component->x < passedX && component->x + component->width > passedX) { |
|
//std::cout << "MultiComponent::searchComponentTree:Tabbed - hit " << typeOfComponent(component) << std::endl; |
|
return component; |
|
} |
|
} |
|
} else if (this->windowed) { |
|
//std::cout << "MultiComponent::searchComponentTree - [Windowed mode] cursor at: " << passedX << "," << passedY << " component at " << static_cast<int>(component->x) << "," << static_cast<int>(component->y) << " size " << static_cast<int>(component->width) << "," << static_cast<int>(component->height) << " type: " << typeOfComponent(component) << std::endl; |
|
int ty = component->y; |
|
int ty1 = component->height + component->y; |
|
// FIXME: hack for tabbed component |
|
if (ty < 0) { |
|
ty = 0; |
|
ty1 += 64; |
|
} |
|
//std::cout << "Window::searchComponentTree - y search: " << ty1 << ">" << static_cast<int>(this->windowHeight - passedY) << ">" << static_cast<int>(ty) << " type: " << typeOfComponent(component) << std::endl; |
|
if (ty1 > this->windowHeight - passedY && this->windowHeight - passedY > ty) { |
|
//std::cout << "y match" << std::endl; |
|
if (component->x < passedX && component->x + component->width > passedX) { |
|
//std::cout << "Window::searchComponentTree - hit " << typeOfComponent(component) << std::endl; |
|
return component; |
|
} |
|
} |
|
} else { |
|
//std::cout << "MultiComponent::searchComponentTree - [normal] y search: " << static_cast<int>(-component->y) << "<" << static_cast<int>(passedY) << "<" << static_cast<int>(-component->y + component->height) << std::endl; |
|
if (-component->y < passedY && -component->y + component->height > passedY) { |
|
//std::cout << "DocumentComponent::searchComponentTree - x search: " << static_cast<int>(component->x) << "<" << static_cast<int>(passedX) << "<" << static_cast<int>(component->x + component->width) << std::endl; |
|
if (component->x < passedX && component->x + component->width > passedX) { |
|
//std::cout << "MultiComponent::searchComponentTree - hit " << typeOfComponent(component) << std::endl; |
|
return component; |
|
} |
|
} |
|
} |
|
} |
|
else { |
|
for (std::shared_ptr<Component> child : component->children) { |
|
std::shared_ptr<Component> found = searchComponentTree(child, passedX, passedY); |
|
if (found) { |
|
return found; |
|
} |
|
} |
|
} |
|
return nullptr; |
|
}
|
|
|