code:
document = {
  head: "",
  documentElement: {},
  getElementsByTagName: function(tag) {
    return [];
  },
  querySelector: function(selector) {
    return null;
  },
  querySelectorAll: function(selector) {
  },
  addEventListener: function(label, cb, overwrite) {
    //
  }
};
window.innerWidth=600
Date = {
  now: function() {
    //__netrunner_datenow();
  }
}
XMLHttpRequest = {
  // withCredentials:
}

repo_name: despair/netrunner

license: mit


code:
#version 330
uniform sampler2D textureIn;
in vec4 colorPipe;
in vec2 texCoordPipe;
out vec4 colorOut;
void main() {
    //colorOut = vec4(0.0, 0.0, 0.0, texture(textureIn, texCoordPipe).r); // TODO blend alpha
    colorOut = vec4(colorPipe.rgb, texture(textureIn, texCoordPipe).r); // TODO blend alpha
}


code:
#version 330
uniform mat4 transform;
layout (location = 0) in vec3 vertexIn;
layout (location = 1) in vec4 colorIn;
layout (location = 2) in vec2 texCoordIn;

out vec4 colorPipe;
out vec2 texCoordPipe;
void main() {
    gl_Position = transform * vec4(vertexIn, 1.0);
    colorPipe = colorIn;
    texCoordPipe = texCoordIn;
}


code:
#version 330
out vec4 colorOut;
in vec2 texCoordPipe;
uniform sampler2D textureIn;
void main() {
    colorOut = texture(textureIn, texCoordPipe);
}


code:
#version 330
uniform mat4 transform;
layout (location = 0) in vec3 vertexIn;
layout (location = 1) in vec2 texCoordIn;
out vec2 texCoordPipe;
void main() {
    gl_Position = transform * vec4(vertexIn, 1.0);
    texCoordPipe = texCoordIn;
}


code:
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by netrunner.rc
//
#define IDI_ICON1                       102
#define IDI_ICON2                       104

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        105
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


code:
#include "App.h"
#include "../tools/Scheduler.h"
#include "../interfaces/components/TabbedComponent.h"
#include "../parsers/markup/ntrml/NTRMLParser.h"
#include "../parsers/markup/html/HTMLParser.h"

#include "../interfaces/components/DocumentComponent.h"
//#include "interfaces/components/ComponentBuilder.h"
#include "../interfaces/components/InputComponent.h"
#include "../interfaces/components/ImageComponent.h"
#include "../interfaces/components/TabbedComponent.h"
#include "../interfaces/components/ButtonComponent.h"

#include <fstream>
#ifndef _WIN32
#include <glob.h>
#else
#include "../win32-glob.h"
#endif

// why can't I const this?
std::unique_ptr<Scheduler> scheduler = std::make_unique<Scheduler>();

void doOnClick(std::string type, Component *component, Window *win) {
    if (type=="back") {
        component->onClick=[win]() {
            std::cout << "Browser::doOnClick.navBackButton->onClick - Back" << std::endl;
            TabbedComponent *pTabComponent = dynamic_cast<TabbedComponent*>(win->tabComponent.get());
            if (pTabComponent) {
                if (pTabComponent->selectedTabId) {
                    pTabComponent->selectedTab->get()->history->print();
                    pTabComponent->selectedTab->get()->history->back();
                } else {
                    std::cout << "Browser::doOnClick.navBackButton->onClick - no tab selected" << std::endl;
                }
            } else {
                std::cout << "Browser::doOnClick.navBackButton->onClick - no tabbed component" << std::endl;
            }
        };
    } else if (type=="forward") {
        component->onClick=[win]() {
            //std::cout << "Forward" << std::endl;
            TabbedComponent *pTabComponent = dynamic_cast<TabbedComponent*>(win->tabComponent.get());
            if (pTabComponent) {
                if (pTabComponent->selectedTabId) {
                    pTabComponent->selectedTab->get()->history->forward();
                }
            }
        };
    } else if (type=="refresh") {
        component->onClick=[win]() {
            std::shared_ptr<DocumentComponent> docComponent = win->getActiveDocumentComponent();
            if (docComponent) {
                std::cout << "Refreshing " << docComponent->currentURL << std::endl;
                // now tell it to navigate somewhere
                docComponent->navTo(docComponent->currentURL.toString());
            }
        };
    } else if (type=="go") {
        component->onClick=[win]() {
            // get address bar value and nav away
            std::string value;
            InputComponent *p_addressComponent = dynamic_cast<InputComponent*>(win->addressComponent.get());
            if (!p_addressComponent) {
                std::cout << "Browser:zdoOnClick.navAddressBar->onClick - can't find address bar in windows " << std::endl;
                return;
            }
            //std::cout << "Browser:zdoOnClick.navAddressBar->onClick - got " << value << std::endl;

            TabbedComponent *p_tabComponent = dynamic_cast<TabbedComponent*>(win->tabComponent.get());
            if (p_tabComponent) {
                if (!p_tabComponent->tabs.size()) {
                    p_tabComponent->addTab("Loading...");
                    p_tabComponent->selectTab(p_tabComponent->tabs.back());

                    win->renderDirty = true;
                    win->render(); // display loading tab before IO
                }
            }

            std::shared_ptr<DocumentComponent> docComponent = win->getActiveDocumentComponent();
            if (docComponent) {
                //std::cout << "Browser:zdoOnClick.navAddressBar->onClick - Found an active document component" << std::endl;
                // now tell it to navigate somewhere
                docComponent->navTo(value);
            } else {
                std::cout << "Browser:zdoOnClick.navAddressBar->onClick - No active document component" << std::endl;
            }
        };
    } else if (type=="nextTab") {
        component->onClick=[win]() {
            // get tab value and nav away
        };
    } else if (type=="prevTab") {
        component->onClick=[win]() {
            // get tab value and nav away
        };
    }
}

