code:
#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;
}


code:
#ifndef MULTICOMPONENT_H
#define MULTICOMPONENT_H

#include "Component.h"
//#include "../opengl/Window.h"

// we're a control with our own layers (each layer has it's own componentTree)
// we can relay events down to these components
// can be used for documents, tabs, menus and dropdowns?
//
// this component is not scrollable
class MultiComponent : public Component {
public:
    //
    // Methods
    //

    MultiComponent(const float rawX, const float rawY, const float rawWidth, const float rawHeight, const int passedWindowWidth, const int passedWindowHeight);
    void updateMouse();
    void render();
    //void renderComponents(std::shared_ptr<Component> component);
    //void renderDocumentComponents(std::shared_ptr<Component> component);
    //void renderBoxComponents(std::shared_ptr<Component> component);
    void renderComponentType(std::string str, std::shared_ptr<Component> component);
    void resize(const int passedWindowWidth, const int passedWindowHeight);
    std::shared_ptr<Component> searchComponentTree(const std::shared_ptr<Component> &component, const int passedX, const int passedY);

    //
    // Properties
    //

    // layers will replace rootComponent
    std::vector<std::shared_ptr<Component>> layers; // each layer's root component
    //std::shared_ptr<Component> rootComponent = std::make_shared<Component>();

    // tabbed coordinate system?
    bool tabbed = false;
    bool windowed = false; // use window/ui picking

    // we'll need a way to pass events down and up
    // also how do we handle scroll?
    // we'll need searchComponentTree for picking
    std::shared_ptr<Component> hoverComponent = nullptr;
    std::shared_ptr<Component> focusedComponent = nullptr;
    std::shared_ptr<Component> relayKeyboardComponent = nullptr;

    // components don't need this...
    std::function<void(int w, int h)> onResize = nullptr;

    // if the component tree changes, we'll need these to recalculate hoverComponent
    double cursorX = 0;
    double cursorY = 0;
};

#endif

repo_name: despair/netrunner

license: mit


code:
#include "TabbedComponent.h"
#include "DocumentComponent.h"
#include "InputComponent.h"
#include "BoxComponent.h"
#include <iostream>

TabbedComponent::TabbedComponent(const float rawX, const float rawY, const float rawWidth, const float rawHeight, const int passedWindowWidth, const int passedWindowHeight) : MultiComponent(rawX, rawY, rawWidth, rawHeight, passedWindowWidth, passedWindowHeight) {

    // picking flag
    tabbed = true;

    std::shared_ptr<Component> rootComponent = std::make_shared<Component>();

    // make 2 multiComponents
    // put into rootDOM
    // one for the document
    // one for the tab UI
    // make sure boxes & text for document is draw first
    // then draw on top:
    // boxes & text for tab UI
    // or we can manually force doc components first and skipped later... sounds like a better start

    std::unique_ptr<BoxComponent> addNewBox = std::make_unique<BoxComponent>(x, passedWindowHeight - 96 - y, 32, 32, this->tabAddColor, passedWindowWidth, passedWindowHeight);
    addNewBox->name = "addNewTab";

    // needs to be adjust when our parent is adjusted...
    // pre-layout
    addNewBox->uiControl.x = { 0  ,   static_cast<int>(x) }; // 0
    addNewBox->uiControl.y = { 100, -96 - static_cast<int>(y) }; // height - 96px
    addNewBox->uiControl.w = { 0  ,  32 }; // 32px
    addNewBox->uiControl.h = { 0  ,  32 }; // 32px

    addNewBox->boundToPage = false;
    addNewBox->onClick = [this]() {
        //std::cout << "new tab" << std::endl;
        this->addTab("New Tab");
    };
    // this will cause problems if addNewBox goes out of scope but then not events should be firing
    BoxComponent *addBoxHandle = addNewBox.get();
    addNewBox->onMouseover = [addBoxHandle, this]() {
        //std::cout << "addNewBox->onMouseover" << std::endl;
        addBoxHandle->changeColor(this->tabAddHoverColor);
        this->win->renderDirty = true;
    };
    addNewBox->onMouseout = [addBoxHandle, this]() {
        //std::cout << "addNewBox->onMouseout" << std::endl;
        addBoxHandle->changeColor(this->tabAddColor);
        this->win->renderDirty = true;
    };
    // add to component tree
    addNewBox->setParent(rootComponent);
    rootComponent->children.push_back(std::move(addNewBox));

    // just set it to the end for now
    this->selectedTab = this->tabs.end();
    this->layers.push_back(rootComponent);
}

