code:
// STUB! This will eventually be replaced with a pdcurses-window
#if 0
#include "../../URL.h"
#include "TextWindow.h"
#include "../../html/TagNode.h"
#include "../../html/TextNode.h"

#include "../../networking/HTTPRequest.h"
#include "../../html/HTMLParser.h"
#include "../../StringUtils.h"
#include "../../URL.h"
#include "../../Log.h"
#include "../components/BoxComponent.h"
#include "../components/AnimeComponent.h"
#include "../components/DocumentComponent.h"
#include "../components/TabbedComponent.h"
#include "../components/TextComponent.h"
#include "../components/TabbedComponent.h"
#include "../components/InputComponent.h"
#include "../opengl/Shader.h"

#include <cmath>
#include <ctime>
#include <iostream>

Window::~Window() {
    glfwTerminate();
}

bool Window::init() {
    if (!initGLFW()) {
        return false;
    }
    if (!initGLEW()) {
        return false;
    }
    initGL();
    GLenum glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "window::init - post initGL - not ok: " << glErr << std::endl;
    }

    // set proper scroll position
    transformMatrix[13] = 2;
    transformMatrixDirty = true;

    // configure root component
    //rootComponent = std::make_shared<Component>();
    rootComponent->name = "rootComponent";
    //rootComponent->y = 0;

    // set up our UI with a new window

    // (re)build UI (top to bottom)
    //std::cout << "Window::setDOM window size: " << windowWidth << "x" << windowHeight << std::endl;

    // layout back to front
    // 4 multigroups
    // 1 -> mascot
    // 2 -> tabbed
    // 3 -> box
    // 4 -> input

    //Mascot
    std::shared_ptr<AnimeComponent> mascot = std::make_unique<AnimeComponent>(windowWidth * 0.75f, 0.0f, 512.0f, 512.0f, windowWidth, windowHeight);
    mascot->uiControl.x = { 75,   0 }; // 75% width
    mascot->uiControl.y = {  0,   0 }; // 0px
    mascot->uiControl.w = {  0, 512 }; // 512px
    mascot->uiControl.h = {  0, 512 }; // 512px
    mascot->boundToPage = false;
    mascot->isPickable = false;
    mascot->name = "mascot";
    // add it to our components
    mascot->setParent(rootComponent);
    rootComponent->children.push_back(mascot);

    // create tabbed component
    // doc text is still showing up ontop of buttons
    std::shared_ptr<TabbedComponent> tabbedComponent = std::make_shared<TabbedComponent>(0, 0, static_cast<float>(windowWidth), static_cast<float>(windowHeight - 64), windowWidth, windowHeight);
    tabbedComponent->name = "tabbedComponent";
    tabbedComponent->y = -64;
    tabbedComponent->uiControl.x = { 0  ,   0 }; // 0
    tabbedComponent->uiControl.y = { 0  , -64 }; // -64px
    tabbedComponent->uiControl.w = { 100,   0 }; // 100%
    tabbedComponent->uiControl.h = { 100, -64 }; // 100% - 64px
    tabbedComponent->boundToPage = false;
    tabbedComponent->win = this;
    // add it to our components
    tabbedComponent->setParent(rootComponent);
    rootComponent->children.push_back(tabbedComponent);

    // Nav background
    std::shared_ptr<BoxComponent> navBackground = std::make_unique<BoxComponent>(0.0f, windowHeight - 64.0f, windowWidth, 64.0f, 0x888888FF, windowWidth, windowHeight);
    //navBackground->y = -64;
    navBackground->uiControl.y = { 100, -64 }; // -64px
    navBackground->uiControl.w = { 100,   0 }; // 100%
    navBackground->uiControl.h = {   0,  64 }; // 64px
    navBackground->isPickable = false;
    navBackground->name = "navBackground";
    navBackground->boundToPage = false;
    // add it to our components
    navBackground->setParent(rootComponent);
    rootComponent->children.push_back(navBackground);

    std::shared_ptr<BoxComponent> navBackButton = std::make_unique<BoxComponent>(32.0f, windowHeight - 48.0f, 32.0f, 32.0f, 0x000000FF, windowWidth, windowHeight);
    navBackButton->uiControl.x = {   0,  32 }; // 32px
    navBackButton->uiControl.y = { 100, -48 }; // top - 48px
    navBackButton->uiControl.w = {   0,  32 }; // 32px
    navBackButton->uiControl.h = {   0,  32 }; // 32px
    navBackButton->onClick=[this]() {
        //std::cout << "Back" << std::endl;
        TabbedComponent *pTabComponent = dynamic_cast<TabbedComponent*>(this->tabComponent.get());
        if (pTabComponent) {
            if (pTabComponent->selectedTabId) {
                pTabComponent->selectedTab->get()->history->back();
            }
        }
    };
    navBackButton->onMouseover = [navBackButton, this]() {
        //std::cout << "navBackButton->onMouseover" << std::endl;
        navBackButton->changeColor(0x880000FF);
        this->renderDirty = true;
    };
    navBackButton->onMouseout = [navBackButton, this]() {
        //std::cout << "navBackButton->onMouseout" << std::endl;
        navBackButton->changeColor(0x000000FF);
        this->renderDirty = true;
    };

    navBackButton->name = "navBackButton";
    navBackButton->boundToPage = false;
    // add it to our components
    navBackButton->setParent(rootComponent);
    rootComponent->children.push_back(navBackButton);

    std::shared_ptr<BoxComponent> navForwardButton = std::make_unique<BoxComponent>(74.0f, windowHeight - 48.0f, 32.0f, 32.0f, 0x000000FF, windowWidth, windowHeight);
    navForwardButton->uiControl.x = {   0,  74 }; // 74px
    navForwardButton->uiControl.y = { 100, -48 }; // top - 48px
    navForwardButton->uiControl.w = {   0,  32 }; // 32px
    navForwardButton->uiControl.h = {   0,  32 }; // 32px

    navForwardButton->onClick=[this]() {
        //std::cout << "Forward" << std::endl;
        TabbedComponent *pTabComponent = dynamic_cast<TabbedComponent*>(this->tabComponent.get());
        if (pTabComponent) {
            if (pTabComponent->selectedTabId) {
                pTabComponent->selectedTab->get()->history->forward();
            }
        }
    };
    navForwardButton->onMouseover = [navForwardButton, this]() {
        //std::cout << "navForwardButton->onMouseover" << std::endl;
        navForwardButton->changeColor(0x008800FF);
        this->renderDirty = true;
    };
    navForwardButton->onMouseout = [navForwardButton, this]() {
        //std::cout << "navForwardButton->onMouseout" << std::endl;
        navForwardButton->changeColor(0x000000FF);
        this->renderDirty = true;
    };
    navForwardButton->name = "navForwardButton";
    navForwardButton->boundToPage = false;
    // add it to our components
    //navForwardButton->parent = rootComponent;
    navForwardButton->setParent(rootComponent);
    rootComponent->children.push_back(navForwardButton);

    std::shared_ptr<BoxComponent> navRefreshButton = std::make_unique<BoxComponent>(116.0f, windowHeight - 48.0f, 32.0f, 32.0f, 0x000000FF, windowWidth, windowHeight);
    navRefreshButton->uiControl.x = {   0, 116 }; // 116px
    navRefreshButton->uiControl.y = { 100, -48 }; // top - 48px
    navRefreshButton->uiControl.w = {   0,  32 }; // 32px
    navRefreshButton->uiControl.h = {   0,  32 }; // 32px
    navRefreshButton->name = "navRefreshButton";
    navRefreshButton->boundToPage = false;
    navRefreshButton->onClick=[this]() {
        std::cout << "Refreshing " << this->currentURL << std::endl;
        this->navTo(this->currentURL.toString());
    };
    navRefreshButton->onMouseover = [navRefreshButton, this]() {
        //std::cout << "navRefreshButton->onMouseover" << std::endl;
        navRefreshButton->changeColor(0x000088FF);
        this->renderDirty = true;
    };
    navRefreshButton->onMouseout = [navRefreshButton, this]() {
        //std::cout << "navRefreshButton->onMouseout" << std::endl;
        navRefreshButton->changeColor(0x000000FF);
        this->renderDirty = true;
    };    // add it to our components
    navRefreshButton->setParent(rootComponent);
    rootComponent->children.push_back(navRefreshButton);

    // Address Bar
    std::shared_ptr<InputComponent> navAddressBar = std::make_unique<InputComponent>(192.0f, windowHeight - 48.0f, windowWidth - 384.0f, 24.0f, windowWidth, windowHeight);
    // add it to our components
    navAddressBar->x = 192.0f;
    navAddressBar->y = windowHeight-48.0f;
    navAddressBar->uiControl.x = {   0,  192 }; // 192px
    navAddressBar->uiControl.y = { 100,  -48 }; // top - 48px
    navAddressBar->uiControl.w = { 100, -384 }; // w - 384px
    navAddressBar->uiControl.h = {   0,   24 }; // 24px
    navAddressBar->name = "navAddressBar";
    navAddressBar->boundToPage = false;
    navAddressBar->win = this;
    //navAddressBar->y = -48; // this works but breaks picking
    navAddressBar->value = currentURL.toString();
    navAddressBar->updateText();
    navAddressBar->onEnter=[this](std::string value) {
        std::cout << "navAddressBar::onEnter got " << value << std::endl;
        this->navTo(value);
    };
    this->addressComponent = navAddressBar;

    // we do this to prevent flashing
    render();
    renderDirty = true;

    //std::cout << "placing inputComponent at " << static_cast<int>(navAddressBar->x) << "," << static_cast<int>(navAddressBar->y) << std::endl;
    navAddressBar->setParent(rootComponent);
    rootComponent->children.push_back(navAddressBar);

    /*
     // textTest
     // 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);
     std::shared_ptr<TextComponent> textTest = std::make_unique<TextComponent>("Test", -99, -99, 12, false, 0x000000ff, windowWidth, windowHeight);
     textTest->x=192.0f;
     textTest->y=-64.0f;
     textTest->isPickable = false;
     // add it to our components
     textTest->parent = rootComponent;
     rootComponent->children.push_back(textTest);
     // we really don't want to layout because that's moves us relative to parent
     // and this is static layout we want, not a free flow
     //textTest->layout(); // to rasterize
     textTest->resize(windowWidth, windowHeight);
     */

    // set tab component
    this->tabComponent = tabbedComponent;

    return true;
}