std::shared_ptr<Node> App::loadTheme(std::string filename) {
    std::string ntrml, line;
    std::ifstream myfile(filename);
    if (myfile.is_open()) {
        while(getline(myfile, line)) {
            ntrml += line;
        }
        myfile.close();
    } else {
        std::cout << "Couldnt read " << filename << std::endl;
    }
    NTRMLParser uiParser;
    //std::cout << "Browser read [" << ntrml << "]" << std::endl;
    return uiParser.parse(ntrml);
}

void App::destroyTheme() {
    for(auto win: this->windows) {
        if (win->tabComponent) {
            TabbedComponent *tabbedComponent = dynamic_cast<TabbedComponent *>(win->tabComponent.get());
            for(auto tab: tabbedComponent->tabs) {
                tabbedComponent->removeTab(tab->id);
            }
        }
        win->addressComponent = nullptr;
        win->tabComponent = nullptr;
        for(auto layer: win->ui->layers) {
            layer.reset();
        }
        win->ui->layers.clear();
    }
}

void App::rebuildTheme() {
    for(auto win: this->windows) {

        // convert uiRootNode into a component list
        std::shared_ptr<Component> rootComponent = std::make_shared<Component>();
        rootComponent->name = "rootComponent of browser";
        // we build one global tree that each layer has a child node in
        this->layers.clear(); // nuke any layers we have
        this->createComponentTree(this->uiRootNode, rootComponent, win);
        //Component::printComponentTree(rootComponent, 0);

        // these probably should be fatal
        if (!win->addressComponent) {
            std::cout << "ERROR: no addressComponent" << std::endl;
        }
        if (!win->tabComponent) {
            std::cout << "ERROR: no tabComponent" << std::endl;
        }

        // we want each window to has it's own component tree, so each address bar can have different values
        size_t layerCount = 0;
        for(auto layer: this->layers) {
            // set proper vertices
            layer->resize(win->windowWidth, win->windowHeight);
            // transfer layer
            win->ui->layers.push_back(layer);
            // debug
            /*
            std::cout << "layer: " << layerCount << std::endl;
            Component::printComponentTree(layer, 0);
             */
            layerCount++;
            layer.reset(); // deallocate our local layers
        }
        rootComponent.reset();

        //newWindow->rootComponent = rootComponent;
        //newWindow->ui->rootComponent = rootComponent;
        win->renderDirty = true;

    }
}