std::shared_ptr<Tab> TabbedComponent::addTab(std::string passedTitle) {
    //std::cout << "TabbedComponent::addTab - windowSize: " << windowWidth << "x" << windowHeight << std::endl;

    std::shared_ptr<Component> rootComponent = this->layers[0]; // we only have one layer (currently)

    auto tabId = ++tabCounter; // always increasing
    std::cout << "TabbedComponent::addTab - new tabId: " << tabId << " title: " << passedTitle << std::endl;

    std::shared_ptr<Tab> newTab=std::make_shared<Tab>();
    //newTab->title = passedTitle;
    newTab->id = tabId;

    //(const std::string &rawText, const int rawX, const int rawY, const unsigned int size, const bool bolded, const unsigned int hexColor, const int passedWindowWidth, const int passedWindowHeight)
    std::shared_ptr<TextComponent> newTabTitle = std::make_shared<TextComponent>(passedTitle, 0, 0, 12, false, this->tabTextColor, windowWidth, windowHeight);
    size_t textWidth = 0;
    newTabTitle->win = this->win;
    std::unique_ptr<sizeResponse> textInfo = newTabTitle->size();
    if (textInfo) {
        textWidth = static_cast<size_t>(textInfo->width);
        //std::cout << "tab text width: " << textWidth << std::endl;
    }
    textWidth += 10; // 5px padding each side
    // well we need to figure out where the last tab start
    size_t startX = x + 32; // our start + 32px for new tab button
    for(std::vector<std::shared_ptr<Tab>>::iterator it = this->tabs.begin(); it!=this->tabs.end(); ++it) {
        startX += it->get()->w;
    }
    newTab->x = static_cast<int>(startX);
    newTab->w = textWidth + 32;

    //std::cout << "TabbedComponent::addTab - windowHeight: " << windowHeight << " y: " << y << std::endl;
    std::shared_ptr<BoxComponent> newTabBox = std::make_shared<BoxComponent>(newTab->x, windowHeight - 96, newTab->w - 32, 32, this->tabInactiveColor, windowWidth, windowHeight);
    newTabBox->uiControl.x = { 0  ,  newTab->x }; // 0
    newTabBox->uiControl.y = { 100,  -96  }; // height - 96px
    newTabBox->uiControl.w = { 0  ,  static_cast<int>(newTab->w) - 32 }; // 32px
    newTabBox->uiControl.h = { 0  ,  32 }; // 32px
    newTabBox->name = "tabSelectorBox";
    newTabBox->boundToPage = false;
    newTabBox->onClick = [this, newTab]() {
        //std::cout << "TabbedComponent::addTab:onClick() - select tab " << newTab->id << std::endl;
        this->selectTab(newTab);
    };
    newTabBox->onMouseover = [newTabBox, this]() {
        //std::cout << "newTabBox->onMouseover" << std::endl;
        newTabBox->changeColor(this->tabHoverColor);
        this->win->renderDirty = true;
    };
    newTabBox->onMouseout = [newTabBox, tabId, this]() {
        //std::cout << "newTabBox->onMouseout" << std::endl;
        // if it an active or inactive tab
        if (this->selectedTabId == tabId) {
            newTabBox->changeColor(this->tabActiveColor);
        } else {
            newTabBox->changeColor(this->tabInactiveColor);
        }
        this->win->renderDirty = true;
    };

    //newTab->selectorBox=std::move(newTabBox);
    // add to component tree
    newTabBox->setParent(rootComponent);
    rootComponent->children.push_back(newTabBox);
    newTab->selectorBox = newTabBox;

    newTabTitle->x = newTab->x + 5;
    newTabTitle->y = -73;
    newTabTitle->uiControl.x = { 0  ,  newTab->x + 5  }; // 0
    newTabTitle->uiControl.y = { 0  ,  -73  }; // height - 73px
    newTabTitle->uiControl.w = { 0  ,  static_cast<int>(textWidth) }; // textWidth
    newTabTitle->uiControl.h = { 0  ,  12 }; // 12px
    newTabTitle->resize(windowWidth, windowHeight); // rasterize
    newTabTitle->name = "add new tab label";
    newTabTitle->boundToPage = false;

    // add to component tree
    newTabTitle->setParent(rootComponent);
    rootComponent->children.push_back(newTabTitle);
    newTab->titleBox = newTabTitle;

    // x + 64 * (tabs.size() + 1)
    std::shared_ptr<BoxComponent> closeTabBox = std::make_shared<BoxComponent>(newTab->x + static_cast<int>(newTab->w) - 32, windowHeight - 96, 32, 32, this->tabCloseColor, windowWidth, windowHeight);
    closeTabBox->uiControl.x = { 0  ,  newTab->x + static_cast<int>(newTab->w) - 32  }; // 0
    closeTabBox->uiControl.y = { 100  ,  -96  }; // height - 96px
    closeTabBox->uiControl.w = { 0  ,  32 }; // 32px
    closeTabBox->uiControl.h = { 0  ,  32 }; // 32px

    closeTabBox->name = "tabCloseBox";
    closeTabBox->boundToPage = false;
    closeTabBox->onClick = [this, tabId]() {
        //std::cout << "close tab " << tabId << std::endl;
        this->removeTab(tabId);
    };

    closeTabBox->onMouseover = [closeTabBox, this]() {
        //std::cout << "closeTabBox->onMouseover" << std::endl;
        closeTabBox->changeColor(this->tabCloseHoverColor);
        this->win->renderDirty = true;
    };
    closeTabBox->onMouseout = [closeTabBox, this]() {
        //std::cout << "closeTabBox->onMouseout" << std::endl;
        closeTabBox->changeColor(this->tabCloseColor);
        this->win->renderDirty = true;
    };

    newTab->closeBox = closeTabBox;
    //newTab->selectorBox=std::move(newTabBox);
    // add to component tree
    closeTabBox->setParent(rootComponent);
    rootComponent->children.push_back(closeTabBox);

    // build empty document
    std::shared_ptr<DocumentComponent> docComponent = std::make_shared<DocumentComponent>(0, 0, windowWidth, windowHeight, windowWidth, windowHeight);
    docComponent->name = "TabX docComponent";
    docComponent->win = this->win;
    docComponent->boundToPage = false;

    docComponent->onBeforeLoad=[this](std::string url) {
        //std::cout << "tabbedComponent::addTab:::docComponent->onBeforeLoad - fire" << std::endl;
        // if we're in a tab
        if (this->mpSelectedTab) {
            // set tab
            TextComponent *textComponent = this->mpSelectedTab->titleBox.get();
            if (textComponent) {
                // similar to loadDomIntoTab
                textComponent->text = url;
                textComponent->wrap();

                // recalc what that just did to our fellow tabs
                this->layoutTabs(this->selectedTab, 0);

                if (this->selectedTab->get()) {
                    this->selectedTab->get()->history->pushState(nullptr, url, url);
                } else {
                    std::cout << "TabbedComponent:addTab.docComponent->onBeforeLoad - no selected tab" << std::endl;
                }
            }
        } else {
            std::cout << "tabbedComponent::addTab:::docComponent->onBeforeLoad - document component but no tab selected" << std::endl;
            // but we're loading into a specific tab, we have a doc component...
            if (!this->tabs.size()) {
                std::cout << "tabbedComponent::addTab:::docComponent->onBeforeLoad - document component but no tabs!" << std::endl;
            }
        }
        // set address bar
        InputComponent *ab = dynamic_cast<InputComponent*>(this->win->addressComponent.get());
        if (ab) {
            ab->setValue(url);
            ab->updateText();
        } else {
            std::cout << "No address component" << std::endl;
        }
        this->updateWindowState(url);
    };

    // set up position and resize info
    // FIXME: get coords from theme or our own uiControl
    docComponent->y = -96; // our bar
    docComponent->uiControl.x = { 0, 0 }; // 0
    docComponent->uiControl.y = { 0, -96 }; // -96px
    docComponent->uiControl.w = { 100, 0 }; // 100%
    docComponent->uiControl.h = { 100, -96 }; // 100% - 96px
    // adjust rootComponent based on y

    docComponent->layers[0]->name = "root of doc";
    docComponent->layers[0]->y = docComponent->y;

    // don't attach this component to our root, yet...

    // don't need to touch dom, should be empty to start
    newTab->contents = docComponent;

    newTab->history = std::make_unique<BrowsingHistory>([this](URL const& goToURL) {
        std::cout << "history goto " << goToURL << std::endl;
        std::shared_ptr<DocumentComponent> p_docComponent = std::static_pointer_cast<DocumentComponent>(this->documentComponent);
        if (p_docComponent) {
            // now tell it to navigate somewhere
            p_docComponent->navTo(goToURL.toString());
        }
        //this->win->navTo(goToURL.toString());
    });

    if (tabs.size()) {
        newTab->previousTab = tabs.back();
    } else {
        newTab->previousTab = nullptr;
    }

    // std::move(
    tabs.push_back(newTab);
    //std::cout << "tabs: " << tabs.size() << std::endl;
    //renderDirty = true;
    win->renderDirty = true;

    // also update the new picking system
    // likely won need to do this because the addTab never moves
    //this->updateMouse();
    return newTab;
}

void TabbedComponent::updateWindowState(std::string newTitle) {
    //std::cout << "TabbedComponent::updateWindowState - newTitle: " << newTitle << std::endl;
    InputComponent *inputComponent = dynamic_cast<InputComponent*>(this->win->addressComponent.get());
    if (inputComponent) {
        inputComponent->setValue(newTitle);
        inputComponent->updateText();
    } else {
        std::cout << "TabbedComponent::updateWindowState - Window addressComponent isn't an inputComponent" << std::endl;
    }
    // need to update currentURL for navTo
    // if we're no tab, we have no relative point, no harm in setting http://
    //if (ab->value!="http://") {
    //this->win->currentURL = newTitle;

    // I'm not sure we need this
    /*
    DocumentComponent *docComponent = dynamic_cast<DocumentComponent*>(this->documentComponent.get());
    if (docComponent) {
        docComponent->currentURL = newTitle;
    }
    */
    //}

    // redraw pls
    this->win->renderDirty = true;

    // on DOM load mouse won't be over a tab
    // and select tab doesn't change any components
    //this->updateMouse();
}

// because iterators invalid, this is good for a quick lookup
std::vector<std::shared_ptr<Tab>>::iterator TabbedComponent::getTab(size_t tabId) {
    // locate tabId
    std::vector<std::shared_ptr<Tab>>::iterator newTab;
    for(std::vector<std::shared_ptr<Tab>>::iterator it = this->tabs.begin(); it != this->tabs.end(); ++it) {
        if (it->get()->id == tabId) {
            return it;
        }
    }
    return this->tabs.end();
}