bool Window::initGLFW() {
    if (!glfwInit()) {
        std::cout << "Could not initialize GLFW" << std::endl;
        return false;
    }

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    //glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);

    glfwSetErrorCallback([](int error, const char* description) {
        std::cout << "glfw error [" << error << "]:" << description << std::endl;
    });

    window = glfwCreateWindow(windowWidth, windowHeight, "NetRunner", nullptr, nullptr);
    if (!window) {
        glfwTerminate();
        std::cout << "Could not create window" << std::endl;
        return false;
    }
    // replace first parameter of all these callbacks with our window object instead of a GLFWwindow
    glfwSetWindowUserPointer(window, this);
    // set window w/h
    //glfwGetFramebufferSize(window, &windowWidth, &windowHeight); // in pixels
    glfwGetWindowSize(window, &windowWidth, &windowHeight); // use screen coordinates (not pixels) more retina friendly

    // set up event callbacks
    glfwSetFramebufferSizeCallback(window, [](GLFWwindow *win, int width, int height) {
        glViewport(0, 0, width, height);
    });
    glfwSetWindowSizeCallback(window, [](GLFWwindow *win, int width, int height) {
        Window *thiz = reinterpret_cast<Window*>(glfwGetWindowUserPointer(win));
        thiz->onResize(width, height);
    });
    cursorHand = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
    cursorArrow = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
    cursorIbeam = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR);
    glfwSetCursorPosCallback(window, [](GLFWwindow *win, double xPos, double yPos) {
        Window *thiz = reinterpret_cast<Window*>(glfwGetWindowUserPointer(win));
        thiz->cursorX = xPos;
        thiz->cursorY = yPos;
        //std::cout << "Window::Window:onMousemove - at " << static_cast<int>(xPos) << "," << static_cast<int>(yPos) << std::endl;
        if (xPos < 0 || yPos < 0) return;
        if (xPos > thiz->windowWidth || yPos > thiz->windowHeight) return;
        // p. much worthless on double
        /*
        static double lx = 0;
        static double ly = 0;
        if (lx == xPos && ly == yPos) {
            return;
        }
        lx = xPos;
        ly = yPos;
        std::cout << "Window::Window:onMousemove - noCache" << std::endl;
        */
        //std::cout << "Window::Window:onMousemove - begin search" << std::endl;
        //std::cout << "Window::Window:onMousemove - windowHeight: " << thiz->windowHeight << " cursorY: " << thiz->cursorY << " scrollY: " << thiz->transformMatrix[13] << std::endl;
        std::shared_ptr<Component> newHover = thiz->searchComponentTree(thiz->rootComponent, thiz->cursorX, (thiz->windowHeight - thiz->cursorY) + ((-thiz->transformMatrix[13] / 2) * thiz->windowHeight));
        if (newHover != thiz->hoverComponent) {
            if (thiz->hoverComponent && thiz->hoverComponent->onMouseout) {
                thiz->hoverComponent->onMouseout();
            }
            if (newHover && newHover->onMouseover) {
                newHover->onMouseover();
            }
            thiz->hoverComponent = newHover;
        }
        if (thiz->hoverComponent) {
            //std::cout << "Window::Window:onMousemove - hover " << typeOfComponent(thiz->hoverComponent) << std::endl;
            if (thiz->hoverComponent->onClick) {
                glfwSetCursor(thiz->window, thiz->cursorHand);
            } else {
                TextComponent *textComponent = dynamic_cast<TextComponent*>(thiz->hoverComponent.get());
                InputComponent *inputComponent = dynamic_cast<InputComponent*>(thiz->hoverComponent.get());
                if (textComponent || inputComponent) {
                    glfwSetCursor(thiz->window, thiz->cursorIbeam);
                }
                // otherwise we could be an Box/Anime or Document Component
            }
            if (thiz->hoverComponent->onMousemove) {
                // this could communicate the cursor to use
                thiz->hoverComponent->onMousemove(thiz->cursorX, thiz->cursorY);
            }
        } else {
            glfwSetCursor(thiz->window, thiz->cursorArrow);
        }
    });
    glfwSetScrollCallback(window, [](GLFWwindow *win, double xOffset, double yOffset) {
        Window *thiz = reinterpret_cast<Window*>(glfwGetWindowUserPointer(win));
        // yOffset is a delta vector
        thiz->transformMatrix[13] += -yOffset * 0.1;
        if (thiz->hoverComponent) {
            //std::cout << "scroll - hovering over a component" << std::endl;
            if (thiz->hoverComponent->onWheel) {
                //std::cout << "scroll - hovering over a scrollabel component" << std::endl;
                thiz->hoverComponent->onWheel(xOffset * 100, yOffset * 100);
            }
        }

        // 2.0 is one screen height
        // we draw from 0 downwards (y+), so can't scroll past our starting draw point
        if (thiz->transformMatrix[13] < 2) {
            thiz->transformMatrix[13] = 2;
        }
        // calculate scroll max by calculating how many screens are in the rootComponent's Height
        if (thiz->transformMatrix[13] > std::max( thiz->rootComponent->height / thiz->windowHeight * 2.0f, 2.0f)) {
            thiz->transformMatrix[13] = std::max( thiz->rootComponent->height / thiz->windowHeight * 2.0f, 2.0f);
        }
        //std::cout << "scroll y is at " << thiz->transformMatrix[13] << "/" << static_cast<int>((thiz->transformMatrix[13]*10000) << std::endl;
        thiz->transformMatrixDirty = true;
    });
    glfwSetMouseButtonCallback(window, [](GLFWwindow *win, int button, int action, int mods) {
        Window *thiz = reinterpret_cast<Window*>(glfwGetWindowUserPointer(win));
        if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) {
            //std::cout << "left press" << std::endl;
            if (thiz->hoverComponent) {
                //std::cout << "focus event" << std::endl;
                if (thiz->focusedComponent != thiz->hoverComponent) {
                    // blur old component
                    if (thiz->focusedComponent) {
                        if (thiz->focusedComponent->onBlur) {
                            thiz->focusedComponent->onBlur();
                        }
                    }
                    // focus new component
                    if (thiz->hoverComponent->onFocus) {
                        thiz->hoverComponent->onFocus();
                    }
                }
                thiz->focusedComponent = thiz->hoverComponent;
                if (thiz->focusedComponent->onMousedown) {
                    //std::cout << "click event" << std::endl;
                    thiz->focusedComponent->onMousedown(thiz->cursorX, thiz->cursorY);
                }
            }
        }
        if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_RELEASE) {
            //std::cout << "left release" << std::endl;
            if (thiz->hoverComponent) {
                //std::cout << "focus event" << std::endl;
                if (thiz->focusedComponent != thiz->hoverComponent) {
                    // blur old component
                    if (thiz->focusedComponent) {
                        if (thiz->focusedComponent->onBlur) {
                            thiz->focusedComponent->onBlur();
                        }
                    }
                    // focus new component
                    if (thiz->hoverComponent->onFocus) {
                        thiz->hoverComponent->onFocus();
                    }
                }
                thiz->focusedComponent = thiz->hoverComponent;
                /*
                InputComponent *inputComponent = dynamic_cast<InputComponent*>(thiz->focusedComponent.get());
                if (inputComponent) {
                    std::cout << "inputComponent focus" << std::endl;
                }
                 */
                if (thiz->focusedComponent && thiz->focusedComponent->onMouseup) {
                    //std::cout << "click event" << std::endl;
                    thiz->focusedComponent->onMouseup(thiz->cursorX, thiz->cursorY);
                }
                if (thiz->focusedComponent && thiz->focusedComponent->onClick) {
                    //std::cout << "click event" << std::endl;
                    thiz->focusedComponent->onClick();
                }
            }
        }
    });

    // works with utf-32 but we'll lkeep the low level for now
    /*
    glfwSetCharCallback(window, [](GLFWwindow* win, unsigned int codepoint) {
        Window *thiz = reinterpret_cast<Window*>(glfwGetWindowUserPointer(win));
        std::cout << "Window::glfwSetCharCallback - codepoint: " << codepoint << std::endl;
    });
    */

    glfwSetKeyCallback(window, [](GLFWwindow *win, int key, int scancode, int action, int mods) {
        Window *thiz = reinterpret_cast<Window*>(glfwGetWindowUserPointer(win));
        // we're focused on something
        //std::cout << "glfwSetKeyCallback" << std::endl;
        if (thiz->focusedComponent) {
            //std::cout << "glfwSetKeyCallback - focused on " << typeOfComponent(thiz->focusedComponent) << std::endl;
            TabbedComponent *p_tabComponent = dynamic_cast<TabbedComponent*>(thiz->focusedComponent.get());
            if (p_tabComponent) {
                // repeat or key up
                if (action == 2 || action == 0) {
                    if (p_tabComponent->onKeyPress) {
                        p_tabComponent->onKeyPress(key, scancode, action, mods);
                    }
                    if (action == 0) {
                        if (p_tabComponent->onKeyup) {
                            p_tabComponent->onKeyup(key, scancode, action, mods);
                        }
                    }
                }
                return;
            }

            DocumentComponent *docComponent = dynamic_cast<DocumentComponent*>(thiz->focusedComponent.get());
            if (docComponent) {
                if (action == 0) {
                    if (docComponent->onKeyup) {
                        docComponent->onKeyup(key, scancode, action, mods);
                    }
                }
                return;
            }
            InputComponent *inputComponent = dynamic_cast<InputComponent*>(thiz->focusedComponent.get());
            if (inputComponent) {
                //std::cout << "inputComponent is focsued, 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 << "enter!" << std::endl;
                        if (inputComponent->onEnter) {
                            inputComponent->onEnter(inputComponent->value);
                        }
                    } 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
                    }
                }
                return;
            }
        }
        switch (key) {
            case GLFW_KEY_1:
            case GLFW_KEY_2:
            case GLFW_KEY_3:
            case GLFW_KEY_4:
            case GLFW_KEY_5:
            case GLFW_KEY_6:
            case GLFW_KEY_7:
            case GLFW_KEY_8:
            case GLFW_KEY_9:
            case GLFW_KEY_0:
                std::cout << "Key was pressed: " << key << std::endl;
                //thiz->transformMatrixIndex = key - GLFW_KEY_0;
                //std::cout << thiz->transformMatrixIndex << std::endl;
                break;
            default:
                break;
        }
        if (key == GLFW_KEY_Q && action == GLFW_PRESS) {
            std::cout << "Q was pressed. Exiting." << std::endl;
            exit(0);
        }
        if (key == GLFW_KEY_E && action == GLFW_RELEASE) {
            printf("Printing NodeTree\n\n");
            TabbedComponent *p_TabComponent = dynamic_cast<TabbedComponent*>(thiz->tabComponent.get());
            if (p_TabComponent) {
                DocumentComponent *p_DocComponent = dynamic_cast<DocumentComponent*>(p_TabComponent->documentComponent.get());
                if (p_DocComponent) {
                    printNode(p_DocComponent->domRootNode, 1);
                }
            }
            printf("\n\n");
        }
        if (key == GLFW_KEY_D && action == GLFW_RELEASE) {
            printf("Printing ComponentTree\n\n");
            TabbedComponent *p_TabComponent = dynamic_cast<TabbedComponent*>(thiz->tabComponent.get());
            if (p_TabComponent) {
                DocumentComponent *p_DocComponent = dynamic_cast<DocumentComponent*>(p_TabComponent->documentComponent.get());
                if (p_DocComponent) {
                    Component::printComponentTree(p_DocComponent->rootComponent, 0);
                }
            }
            printf("\n\n");
        }
        if (key == GLFW_KEY_F && action == GLFW_RELEASE) {
            printf("Printing UI ComponentTree\n\n");
            Component::printComponentTree(thiz->rootComponent, 0);
            printf("\n\n");
        }
        int yOffsetScroll = 1;
        if (key == GLFW_KEY_PAGE_UP && (action == GLFW_PRESS || action == GLFW_REPEAT)) {
            TabbedComponent *p_TabComponent = dynamic_cast<TabbedComponent*>(thiz->tabComponent.get());
            if (p_TabComponent) {
                DocumentComponent *p_DocComponent = dynamic_cast<DocumentComponent*>(p_TabComponent->documentComponent.get());
                if (p_DocComponent) {
                    p_DocComponent->transformMatrix[13] += -yOffsetScroll * 0.1;
                    p_DocComponent->transformMatrixDirty = true;
                    thiz->renderDirty = true;
                }
            }
        }
        if (key == GLFW_KEY_PAGE_DOWN && (action == GLFW_PRESS || action == GLFW_REPEAT)) {
            TabbedComponent *p_TabComponent = dynamic_cast<TabbedComponent*>(thiz->tabComponent.get());
            if (p_TabComponent) {
                DocumentComponent *p_DocComponent = dynamic_cast<DocumentComponent*>(p_TabComponent->documentComponent.get());
                if (p_DocComponent) {
                    p_DocComponent->transformMatrix[13] += yOffsetScroll * 0.1;
                    p_DocComponent->transformMatrixDirty = true;
                    thiz->renderDirty = true;
                }
            }
        }

        // FIXME Scrolling with this can scroll past boundary whereas the same doesn't happen with
        // scrolling wheel
        if (key == GLFW_KEY_J && (action == GLFW_PRESS || action == GLFW_REPEAT)) {
            std::cout << "J is/was pressed. Scrolling down." << std::endl;
            TabbedComponent *p_TabComponent = dynamic_cast<TabbedComponent*>(thiz->tabComponent.get());
            if (p_TabComponent) {
                DocumentComponent *p_DocComponent = dynamic_cast<DocumentComponent*>(p_TabComponent->documentComponent.get());
                if (p_DocComponent) {
                    p_DocComponent->transformMatrix[13] += yOffsetScroll * 0.1;
                    p_DocComponent->transformMatrixDirty = true;
                    thiz->renderDirty = true;
                }
            }
        }
        // FIXME Scrolling with this can scroll past boundary whereas the same doesn't happen with
        // scrolling wheel
        if (key == GLFW_KEY_K && (action == GLFW_PRESS || action == GLFW_REPEAT)) {
            std::cout << "K is/was pressed. Scrolling up." << std::endl;
            TabbedComponent *p_TabComponent = dynamic_cast<TabbedComponent*>(thiz->tabComponent.get());
            if (p_TabComponent) {
                DocumentComponent *p_DocComponent = dynamic_cast<DocumentComponent*>(p_TabComponent->documentComponent.get());
                if (p_DocComponent) {
                    p_DocComponent->transformMatrix[13] += -yOffsetScroll * 0.1;
                    p_DocComponent->transformMatrixDirty = true;
                    thiz->renderDirty = true;
                }
            }
        }
    });

    glfwMakeContextCurrent(window);

    return true;
}