void App::transferTheme(std::string filename) {
    // assuming we don't need the NodeTree to get our values
    this->uiRootNode.reset();
    this->uiRootNode = this->loadTheme(filename);

    for(auto win: this->windows) {
        glfwMakeContextCurrent(win->window);

        TabbedComponent *oldTabbedComponent = nullptr;
        if (win->tabComponent) {
            oldTabbedComponent = dynamic_cast<TabbedComponent *>(win->tabComponent.get());
        }
        win->tabComponent = nullptr; // release to release
        win->addressComponent = nullptr; // I think this is ok to release now

        // convert uiRootNode into a component list
        std::shared_ptr<Component> rootComponent = std::make_shared<Component>();
        rootComponent->name = "rootComponent of browser";
        this->layers.clear(); // nuke any layers we had (doesn't nuke window layers)
        // we build one global tree that each layer has a child node in
        this->createComponentTree(this->uiRootNode, rootComponent, win);
        //Component::printComponentTree(rootComponent, 0);

        // these probably should be fatal
        if (!win->addressComponent) {
            std::cout << "ERROR: no addressComponent" << std::endl;
        }
        if (!win->tabComponent) {
            std::cout << "ERROR: no tabComponent" << std::endl;
        }

        // ok now we need to transfer the tabs over
        if (oldTabbedComponent) {
            // should have been set in createComponentTree
            TabbedComponent *newTabbedComponent = dynamic_cast<TabbedComponent *>(win->tabComponent.get());
            if (newTabbedComponent) {
                size_t oldSelectedTabId = oldTabbedComponent->selectedTabId;
                std::shared_ptr<Tab> newSelectedTab = nullptr;
                std::cout << "going to copy " << oldTabbedComponent->tabs.size() << " tab(s) over to new theme" << std::endl;
                for(auto tab: oldTabbedComponent->tabs) {
                    DocumentComponent *oldDocComponent = dynamic_cast<DocumentComponent *>(tab->contents.get());
                    if (oldDocComponent) {
                        //std::cout << "Reading DOM" << std::endl;
                        //printNode(oldDocComponent->domRootNode, 0);

                        std::shared_ptr<Tab> nTab = newTabbedComponent->addTab(tab->titleBox->text);
                        newTabbedComponent->selectTab(nTab);
                        // maybe faster if we can skip the component tree rebuild
                        newTabbedComponent->loadDomIntoTab(oldDocComponent->domRootNode, tab->titleBox->text);
                        //newTabbedComponent->documentComponent.get()
                        DocumentComponent *newDocComponent = dynamic_cast<DocumentComponent *>(newTabbedComponent->tabs.back()->contents.get());
                        if (newDocComponent) {
                            // really slow and we don't need to do this for all tabs
                            // not sure why this is required...
                            //newDocComponent->setDOM(oldDocComponent->domRootNode);
                            //std::cout << "Checking DOM" << std::endl;
                            //printNode(newTabbedComponent->documentComponent->, 0);
                            newDocComponent->render(); // flush out components
                            //newDocComponent->domDirty = true; // is most accurate
                        } else {
                            std::cout << "new tab had no doc" << std::endl;
                        }
                        if (tab->id == oldSelectedTabId) {
                            // translate old selected id into new id
                            newSelectedTab = nTab;
                        }
                    } else {
                        std::cout << "old tab had no doc" << std::endl;
                    }
                    // this breaks everything
                    //oldTabbedComponent->removeTab(tab->id);
                }
                if (newSelectedTab) {
                    newTabbedComponent->selectTab(newSelectedTab);
                    /*
                    DocumentComponent *newDocComponent = dynamic_cast<DocumentComponent *>(newTabbedComponent->documentComponent.get());
                    if (newDocComponent) {
                        // FIXME: can only in render active window/ogl context
                        newDocComponent->render(); // flush out components
                    } else {
                        std::cout << "new selected tab had no doc" << std::endl;
                    }
                    */
                } else {
                    std::cout << "no new selected tab" << std::endl;
                }
                /*
                std::cout << "new tabbed component has " << newTabbedComponent->tabs.size() << " tab(s) over from old theme" << std::endl;
                for(auto tab: newTabbedComponent->tabs) {
                    DocumentComponent *newDocComponent = dynamic_cast<DocumentComponent *>(tab->contents.get());
                    std::cout << "checking tab DOM" << std::endl;
                    printNode(newDocComponent->domRootNode, 0);
                }
                if (newTabbedComponent->documentComponent) {
                    DocumentComponent *newDocComponent = dynamic_cast<DocumentComponent *>(newTabbedComponent->documentComponent.get());
                    std::cout << "checking selected DOM" << std::endl;
                    printNode(newDocComponent->domRootNode, 0);
                }
                */
            } else {
                std::cout << "new theme didnt have tab component" << std::endl;
            }
        } else {
            std::cout << "old theme didnt have tab component" << std::endl;
        }

        // now we're officially done with old layer, nuked them
        for(auto layer: win->ui->layers) {
            layer.reset();
        }
        win->ui->layers.clear();

        // we want each window to has it's own component tree, so each address bar can have different values
        size_t layerCount = 0;
        for(auto layer: this->layers) {
            // set proper vertices
            layer->resize(win->windowWidth, win->windowHeight);
            // transfer layer
            win->ui->layers.push_back(layer);
            // debug
            /*
             std::cout << "layer: " << layerCount << std::endl;
             Component::printComponentTree(layer, 0);
             */
            layerCount++;
            layer.reset(); // deallocate our local layers
        }
        rootComponent.reset();

        //newWindow->rootComponent = rootComponent;
        //newWindow->ui->rootComponent = rootComponent;
        win->renderDirty = true;
    }
}

void App::NextTheme() {
    glob_t glob_result;
    static unsigned int t = 0;
#ifndef _WIN32
    glob("*.ntrml", GLOB_TILDE, nullptr, &glob_result);
#else
    glob("*.ntrml", GLOB_NOCHECK, nullptr, &glob_result);
#endif
    t++;
    if (t == glob_result.gl_pathc) t = 0;
    /*
    for(unsigned int i = 0; i < glob_result.gl_pathc; ++i){
        if (t == i) std::cout << "*";
        std::cout << glob_result.gl_pathv[i] << std::endl;
    }
    */
    // this works decently, but only destroys first window
    /*
    this->destroyTheme();
    //deleteNode(this->uiRootNode);
    this->uiRootNode.reset();
    this->uiRootNode = this->loadTheme(std::string(glob_result.gl_pathv[t]));
    this->rebuildTheme();
     */
    this->transferTheme(glob_result.gl_pathv[t]);
    // start with removing existing theme
    // and replacing with new version of existing theme
}