void TabbedComponent::selectTab(std::shared_ptr<Tab> tab) {
    //std::cout << "TabbedComponent::selectTab - select tab " << tabId << std::endl;

    // locate tab
    if (tab == nullptr) {
        std::cout << "TabbedComponent::selectTab - tab is nullptr" << std::endl;
        return;
    }
    /*
    std::vector<std::shared_ptr<Tab>>::iterator newTab = this->getTab(tabId);
    if (newTab == this->tabs.end()) {
        std::cout << "tab" << tabId << " doesn't exist" << std::endl;
        return;
    }
    if (newTab->get() == nullptr) {
        std::cout << "tab" << tabId << " points to null" << std::endl;
        return;
    }
    */
    std::shared_ptr<Component> rootComponent = this->layers[0]; // we only have one layer (currently)

    //std::cout << "Found tab" << std::endl;

    // tab system is currently keeping component tree in memory
    // swap contents with document components

    // remove from our children
    std::vector<std::shared_ptr<Component>>::iterator it2;
    it2 = std::find(rootComponent->children.begin(), rootComponent->children.end(), this->documentComponent);
    if ( it2 != rootComponent->children.end() ) {
        //std::cout << "Found old contents" << std::endl;
        // FIXME: cast to doccomp and setDOM to null to make sure its clean
        it2->get()->parent = nullptr;
        rootComponent->children.erase(it2);
    }

    //std::cout << "adding new contents" << std::endl;
    // select new document
    rootComponent->children.push_back(tab->contents);
    tab->contents->setParent(rootComponent);

    // resize this content incase the window was resized since last selection
    if (tab->contents->windowWidth != this->windowWidth || tab->contents->windowHeight != this->windowHeight) {
        std::cout << "detected resize, resizing tab's doc component" << std::endl;
        tab->contents->resize(this->windowWidth, this->windowHeight);
    }

    this->documentComponent = tab->contents;

    InputComponent *ab = dynamic_cast<InputComponent*>(this->win->addressComponent.get());
    if (ab) {
        ab->setValue(tab->titleBox->text);
        //std::cout << "tab->titleBox->text: " << tab->titleBox->text << std::endl;
        if (ab->getValue() == "New Tab") {
            ab->setValue("http://"); // should be "" but we don't have an awesome-bar yet
        }
        ab->updateText();
    } else {
        std::cout << "TabbedComponent::selectTab - Window addressComponent isnt an inputcomponent" << std::endl;
    }

    // I can't get selectedTab to works always (tab size = 1 or 2)
    // so we'll rely on this more complex system
    int i=0;
    bool found = false;
    for(std::vector<std::shared_ptr<Tab>>::iterator it=this->tabs.begin(); it!=this->tabs.end(); ++it) {
        if (it==this->selectedTab) {
        //if (it->get()->id == tabId) {
            //std::cout << "found it at " << i << std::endl;
            found = true;
            break;
        }
        i++;
    }
    //std::cout << "i" << i << "/" << this->tabs.size() << std::endl;
    //std::cout << "TabbedComponent::selectTab - selector found: " << found << std::endl;
    if (found) {
        // was there a previously selected tab?
        //if (this->selectedTab != this->tabs.end() && this->selectedTab != this->tabs.begin()) {
        BoxComponent *selector = dynamic_cast<BoxComponent*>(tab->selectorBox.get());
        if (selector) {
            selector->changeColor(this->tabActiveColor);
        }

        // if we had something selected
        // make sure we didn't just reselect ourself
        //if (this->selectedTabId != newTab->get()->id) {
        if (this->mpSelectedTab && this->mpSelectedTab != tab) {
            selector = dynamic_cast<BoxComponent*>(this->selectedTab->get()->selectorBox.get());
            if (selector) {
                // reset it's color
                selector->changeColor(this->tabInactiveColor);
            }
        }
        //}
    } else {
        // just mark all non-active color
        BoxComponent *selector;
        for(std::vector<std::shared_ptr<Tab>>::iterator it=this->tabs.begin(); it!=this->tabs.end(); ++it) {
            selector = dynamic_cast<BoxComponent*>(it->get()->selectorBox.get());
            if (selector) {
                // reset color
                selector->changeColor(this->tabInactiveColor);
            }
        }
        // mark selected one as active
        selector = dynamic_cast<BoxComponent*>(tab->selectorBox.get());
        if (selector) {
            selector->changeColor(this->tabActiveColor);
        } else {
            if (this->tabs.size()) {
                std::cout << "TabbedComponent::selectTab - Can't find selectorBox for tab" << std::endl;
            }
        }
    }

    // make sure we're the selected one
    this->selectedTab = this->getTab(tab->id); // iterator
    this->selectedTabId = tab->id; // integer
    // doesn't move, will this double free?
    this->mpSelectedTab = tab; // pointer

    if (ab) {
        this->updateWindowState(ab->getValue());
    } else {
        std::cout << "TabbedComponent::selectTab - cant update window no address bar" << std::endl;
    }
}

// we expect the tab to be set up (all components allocated)
// calculate size and position for a tab
// we're not going to do a full set up uiContorl, we'll expect those to be set up and just adjust them
// but then, we can't just call this to place things where we want in the cstr... hrm..
void TabbedComponent::layoutTab(std::vector<std::shared_ptr<Tab>>::iterator tab) {
    //std::cout << "TabbedComponent::layoutTab - id: " << tab->get()->id << std::endl;
    // find text
    TextComponent *textComponent = tab->get()->titleBox.get();
    if (!textComponent) {
        std::cout << "TabbedComponent::layoutTab - titleBox isn't a TextComponent" << std::endl;
        return;
    }

    // vector iterators can't go backwards
    //std::vector<std::shared_ptr<Tab>>::iterator it = tab;
    //--it;
    //Tab *prev = it->get();
    Tab *prev = tab->get()->previousTab.get();
    if (prev == nullptr) {
        //std::cout << "TabbedComponent::layoutTab - relaying out first tab, no prev" << std::endl;
        //prev = nullptr;
        tab->get()->x = x + 32; // our start + 32px for new tab button
    } else {
        tab->get()->x = prev->x + static_cast<int>(prev->w);
    }
    //std::cout << "TabbedComponent::layoutTab - placing tab at " << tab->get()->x << std::endl;

    // get text size for it's current string
    // we don't always need to adjust textWidth, only on text change
    int textWidthWPad = 0;
    std::unique_ptr<sizeResponse> textInfo = textComponent->size();
    if (textInfo) {
        textWidthWPad = textInfo->width;
        //std::cout << "tab text width: " << textWidth << std::endl;
    }
    // pad
    textWidthWPad += 10; // 5px padding each side

    // update tab width
    tab->get()->w = static_cast<size_t>(textWidthWPad) + 32; // selector Box (text + padding) + close Box

    // adjust ui map accordingly
    // adjust x of text
    textComponent->x = 5 + tab->get()->x;
    textComponent->width = textWidthWPad - 10; // ugh textComponent should be managing this...
    textComponent->uiControl.x = { 0, static_cast<int>(textComponent->x) };
    textComponent->uiControl.w = { 0, static_cast<int>(textComponent->width) };
    textComponent->resize(windowWidth, windowHeight); // apply new width

    // find selector
    // adjust x and w of selector
    tab->get()->selectorBox->x = tab->get()->x;
    tab->get()->selectorBox->width = textWidthWPad;
    // need to update the uiControl
    tab->get()->selectorBox->uiControl.x = { 0, tab->get()->x }; // tab->get()->x
    tab->get()->selectorBox->uiControl.w = { 0, textWidthWPad }; // textWidthWPad
    tab->get()->selectorBox->resize(windowWidth, windowHeight); // apply new width

    // find close and adjust x
    tab->get()->closeBox->x = tab->get()->x + static_cast<int>(tab->get()->w) - 32;
    tab->get()->closeBox->uiControl.x = { 0, tab->get()->x + static_cast<int>(tab->get()->w) - 32 }; // note it's not our width
    // need to update the uiControl
    tab->get()->closeBox->resize(windowWidth, windowHeight); // apply new position
}

// maybe take in a starting tab? as tabs to left aren't affected
void TabbedComponent::layoutTabs(std::vector<std::shared_ptr<Tab>>::iterator startTab, int xAdj) {
    //std::cout << "TabbedComponent::layoutTabs - startId: " << startTab->get()->id << " xadj: " << xAdj << std::endl;
    // luckily only one component at a time changes, we'll need to know how much x shifted
    for(std::vector<std::shared_ptr<Tab>>::iterator it = startTab; it!=this->tabs.end(); ++it) {
        //it->get()->x += xAdj; // adjust position
        this->layoutTab(it);
    }
}

void TabbedComponent::loadDomIntoTab(std::shared_ptr<Node> newRoot, std::string newTitle) {
    DocumentComponent *docComponent = dynamic_cast<DocumentComponent*>(this->documentComponent.get());
    if (!docComponent) {
        // FIXME: could mean no tab is selected (on "default" no tab and a tab has been created)
        std::cout << "TabbedComponent::loadDomIntoTab - 'documentComponent' isn't a DocumentComponent" << std::endl;
        return;
    }
    docComponent->setDOM(newRoot);

    if (this->selectedTab == this->tabs.end()) {
        std::cout << "TabbedComponent::loadDomIntoTab - No current tab selected" << std::endl;
        return;
    }
    if (this->tabs.size() == 0) {
        std::cout << "TabbedComponent::loadDomIntoTab - No tabs that can be selected" << std::endl;
        return;
    }

    // std::string const& title, URL const& url
    // FIXME: reload, don't push on reload?
    // if the history cursor isn't at the end, we need to replace I think and nuked everything after it
    this->selectedTab->get()->history->pushState(nullptr, newTitle, newTitle);
    this->selectedTab->get()->history->print();

    std::shared_ptr<Component> rootComponent = this->layers[0]; // we only have one layer (currently)

    // find our titleBox
    std::vector<std::shared_ptr<Component>>::iterator it2 = std::find(rootComponent->children.begin(), rootComponent->children.end(), this->selectedTab->get()->titleBox);
    if ( it2 == rootComponent->children.end() ) {
        std::cout << "TabbedComponent::loadDomIntoTab - Can't find selectedTab's titleBox in rootComponent" << std::endl;
        return;
    }

    std::cout << "found our titleBox component in parent tree" << std::endl;

    // get text component
    TextComponent *textComponent = dynamic_cast<TextComponent*>(it2->get());
    // actually change the text
    textComponent->text = newTitle;
    textComponent->wrap();

    // recalc what that just did to our fellow tabs
    this->layoutTabs(this->selectedTab, 0);

    /*
    TextComponent *textComponent = dynamic_cast<TextComponent*>(it2->get());
    if (!textComponent) {
        std::cout << "TabbedComponent::loadDomIntoTab - titleBox isn't a TextComponent" << std::endl;
        return;
    }
    textComponent->text = newTitle;
    textComponent->wrap();

    int textWidth = 0;
    std::unique_ptr<std::pair<int, int>> textInfo = textComponent->size();
    if (textInfo) {
        textWidth = textInfo->first;
        //std::cout << "tab text width: " << textWidth << std::endl;
    }
    textWidth += 10; // 5px padding each side

    // update width
    this->selectedTab->get()->w = static_cast<size_t>(textWidth) + 32; // selector Box (text + padding) + close Box

    // need to update selectorBox's width
    this->selectedTab->get()->selectorBox->width = textWidth;
    // need to update the uiControl
    this->selectedTab->get()->selectorBox->uiControl.w = { 0, textWidth }; //
    this->selectedTab->get()->selectorBox->resize(windowWidth, windowHeight); // apply new width

    // need to move all tabs after this one
    this->selectedTab->get()->closeBox->x = this->selectedTab->get()->x + static_cast<int>(this->selectedTab->get()->w) - 32;
    this->selectedTab->get()->closeBox->uiControl.x = { 0, this->selectedTab->get()->x + static_cast<int>(this->selectedTab->get()->w) - 32 }; // note it's not our width
    // need to update the uiControl
    this->selectedTab->get()->closeBox->resize(windowWidth, windowHeight); // apply new position
    */

    this->updateWindowState(newTitle);
}