bool Window::initGLEW() const {
    glewExperimental = GL_TRUE;
    const GLenum err = glewInit();
    if (err != GLEW_OK) {
        std::cout << "Could not initialize GLEW: " << glewGetErrorString(err) << std::endl;
        return false;
    }
    return true;
}

bool Window::initGL() {
    const GLubyte *renderer = glGetString(GL_RENDERER);
    const GLubyte *version = glGetString(GL_VERSION);
    std::cout << "Renderer: " << renderer << std::endl;
    std::cout << "Version: " << version << std::endl;

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glClearColor(0.8f, 0.8f, 0.8f, 0.8f);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    GLenum glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "window::initGL - blend, clear, texParam - not ok: " << glErr << std::endl;
    }

    //std::cout << "OpenGL is set up" << std::endl;

    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);

    return true;
}

void Window::onResize(int passedWidth, int passedHeight) {
    this->windowWidth = passedWidth;
    this->windowHeight = passedHeight;
    this->delayResize = 1;
}

void Window::resize() {
    //std::cout << "Window::resize" << std::endl;
    const std::clock_t begin = clock();
    //resizeComponentTree(rootComponent, windowWidth, windowHeight);

    //this->printComponentTree(rootComponent, 0);

    //this->buildUI();
    rootComponent->windowWidth = windowWidth;
    rootComponent->windowHeight = windowHeight;
    rootComponent->layout();

    const std::clock_t end = clock();
    std::cout << "Window::resize - resized components in: " << std::fixed << ((static_cast<double>(end - begin)) / CLOCKS_PER_SEC) << std::scientific << " seconds" << std::endl;
    //this->printComponentTree(rootComponent, 0);

    // 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;
    }

    //thiz->printComponentTree(thiz->rootComponent, 0);
    //printComponentTree(rootComponent, 0);
    renderDirty = true;
}

void Window::render() {
    if (delayResize) {
        delayResize--;
        if (delayResize) {
            return;
        }
        //std::cout << "Window::render - restarting drawing" << std::endl;
        this->resize();
    }
    /*
    if (domDirty) {
        const std::clock_t begin = clock();
        createComponentTree(domRootNode, rootComponent);
        const std::clock_t end = clock();
        std::cout << "built & laid out window components in: " << std::fixed << ((static_cast<double>(end - begin)) / CLOCKS_PER_SEC) << std::scientific << " seconds" << std::endl;
        //printComponentTree(rootComponent, 0);
        //std::cout << "Window printComponentTree end" << std::endl;
        domDirty = false;
        renderDirty = true;
    }
    */
    if (renderDirty || transformMatrixDirty) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        GLenum glErr=glGetError();
        if(glErr != GL_NO_ERROR) {
            std::cout << "window::render - box render start - not ok: " << glErr << std::endl;
        }

		Shader *textureShader = shaderLoader.getShader(VertexShader("TextureShader.vert"),
				FragmentShader("TextureShader.frag"));
        if (!textureShader) {
            std::cout << "Cant load shader" << std::endl;
            return;
        }
		textureShader->bind();
        glErr=glGetError();
        if(glErr != GL_NO_ERROR) {
            std::cout << "window::render - glUseProgram - not ok: " << glErr << std::endl;
        }

        // draw anime 1st, tabbed components 2nd
        // then box shit
        // text last
        renderComponentType("anime", rootComponent);

        renderComponentType("tab", rootComponent);
		textureShader->bind();

        //GLint transformLocation = glGetUniformLocation(textureProgram, "transform");
        //glUniformMatrix4fv(transformLocation, 1, GL_FALSE, transformMatrix);
        renderComponentType("box", rootComponent);
        renderComponentType("input", rootComponent);

        // it's quick but done on scroll
		Shader *fontShader = shaderLoader.getShader(VertexShader("FontShader.vert"),
				FragmentShader("FontShader.frag"));
		fontShader->bind();
        // reset both, since components can change this
        // god we may have to reset this after each component render...
        // maybe we don't need too
        if (transformMatrixDirty) {
            //const std::clock_t begin = clock();
            GLint transformLocation = fontShader->uniform("transform");
            glErr=glGetError();
            if(glErr != GL_NO_ERROR) {
                std::cout << "window::render - glGetUniformLocation - not ok: " << glErr << std::endl;
            }
            glUniformMatrix4fv(transformLocation, 1, GL_FALSE, transformMatrix);
            glErr=glGetError();
            if(glErr != GL_NO_ERROR) {
                std::cout << "window::render - glUniformMatrix4fv - not ok: " << glErr << std::endl;
            }

            // didn't change much (could be we didnt' select the textureProgram first)
            //GLint transformLocation2 = glGetUniformLocation(textureProgram, "transform");
            //glUniformMatrix4fv(transformLocation2, 1, GL_FALSE, transformMatrix);

            //const std::clock_t end = clock();
            //std::cout << "Updated font matrix in: " << std::fixed << ((static_cast<double>(end - begin)) / CLOCKS_PER_SEC) << std::scientific << " seconds" << std::endl;
            transformMatrixDirty = false;
        }
        //renderComponents(rootComponent);
        renderComponentType("text", rootComponent);
        glfwSwapBuffers(window);

        // update 2nd buffer
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		textureShader->bind();
        renderComponentType("anime", rootComponent);
        renderComponentType("tab", rootComponent);
		textureShader->bind();
        renderComponentType("box", rootComponent);
        renderComponentType("input", rootComponent);
        // it's quick but done on scroll
		fontShader->bind();
        renderComponentType("text", rootComponent);
        glfwSwapBuffers(window);

        renderDirty = false;
    }
    glfwSwapBuffers(window);
    glfwPollEvents();
}

void Window::setDOM(const std::shared_ptr<Node> rootNode) {

    // reset scroll position
    transformMatrix[13] = 2;
    transformMatrixDirty = true;

    if (!tabComponent) {
        std::cout << "Window::setDOM - no tabComponent" << std::endl;
        return;
    }

    std::cout << "Window::setDOM - tabComponent is already set up" << std::endl;
    TabbedComponent *tabbedComponent = dynamic_cast<TabbedComponent*>(tabComponent.get());
    if (!tabbedComponent->tabs.size()) {
        // if no tabs, make one
        tabbedComponent->addTab(currentURL.toString());
        //tabbedComponent->selectTab(tabbedComponent->tabs.back()->id);
        tabbedComponent->selectTab(tabbedComponent->tabs.back());
    }
    tabbedComponent->loadDomIntoTab(rootNode, currentURL.toString());
}

void Window::createComponentTree(const std::shared_ptr<Node> node, const std::shared_ptr<Component> &parentComponent) {
    std::shared_ptr<Component> component = componentBuilder.build(node, parentComponent, windowWidth, windowHeight);

    // ComponentBuilder now calls setParent and setParents adds children
    //component->setParent(parentComponent);
    // set parent of the newly created component
    //component->parent = parentComponent;
    //if (parentComponent) {
        // add new component as child to parent
        //parentComponent->children.push_back(component);
    //}
    if (component && component==this->rootComponent) {
        // if this is the root node
        component->reqWidth = windowWidth;
        component->reqHeight = windowHeight;
    }

    if (!node) {
        return;
    }

    // create children elements
    for (std::shared_ptr<Node> child : node->children) {
        createComponentTree(child, component);
    }
    // update parents, creation brings them up to date
    //updateComponentSize(component);
}