// Previously defined components with functionality
// 1+ image (no functionality)
// 1+ tabbed document
// 1+ back/fwd/reload/stop components
// 1+ address bar
void App::createComponentTree(const std::shared_ptr<Node> node, std::shared_ptr<Component> &parentComponent, std::shared_ptr<Window> win) {
    // FIXME: remove these 2 vars
    int winWidth = win->windowWidth;
    int winHeight = win->windowHeight;
    std::string tag;
    if (node == nullptr) {
        std::cout << "ComponentBuilder::build - node is null" << std::endl;
        return;
    }
    TagNode *tagNode = nullptr;
    if (node->nodeType == NodeType::TAG) {
        tagNode = dynamic_cast<TagNode*>(node.get());
        if (tagNode) {
            tag = tagNode->tag;
        }
    } else if (node->nodeType == NodeType::TEXT) {
        // just ignore text blocks
        return;
        tagNode = dynamic_cast<TagNode*>(node->parent.get());
        if (tagNode) {
            tag = tagNode->tag;
        }
    }
    std::shared_ptr<Component> component = nullptr;
    if (!tagNode) {
        // usually the root node...
        //std::cout << "!tagnode" << std::endl;
        component = std::make_shared<Component>();
        for (std::shared_ptr<Node> child : node->children) {
            createComponentTree(child, component, win);
        }
        return;
    }

    //std::cout << "Looking at tag[" << tag << "]" << std::endl;
    if (tag == "layer") {
        // the layering only works for default layout due to the ordering we're doign in window.render
        component = std::make_shared<Component>();
        this->layers.push_back(component);
        //parentComponent = component;
        //std::swap(component, parentComponent);
        //std::cout << "there are now " << this->layers.size() << " layers" << std::endl;
    } else if (tag == "body") {
        if (tagNode->properties.find("bgcolor") != tagNode->properties.end()) {
            std::stringstream ss;
            ss << std::hex << tagNode->properties["bgcolor"];
            ss >> win->clearColor;
            //std::cout << "set clear color " << std::hex << win->clearColor << std::dec << std::endl;

            float r = (static_cast<float>((win->clearColor >> 24) & 0xFF)) / 255;
            float g = (static_cast<float>((win->clearColor >> 16) & 0xFF)) / 255;
            float b = (static_cast<float>((win->clearColor >>  8) & 0xFF)) / 255;
            float a = (static_cast<float>((win->clearColor >>  0) & 0xFF)) / 255;
            glClearColor(r, g, b, a);
        }
    } else if (tag == "img") {
        std::string src = "anime.pnm";
        if (tagNode->properties.find("src") != tagNode->properties.end()) {
            src = tagNode->properties["src"];
        }
        //std::shared_ptr<ImageComponent> img = std::make_unique<ImageComponent>(src, winWidth * imgSetup.left.pct * 0.01 + imgSetup.left.px, winHeight * imgSetup.top.pct * 0.01 + imgSetup.top.px, imgSetup.width.px, imgSetup.height.px, winWidth, winHeight);
        std::shared_ptr<ImageComponent> img = std::make_unique<ImageComponent>(src, 0, 0, 512, 512, winWidth, winHeight);
        img->boundToPage = false; // have to set this before setUpUI
        img->setUpUI(tagNode->properties, win.get());
        // set up interactivity
        if (tagNode->properties.find("onClick") != tagNode->properties.end()) {
            img->isPickable = true;
            //doOnClick(std::string type, Component &component, Window *win)
            doOnClick(tagNode->properties["onClick"], img.get(), win.get());
        }
        img->isPickable = false;
        img->name = "img";
        if (tagNode->properties.find("name") != tagNode->properties.end()) {
            img->name = tagNode->properties["name"];
        }
        component = img;
        //std::cout << "Added img component to ui" << std::endl;
    } else if (tag == "font") {
        // 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::string label = "NeTRunner";
        if (node->children.size()) {
            TextNode *textNode = dynamic_cast<TextNode*>(node->children.front().get());
            if (textNode) {
                label = textNode->text;
            }
        }
        std::shared_ptr<TextComponent> text = std::make_unique<TextComponent>(label, 0, 0, 12, false, 0x000000FF, winWidth, winHeight);
        text->boundToPage = false; // have to set this before setUpUI
        text->win = win;
        // this may not work for the text shader... yea doesn't seem to
        text->setUpUI(tagNode->properties, win.get());
        text->isPickable = false;
        //text->resize(win->windowWidth, win->windowHeight); // force a re-raster
        // set up interactivity
        if (tagNode->properties.find("onClick") != tagNode->properties.end()) {
            text->isPickable = true;
            //doOnClick(std::string type, Component &component, Window *win)
            doOnClick(tagNode->properties["onClick"], text.get(), win.get());
        }
        component = text;
    } else if (tag == "box") {
        unsigned int color = 0x888888FF;
        if (tagNode->properties.find("color") != tagNode->properties.end()) {
            std::stringstream ss;
            ss << std::hex << tagNode->properties["color"];
            ss >> color;
            //std::cout << "read color " << tagNode->properties["color"] << " as " << std::hex << color << std::dec << std::endl;
        }

        // winHeight minus because box coordinates? yes
        //std::cout << "placing box at " << cX << "," << cY << " " << cW << "x" << cH << " color: " << std::hex << color << std::dec << std::endl;
        //std::shared_ptr<BoxComponent> box = std::make_unique<BoxComponent>(cX, winHeight - cH - cY, cW, cH, color, winWidth, winHeight);
        std::shared_ptr<BoxComponent> box = nullptr;
        if (node->children.size()) {
            box = std::make_unique<ButtonComponent>(0, 0, 1, 1, color, winWidth, winHeight);
        } else {
            box = std::make_unique<BoxComponent>(0, 0, 1, 1, color, winWidth, winHeight);
        }
        box->boundToPage = false; // have to set this before setUpUI
        box->setUpUI(tagNode->properties, win.get());
        box->isPickable = false;
        box->name = "box";
        if (tagNode->properties.find("name") != tagNode->properties.end()) {
            box->name = tagNode->properties["name"];
        }
        // set up interactivity
        if (tagNode->properties.find("hover") != tagNode->properties.end()) {
            box->isPickable = true;
            unsigned int hoverColor = 0;
            std::stringstream ss;
            ss << std::hex << tagNode->properties["hover"];
            ss >> hoverColor;
            //std::cout << "setHover Color " << std::hex << hoverColor << std::dec << std::endl;
            box->onMouseover = [box, win, hoverColor]() {
                //std::cout << "box->onMouseover" << std::endl;
                box->changeColor(hoverColor);
                win->renderDirty = true;
            };
            box->onMouseout = [box, win, color]() {
                //std::cout << "box->onMouseout" << std::endl;
                box->changeColor(color);
                win->renderDirty = true;
            };
        }
        if (tagNode->properties.find("onClick") != tagNode->properties.end()) {
            box->isPickable = true;
            //doOnClick(std::string type, Component &component, Window *win)
            doOnClick(tagNode->properties["onClick"], box.get(), win.get());
        }
        component = box;
        // if this button node has children, extract the text before discarding it
        if (node->children.size()) {
            TextNode *textNode = dynamic_cast<TextNode*>(node->children.front().get());
            if (textNode) {
                // so now cast back to a ButtonComponent and set it
                std::cout << "Box text: " << textNode->text << std::endl;
                /*
                ButtonComponent *buttonComponent = dynamic_cast<ButtonComponent*>(component.get());
                if (buttonComponent) {
                    buttonComponent->value = textNode->text;
                    buttonComponent->resizeToTextSize();
                    buttonComponent->updateText();
                }
                */
            }
        }
        // manual quick add before we bail
        component->setParent(parentComponent);
        parentComponent->children.push_back(component);
        // skip my children
        return;
    } else if (tag == "input") {
        //std::shared_ptr<InputComponent> navAddressBar = std::make_unique<InputComponent>(192.0f, winHeight - 48.0f, winWidth - 384.0f, 24.0f, winWidth, winHeight);
        std::shared_ptr<InputComponent> navAddressBar = std::make_unique<InputComponent>(0, 0, 384.0f, 24.0f, winWidth, winHeight);
        navAddressBar->boundToPage = false; // have to set this before setUpUI
        navAddressBar->setUpUI(tagNode->properties, win.get());
        navAddressBar->name = "navAddressBar";

        navAddressBar->onEnter=[win](std::string value) {
            std::cout << "navAddressBar::onEnter got " << value << std::endl;

            TabbedComponent *p_tabComponent = dynamic_cast<TabbedComponent*>(win->tabComponent.get());
            if (p_tabComponent) {
                if (!p_tabComponent->tabs.size()) {
                    p_tabComponent->addTab("Loading...");
                    p_tabComponent->selectTab(p_tabComponent->tabs.back());

                    win->renderDirty = true;
                    win->render(); // display loading tab before IO
                }
            }

            std::shared_ptr<DocumentComponent> docComponent = win->getActiveDocumentComponent();
            if (docComponent) {
                std::cout << "Found an active document component" << std::endl;
                // now tell it to navigate somewhere
                docComponent->navTo(value);
            } else {
                std::cout << "No active document component" << std::endl;
            }
        };

        navAddressBar->win = win;
        win->addressComponent = navAddressBar;
        component = navAddressBar;
    } else if (tag == "tabselector") {
        std::shared_ptr<TabbedComponent> tabbedComponent = std::make_shared<TabbedComponent>(0, 0, static_cast<float>(winWidth), static_cast<float>(winHeight - 64), winWidth, winHeight);
        tabbedComponent->win = win;
        win->tabComponent = tabbedComponent;
        tabbedComponent->boundToPage = false; // have to set this before setUpUI
        tabbedComponent->setUpUI(tagNode->properties, win.get());
        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;
        */
        component = tabbedComponent;
        // make the UI focus on it to relay keyboard events
        win->ui->relayKeyboardComponent = tabbedComponent;
    } else {
        std::cout << "Browser::createComponentTree - unknown tag[" << tag << "]" << std::endl;
    }
    if (!component) {
        component = std::make_shared<Component>();
    }
    // add it to our parent
    component->setParent(parentComponent);
    parentComponent->children.push_back(component);
    // , children count: " << parentComponent->children.size()

    // create children elements
    for (std::shared_ptr<Node> child : node->children) {
        createComponentTree(child, component, win);
    }
    component.reset();
}