//FIXME: make sure we update window's ptrs, we can't have documentComponent not pointing to nullptr when there's no tabs
void TabbedComponent::removeTab(size_t tabId) {

    std::shared_ptr<Component> rootComponent = this->layers[0]; // we only have one layer (currently)

    // find tabId
    bool found = false;
    size_t widthToRemove = 0;
    std::vector<std::shared_ptr<Tab>>::iterator prev = this->tabs.begin();
    for(std::vector<std::shared_ptr<Tab>>::iterator it = this->tabs.begin(); it!=this->tabs.end(); prev = it, ++it) {
        if (it->get()->id == tabId) {
            //std::cout << "TabbedComponent::removeTab - found our tab" << std::endl;
            // remove our components form the component tree
            std::vector<std::shared_ptr<Component>>::iterator it2;
            it2 = std::find(rootComponent->children.begin(), rootComponent->children.end(), it->get()->selectorBox);
            if ( it2 != rootComponent->children.end() ) {
                //std::cout << "found our select component in parent tree" << std::endl;
                it2->get()->parent = nullptr;
                rootComponent->children.erase(it2);
            }
            it2 = std::find(rootComponent->children.begin(), rootComponent->children.end(), it->get()->closeBox);
            if ( it2 != rootComponent->children.end() ) {
                //std::cout << "found our close component in parent tree" << std::endl;
                it2->get()->parent = nullptr;
                rootComponent->children.erase(it2);
            }
            it2 = std::find(rootComponent->children.begin(), rootComponent->children.end(), it->get()->titleBox);
            if ( it2 != rootComponent->children.end() ) {
                //std::cout << "found our titleBox component in parent tree" << std::endl;
                it2->get()->parent = nullptr;
                rootComponent->children.erase(it2);
            }
            it2 = std::find(rootComponent->children.begin(), rootComponent->children.end(), it->get()->contents);
            if ( it2 != rootComponent->children.end() ) {
                //std::cout << "found our document component in parent tree" << std::endl;
                DocumentComponent *docComponent = dynamic_cast<DocumentComponent*>(it2->get());
                if (docComponent) {
                    // unload components
                    docComponent->setDOM(std::make_shared<Node>(NodeType::ROOT));
                } else {
                    std::cout << "TabbedComponent::removeTab - our contents aren't a document component" << std::endl;
                }
                it2->get()->parent = nullptr;
                rootComponent->children.erase(it2);
            }
            // remove tab
            widthToRemove = it->get()->w;
            // are we the selected tab
            if (this->selectedTab == it) {
                std::cout << "TabbedComponent::removeTab - removing selected tab" << std::endl;
                if (this->tabs.size() > 1) {
                    size_t nextTabId = this->tabs.front()->id; // grab first tab
                    std::shared_ptr<Tab> pSelectedTab = this->tabs.front();
                    // if we are the firstTab, get the 2nd
                    if (it->get()->id == nextTabId) {
                        //nextTabId = this->tabs[1]->id;
                        pSelectedTab = this->tabs[1];
                    }
                    this->selectTab(pSelectedTab);
                }
            }
            it->get()->contents.reset(); // nuke document
            //it->reset(); // free memory before losing track of it
            it = this->tabs.erase(it);
            it->get()->previousTab = *prev; // next tab, set it to our previous
            if (!this->tabs.size()) {
                // no tabs left
                std::cout << "TabbedComponent::removeTab - all tabs removed" << std::endl;
                this->selectedTab = this->tabs.end();
                this->selectedTabId = 0;
                this->mpSelectedTab = nullptr;
                // get last doc Component
                DocumentComponent *docComponent = dynamic_cast<DocumentComponent*>(this->documentComponent.get());
                if (docComponent) {
                    // create empty document
                    //docComponent->domRootNode = std::make_shared<Node>(NodeType::ROOT);
                    //docComponent->domDirty = true;
                    //docComponent->rootComponent = std::make_shared<Component>();
                    //docComponent->rootComponent->name = "rootComponent";
                    // properly clean up the DOM Tree
                    docComponent->setDOM(std::make_shared<Node>(NodeType::ROOT));
                }
                //this->documentComponent = nullptr;
            }
            found = true;
            if (it == this->tabs.end()) break;
        }
        // we need to move (relayout) all remove tabs after this id
        if (found) {
            //std::cout << "TabbedComponent::removeTab - Need to adjust: " << it->get()->id << std::endl;
            it->get()->titleBox->x -= widthToRemove;
            it->get()->titleBox->resize(windowWidth, windowHeight); // TextComponent, need a resize() to move vertices
            it->get()->selectorBox->x -= widthToRemove;
            it->get()->selectorBox->resize(windowWidth, windowHeight); // BoxComponent, need a resize() to move vertices
            it->get()->closeBox->x -= widthToRemove;
            it->get()->closeBox->resize(windowWidth, windowHeight); // BoxComponent, need a resize() to move vertices
        }
    }
    //this->renderDirty = true;
    this->win->renderDirty = true;

    // also update the new picking system
    this->updateMouse();
}


code:
#ifndef TABBEDCOMPONENT_H
#define TABBEDCOMPONENT_H

#include "MultiComponent.h"
#include "TextComponent.h"
#include "../../tools/BrowsingHistory.h"

struct Tab {
    //std::string title;
    // I think these could be unique_ptrs
    // have to be shared, because we want shortcut ptrs to them
    // would make cleaning up a tab nice
    std::shared_ptr<TextComponent> titleBox;
    // these could be BoxComponents
    std::shared_ptr<Component> selectorBox;
    std::shared_ptr<Component> closeBox;
    // this can be generic
    std::shared_ptr<Component> contents;
    int x;
    int y;
    size_t w;
    size_t h;
    size_t id;
    // need to generalize these out
    //std::string url;
    // we need to cache components to save inputted data
    // or create a special store/load system for it (might be less memory but more cpu)
    std::shared_ptr<Node> domRootNode = nullptr;
    //BrowsingHistory history;
    // FIXME: we should move this into document component
    // but then you can't unloaded delete the document component if you do that...
    std::unique_ptr<BrowsingHistory> history = nullptr;
    std::shared_ptr<Tab> previousTab = nullptr;
};

// could eventually be broken into "tabSelectorComponent" and "tabViewComponent" so the selector doesn't have to be attached to the view area
class TabbedComponent : public MultiComponent {
public:
    TabbedComponent(const float rawX, const float rawY, const float rawWidth, const float rawHeight, const int passedWindowWidth, const int passedWindowHeight);
    std::shared_ptr<Tab> addTab(std::string passedTitle);
    void updateWindowState(std::string newTitle);
    std::vector<std::shared_ptr<Tab>>::iterator getTab(size_t tabId);
    void selectTab(std::shared_ptr<Tab> tab);
    void layoutTab(std::vector<std::shared_ptr<Tab>>::iterator tab);
    void layoutTabs(std::vector<std::shared_ptr<Tab>>::iterator startTab, int xAdj);
    void loadDomIntoTab(std::shared_ptr<Node> newRoot, std::string newTitle);
    void removeTab(size_t tabId);
    std::vector<std::shared_ptr<Tab>> tabs;

    size_t tabCounter = 0;
    size_t selectedTabId = 0;
    std::shared_ptr<Tab> mpSelectedTab = nullptr; // we just want a pointer to where we want to go
    // this sucks tbh, just phase it out
    std::vector<std::shared_ptr<Tab>>::iterator selectedTab;

    // was DocumentComponent but generalized to be more general (and compatible with Tab)
    std::shared_ptr<Component> documentComponent = nullptr;