void Window::renderComponentType(std::string str, std::shared_ptr<Component> component) {
    if (!component) {
        std::cout << "Window::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 =="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 =="anime") {
            AnimeComponent *animeComponent = dynamic_cast<AnimeComponent*>(component.get());
            animeComponent->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
// should return multiple components
std::shared_ptr<Component> Window::searchComponentTree(const std::shared_ptr<Component> &component, const int x, const int y) {
    if (component->children.empty()) {
        // x,y: 0,0 is the upper left
        //std::cout << "cursor at: " << x << "," << y << " 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;
        int ty = component->y;
        int ty1 = component->height + component->y;
        if (ty < 0) {
            ty = 0;
            ty1 += 64; // FIXME: hack (for what?)
        }
        //std::cout << "Window::searchComponentTree - y search: " << ty1 << ">" << static_cast<int>(this->windowHeight + y) << ">" << static_cast<int>(ty) << std::endl;
        if (ty1 > this->windowHeight + y && this->windowHeight + y > ty) {
            //std::cout << "y match" << std::endl;
            if (component->x < x && component->x + component->width > x) {
                //std::cout << "Window::searchComponentTree - hit " << typeOfComponent(component) << std::endl;
                return component;
            }
        }
    }
    else {
        for (std::shared_ptr<Component> child : component->children) {
            if (!child->isPickable) continue;
            std::shared_ptr<Component> found = searchComponentTree(child, x, y);
            if (found) {
                return found;
            }
        }
    }
    return nullptr;
}

// moving naviagtion closer to window, as window is now the owner of currentURL
// preparation for multiple HTML documents
void Window::navTo(std::string url) {
    logDebug() << "Window::navTo(" << url << ")" << std::endl;
    URL link=URL(url);
    logDebug() << "Window::navTo - URL marshalled [" << link << "]" << std::endl;
    currentURL = currentURL.merge(link);
    logDebug() << "go to: " << currentURL << std::endl;
    focusedComponent = nullptr;
    hoverComponent = nullptr;
    setWindowContent(currentURL);
}

/*
 void handleRequest(const HTTPResponse &response) {
 std::cout << "main:::handleRequest - statusCode: " << response.statusCode << std::endl;
 if (response.statusCode == 200) {
 const std::unique_ptr<HTMLParser> parser = std::make_unique<HTMLParser>();
 const std::clock_t begin = clock();
 std::shared_ptr<Node> rootNode = parser->parse(response.body);
 const std::clock_t end = clock();
 std::cout << "Parsed document in: " << std::fixed << ((static_cast<double>(end - begin)) / CLOCKS_PER_SEC) << std::scientific << " seconds" << std::endl;
 window->setDOM(rootNode);
 }
 else if (response.statusCode == 301) {
 std::string location;
 if (response.properties.find("Location")==response.properties.end()) {
 if (response.properties.find("location")==response.properties.end()) {
 std::cout << "::handleRequest - got 301 without a location" << std::endl;
 for(auto const &row : response.properties) {
 std::cout << "::handleRequest - " << row.first << "=" << response.properties.at(row.first) << std::endl;
 }
 return;
 } else {
 location = response.properties.at("location");
 }
 } else {
 location = response.properties.at("Location");
 }
 std::cout << "Redirect To: " << location << std::endl;
 std::shared_ptr<URI> uri = parseUri(location);
 const std::unique_ptr<HTTPRequest> request = std::make_unique<HTTPRequest>(uri);
 request->sendRequest(handleRequest);
 return;
 }
 else {
 std::cout << "Unknown Status Code: " << response.statusCode << std::endl;
 }
 }
 */

// tried to make a window method
void handleRequest(const HTTPResponse &response) {
    std::cout << "Window:::handleRequest - statusCode: " << response.statusCode << std::endl;
    if (response.statusCode == 200) {
        const std::unique_ptr<HTMLParser> parser = std::make_unique<HTMLParser>();
        const std::clock_t begin = clock();
        std::shared_ptr<Node> rootNode = parser->parse(response.body);
        const std::clock_t end = clock();
        std::cout << "Parsed document in: " << std::fixed << ((static_cast<double>(end - begin)) / CLOCKS_PER_SEC) << std::scientific << " seconds" << std::endl;
        window->setDOM(rootNode);
    }
    else if (response.statusCode == 301) {
        std::string location;
        if (response.properties.find("Location")==response.properties.end()) {
            if (response.properties.find("location")==response.properties.end()) {
                std::cout << "::handleRequest - got 301 without a location" << std::endl;
                for(auto const &row : response.properties) {
                    std::cout << "::handleRequest - " << row.first << "=" << response.properties.at(row.first) << std::endl;
                }
                return;
            } else {
                location = response.properties.at("location");
            }
        } else {
            location = response.properties.at("Location");
        }
        std::cout << "Redirect To: " << location << std::endl;
        std::tuple<std::unique_ptr<URL>,enum URIParseError> result = parseUri(location);
        if (std::get<1>(result) != URI_PARSE_ERROR_NONE) {
            // TODO We probably wanna handle this better..
            std::cerr << "error parsing uri" << std::endl;
            return;
        }
        std::unique_ptr<URL> uri = std::move(std::get<0>(result));
        const std::unique_ptr<HTTPRequest> request = std::make_unique<HTTPRequest>(std::move(uri));
        request->sendRequest(handleRequest);
        return;
    }
    else {
        std::cout << "Unknown Status Code: " << response.statusCode << std::endl;
    }
}
#endif


code:
#ifndef TEXTWINDOW_H
#define TEXTWINDOW_H

#include "../components/ComponentBuilder.h"
#include "../components/Component.h"
#include "ShaderLoader.h"
#include "../../html/Node.h"
#include "Window.h"
#include <memory>
#include <vector>
#include <algorithm>
#include "../../networking/HTTPResponse.h"
#include "../../URL.h"

class TextWindow : public Window {
private:
public:
    ~TextWindow();
    bool init();

    void render();
    void setDOM(const std::shared_ptr<Node> rootNode);
    void createComponentTree(const std::shared_ptr<Node> rootNode, const std::shared_ptr<Component> &parentComponent);
    void renderComponentType(std::string str, std::shared_ptr<Component> component);

    void onResize(int passedWidth, int passedHeight);
    void checkForResize();
    void resize();

    void resizeComponentTree(const std::shared_ptr<Component> &component, const int windowWidth, const int windowHeight);
    std::shared_ptr<Component> searchComponentTree(const std::shared_ptr<Component> &component, const int x, const int y);
    void navTo(std::string url);

    // properties
    int maxTextureSize = 0;
    float transformMatrix[16] = {
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    };
    bool transformMatrixDirty = true;
    GLFWwindow *window;
    int windowWidth;
    int windowHeight;
    //std::shared_ptr<Node> domRootNode = nullptr;
    bool domDirty = false;
    bool renderDirty = true;

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

    // I hate doing this but we currently require this
    std::shared_ptr<Component> tabComponent = nullptr;
    std::shared_ptr<Component> addressComponent = nullptr;

    // could break these out in some sort of cursor class
    // to minimize dependencies
    std::shared_ptr<Component> focusedComponent = nullptr;
    std::shared_ptr<Component> hoverComponent = nullptr;
    double cursorX = 0;
    double cursorY = 0;
    unsigned int delayResize = 0;
    GLFWcursor* cursorHand;
    GLFWcursor* cursorArrow;
    GLFWcursor* cursorIbeam;
    URL currentURL;
	ShaderLoader shaderLoader;
    // is there only one selection per window? (probably not)
    int highlightStartX = 0;
    int highlightStartY = 0;
    std::vector<std::shared_ptr<Component>> selectionList;
};

bool setWindowContent(URL const& url);
void handleRequest(const HTTPResponse &response);

extern const std::unique_ptr<Window> window;

#endif

repo_name: vaartis/netrunner

license: mit


code:
#include "BoxComponent.h"
#include <cmath>
#include <iostream>

BoxComponent::BoxComponent() {
    //std::cout << "BoxComponent::BoxComponent - empty" << std::endl;
}

BoxComponent::BoxComponent(const float rawX, const float rawY, const float rawWidth, const float rawHeight, const unsigned int hexColor, const int passedWindowWidth, const int passedWindowHeight) {
    //std::cout << "BoxComponent::BoxComponent - data" << std::endl;
    //std::cout << "BoxComponent::BoxComponent - window: " << windowWidth << "x" << windowHeight << " passed " << passedWindowWidth << "x" << passedWindowHeight << std::endl;

    //boundToPage = false;
    useBoxShader = true;

    // set up state
    windowWidth = passedWindowWidth;
    windowHeight = passedWindowHeight;
    width = rawWidth;
    height = rawHeight;
    x = rawX;
    y = rawY;

    if (height < 0) {
        std::cout << "BoxComponent::BoxComponent - height was less than zero" << std::endl;
        height = 0;
    }

    // copy initial state
    initialX = x;
    initialY = y;
    initialWidth = width;
    initialHeight = height;
    initialWindowWidth = windowWidth;
    initialWindowHeight = windowHeight;

    // set texture color
    data[0][0][0] = (hexColor >> 24) & 0xFF;
    data[0][0][1] = (hexColor >> 16) & 0xFF;
    data[0][0][2] = (hexColor >>  8) & 0xFF;
    data[0][0][3] = (hexColor >>  0) & 0xFF;
    //std::cout << "box color got " << (int)data[0][0][0] << "," << (int)data[0][0][1] << "," << (int)data[0][0][2] << "@" << (int)data[0][0][3] << std::endl;

    float vx = rawX;
    float vy = rawY;
    //std::cout << "placing box at " << (int)vx << "x" << (int)vy << " size: " << (int)rawWidth << "x" << (int)rawHeight << std::endl;
    float vWidth = width;
    float vHeight = height;
    pointToViewport(vx, vy);

    /*
    // if has rawWidth > 1
    if (std::abs(vWidth) > 1) {
        // try to reduce vWidth < 1
        vWidth /= windowWidth;
    }
    // if has rawWidth > 1
    if (std::abs(vWidth) > 1) {
        // try to reduce vWidth < 1
        vWidth /= windowHeight;
    }
    // double w/h
    vWidth *= 2;
    vWidth *= 2;
    */
    // converts 512 to 1 and 1 to 2
    //std::cout << "vWidth before: " << (int)vWidth << std::endl;
    distanceToViewport(vWidth, vHeight);
    //std::cout << "vWidth after: " << (int)vWidth << std::endl;

    //std::cout << "placing box at GL " << (int)vx << "x" << (int)vy << " size: " << (int)(vWidth*10000) << "x" << (int)(vHeight*10000) << std::endl;

    vertices[(0 * 5) + 0] = vx;
    vertices[(0 * 5) + 1] = vy + vHeight;
    vertices[(1 * 5) + 0] = vx + vWidth;
    vertices[(1 * 5) + 1] = vy + vHeight;
    vertices[(2 * 5) + 0] = vx + vWidth;
    vertices[(2 * 5) + 1] = vy;
    vertices[(3 * 5) + 0] = vx;
    vertices[(3 * 5) + 1] = vy;

    glGenVertexArrays(1, &vertexArrayObject);
    glGenBuffers(1, &vertexBufferObject);
    glGenBuffers(1, &elementBufferObject);

    glBindVertexArray(vertexArrayObject);

    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBufferObject);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), nullptr);
    glEnableVertexAttribArray(0);

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

    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);

    glBindVertexArray(0); // protect what we created against any further modification
}