void App::addJSDebuggerWindow() {
    windowCounter++;
    std::shared_ptr<Window> newWindow = std::make_shared<Window>();
    newWindow->id = windowCounter;
    newWindow->windowWidth = 1024;
    newWindow->windowHeight = 640;
    newWindow->init(); // load our UI into it
    std::shared_ptr<Component> p_rootComponent = std::make_shared<Component>();
    p_rootComponent->name = "rootComponent of jsconsole";

    std::shared_ptr<TextComponent> outputComp = std::make_shared<TextComponent>("NeTRunner JavaScript console", 0, 0, 12, false, 0x000000FF, newWindow->windowWidth, newWindow->windowHeight);
    outputComp->win = newWindow;
    newWindow->ui->layers.push_back(outputComp);
    outputComp.reset();

    /*
    std::shared_ptr<InputComponent> inputComp = std::make_shared<InputComponent>(0, 0, 1024, 12, newWindow->windowWidth, newWindow->windowHeight);
    inputComp->win = newWindow;
    newWindow->ui->layers.push_back(inputComp);
    inputComp.reset();
     */

    this->windows.push_back(newWindow);
    newWindow.reset();
}

void App::addWindow() {
    windowCounter++;
    std::shared_ptr<Window> newWindow = std::make_shared<Window>();
    newWindow->id = windowCounter;
    newWindow->windowWidth = 1024;
    newWindow->windowHeight = 640;
    newWindow->init(); // load our UI into it

    // convert uiRootNode into a component list
    std::shared_ptr<Component> p_rootComponent = std::make_shared<Component>();
    p_rootComponent->name = "rootComponent of browser";
    // we build one global tree that each layer has a child node in

    this->createComponentTree(this->uiRootNode, p_rootComponent, newWindow);
    //Component::printComponentTree(p_rootComponent, 0);

    // these probably should be fatal
    if (!newWindow->addressComponent) {
        std::cout << "ERROR: no addressComponent" << std::endl;
    }
    if (!newWindow->tabComponent) {
        std::cout << "ERROR: no tabComponent" << std::endl;
    }

    // we want each window to has it's own component tree, so each address bar can have different values
    size_t layerCount = 0;
    for(auto layer: this->layers) {
        // set proper vertices
        layer->resize(newWindow->windowWidth, newWindow->windowHeight);
        // transfer layer
        newWindow->ui->layers.push_back(layer);
        // debug
        /*
        std::cout << "layer: " << layerCount << std::endl;
        Component::printComponentTree(layer, 0);
        */
        layerCount++;
        layer.reset(); // deallocate our local layers
    }
    this->layers.clear(); // nuke any layers we have
    p_rootComponent.reset();

    //newWindow->rootComponent = rootComponent;
    //newWindow->ui->rootComponent = rootComponent;
    newWindow->renderDirty = true;

    this->windows.push_back(newWindow);
    newWindow.reset();

    if (!activeWindow) {
        //std::cout << "Browser::addWindow - setting active window" << std::endl;
        activeWindow = this->windows.back().get();
    }
}