    unsigned int tabAddColor = 0xF0F0F0FF;
    unsigned int tabAddHoverColor = 0x008800FF;

    unsigned int tabInactiveColor = 0x808080FF;
    unsigned int tabHoverColor = 0x8888BBFF;
    unsigned int tabActiveColor = 0xE0E0E0FF;

    unsigned int tabTextColor = 0x000000FF;
    unsigned int tabTextHoverColor = 0x008888FF;

    unsigned int tabCloseColor = 0x222222FF;
    unsigned int tabCloseHoverColor = 0x880000FF;
};

#endif


code:
#include "TextComponent.h"
#include <iostream>
#include <ctime>
#include <climits>

extern TextRasterizerCache *rasterizerCache;

TextComponent::TextComponent(const std::string &rawText, const int rawX, const int rawY, const unsigned int size, const bool bolded, const unsigned int hexColor, const int passedWindowWidth, const int passedWindowHeight) {

    windowWidth = passedWindowWidth;
    windowHeight = passedWindowHeight;

    //const std::clock_t begin = clock();
    text = rawText;
    //x = rawX;
    //y = rawY;
    x = 0;
    y = 0;
    fontSize = size;
    bold = bolded;
    color = hexColor;

    // html entity decode
    sanitize(text);

    // load font, rasterize, save vertices
    //rasterize(x, y, windowWidth, windowHeight);

    // send to video card
    glGenBuffers(1, &elementBufferObject);
    //for (unsigned int i = 0; i < glyphVertices.size(); i++) {
    //int i = 0;
        //const Glyph &glyph = glyphs[i];
        //const std::unique_ptr<float[]> &glyphVertice = glyphVertices[i];

        vertexArrayObjects.push_back(0);
        vertexBufferObjects.push_back(0);

        glGenVertexArrays(1, &vertexArrayObjects.back());
        glGenBuffers(1, &vertexBufferObjects.back());

        glBindVertexArray(vertexArrayObjects.back());

        // the data array
        glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObjects.back());
        std::unique_ptr<float[]> vertices = std::make_unique<float[]>(36); // upload garbage for now
        glBufferData(GL_ARRAY_BUFFER, ((3 + 4 + 2) * 4) * sizeof(float), vertices.get(), GL_STATIC_DRAW);
        //glBufferData(GL_ARRAY_BUFFER, ((3 + 4 + 2) * 4) * sizeof(float), glyphVertice.get(), GL_STATIC_DRAW);

        // indexes of the array
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBufferObject);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, (3 + 4 + 2) * sizeof(float), nullptr);
        glEnableVertexAttribArray(0);

        glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, (3 + 4 + 2) * sizeof(float), reinterpret_cast<void*>(3 * sizeof(float)));
        glEnableVertexAttribArray(1);

        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, (3 + 4 + 2) * sizeof(float), reinterpret_cast<void*>(7 * sizeof(float)));
        glEnableVertexAttribArray(2);

        /*
        textures.push_back(0);
        glGenTextures(1, &textures.back());
        glBindTexture(GL_TEXTURE_2D, textures.back());
         */

        // upload texture
        //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, glyph.textureWidth, glyph.textureHeight, 0, GL_RED, GL_UNSIGNED_BYTE, glyph.textureData.get());
        //glGenerateMipmap(GL_TEXTURE_2D);

    //}
    //const std::clock_t end = clock();
    //std::cout << "buffering text [" <<  text << "] in: " << std::fixed << ((static_cast<double>(end - begin)) / CLOCKS_PER_SEC) << std::scientific << " seconds" << std::endl;
}

TextComponent::~TextComponent() {
    glDeleteBuffers(1, &elementBufferObject);
    for (unsigned int i = 0; i < vertexArrayObjects.size(); i++) {
        glDeleteVertexArrays(1, &vertexArrayObjects[i]);
        glDeleteBuffers(1, &vertexBufferObjects[i]);
        if (textures.size()>i) {
            glDeleteTextures(1, &textures[i]);
        }
    }
}

#define posMac(p) (p * (3 + 4 + 2)) // 3 positions + 4 color channels + 2 S&T (texture mapping)

inline void setVerticesColor(std::unique_ptr<float[]> &vertices, int p, unsigned int color) {
    vertices[static_cast<size_t>(posMac(p) + 2)] = 0.0f;
    vertices[static_cast<size_t>(posMac(p) + 3)] = (static_cast<float>((color >> 24) & 0xFF)) / 255;
    vertices[static_cast<size_t>(posMac(p) + 4)] = (static_cast<float>((color >> 16) & 0xFF)) / 255;
    vertices[static_cast<size_t>(posMac(p) + 5)] = (static_cast<float>((color >>  8) & 0xFF)) / 255;
    vertices[static_cast<size_t>(posMac(p) + 6)] = (static_cast<float>((color >>  0) & 0xFF)) / 255;
}

void TextComponent::rasterize(const int rawX, const int rawY) {
    //const std::clock_t begin = clock();
    const std::shared_ptr<TextRasterizer> textRasterizer = rasterizerCache->loadFont(fontSize, bold);
    //unsigned int glyphCount;
    // we need to know how much width we have between x and windowWidth
    // and preferrable where the inline starts
    // my inline start is parent->children adding width from start to me
    // actually the run of all inline before me, starting at parent x as a minimum
    //std::cout << "TextComponent::rasterize pre-height: " << (int)height << std::endl;
    int wrapToX = 0; // windowWidth - rawX;
    //bool wrapped;
    //glyphs = textRasterizer->rasterize(text, rawX, windowWidth, wrapToX, width, height, glyphCount, endingX, endingY, wrapped);
    rasterizationRequest request;
    request.text = this->text;
    request.startX = rawX; // - x
    if (!boundToPage) {
        // startX needs to be relative to the parent x. Why?
        request.startX -= this->x;
    }
    //std::cout << "TextComponent::rasterize - [" << text << "] request.startX: " << request.startX << " x: " << x << " rawX: " << rawX << std::endl;
    request.availableWidth = this->availableWidth;
    request.sourceStartX = this->rasterStartX;
    request.sourceStartY = this->rasterStartY;
    request.cropWidth = this->rasterCropX;
    request.cropHeight = this->rasterCropY;
    if (this->win) {
        request.maxTextureSize = this->win->maxTextureSize & INT_MAX;
    } // else called from input cstr, no win yet
    request.noWrap = noWrap;
    //std::cout << "rasterizing [" << text << "] @" << rawX << " availableWidth: " << availableWidth << " sourceStartX: " << rasterStartX << " noWrap: " << noWrap << std::endl;
    std::shared_ptr<rasterizationResponse> response = textRasterizer->rasterize(request);
    if (response.get() == nullptr) {
        // window could be minimized
        //std::cout << "TextComponent::rasterize - got nullptr from rasterizer" << std::endl;
        return;
    }
    width = response->width;
    height = response->height;
    endingX = response->endingX;
    endingY = response->endingY;

    // << " wrapTo: " << wrapToX
    //std::cout << "TextComponent::rasterize done [" <<  text << "] - start x: " << rawX << " width: " << (int)width << " wrapWidth: " << windowWidth << std::endl;
    //std::cout << "TextComponent::rasterize post-height: " << (int)height << std::endl;
    //if (glyphs == nullptr) {
    //    return;
    //}
    // did we wrap
    int startX = rawX;
    // if we didn't we have a nice little texture starting at rawX potentially up to windowWidth
    // if we did wrap, then we start at wrapToX (0) and we're a big square texture
    if (response->wrapped) {
        //std::cout << "[" << text  << "] it's wrapped starting at " << wrapToX << std::endl;
        startX = wrapToX;
    }
    //std::cout << "startX: " << startX << std::endl;

    glyphVertices.clear();
    //for (unsigned int i = 0; i < glyphCount; i++) {
    //for(std::vector<Glyph>::iterator it=glyphs->begin(); it!=glyphs->end(); ++it) {
        //const Glyph &glyph = glyphs[i];
        //Glyph &glyph=*it;

        float vx0 = startX;
        float vy0 = response->y0 + rawY;
        //std::cout << "glyph x from: " << (int)glyph.x0 << " to " << (int)glyph.x1 << std::endl;
        float vx1 = startX + (response->x1 - response->x0);
        float vy1 = response->y1 + rawY;
        //std::cout << "TextComponent::rasterize - placing at " << (int)vx0 << "," << (int)vy0 << " to " << (int)vx1 << "," << (int)vy1 << std::endl;

        // convert our local x,y,w,h into actual ogl coords

        // map vx0 between -1 and 1
        //vx0 = ((vx0 / windowWidth) * 2) - 1;
        //vy0 = ((vy0 / windowHeight) * 2) - 1;

        pointToViewport(vx0, vy0);
        pointToViewport(vx1, vy1);

        //std::cout << "TextComponent::rasterize - placing at GL" << vx0 << "," << vy0 << " to GL" << vx1 << "," << vy1 << std::endl;

        std::unique_ptr<float[]> vertices = std::make_unique<float[]>(36);
        vertices[posMac(0) + 0] = vx0;
        vertices[posMac(0) + 1] = vy0;
        setVerticesColor(vertices, 0, color);
        vertices[posMac(0) + 7] = response->s0;
        vertices[posMac(0) + 8] = response->t0;

        vertices[posMac(1) + 0] = vx0;
        vertices[posMac(1) + 1] = vy1;
        setVerticesColor(vertices, 1, color);
        vertices[posMac(1) + 7] = response->s0;
        vertices[posMac(1) + 8] = response->t1;

        vertices[posMac(2) + 0] = vx1;
        vertices[posMac(2) + 1] = vy1;
        setVerticesColor(vertices, 2, color);
        vertices[posMac(2) + 7] = response->s1;
        vertices[posMac(2) + 8] = response->t1;

        vertices[posMac(3) + 0] = vx1;
        vertices[posMac(3) + 1] = vy0;
        setVerticesColor(vertices, 3, color);
        vertices[posMac(3) + 7] = response->s1;
        vertices[posMac(3) + 8] = response->t0;

        glyphVertices.push_back(std::move(vertices));
    //std::cout << "TextComponent::rasterize - glyphVertices count: " << glyphVertices.size() << std::endl;

    textureWidth = static_cast<GLsizei>(response->textureWidth);
    textureHeight = static_cast<GLsizei>(response->textureHeight);
    textureData = std::move(response->textureData);
    //}
    //const std::clock_t end = clock();
    //std::cout << "rasterize text [" <<  text << "] in: " << std::fixed << ((static_cast<double>(end - begin)) / CLOCKS_PER_SEC) << std::scientific << " seconds" << std::endl;
    verticesDirty = true;
    response.reset();
}