BoxComponent::~BoxComponent() {
    glDeleteVertexArrays(1, &vertexArrayObject);
    glDeleteBuffers(1, &vertexBufferObject);
    glDeleteBuffers(1, &elementBufferObject);
    glDeleteTextures(1, &texture);
}

void BoxComponent::render() {
    //std::cout << "BoxComponent::render" << std::endl;
    GLenum glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "BoxComponent::render - start not ok: " << glErr << std::endl;
    }
    if (verticesDirty) {
        //std::cout << "BoxComponent::render - update dirty vertex" << std::endl;
        glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
        glErr=glGetError();
        if(glErr != GL_NO_ERROR) {
            std::cout << "BoxComponent::render - glBindBuffer not ok: " << glErr << std::endl;
        }
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        glErr=glGetError();
        if(glErr != GL_NO_ERROR) {
            std::cout << "BoxComponent::render - glBufferData not ok: " << glErr << std::endl;
        }
        verticesDirty = false;
    }
    glBindVertexArray(vertexArrayObject);
    glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "BoxComponent::render - glBindVertexArray not ok: " << glErr << std::endl;
    }
    glBindTexture(GL_TEXTURE_2D, texture);
    glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "BoxComponent::render - glBindTexture not ok: " << glErr << std::endl;
    }
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
    glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "BoxComponent::render - glDrawElements not ok: " << glErr << std::endl;
    }
    glBindVertexArray(0);
    // can actuall delete vertices here
}

void BoxComponent::changeColor(const unsigned int hexColor) {
    // really a texture swap

    // set texture color
    data[0][0][0] = (hexColor >> 24) & 0xFF;
    data[0][0][1] = (hexColor >> 16) & 0xFF;
    data[0][0][2] = (hexColor >>  8) & 0xFF;
    data[0][0][3] = (hexColor >>  0) & 0xFF;

    glBindVertexArray(vertexArrayObject);

    // now replace existing texture
    glBindTexture(GL_TEXTURE_2D, texture);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);

    glBindVertexArray(0); // unbind to prevent anything from accidentally changing it
}

// this seems to work 17-08-10
// for box
void BoxComponent::resize(const int passedWindowWidth, const int passedWindowHeight) {
    //std::cout << "BoxComponent::resize" << std::endl;
    windowWidth = passedWindowWidth;
    windowHeight = passedWindowHeight;

    //std::cout << "BoxComponent::resize - boundToPage " << boundToPage << std::endl;
    // figure out new vertices
    float vx = x;
    float vy = y;
    //std::cout << "placing box at " << (int)vx << "x" << (int)vy << " size: " << (int)rawWidth << "x" << (int)rawHeight << std::endl;
    float vWidth = width;
    float vHeight = height;
    pointToViewport(vx, vy);

    //std::cout << "vWidth before: " << (int)vWidth << std::endl;
    distanceToViewport(vWidth, vHeight);
    //std::cout << "vWidth after: " << (int)vWidth << std::endl;

    //std::cout << "placing box at GL " << (int)vx << "x" << (int)vy << " size: " << (int)(vWidth*10000) << "x" << (int)(vHeight*10000) << std::endl;

    vertices[(0 * 5) + 0] = vx;
    vertices[(0 * 5) + 1] = vy + vHeight;
    vertices[(1 * 5) + 0] = vx + vWidth;
    vertices[(1 * 5) + 1] = vy + vHeight;
    vertices[(2 * 5) + 0] = vx + vWidth;
    vertices[(2 * 5) + 1] = vy;
    vertices[(3 * 5) + 0] = vx;
    vertices[(3 * 5) + 1] = vy;

    verticesDirty = true;
}


code:
#ifndef BOXCOMPONENT_H
#define BOXCOMPONENT_H

#include <GL/glew.h>
#include "Component.h"

class BoxComponent : public Component {
protected:
    float vertices[20] = {
        0.0f, 0.0f, 0.0f,    0.0f, 1.0f,
        0.0f, 0.0f, 0.0f,    1.0f, 1.0f,
        0.0f, 0.0f, 0.0f,    1.0f, 0.0f,
        0.0f, 0.0f, 0.0f,    0.0f, 0.0f
    };
    unsigned char data[1][1][4];
    GLuint vertexArrayObject = 0;
    GLuint vertexBufferObject = 0;
    GLuint elementBufferObject = 0;
    GLuint texture = 0;
    // FIXME: deprecate these
    int initialX;
    int initialY;
    int initialWidth;
    int initialHeight;
    int initialWindowWidth;
    int initialWindowHeight;
public:
    BoxComponent();
    BoxComponent(const float rawX, const float rawY, const float rawWidth, const float rawHeight, const unsigned int hexColor, const int passedWindowWidth, const int passedWindowHeight);
    ~BoxComponent();
    void changeColor(const unsigned int hexColor);
    virtual void render();
    virtual void resize(const int passedWindowWidth, const int passedWindowHeight);
    // FIXME: create: createVerices, updateVetices, createTexture, updateTexture
};

#endif


code:
#include "ButtonComponent.h"

extern TextRasterizerCache *rasterizerCache;

ButtonComponent::ButtonComponent(const float rawX, const float rawY, const float rawWidth, const float rawHeight, const unsigned int hexColor, const int passedWindowWidth, const int passedWindowHeight) : BoxComponent(rawX, rawY, rawWidth, rawHeight, hexColor, passedWindowWidth, passedWindowHeight) {
    // BoxComponent should set up everything we need
    //std::cout << "ButtonComponent::ButtonComponent - boxShader " << this->useBoxShader << std::endl;
    // looks like it is
}

void ButtonComponent::render() {
    //std::cout << "ButtonComponent::render" << std::endl;
    //std::cout << "ButtonComponent::render at " << (int)x << "," << (int)y << std::endl;
    GLenum glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "ButtonComponent::render - start not ok: " << glErr << std::endl;
    }
    if (verticesDirty) {
        //std::cout << "ButtonComponent::render - update dirty vertex" << std::endl;
        glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
        glErr=glGetError();
        if(glErr != GL_NO_ERROR) {
            std::cout << "ButtonComponent::render - glBindBuffer not ok: " << glErr << std::endl;
        }
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        glErr=glGetError();
        if(glErr != GL_NO_ERROR) {
            std::cout << "ButtonComponent::render - glBufferData not ok: " << glErr << std::endl;
        }
        verticesDirty = false;
    }
    glBindVertexArray(vertexArrayObject);
    glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "ButtonComponent::render - glBindVertexArray not ok: " << glErr << std::endl;
    }
    glBindTexture(GL_TEXTURE_2D, texture);
    glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "ButtonComponent::render - glBindTexture not ok: " << glErr << std::endl;
    }
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
    glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "ButtonComponent::render - glDrawElements not ok: " << glErr << std::endl;
    }
    glBindVertexArray(0);
    // can actuall delete vertices here
    if (textLabel) {
        // make sure we're using win's transformMatrix
        //glUseProgram(this->win->fontProgram);
        Shader *fontShader = this->win->shaderLoader.getShader(VertexShader("FontShader.vert"),
                                                            FragmentShader("FontShader.frag"));
        fontShader->bind();
        if (!boundToPage) {
            GLint transformLocation = fontShader->uniform("transform");
            //GLint transformLocation = glGetUniformLocation(this->win->fontProgram, "transform");
            glErr=glGetError();
            if(glErr != GL_NO_ERROR) {
                std::cout << "ButtonComponent::render - glGetUniformLocation not ok: " << glErr << std::endl;
            }
            glUniformMatrix4fv(transformLocation, 1, GL_FALSE, this->win->transformMatrix);
            glErr=glGetError();
            if(glErr != GL_NO_ERROR) {
                std::cout << "ButtonComponent::render - glUniformMatrix4fv not ok: " << glErr << std::endl;
            }
        }
        //std::cout << "rendering some text" << std::endl;
        textLabel->render();
        //glUseProgram(this->win->textureProgram);
        Shader *textureShader = this->win->shaderLoader.getShader(VertexShader("TextureShader.vert"),
                                                               FragmentShader("TextureShader.frag"));
        textureShader->bind();
    }
}

void ButtonComponent::resize(const int passedWindowWidth, const int passedWindowHeight) {
    //std::cout << "ButtonComponent::resize" << std::endl;
    //std::cout << "ButtonComponent::resize - rasterizing at " << (int)x << "x" << (int)y << " size: " << (int)width << "x" << (int)height << std::endl;

    // maybe already done at component::resize
    // set up state
    windowWidth = passedWindowWidth;
    windowHeight = passedWindowHeight;

    //std::cout << "ButtonComponent::resize - placing box at " << (int)x << "," << (int)y << " size: " << (int)width << "x" << (int)height << std::endl;

    float vx = x;
    float vy = y;
    //std::cout << "ButtonComponent::resize - boundToPage " << this->boundToPage << std::endl;
    if (this->boundToPage) {
        vy = this->windowHeight + y - height;
        //std::cout << "InputComponent::resize - Adjust y to " << vy << " from " << y << " h: " << (int)height << std::endl;
    }

    pointToViewport(vx, vy);

    float vWidth = width;
    float vHeight = height;
    distanceToViewport(vWidth, vHeight);
    float vx1 = vx + vWidth;
    float vy1 = vy + vHeight;

    vertices[(0 * 5) + 0] = vx;
    vertices[(0 * 5) + 1] = vy1;

    vertices[(1 * 5) + 0] = vx1;
    vertices[(1 * 5) + 1] = vy1;

    vertices[(2 * 5) + 0] = vx1;
    vertices[(2 * 5) + 1] = vy;

    vertices[(3 * 5) + 0] = vx;
    vertices[(3 * 5) + 1] = vy;

    glBindVertexArray(vertexArrayObject);

    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glBindVertexArray(0); // protect what we created against any further modification

    //updateText();
    if (textLabel) {
        textLabel->resize(passedWindowWidth, passedWindowHeight);
    }
}