// FIXME: put quit check in here and clean up windows
void App::render() {
    // guessing during render this iterator can be invalidated
    size_t w = this->windows.size();
    //for(std::vector<std::shared_ptr<Window>>::iterator it = this->windows.begin(); it != this->windows.end(); ++it) {
        //it->get()->render();
    // definitely safer
    for(size_t i = 0; i < w; ++i) {
        glfwMakeContextCurrent(this->windows[i]->window);
        this->windows[i]->render();
    }
}

void App::loop() {
    bool shouldQuit = false;
    while (!shouldQuit) {
        //const std::clock_t begin = clock();
        this->render();
        scheduler->fireTimers(); // render may have taken some time
        double next = scheduler->getNext();
        //if (!next) std::cout << "timer starvation, refiring" << std::endl;
        /*
        while(!next) {
            std::cout << "timer starvation, refiring" << std::endl;
            scheduler->fireTimers(); // render may have taken some time
            next = scheduler->getNext();
        }
         */
        //std::cout << "Browser::loop - next timer at " << next << std::endl;
        if (next == LONG_MAX) {
            glfwWaitEvents(); // block until something changes
        } else {
            glfwWaitEventsTimeout(next / 1000);
        }
        scheduler->fireTimers(); // check before we go into render again
        //glfwWaitEventsTimeout(1.0 / 60.0); // increase the cpu from 0% to 2% on idle
        //const std::clock_t end = clock();
        //std::cout << '\r' << std::fixed << (((static_cast<double>(end - begin)) / CLOCKS_PER_SEC) * 1000) << std::scientific << " ms/f    " << std::flush;
        // FIXME: one close shouldn't close the app
        for(std::vector<std::shared_ptr<Window>>::iterator it = this->windows.begin(); it != this->windows.end(); ++it) {
            if (glfwWindowShouldClose(it->get()->window)) {
                // FIXME: just remove window from stack
                shouldQuit = true;
                break;
            }
        }
    }
}


code:
#ifndef APP_H
#define APP_H

#include "../interfaces/graphical/renderers/glfw/Window.h"
#include "../interfaces/components/Component.h"
#include "../parsers/markup/Node.h"
//#include <GL/glew.h>
//#include <GLFW/glfw3.h>
//#include <memory>
#include <vector>
#include "../platform/tlsf.h"
//#include <algorithm>

void doOnClick(std::string type, Component *component, Window *win);

// this is the actually application that
// contains a list of windows
class App {
public:
    std::shared_ptr<Node> loadTheme(std::string filename);
    void destroyTheme();
    // maybe applyTheme?
    void rebuildTheme();
    void transferTheme(std::string filename);
    void NextTheme();
    void createComponentTree(const std::shared_ptr<Node> node, std::shared_ptr<Component> &parentComponent, std::shared_ptr<Window> win);
    void addJSDebuggerWindow();
    void addWindow();
    void render();
    void loop();

    //
    // properties
    //
    std::shared_ptr<Node> uiRootNode;
    std::vector<std::shared_ptr<Component>> layers;

    // because components take a shared_ptr for win, this has to be shared too
    std::vector<std::shared_ptr<Window>> windows;
    Window *activeWindow = nullptr;
    size_t windowCounter = 0;
};

#endif


code:
#include "Browser.h"
#include "../../interfaces/components/TabbedComponent.h"
#include "../../parsers/markup/html/HTMLParser.h"

#include "../../interfaces/components/InputComponent.h"
#include "../../interfaces/components/ImageComponent.h"
#include "../../interfaces/components/TabbedComponent.h"
#include "../../interfaces/components/ButtonComponent.h"