void TextComponent::render() {
    //std::cout << "TextComponent::render [" << text << "]" << std::endl;
    GLenum glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "TextComponent::render - start not ok: " << glErr << std::endl;
    }
    if (verticesDirty) {
        // only called on resize
        //const std::clock_t begin = clock();
        for (unsigned int i = 0; i < vertexBufferObjects.size(); i++) {
            glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObjects[i]);
            glBufferData(GL_ARRAY_BUFFER, ((3 + 4 + 2) * 4) * sizeof(float), glyphVertices[i].get(), GL_STATIC_DRAW);
        }
        verticesDirty = false;
        glErr=glGetError();
        if(glErr != GL_NO_ERROR) {
            std::cout << "TextComponent::render - verticesDirty, not ok: " << glErr << std::endl;
        }

        //const std::clock_t end = clock();
        //std::cout << "undirty TextComponent render [" <<  text << "] in: " << std::fixed << ((static_cast<double>(end - begin)) / CLOCKS_PER_SEC) << std::scientific << " seconds" << std::endl;
    }
    // have to have a texture to draw
    if (textures.size()) {
        //std::cout << "has textures" << std::endl;
        for (unsigned long i = vertexArrayObjects.size(); i > 0; i--) {
            glBindVertexArray(vertexArrayObjects[i - 1]); // load vertices
            glBindTexture(GL_TEXTURE_2D, textures[i - 1]); // load texture
            glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr); // draw primitives using vertices and texture
        }
        glErr=glGetError();
        if(glErr != GL_NO_ERROR) {
            std::cout << "TextComponent::render - texture render, not ok: " << glErr << std::endl;
        }
    } else {
        std::cout << "TextComponent::render - no textures" << std::endl;
    }
}

// compatible adapter
void TextComponent::resize(const int passedWindowWidth, const int passedWindowHeight) {
    resize(passedWindowWidth, passedWindowHeight, passedWindowWidth);
}

// more detailed control
void TextComponent::resize(const int passedWindowWidth, const int passedWindowHeight, const int passedAvailableWidth) {
    GLenum glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "TextComponent::resize - start not ok: " << glErr << std::endl;
    }
    windowWidth = passedWindowWidth;
    windowHeight = passedWindowHeight;
    availableWidth = passedAvailableWidth;
    //std::cout << "TextComponent::resize" << std::endl;
    //std::cout << "TextComponent::resize - rasterizing at " << (int)x << "x" << (int)y << " size: " << (int)width << "x" << (int)height << std::endl;

    // update the vertices and any wrapping
    rasterize(x, y);

    //std::cout << "TextComponent::resize - rasterized at " << (int)x << "x" << (int)y << " size: " << (int)width << "x" << (int)height << std::endl;

    // make sure we have glyphs
    if (!glyphVertices.size()) {
        std::cout << "TextComponent::resize - no glyphs" << std::endl;
        return;
    }

    // reupload NEW texture to video card
    /*
    const Glyph &glyph = glyphs[0];
    if (!glyphs) {
        std::cout << "TextComponent::resize - glyph points no where" << std::endl;
        return;
    }
    */
    if (!textures.size()) {
        textures.push_back(0);
        glGenTextures(1, &textures.back());
        glErr=glGetError();
        if(glErr != GL_NO_ERROR) {
            std::cout << "TextComponent::resize - glGenTextures, not ok: " << glErr << std::endl;
        }
    }
    glBindTexture(GL_TEXTURE_2D, textures.back()); // select texture
    glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "TextComponent::resize - glBindTexture, not ok: " << glErr << std::endl;
    }
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textureWidth, textureHeight, 0, GL_RED, GL_UNSIGNED_BYTE, textureData.get()); // update texture
    glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "TextComponent::resize - glTexImage2D, not ok: " << glErr << std::endl;
    }
    glGenerateMipmap(GL_TEXTURE_2D);
    glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "TextComponent::render - glGenerateMipmap, not ok: " << glErr << std::endl;
    }
    textureData.reset(); // it's uploaded, we're done
}

void TextComponent::sanitize(std::string &str) {
    size_t found = 0; // position
    while ((found = str.find("&", found)) != std::string::npos) {
        if (str.substr(found, 4) == ">") {
            str.replace(found, 4, ">");
        }
        found++;
    }
    found = 0;
    while ((found = str.find("\n", found)) != std::string::npos) {
        str.replace(found, 1, "");
        found++;
    }
}

std::unique_ptr<sizeResponse> TextComponent::size() const {
    const std::shared_ptr<TextRasterizer> textRasterizer=rasterizerCache->loadFont(fontSize, bold);
    rasterizationRequest request;
    request.text = this->text;
    // startX needs to be relative to the parent x
    request.startX = this->x;
    request.availableWidth = this->windowWidth;
    request.sourceStartX = this->rasterStartX;
    request.sourceStartY = this->rasterStartY;
    request.noWrap = this->noWrap;
    return textRasterizer->size(request);
}

void TextComponent::updateHighlight() {
    if (this->textSelected) {
        color = 0x0000FFFF;
    } else {
        color = 0x000000FF;
    }
    // update the vertices, colors and any wrapping
    rasterize(x, y);
}


code:
#ifndef TEXTCOMPONENT_H
#define TEXTCOMPONENT_H

#include <GL/glew.h>
#include "../graphical/font/TextRasterizerCache.h"
#include "Component.h"
#include <array>
#include <memory>
#include <vector>
#include <iostream>

class TextComponent : public Component {
private:
    unsigned int fontSize;
    bool bold;
    unsigned int color;
    std::unique_ptr<Glyph[]> glyphs;
    std::vector<std::unique_ptr<float[]>> glyphVertices;
    std::vector<GLuint> vertexArrayObjects;
    std::vector<GLuint> vertexBufferObjects;
    GLuint elementBufferObject;
    std::vector<GLuint> textures;

public:
    std::string text;
    TextComponent(const std::string &rawText, const int rawX, const int rawY, const unsigned int size, const bool bolded, const unsigned int hexColor, const int passedWindowWidth, const int passedWindowHeight);
    ~TextComponent();
    void rasterize(const int rawX, const int rawY);
    void render();
    void resize(const int passedWindowWidth, const int passedWindowHeight); // compatible adapter
    void resize(const int passedWindowWidth, const int passedWindowHeight, const int passedAvailableWidth); // more detailed control
    void sanitize(std::string &str);
    void updateHighlight();
    std::unique_ptr<sizeResponse> size() const;

    // input needed stuff for scrollin
    int rasterStartX = 0; // start reading text source at and place at destination 0
    int rasterStartY = 0;
    int rasterCropX = 0;
    int rasterCropY = 0;
    bool noWrap = false; // different than overflow but related
    bool textSelected = false;
    int availableWidth = 0;

    GLsizei textureWidth;
    GLsizei textureHeight;
    std::unique_ptr<unsigned char[]> textureData;