void ButtonComponent::resizeToTextSize() {
    if (!textLabel) {
        std::cout << "ButtonComponent::resizeToTextSize - no textLabel to resize" << std::endl;
        return;
    }
    if (!textLabel->win && this->win) {
        textLabel->win = this->win;
    }
    textLabel->x = x;
    if (y < 0) {
        textLabel->y = y;
    } else {
        textLabel->y = y - windowHeight + 16;
    }
    //std::cout << "placed userInputText at " << static_cast<int>(x) << "," << static_cast<int>(y - windowHeight) << std::endl;
    // 62 pixels width
    // but first we need to know how wide the text is
    const std::shared_ptr<TextRasterizer> textRasterizer=rasterizerCache->loadFont(12, false); // fontSize, bold
    rasterizationRequest request;
    request.text = value;
    request.startX = x;
    request.availableWidth = windowWidth; // have to include x, because width=62, and we need to render that
    request.sourceStartX = 0;
    request.sourceStartY = 0;
    request.noWrap = true;
    // FIXME: center this!
    std::unique_ptr<sizeResponse> textInfo = textRasterizer->size(request);
    if (textInfo.get() == nullptr) {
        std::cout << "ButtonComponent::updateText - couldn't estimate value[" << value << "] size" << std::endl;
        return;
    }
    //int textWidth = textInfo->first;
    int estWidth = textInfo->width;
    //int estHeight = std::get<1>(*textInfo.get());
    textLabel->rasterStartX = 0;
    textLabel->rasterStartY = 0;
    //std::cout << "ButtonComponent::updateText - estWidth: " << estWidth << " width: " << static_cast<int>(width) << std::endl;
    /*
    if (estWidth > width) {
        //std::cout << "scrolling text" << std::endl;
        textLabel->rasterStartX = estWidth - width;
    }
     */
    this->width = estWidth + 10; // 5px padding on each side
    this->resize(windowWidth, windowHeight);
    textLabel->noWrap = true;
    // why does changing the width mess shit up?
    //std::cout << "ButtonComponent::updateText - our width: " << static_cast<int>(width) << " windowWidth: " << windowWidth << std::endl;
    textLabel->resize(windowWidth, windowHeight, windowWidth); // need to make sure there's a texture

    if (this->win) {
        this->win->renderDirty = true;
    }
}

// most of this functionality should be inside TextComponent
void ButtonComponent::updateText() {
    //std::cout << "ButtonComponent::updateText - input value is now: " << value << std::endl;
    // maybe tie the fontsize to height
    if (textLabel) {
        delete textLabel;
    }
    textLabel=new TextComponent(value, 0, 0, 12, false, 0x000000FF, windowWidth, windowHeight);
    if (!textLabel->win && this->win) {
        textLabel->win = this->win;
    }
    //std::cout << "placing userInputText at " << static_cast<int>(x) << "," << static_cast<int>(y) << std::endl;
    textLabel->x = x;
    if (y < 0) {
        textLabel->y = y;
    } else {
        textLabel->y = y - windowHeight + 16;
    }
    //std::cout << "placed userInputText at " << static_cast<int>(x) << "," << static_cast<int>(y - windowHeight) << std::endl;
    // 62 pixels width
    // but first we need to know how wide the text is
    const std::shared_ptr<TextRasterizer> textRasterizer=rasterizerCache->loadFont(12, false); // fontSize, bold
    rasterizationRequest request;
    request.text = value;
    request.startX = x;
    request.availableWidth = windowWidth; // have to include x, because width=62, and we need to render that
    request.sourceStartX = 0;
    request.sourceStartY = 0;
    request.noWrap = true;
    // FIXME: center this!
    std::unique_ptr<sizeResponse> textInfo = textRasterizer->size(request);
    if (textInfo.get() == nullptr) {
        std::cout << "ButtonComponent::updateText - couldn't estimate value[" << value << "] size" << std::endl;
        return;
    }
    int estWidth = textInfo->width;
    //int estHeight = std::get<1>(*textInfo.get());
    textLabel->rasterStartX = 0;
    textLabel->rasterStartY = 0;
    //std::cout << "ButtonComponent::updateText - estWidth: " << estWidth << " width: " << static_cast<int>(width) << std::endl;
    if (estWidth > width) {
        //std::cout << "scrolling text" << std::endl;
        textLabel->rasterStartX = estWidth - width;
    }
    textLabel->noWrap = true;
    // why does changing the width mess shit up?
    //std::cout << "ButtonComponent::updateText - our width: " << static_cast<int>(width) << " windowWidth: " << windowWidth << std::endl;
    textLabel->resize(windowWidth, windowHeight, width + x); // need to make sure there's a texture

    if (this->win) {
        this->win->renderDirty = true;
    }
}


code:
#ifndef BUTTONCOMPONENT_H
#define BUTTONCOMPONENT_H

#include <GL/glew.h>
#include "BoxComponent.h"
#include "TextComponent.h"

// we may not need this if textComponent took a background color
// I guess we'll be inline for now
class ButtonComponent : public BoxComponent {
public:
    ButtonComponent(const float rawX, const float rawY, const float rawWidth, const float rawHeight, const unsigned int hexColor, const int passedWindowWidth, const int passedWindowHeight);
    void render();
    void resize(const int passedWindowWidth, const int passedWindowHeight);
    void resizeToTextSize();
    void updateText();
    std::string value="";
    TextComponent *textLabel = nullptr;
};

#endif


code:
#include "Component.h"
#include <iostream>
#include <algorithm>
#include <cmath>
#include "TextComponent.h"
#include "InputComponent.h"
#include "ButtonComponent.h"

#include "ComponentBuilder.h"

// pos, size
std::unique_ptr<std::pair<UImetricV2, UImetricV2>> getUIMetric(UImetricV2 &s1, UImetricV2 &s2, UImetricV2 &size) {
    // only 2 of these can be requested
    if ((s1.requested && s2.requested && size.requested) || (!s1.requested && !s2.requested && !size.requested)) {
        // FIXME: better message
        std::cout << "component.getUIMetric - can't resolve" << std::endl;
        return nullptr;
    }
    std::unique_ptr<std::pair<UImetricV2, UImetricV2>> result=std::make_unique<std::pair<UImetricV2, UImetricV2>>();
    if (s1.requested && s2.requested) {
        // left/right
        //std::cout << "left/right px " << s1.px << " & " << s2.px << std::endl;
        //std::cout << "left/right %  " << s1.pct << " & " << s2.pct << std::endl;
        result->first = { true, s1.pct, s1.px }; // pos
        result->second = { true, (100.0 - s2.pct) - s1.pct, -s2.px - s1.px }; // size
        /*
        if (s1.pct && s2.pct) {
            // right 25% = left 75%
            result->second = { true, (100 - s2.pct) - s1.pct, 0 }; // size
        } else
        if (!s1.pct && !s2.pct) {
            result->second = { true, 100.0 - s2.pct, -s2.px };
        } else if (s1.pct) {
            result->second = { true, 100.0 - s2.pct, -s2.px };
        } else if (s2.pct) {
            // right 25% = left 75%
            result->second = { true, 100.0 - s2.pct, -s2.px };
        } else {
            // unknown
        }
        */
    } else
    if (s1.requested && size.requested) {
        // left + width
        result->first = { true, s1.pct, s1.px }; // pos
        result->second = { true, size.pct, size.px };
    } else
    if (s2.requested && size.requested) {
        // right - width
        // right 25% = left 75%
        // + width 50% means start at 25%
        result->first = { true, (100.0 - s2.pct) - size.pct, -s2.px - size.px }; // pos
        result->second = { true, size.pct, size.px };
    } else {
        std::cout << "component.getUIMetric - Unkown state" << std::endl;
    }
    return result;
}

Component::~Component() {
}