Browser::Browser() {
    /*
    std::string ntrml, line;
    std::ifstream myfile("browser.ntrml");
    if (myfile.is_open()) {
        while(getline(myfile, line)) {
            ntrml += line;
        }
        myfile.close();
    } else {
        std::cout << "Couldnt read browser.ntrml" << std::endl;
    }
    NTRMLParser uiParser;
    //std::cout << "Browser read [" << ntrml << "]" << std::endl;
    */
    //this->uiRootNode = uiParser.parse(ntrml);
    this->uiRootNode = this->loadTheme("browser.ntrml");
    //printNode(this->uiRootNode, 0);
}

/*
void Browser::addWindow() {
    App::addWindow();
}
 */

std::shared_ptr<DocumentComponent> Browser::getActiveDocumentComponent() {
    if (!activeWindow) {
        std::cout << "Browser::getActiveDocumentComponent - No active window" << std::endl;
        return nullptr;
    }
    return this->activeWindow->getActiveDocumentComponent();
}


code:
#ifndef BROWSER_H
#define BROWSER_H

#include "../App.h"
//#include "interfaces/graphical/renderers/glfw/Window.h"
//#include "interfaces/components/Component.h"
#include "../../interfaces/components/DocumentComponent.h"
//#include "interfaces/components/ComponentBuilder.h"
//#include "parsers/markup/Node.h"
//#include <GL/glew.h>
//#include <GLFW/glfw3.h>
//#include <memory>
//#include <vector>
//#include <algorithm>
//#include "networking/HTTPResponse.h"
//#include "URL.h"
//#include "interfaces/graphical/renderers/glfw/Window.h"

// separate window functions from browser functions
class Browser : public App {
private:
public:
    Browser();
    //void addWindow();
    std::shared_ptr<DocumentComponent> getActiveDocumentComponent();

    std::shared_ptr<Component> tabComponent = nullptr;
    std::shared_ptr<Component> addressComponent = nullptr;

    //URL currentURL;
};

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

#endif


code:
#include "Environment.h"

#include <vector>

#include "Path.h"
#include "../tools/Log.h"

std::string Environment::resourceDir = "";

void Environment::init() {
	if (!resourceDir.empty())
		return; // already initialized O_o

	std::vector<std::string> paths = {
		"res"
	};
#ifndef _WIN32
	// linux or osx
    paths.push_back("/usr/share/netrunner/resources");
    paths.push_back("/usr/local/share/netrunner/resources");
#else
	paths.push_back(""); //TODO: place it somewhere for windows
#endif

	for (std::string &path : paths) {
		if (Path::directoryExists(path)) {
			resourceDir = path;
			logInfo() << "Found resource dir at " << path << "!" << std::endl;
			break;
		}
	}
}

std::string Environment::getResourceDir() {
	return resourceDir;
}


code:
#ifndef ENVIRONMENT_H
#define ENVIRONMENT_H

#include <string>

class Environment {
public:
	static void init();

	static std::string getResourceDir();

	static void setResourceDir();
private:
	static std::string resourceDir;
};
#endif


code:
#include "Path.h"

#include <sys/stat.h>