    // backgroundColor
    unsigned int backgroundColor;
};

#endif


code:
#include "AElement.h"
#include "../../parsers/markup/TagNode.h"
#include <iostream>
#include "../../app/browser/Browser.h"

extern const std::unique_ptr<Browser> browser;

AElement::AElement() {
    isInline = true;
}

std::unique_ptr<Component> AElement::renderer(const ElementRenderRequest &request) {
    TextNode *textNode = dynamic_cast<TextNode*>(request.node.get());
    if (textNode) {
        std::unique_ptr<Component> component = std::make_unique<TextComponent>(textNode->text, 0, 0, 12, false, 0x00FFFF, request.parentComponent->win->windowWidth, request.parentComponent->win->windowHeight);
        TagNode *tagNode = dynamic_cast<TagNode*>(textNode->parent.get());
        if (tagNode) {
            std::map<std::string, std::string>::const_iterator hrefPair = tagNode->properties.find("href");
            if (hrefPair != tagNode->properties.end()) {
                component->onClick = [request, hrefPair]() {
                    std::cout << "AElement::renderer:::onClick - Direct to: " << hrefPair->second << std::endl;
                    // new window
                    // new tab
                    // same documentComponent (iframe, document, tab)
                    browser->getActiveDocumentComponent()->focusedComponent = nullptr;
                    browser->getActiveDocumentComponent()->navTo(hrefPair->second);
                };
            }
        }
        return component;
    }
    return nullptr;
}


code:
#ifndef AELEMENT_H
#define AELEMENT_H

#include "Element.h"
#include "../components/Component.h"
#include "../components/TextComponent.h"
#include "../../parsers/markup/TextNode.h"

void navTo(std::string url); // forward declaration

class AElement : public Element {
public:
    AElement();
    virtual std::unique_ptr<Component> renderer(const ElementRenderRequest &request);
};

#endif


code:
#include "BUTTONElement.h"
#include "INPUTElement.h"
#include "../components/InputComponent.h"
#include "../components/ButtonComponent.h"
#include "../components/DocumentComponent.h"
#include "../../tools/WebResource.h"
#include "../../tools/Log.h"
#include "../../tools/FormData.h"

BUTTONElement::BUTTONElement() {
    isInline = true;
}

std::unique_ptr<Component> BUTTONElement::renderer(const ElementRenderRequest &request) {
    // const float rawX, const float rawY, const float rawWidth, const float rawHeight, const int windowWidth, const int windowHeight
    // what should our default size be?
    //std::cout << "INPUTElement::renderer - creating InputComponent at " << x << "x" << y << std::endl;
    TagNode *tagNode = dynamic_cast<TagNode*>(request.node.get());
    if (tagNode) {

        //std::cout << "Creating submit" << std::endl;
        std::unique_ptr<ButtonComponent> butComponent = std::make_unique<ButtonComponent>(0, 0, 62.0f, 13.0f, 0xA0A0A0FF, request.parentComponent->win->windowWidth, request.parentComponent->win->windowHeight);
        butComponent->value = "Button";
        butComponent->name = "button";
        butComponent->onClick=[tagNode, request]() {
            // recurse up to find oug form tag
            std::shared_ptr<Node> formNode = Node::findTagNodeParent("form", request.node);
            if (!formNode) {
                std::cout << "BUTTONElement::renderer:butComponent->onClick - Can't find form parent for submit" << std::endl;
                return;
            }
            if (!request.docComponent) {
                std::cout << "BUTTONElement::renderer:butComponent->onClick - Can't find documentComponent for submit" << std::endl;
                return;
            }
            TagNode *formTagNode = dynamic_cast<TagNode*>(formNode.get());
            auto formNodeActionIter = formTagNode->properties.find("action");
            if (formNodeActionIter == formTagNode->properties.end()) {
                std::cout << "Form has no action" << std::endl;
                return;
            }
            auto formData = buildFormData(formNode, nullptr);
            /*
            // add our name/value (because we skip all submit buttons, there can only be one)
            auto submitButtonNameValue = getTagNodeNameValue(*tagNode);
            if (submitButtonNameValue) {
                formData->insert(*submitButtonNameValue);
            }
            */

            auto formNodeMethodIter = formTagNode->properties.find("method");
            std::string method="GET";
            if (formNodeMethodIter != formTagNode->properties.end()) {
                method=formNodeMethodIter->second;
            }
            std::cout << "Form method is " << method << std::endl;

            if (method=="POST" || method=="post") {
                // need documentComponent
                URL uAction = request.docComponent->currentURL.merge(URL(formNodeActionIter->second));
                std::cout << "Action URL is " << uAction.toString() << std::endl;
                // download URL
                WebResource res = getOnlineWebResource(uAction, std::move(formData));
                request.docComponent->handleResource(res, uAction.toString());
            } else {
                // need documentComponent

                std::string queryString = "";
                if (formData) {
                    // iterator over formData
                    for (auto &entry : *formData) {
                        queryString += entry.first + "=" + entry.second + "&";
                    }
                    // strip last & off
                    queryString = queryString.substr(0, queryString.size() - 1);
                    std::cout << "queryString is " << queryString << std::endl;

                    // The moral of the story is, if you have binary (non-alphanumeric) data (or a significantly sized payload) to transmit, use multipart/form-data
                    auto search = queryString.find(" ");
                    if (search != std::string::npos) {
                        queryString.replace(search, 1, "+");
                    }
                }

                URL uAction = request.docComponent->currentURL.merge(URL(formNodeActionIter->second+"?"+queryString));
                std::cout << "Action URL is " << uAction.toString() << std::endl;
                // download URL
                WebResource res = getOnlineWebResource(uAction, nullptr);
                request.docComponent->handleResource(res, uAction.toString());
            }

        };
        return std::move(butComponent);
    }
    return nullptr;
}


code:
#ifndef BUTTONELEMENT_H
#define BUTTONELEMENT_H

#include "Element.h"
#include "../components/Component.h"
#include "../../parsers/markup/TextNode.h"

class BUTTONElement : public Element {
public:
    BUTTONElement();
    virtual std::unique_ptr<Component> renderer(const ElementRenderRequest &request);
};

#endif


code:
#include "DIVElement.h"

std::unique_ptr<Component> DIVElement::renderer(const ElementRenderRequest &request) {
    TextNode *textNode = dynamic_cast<TextNode*>(request.node.get());
    if (textNode) {
        //if (node->parent->children.size() == 1) {
        std::unique_ptr<Component> component = std::make_unique<TextComponent>(textNode->text, 0, 0, 12, false, 0x000000FF, request.parentComponent->win->windowWidth, request.parentComponent->win->windowHeight);
        return component;
        //}
    }
    return nullptr;
}


code:
#ifndef DIVELEMENT_H
#define DIVELEMENT_H

#include "Element.h"
#include "../components/Component.h"
#include "../components/TextComponent.h"
#include "../../parsers/markup/TextNode.h"

class DIVElement : public Element {
public:
    //DIVElement();
    virtual std::unique_ptr<Component> renderer(const ElementRenderRequest &request);
};

#endif


code:
#include "Element.h"

Element::~Element() {
}

std::unique_ptr<Component> Element::renderer(const ElementRenderRequest &request) {
    return nullptr;
}


code:
#ifndef ELEMENT_H
#define ELEMENT_H

#include <functional>
#include <memory>
#include "../components/Component.h"
#include "../graphical/renderers/glfw/Window.h"
#include "../../parsers/markup/Node.h"

class ElementRenderRequest {
public:
    // we read the attributes from node (color, size, positioning, font info)
    // and pass them to a newly made component
    // and then we set up behaviors for that new component
    std::shared_ptr<Node> node;

    // has really no purpose except to pass win
    // it was supposed to be for positions, but ComponentBuilder handles all that now
    // as an element we shouldn't really care too much about position
    // if we set up the component right, it'll handle all that
    std::shared_ptr<Component> parentComponent;
    // can get this from parentComponent
    //std::shared_ptr<Window> win;
    DocumentComponent *docComponent = nullptr;
};

// we define tokens in a language
// and convert them into a component (UI element)
class Element {
public:
    bool isInline = false;
    virtual ~Element();
    virtual std::unique_ptr<Component> renderer(const ElementRenderRequest &request);
};

#endif


code:
#include "H1Element.h"

std::unique_ptr<Component> H1Element::renderer(const ElementRenderRequest &request) {
    TextNode *textNode = dynamic_cast<TextNode*>(request.node.get());
    if (textNode) {
        return std::make_unique<TextComponent>(textNode->text, 0, 0, 24, true, 0x000000FF, request.parentComponent->win->windowWidth, request.parentComponent->win->windowHeight);
    }
    return nullptr;
}


code:
#ifndef H1ELEMENT_H
#define H1ELEMENT_H