// translate properties (left/right/width && top/bottom/height) into resizeable layout
// we may not need pwin (we have this->win)
void Component::setUpUI(std::map<std::string, std::string> &properties, Window *pwin) {
    UILayoutV2 boxSetup;
    boxSetup.width.px  = 32;
    boxSetup.height.px = 32;
    if (properties.find("width") != properties.end()) {
        boxSetup.width.requested = true;
        if (properties["width"][properties["width"].size() - 1] == '%') {
            //std::cout << "Component::setUpUI - found width %" << std::endl;
            boxSetup.width.pct = std::stoi(properties["width"]);
        } else {
            boxSetup.width.px = std::stoi(properties["width"]);
        }
    }
    if (properties.find("height") != properties.end()) {
        boxSetup.height.requested = true;
        if (properties["height"][properties["height"].size() - 1] == '%') {
            //std::cout << "Component::setUpUI - found height %" << std::endl;
            boxSetup.height.pct = std::stoi(properties["height"]);
        } else {
            boxSetup.height.px = std::stoi(properties["height"]);
        }
    }
    if (properties.find("top") != properties.end()) {
        boxSetup.top.requested = true;
        if (properties["top"][properties["top"].size() - 1] == '%') {
            //std::cout << "Component::setUpUI - found top %" << std::endl;
            boxSetup.top.pct = std::stoi(properties["top"]);
        } else {
            boxSetup.top.px = std::stoi(properties["top"]);
        }
    }
    if (properties.find("bottom") != properties.end()) {
        boxSetup.bottom.requested = true;
        if (properties["bottom"][properties["bottom"].size() - 1] == '%') {
            //std::cout << "Component::setUpUI - found bottom %" << std::endl;
            boxSetup.bottom.pct = std::stoi(properties["bottom"]);
        } else {
            boxSetup.bottom.px = std::stoi(properties["bottom"]);
        }
    }
    if (properties.find("left") != properties.end()) {
        boxSetup.left.requested = true;
        if (properties["left"][properties["left"].size() - 1] == '%') {
            //std::cout << "Component::setUpUI - found left %" << std::endl;
            boxSetup.left.pct = std::stoi(properties["left"]);
        } else {
            boxSetup.left.px = std::stoi(properties["left"]);
        }
    }
    if (properties.find("right") != properties.end()) {
        boxSetup.right.requested = true;
        if (properties["right"][properties["right"].size() - 1] == '%') {
            //std::cout << "Component::setUpUI - found right %" << std::endl;
            boxSetup.right.pct = std::stoi(properties["right"]);
        } else {
            boxSetup.right.px = std::stoi(properties["right"]);
        }
    }
    std::unique_ptr<std::pair<UImetricV2, UImetricV2>> xUI = getUIMetric(boxSetup.left, boxSetup.right, boxSetup.width);
    std::unique_ptr<std::pair<UImetricV2, UImetricV2>> yUI = getUIMetric(boxSetup.top, boxSetup.bottom, boxSetup.height);
    //int cX = 0, cY = 0;
    int cW = boxSetup.width.px, cH = boxSetup.height.px;
    int xPx = 0, yPx = 0;
    double xPct = 0, yPct = 0;
    double wPct = 100, hPct = 100;
    int wPx = 0, hPx = 0;
    //int winWidth = pwin->windowWidth;
    int winHeight = pwin->windowHeight;
    //std::cout << "Component::setUpUI - laying out a " << typeOfComponent(this) << std::endl;
    if (xUI) {
        //std::cout << "box XUI at " << xUI->first.pct << "% +" << xUI->first.px << std::endl;
        //std::cout << "box XUI for " << xUI->second.pct << "% +" << xUI->second.px << std::endl;
        // QUESTION: I don't think cx should take pct into account
        // I think it does because 25% into starting px is important
        // no because we could just set uiControl.x correctly...
        // and if we did that we could pull out win
        // well we still need winHeight tho, because of the coords
        //cX = (xUI->first.pct * 0.01 * winWidth) + xUI->first.px;
        xPct = xUI->first.pct;
        xPx = xUI->first.px;
        //cW = (xUI->second.pct * 0.01 * winWidth) + xUI->second.px;
        wPct = xUI->second.pct;
        wPx = xUI->second.px;
        //std::cout << "Component::setUpUI X at " << cX << " for " << cW << std::endl;
        //std::cout << "Component::setUpUI W at " << wPct << "% for " << wPx << std::endl;
    }
    if (yUI) {
        //std::cout << "Component::setUpUI pct" << yUI->first.pct << "/100 - " << yUI->first.px << std::endl;
        //cY = (yUI->first.pct * 0.01 * winHeight) - yUI->first.px;
        // bottom=0 is 0, 0, so we do 100- here
        yPct = 100 - yUI->first.pct;
        yPx = yUI->first.px;
        cH = (yUI->second.pct * 0.01 * winHeight) + yUI->second.px;
        hPct = yUI->second.pct;
        hPx = yUI->second.px;
        //std::cout << "Component::setUpUI Y at " << cY << " for " << cH << std::endl;
        //std::cout << "Component::setUpUI Y would be " << (-cH - cY) << " and  new is " << yPx << std::endl;
        //std::cout << "Component::setUpUI H at " << hPct << "% for " << hPx << std::endl;
    }

    // set up proper component information for resizing
    this->uiControl.x = { xPct,  xPx      }; //

    this->uiControl.y = { yPct, -cH - yPx }; //
    this->uiControl.w = { wPct, wPx      }; //
    this->uiControl.h = { hPct, hPx      }; //

    // recalculate new vertices
    this->layout(); // move from uiContorl to x,y
    // we don't need to set to call resize because layout already calls it (always calls wrap->resize)
    //this->resize(winWidth, winHeight); // apply x,y state to vertices
}

// resize is where width/height can change within the confines of the window
void Component::resize(const int passedWindowWidth, const int passedWindowHeight) {
    //std::cout << "Component::resize - doing nothing" << std::endl;
    windowWidth = passedWindowWidth;
    windowHeight = passedWindowHeight;
    /*
    if (boundToPage) {

    } else {
        */
        // no vertices in component
        /*
        float vx = x;
        float vy = y;
        float vWidth = width;
        float vHeight = height;
        pointToViewport(vx, vy);
        distanceToViewport(vWidth, vHeight);

        vertices[(0 * 5) + 0] = vx;
        vertices[(0 * 5) + 1] = vy + vHeight;
        vertices[(1 * 5) + 0] = vx + vWidth;
        vertices[(1 * 5) + 1] = vy + vHeight;
        vertices[(2 * 5) + 0] = vx + vWidth;
        vertices[(2 * 5) + 1] = vy;
        vertices[(3 * 5) + 0] = vx;
        vertices[(3 * 5) + 1] = vy;

        verticesDirty = true;
        */
    //}

    // do we recurse down through children?
    // well if a parent resizes, yea we should tell our children the window size has changed
    // btw a layout already does this
    // yea let's let layout handle this
    /*
    for (std::shared_ptr<Component> child : children) {
        // update it's size
        // maybe we should called wrap
        child->resize(windowWidth, windowHeight);
    }
    */
}

void Component::setParent(std::shared_ptr<Component> passedParent) {
    if (!passedParent) {
        std::cout << "Component::setParent is empty" << std::endl;
    }
    if (parent) {
        std::cout << "Component::setParent - triggering component move" << std::endl;
    }
    // set parent of the newly created component
    parent = passedParent;

    // update our position
    // probably could move this out of here
    if (boundToPage) {
        x = passedParent->x;
        y = passedParent->y - passedParent->height;
    }

    //std::cout << "Component::setParent - placing at " << static_cast<int>(x) << "," << static_cast<int>(y) << std::endl;
    if (passedParent) {
        if (passedParent->children.size()) {
            this->previous = passedParent->children.back();
        }
        // need to figure out how to make this work
        //passedParent->children.push_back(std::make_shared<Component>(this));
    }

    // add new component as child to parent
    //std::shared_ptr<Component> child=*new std::shared_ptr<Component>(this);
    //parent->children.push_back(child);
}

// window size is required because we need to know if our x is placed outside bounds
// our size is not required
// but all previous children sizes and positions are
// this function should be smart enough to read state, figure out what changes are actually needed and execute them
void Component::layout() {
    // back up current position
    //int lx = x;
    //int ly = y;

    // if we want to position the root component and layout (because to move things we need to trigger a resize)
    // then we need this to not reset because it will affect the position we're trying to relayout them out at
    // reset position
    //x = 0;
    //y = 0;

    /*
    TextComponent *textComponent = dynamic_cast<TextComponent*>(this);
    if (textComponent) {
        std::cout << "Component::layout[" << textComponent->text << "]" << std::endl;
    }
    */
    //std::cout << "Component::layout - name: " << name << " type " << typeOfComponent(this) << " boundToPage: " << boundToPage << std::endl;

    // if we're a child, get our parents position
    if (parent && boundToPage) {
        //std::cout << "Component::layout - copying position from parent: " << (int)parent->x << "x" << (int)parent->y << std::endl;

        x = parent->x;
        y = parent->y;
        // if we have sibilings see if they're inline or block
        if (parent->children.size()) {
            //std::cout << "Component::layout - parent children: " << parent->children.size() << std::endl;
            if (previous) {
                //std::cout << "Component::layout - has previous " << std::endl;
                /*
                TextComponent *prevTextComponent = dynamic_cast<TextComponent*>(previous.get());
                if (prevTextComponent) {
                    std::cout << "Component::layout - previous [" << prevTextComponent->text << "] ending at: " << (int)prevTextComponent->endingX << "x" << (int)prevTextComponent->endingY << "" << std::endl;
                }
                */
                //std::cout << "Component::layout - previous at: " << (int)previous->x << "x" << (int)previous->y << " size: " << (int)previous->width << "x" << (int)previous->height << " ending at: " << (int)previous->endingX << "x" << (int)previous->endingY << std::endl;
                // 2nd or last
                if (previous->isInline) {
                    // last was inline
                    if (isInline) {
                        x = previous->x + previous->width;
                        y = previous->y; // keep on same line
                        //std::cout << "Component::layout - inLine (" << (int)previous->width << " wide) inLine" << std::endl;
                        if (x >= windowWidth) {
                            //std::cout << "Component::layout - inline inline wrapping because x: " << (int)x << " window: " << windowWidth << std::endl;
                            x = previous->endingX;
                            //std::cout << "Component::layout - p.y: " << (int)previous->y << " p.ey: " << previous->endingY << " p.h" << (int)previous->height << std::endl;
                            y = previous->y - previous->height + previous->endingY;
                        }
                    } else {
                        // we're block
                        y = previous->y - previous->height; // advance down one height
                        if (!previous->height) {
                            // ok if last is a nothing burger (0x0 size)
                            // then we need the parent height
                            // or we could spool to get the height for the last inline that's been set
                            // this isn't right
                            //y -= this->parent->height;
                            // this is bad because we assume there's only going to be one
                            std::shared_ptr<Component> pos = this->previous;
                            bool found = false;
                            while(pos->previous) {
                                pos = pos->previous;
                                if (!pos->isInline) {
                                    std::cout << "Component::layout - found block element at " << pos->y << std::endl;
                                    break;
                                }
                                if (pos->height) {
                                    //std::cout << "no size, grabbing height " << static_cast<int>(pos->height) << std::endl;
                                    y -= pos->height;
                                    found = true;
                                    break;
                                }
                            }
                            if (!found) {
                                std::cout << "Component::layout - couldnt adjust height properly" << std::endl;
                            }
                        }
                        //std::cout << "Component::layout - inLine block" << std::endl;
                    }
                } else {
                    // last was block
                    y = previous->y - previous->height;
                    //std::cout << "Component::layout - block *  p.x: " << (int)previous->x << " p.h: " << (int)previous->height << std::endl;
                }
                // really only inline but can't hurt block AFAICT
                if (x >= windowWidth) {
                    //std::cout << "Component::layout - wrapping because x: " << (int)x << " window: " << windowWidth << std::endl;
                    x = 0;
                    y -= previous->height; // how far down do we need to wrap?, the previous height?
                }
            } else {
                // first, there will be no width to add
            }
        } else {
            // first component for this parent
        }
    }

    if (!boundToPage) {
        // select new x,y & w/h bassed on UImetric
        //std::cout << "!boundToPage " << name << " from: " << (int)x << "," << (int)y << " " << (int)width << "," << (int)height << std::endl;
        //std::cout << "uic.x.lenghtPct: " << uiControl.x.lengthPct << " width: " << windowWidth << " uic.x.offset: " << uiControl.x.offset << std::endl;
        this->x = (uiControl.x.lengthPct / 100.0) * this->windowWidth + uiControl.x.offset;
        //std::cout << "uic.y.lenghtPct: " << uiControl.y.lengthPct << " height: " << windowHeight << " uic.y.offset: " << uiControl.y.offset << std::endl;
        this->y = (uiControl.y.lengthPct / 100.0) * this->windowHeight + uiControl.y.offset;
        this->width = (uiControl.w.lengthPct / 100.0) * this->windowWidth + uiControl.w.offset;
        this->height = (uiControl.h.lengthPct / 100.0) * this->windowHeight + uiControl.h.offset;
        //std::cout << "!boundToPage " << name << " to: " << (int)x << "," << (int)y << " " << (int)width << "," << (int)height << std::endl;
    }

    //std::cout << "Component::layout - adjusted by prev: " << (int)x << "x" << (int)y << std::endl;
    //std::cout << "Component::layout - moving component to " << (int)x << "x" << (int)y << std::endl;

    // change in X position
    //std::cout << "Component::layout - component was at " << lx << " moved(?) to " << (int)x << std::endl;

    // also need to update texture if our width (which is calculated from windowWidth) changes
    // it's more than width because a change to windowWidth fucks up all coordinates in it
    // so it has to be recalculated (??)
    //if (x!=lx || !textureSetup) {
        // this line may have more or less room
        // we'll need to rewrap component
        wrap();
    //}

    // recurse down over my children and update their position
    // if parent x or y changes or windowWindow changes, all children need to be adjust
    // what about recurse up, if this element changes? well wrap recurses up the parent sizes
    //std::cout << "Component::layout - I have " << children.size() << " children affected by my moved" << std::endl;
    for (std::shared_ptr<Component> child : children) {
        // maybe it should be like this:
        // hey new w/h
        // update wras
        // where do we update layout?
        // So no, because this is a relayout, we don't know sf w/h has changes
        // most component resize()s already update the w/h
        //std::cout << "component::layout - " << name <<  " Doing relayout of a " << typeOfComponent(child) << "-" << child->name << std::endl;

        // update new sizes
        child->windowWidth = windowWidth;
        child->windowHeight = windowHeight;
        // update it's layout
        child->layout();
    }
    // also maybe bump siblings after us (inline if x/y moved, block if y moved)
}