bool Path::directoryExists(const std::string &path) {
	struct stat sb;

	if (stat(path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) {
		return true;
	}
	return false;
}

std::string Path::fromUnixPath(const std::string &path) {
	std::string returnPath(path);
#ifdef _WIN32
	for (int i = 0; i < path.size(); i++)
		if (path.at(i) == '/') returnPath.at(i) = '\\';
#endif
	return returnPath;
}


code:
#ifndef PATH_H
#define PATH_H

#include <string>

class Path {
public:
	static bool directoryExists(const std::string &path);

	static std::string fromUnixPath(const std::string &path);
};

#endif


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


code:
#ifndef COMPONENT_H
#define COMPONENT_H

#include <functional>
#include <memory>
#include <vector>
#include <string>
#include <map>
#include <GL/glew.h>
//#include "../opengl/Window.h"

class Window;

const unsigned int indices[6] = {
    0, 1, 2,
    0, 2, 3
};

struct UImetric {
    double lengthPct = 0; // 100% is 1.0 or 100.0 ? 100.0
    int offset = 0;
};

struct UIlayout {
    UImetric x = { false, 0 };
    UImetric y = { false, 0 };
    UImetric w = { false, 0 };
    UImetric h = { false, 0 };
};

struct UImetricV2 {
    bool requested; // initially set
    double pct = 0; // percentage
    int px = 0; // pixels
};

// defines an area that a component covers
class UILayoutV2 {
public:
    // only height or 2 of these can be set
    UImetricV2 top = { false, 0.0, 0 };
    UImetricV2 bottom = { false, 0.0, 0 };
    UImetricV2 height = { false, 0.0, 0 };
    // only width or 2 of these can be set
    UImetricV2 left = { false, 0.0, 0 };
    UImetricV2 right = { false, 0.0, 0 };
    UImetricV2 width = { false, 0.0, 0 };
};
std::unique_ptr<std::pair<UImetricV2, UImetricV2>> getUIMetric(UImetricV2 &s1, UImetricV2 &s2, UImetricV2 &size);

// texturedComponent
// dual textComponent
// container component (drop down, drop text, list)

// should have 2 classes after this
// non-rendererable component (wtf are these?)
// and renderable component
class Component {
public:
    // constructing with a window would make a lot of sense (textcomponent can't rasterize until we have the max texture size)
    virtual ~Component();
    // we're currently constructed with a pos
    // so recalculating it is only good for resize
    // the goal is to combine the resize and initial positioning
    void setUpUI(std::map<std::string, std::string> &properties, Window *win);
    void layout();
    void wrap(); // what resize should be really called but also calls updateParentSize
    void updateParentSize(); // on w/h change
    void setParent(std::shared_ptr<Component> passedParent); // why even?
    // we don't need windowHeight because we prefer vertical pages
    // we shouldn't need to pass these... we should somehow get these from the parent
    // well we need windowWidth/windowHeight for coord conversion
    // we could create a property but then we'd have to update all components on resize
    // which we do already
    // since we update the window size before resize, why just not pass it
    // well no need to pass it if it's just a window* and then we'd already have it
    // but we may hang onto the variables to know what the state is set up for
    // why pass it here? on resize we call layout no this...
    virtual void resize(const int passedWindowWidth, const int passedWindowHeight);

    // vab container has handle, shaderProtoType, vbo, ebo (const)
    // we could just have 2 global vabs on for each shaderProto
    // but then we'd might need to vbos each time (on top of the texture)
    void initTheElementBufferObject() const;
    GLuint CreateVertexArrayObject() const;
    GLuint CreateVertexBufferObject() const;
    bool setPosition4(GLuint vertexBufferObject, int x0, int y0, int x1, int y1) const ;
    GLuint CreateTexture() const ;
    bool setTexture(GLuint textureNum, GLsizei w, GLsizei h, const unsigned char *texture) const ;

    void pointToViewport(float &rawX, float &rawY) const ;
    void distanceToViewport(float &rawX, float &rawY) const ;
    static void printComponentTree(const std::shared_ptr<Component> &component, int depth);

    void unload(); // unload signal hook, pass down to children

    // HELP I'M LOOKING FOR YOUR OPINION
    // a ptr to window would be less memory
    // but it would increase Component's depenedencies
    // could use a base class and that would minimize the dependency
    // but is that really even a problem? does minimizing it help?
    /*
     class screenCoordinates {
         public:
             int windowWidth;
             int windowHeight;
     };
     */
    // possible badly named as these aren't bound to a window
    // how are these used?
    int windowWidth;
    int windowHeight;
    // handle to signal that a redraw is needed, and access to shader programs
    std::shared_ptr<Window> win;
    bool boundToPage = true;
    bool useBoxShader = true;
    bool verticesDirty = false;
    std::shared_ptr<Component> parent = nullptr;
    std::shared_ptr<Component> previous = nullptr;
    std::vector<std::shared_ptr<Component>> children;
    // current placement on screen
    float x = 0.0f;
    float y = 0.0f;
    // text flow (needed here because parent elements of textnode need to pass this info onto next inline element)
    int endingX = 0;
    int endingY = 0;
    // height/width on screen
    float height = 0.0f;
    float width = 0.0f;
    // calculated minw/h (hey we can't wrap smaller than)
    unsigned int calcMinWidth = 0; // widest glyph we have
    unsigned int calcMinHeight = 0; // tallest glyph we have (thin aspect ratio) or ?? (because we scroll, and act like inifinite vertically) (we don't vertically wrap)
    unsigned int calcMaxWidth = 0; // windowWidth or larger (max rs)
    unsigned int calcMaxHeight = 0; // windowHeight or larger (max rs)
    // requested
    float reqHeight = 0.0f;
    float reqWidth = 0.0f;
    unsigned int reqMinHeight = 0;
    unsigned int reqMinWidth = 0;
    unsigned int reqMaxHeight = 0;
    unsigned int reqMaxWidth = 0;
    // margin
    unsigned int topMargin = 0;
    unsigned int leftMargin = 0;
    unsigned int rightMargin = 0;
    unsigned int bottomMargin = 0;
    // padding
    unsigned int topPadding = 0;
    unsigned int leftPadding = 0;
    unsigned int rightPadding = 0;
    unsigned int bottomPadding = 0;
    // x + left margin + left padding = start x for texture
    // size preference (could also prefer t/b, l/r)
    bool growRight = false; // if less than windowWidth, why not be greedy?
    bool growLeft = false;
    bool growTop = false; // if top/bott grow from center
    bool growBottom = false;
    //
    UIlayout uiControl;
    // color
    unsigned int color;
    bool isVisibile = true;
    bool isInline = false; // text-only thing but there maybe other non-text inline
    bool isPickable = true;
    bool textureSetup = false;
    // debug
    std::string name;
    // probably shoudl be vector (or maybe use the event emitter)
    std::function<void(int x, int y)> onMousedown = nullptr;
    std::function<void(int x, int y)> onMouseup = nullptr;
    std::function<void(int x, int y)> onMousemove = nullptr;
    std::function<void(int x, int y)> onWheel = nullptr;
    // https://www.quirksmode.org/dom/events/keys.html
    // FIXME: there's only one action on a keyUp and that's keyRelease
    std::function<void(int key, int scancode, int action, int mods)> onKeyUp = nullptr;
    // "Fires when an actual character is being inserted in, for instance, a text input. It repeats while the user keeps the key depressed."
    std::function<void(int key, int scancode, int action, int mods)> onKeyPress = nullptr;
    std::function<void()> onClick = nullptr;
    std::function<void()> onFocus = nullptr;
    std::function<void()> onBlur = nullptr;
    std::function<void()> onMouseover = nullptr;
    std::function<void()> onMouseout = nullptr;
};

#endif