#include "Element.h"
#include "../components/Component.h"
#include "../components/TextComponent.h"
#include "../../parsers/markup/TextNode.h"

class H1Element : public Element {
public:
    virtual std::unique_ptr<Component> renderer(const ElementRenderRequest &request);
};

#endif


code:
#include "H2Element.h"

std::unique_ptr<Component> H2Element::renderer(const ElementRenderRequest &request) {
    TextNode *textNode = dynamic_cast<TextNode*>(request.node.get());
    if (textNode) {
        return std::make_unique<TextComponent>(textNode->text, 0, 0, 18, true, 0x000000FF, request.parentComponent->win->windowWidth, request.parentComponent->win->windowHeight);
    }
    return nullptr;
}


code:
#ifndef H2ELEMENT_H
#define H2ELEMENT_H

#include "Element.h"
#include "../components/Component.h"
#include "../components/TextComponent.h"
#include "../../parsers/markup/TextNode.h"

class H2Element : public Element {
public:
    virtual std::unique_ptr<Component> renderer(const ElementRenderRequest &request);
};

#endif


code:
#include "H3Element.h"

std::unique_ptr<Component> H3Element::renderer(const ElementRenderRequest &request) {
    TextNode *textNode = dynamic_cast<TextNode*>(request.node.get());
    if (textNode) {
        return std::make_unique<TextComponent>(textNode->text, 0, 0, 14, true, 0x000000FF, request.parentComponent->win->windowWidth, request.parentComponent->win->windowHeight); // Should be 14.04pt
    }
    return nullptr;
}


code:
#ifndef H3ELEMENT_H
#define H3ELEMENT_H

#include "Element.h"
#include "../components/Component.h"
#include "../components/TextComponent.h"
#include "../../parsers/markup/TextNode.h"

class H3Element : public Element {
public:
    virtual std::unique_ptr<Component> renderer(const ElementRenderRequest &request);
};

#endif


code:
#include "INPUTElement.h"
#include "../components/InputComponent.h"
#include "../components/ButtonComponent.h"
#include "../components/DocumentComponent.h"
#include "../../parsers/markup/html/HTMLParser.h"
#include "../../tools/Log.h"
#include "../../tools/FormData.h"
#include <ctime>

INPUTElement::INPUTElement() {
    isInline = true;
}

std::unique_ptr<Component> INPUTElement::renderer(const ElementRenderRequest &request) {
    // const float rawX, const float rawY, const float rawWidth, const float rawHeight, const int windowWidth, const int windowHeight
    // what should our default size be?
    //std::cout << "INPUTElement::renderer - creating InputComponent at " << x << "x" << y << std::endl;
    TagNode *tagNode = dynamic_cast<TagNode*>(request.node.get());
    if (tagNode) {
        std::string type = "text";
        auto propIter = tagNode->properties.find("type");
        if (propIter != tagNode->properties.end()) {
            //std::cout << "input is of type: " << propIter->second << std::endl;
            type = propIter->second;
        }
        //std::cout << "type is " << type << std::endl;
        if (type == "text") {
            std::unique_ptr<InputComponent> inputComponent = std::make_unique<InputComponent>(0, 0, 125.0f, 13.0f, request.parentComponent->win->windowWidth, request.parentComponent->win->windowHeight);
            inputComponent->node = tagNode;
            inputComponent->name = "textInput";
            auto propIter2 = tagNode->properties.find("value");
            if (propIter2 != tagNode->properties.end()) {
                inputComponent->setValue(propIter2->second);
            }
            return std::move(inputComponent);
        } else if (type ==  "submit") {
            //std::cout << "Creating submit" << std::endl;
            std::unique_ptr<ButtonComponent> butComponent = std::make_unique<ButtonComponent>(0, 0, 62.0f, 13.0f, 0xA0A0A0FF, request.parentComponent->win->windowWidth, request.parentComponent->win->windowHeight);
            auto propIter3 = tagNode->properties.find("value");
            if (propIter3 != tagNode->properties.end()) {
                butComponent->value = propIter3->second;
                // resize button to text size
                butComponent->resizeToTextSize();
            } else {
                butComponent->value = "Submit";
            }
            butComponent->onClick=[tagNode, request]() {
                // recurse up to find oug form tag
                std::shared_ptr<Node> formNode = Node::findTagNodeParent("form", request.node);
                if (!formNode) {
                    std::cout << "INPUTElement::renderer:butComponent->onClick - Can't find form parent for submit" << std::endl;
                    return;
                }
                if (!request.docComponent) {
                    std::cout << "INPUTElement::renderer:butComponent->onClick - Can't find documentComponent for submit" << std::endl;
                    return;
                }

                TagNode *formTagNode = dynamic_cast<TagNode*>(formNode.get());
                auto formNodeActionIter = formTagNode->properties.find("action");
                if (formNodeActionIter == formTagNode->properties.end()) {
                    std::cout << "Form has no action" << std::endl;
                    return;
                }

                auto formNodeMethodIter = formTagNode->properties.find("method");
                std::string method="GET";
                if (formNodeMethodIter != formTagNode->properties.end()) {
                    method=formNodeMethodIter->second;
                }
                std::cout << "Form method is " << method << std::endl;

                auto formData = buildFormData(formNode, nullptr);
                // add our name/value (because we skip all submit buttons, there can only be one)
                auto submitButtonNameValue = tagNode->getTagNodeNameValue();
                if (submitButtonNameValue) {
                    formData->insert(*submitButtonNameValue);
                }

                if (method=="POST" || method=="post") {
                    // need documentComponent
                    URL uAction = request.docComponent->currentURL.merge(URL(formNodeActionIter->second));
                    std::cout << "Action URL is " << uAction.toString() << std::endl;
                    // download URL
                    WebResource res = getOnlineWebResource(uAction, std::move(formData));
                    request.docComponent->handleResource(res, uAction.toString());
                } else {
                    // need documentComponent

                    std::string queryString = "";
                    if (formData) {
                        // iterator over formData
                        for (auto &entry : *formData) {
                            queryString += entry.first + "=" + entry.second + "&";
                        }
                        // strip last & off
                        queryString = queryString.substr(0, queryString.size() - 1);
                        std::cout << "queryString is " << queryString << std::endl;

                        // The moral of the story is, if you have binary (non-alphanumeric) data (or a significantly sized payload) to transmit, use multipart/form-data
                        auto search = queryString.find(" ");
                        if (search != std::string::npos) {
                            queryString.replace(search, 1, "+");
                        }
                    }

                    URL uAction = request.docComponent->currentURL.merge(URL(formNodeActionIter->second+"?"+queryString));
                    std::cout << "Action URL is " << uAction.toString() << std::endl;
                    // download URL
                    WebResource res = getOnlineWebResource(uAction, nullptr);
                    request.docComponent->handleResource(res, uAction.toString());
                }

            };
            return std::move(butComponent);
        } else if (type == "hidden") {
            // we don't need a UI component but we do belong in the form
        } else {
            // submit, button, checkbox, file
            //std::cout << "INPUTElement::renderer - ignoring input is of type: " << type << std::endl;
        }
    }
    return nullptr;
}


code:
#ifndef INPUTELEMENT_H
#define INPUTELEMENT_H

#include "Element.h"
#include "../components/Component.h"

class INPUTElement : public Element {
public:
    INPUTElement();
    virtual std::unique_ptr<Component> renderer(const ElementRenderRequest &request);
};

#endif


code:
#include "LIElement.h"

std::unique_ptr<Component> LIElement::renderer(const ElementRenderRequest &request) {
    TextNode *textNode = dynamic_cast<TextNode*>(request.node.get());
    if (textNode) {
        return std::make_unique<TextComponent>("• " + textNode->text, 0, 0, 12, false, 0x000000FF, request.parentComponent->win->windowWidth, request.parentComponent->win->windowHeight);
    }
    return nullptr;
}


code:
#ifndef LIELEMENT_H
#define LIELEMENT_H

#include "Element.h"
#include "../components/Component.h"
#include "../components/TextComponent.h"
#include "../../parsers/markup/TextNode.h"

class LIElement : public Element {
public:
    virtual std::unique_ptr<Component> renderer(const ElementRenderRequest &request);
};

#endif


code:
#include "LINKElement.h"

std::unique_ptr<Component> LINKElement::renderer(const ElementRenderRequest &request) {
    TagNode *tagNode = dynamic_cast<TagNode*>(request.node.get());
    if (tagNode) {

    }
    return nullptr;
}


code:
#ifndef LINKELEMENT_H
#define LINKELEMENT_H

#include "Element.h"
#include "../components/Component.h"

class LINKElement : public Element {
public:
    virtual std::unique_ptr<Component> renderer(const ElementRenderRequest &request);
};

#endif