// resize/wordwrap to available width
// our position is required
void Component::wrap() {
    //std::cout << "Component::wrap - " << typeOfComponent(this) << std::endl;
    float lW = width;
    float lH = height;
    resize(windowWidth, windowHeight); // this may change our w/h
    textureSetup = true;
    if (lW != width || lH != height) {
        //std::cout << "Component::wrap - component was " << (int)lW << "x" << (int)lH << " now " << (int)width << "x" << (int)height << std::endl;
        // size has change, update our parent
        updateParentSize();
    } else {
        //std::cout << "Component::wrap - component same size" << std::endl;
    }
}

// measure current, apply changes to parent
// our size is required
void Component::updateParentSize() {
    //std::cout << "Component::updateParentSize - " << typeOfComponent(this) << std::endl;
    if (!parent) {
        //std::cout << "Component::updateParentSize - can't update parent size, no parent" << std::endl;
        return;
    }
    // back up current size
    unsigned int lastParentWidth = parent->width;
    unsigned int lastParentHeight = parent->height;

    parent->endingX = endingX;
    parent->endingY = endingY;

    // find max width of all siblings
    unsigned int maxWidth = width; // float?
    unsigned int heightAccum = 0;
    unsigned int widthAccum = 0;
    unsigned int totalHeight = 0;

    // integrity check
    if (!parent->children.size()) {
        // currently means creation
        std::cout << "Component::updateParentSize - parent's has no children, strange since we are one" << std::endl;
        totalHeight = height;
    }

    // look at siblings
    bool wasInline =  false;
    //std::cout << "Component::updateParentSize - assessing " << parent->children.size() << " children" << std::endl;
    for (std::shared_ptr<Component> child : parent->children) {
        maxWidth = std::max(maxWidth, static_cast<unsigned int>(child->width));
        if (child->isInline) {
            heightAccum = std::max(heightAccum, static_cast<unsigned int>(child->height));
            widthAccum += child->width;
            /*
            if (wasInline) {
                // in in
                heightAccum = std::max(heightAccum, static_cast<int>(child->height));
            } else {
                // bl in
                heightAccum = static_cast<int>(child->height);
            }
            */
        } else {
            if (wasInline) {
                // in bl
                // flush height of previous line + our height
                totalHeight += heightAccum + child->height;
                heightAccum = 0; // reset
                maxWidth = std::max(maxWidth, widthAccum);
                widthAccum = 0;
            } else {
                // bl bl
                totalHeight += child->height;
                // than a max of all widths of the two is fine
            }
        }
        wasInline = child->isInline;
    }
    // flush any remaining width/heightAccum
    totalHeight += heightAccum;
    maxWidth = std::max(maxWidth, widthAccum);

    //std::cout << "new size " << maxWidth << "x" << totalHeight << std::endl;
    // did our size actually change
    if (lastParentWidth != maxWidth || lastParentHeight != totalHeight) {
        //std::cout << "Component::updateParentSize - from " << (int)height << " totalHeight: " << totalHeight << std::endl;
        parent->width = maxWidth;
        parent->height = totalHeight;
        parent->updateParentSize(); // our parent size can only be recalculated by looking at it's children
    }
}

GLuint elementBufferObject = 0;
void Component::initTheElementBufferObject() const {
    glGenBuffers(1, &elementBufferObject);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBufferObject);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
}

GLuint Component::CreateVertexArrayObject() const {
    GLuint vertexArrayObject = 0;
    glGenVertexArrays(1, &vertexArrayObject);
    glBindVertexArray(vertexArrayObject);

    // we may need the VBO here...

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBufferObject);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), nullptr);
    glEnableVertexAttribArray(0);

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

    //glBindVertexArray(0);
    return vertexArrayObject;
}

GLuint Component::CreateVertexBufferObject() const {
    GLuint vertexBufferObject = 0;
    glGenBuffers(1, &vertexBufferObject);
    return vertexBufferObject;
}

// this always creates, we probably should just respond to existing state
bool Component::setPosition4(GLuint vertexBufferObject, int x0, int y0, int x1, int y1) const {
    float vertices[20] = {
        0.0f, 0.0f, 0.0f,    0.0f, 1.0f,
        0.0f, 0.0f, 0.0f,    1.0f, 1.0f,
        0.0f, 0.0f, 0.0f,    1.0f, 0.0f,
        0.0f, 0.0f, 0.0f,    0.0f, 0.0f
    };

    vertices[(0 * 5) + 0] = x0;
    vertices[(0 * 5) + 1] = y1;

    vertices[(1 * 5) + 0] = x1;
    vertices[(1 * 5) + 1] = y1;

    vertices[(2 * 5) + 0] = x1;
    vertices[(2 * 5) + 1] = y0;

    vertices[(3 * 5) + 0] = x0;
    vertices[(3 * 5) + 1] = y0;

    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject); // selects buffer
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // writes buffer

    return true;
}

GLuint Component::CreateTexture() const {
    GLuint textureNum = 0;
    glGenTextures(1, &textureNum);
    return textureNum;
}

bool Component::setTexture(GLuint textureNum, GLsizei w, GLsizei h, const unsigned char *texture) const {
    glBindTexture(GL_TEXTURE_2D, textureNum);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture);
    glGenerateMipmap(GL_TEXTURE_2D);
    return true;
}

// converts 0-1 to screen
// but centered
void Component::pointToViewport(float &rawX, float &rawY) const {
    if (useBoxShader) {
        //std::cout << "Component::pointToViewport - notBoundToPage converting from " << static_cast<int>(rawX) << "," << static_cast<int>(rawY) << std::endl;
        //std::cout << "BoundToPage using " << screenWidth << "x" << screenHeight << std::endl;
        rawX = ((rawX / windowWidth) * 2) - 1;
        rawY = ((rawY / windowHeight) * 2) - 1;
        //std::cout << "Component::pointToViewport - BoundToPage using " << static_cast<int>(rawX) << "x" << static_cast<int>(rawY) << std::endl;
        //std::cout << "Component::pointToViewport - BoundToPage converted to " << rawX << "," << rawY << std::endl;
    } else {
        //std::cout << "notBoundToPage using " << screenWidth << "x" << screenHeight << std::endl;
        //std::cout << "Component::pointToViewport - notBoundToPage converting from " << static_cast<int>(rawX) << "," << static_cast<int>(rawY) << std::endl;
        if (rawX < 0) {
            rawX += windowWidth;
        }
        if (rawY < 0) {
            rawY += windowHeight;
        }
        if (rawX > 1) {
            rawX /= windowWidth;
        }
        if (rawY > 1) {
            rawY /= windowHeight;
        }
        rawX = (rawX * 2) - 1;
        rawY = (rawY * 2) - 1;
        //std::cout << "Component::pointToViewport - notBoundToPage converted to " << rawX << "," << rawY << std::endl;
    }
}

// keeps 0-1 (and *2 to convert to screen)
// but also takes pixels (and converts to screen)
// anchors to upperleft
void Component::distanceToViewport(float &rawX, float &rawY) const {
    if (std::abs(rawX) > 1) {
        rawX /= windowWidth;
    }
    if (std::abs(rawY) > 1) {
        rawY /= windowHeight;
    }
    rawX *= 2;
    rawY *= 2;
}

void Component::printComponentTree(const std::shared_ptr<Component> &component, int depth) {
    for (int i = 0; i < depth; i++) {
        std::cout << '\t';
    }
    ButtonComponent *butComponent = dynamic_cast<ButtonComponent*>(component.get());
    InputComponent *inputComponent = dynamic_cast<InputComponent*>(component.get());
    if (butComponent) {
        std::cout << std::fixed << "X: " << static_cast<int>(butComponent->x) << " Y: " << static_cast<int>(butComponent->y) << " WIDTH: " << static_cast<int>(butComponent->width) << " HEIGHT: " << static_cast<int>(butComponent->height) << " INLINE: " << butComponent->isInline << " Bound: " << butComponent->boundToPage << " BUTTON: " << butComponent->value << std::endl;
    } else
    if (inputComponent) {
        std::cout << std::fixed << "X: " << static_cast<int>(inputComponent->x) << " Y: " << static_cast<int>(inputComponent->y) << " WIDTH: " << static_cast<int>(inputComponent->width) << " HEIGHT: " << static_cast<int>(inputComponent->height) << " INLINE: " << inputComponent->isInline << " Bound: " << inputComponent->boundToPage << " INPUT: " << inputComponent->getValue() << std::endl;
    } else {
        TextComponent *textComponent = dynamic_cast<TextComponent*>(component.get());
        if (textComponent) {
            std::cout << std::fixed << "X: " << static_cast<int>(textComponent->x) << " Y: " << static_cast<int>(textComponent->y) << " WIDTH: " << static_cast<int>(textComponent->width) << " HEIGHT: " << static_cast<int>(textComponent->height) << " INLINE: " << textComponent->isInline << " TEXT: " << textComponent->text << std::endl;
        }
        else {
            std::cout << std::fixed << "X: " << static_cast<int>(component->x) << " Y: " << static_cast<int>(component->y) << " WIDTH: " << static_cast<int>(component->width) << " HEIGHT: " << static_cast<int>(component->height) << " INLINE: " << component->isInline << " NAME: " << component->name << std::endl;
        }
    }
    for (std::shared_ptr<Component> child : component->children) {
        Component::printComponentTree(child, depth + 1);
    }
}