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


code:
#include "ComponentBuilder.h"
#include <iostream>

#include "../elements/AElement.h"
#include "../elements/H1Element.h"
#include "../elements/H2Element.h"
#include "../elements/H3Element.h"
#include "../elements/LIElement.h"
#include "../elements/SPANElement.h"
#include "../elements/DIVElement.h"
#include "../elements/STRONGElement.h"
#include "../elements/INPUTElement.h"
#include "../elements/BUTTONElement.h"
#include "../elements/TEXTAREAElement.h"

#include "InputComponent.h"
#include "ButtonComponent.h"

// fwd decl
std::shared_ptr<Component> searchComponentTree(const std::shared_ptr<Component> &component, const int passedX, const int passedY);

const std::unordered_map<std::string, std::shared_ptr<Element>> ComponentBuilder::elementMap {
    {"a", std::make_shared<AElement>()},
    {"blockquote", std::make_shared<DIVElement>()},
    {"h1", std::make_shared<H1Element>()},
    {"h2", std::make_shared<H2Element>()},
    {"h3", std::make_shared<H3Element>()},
    {"li", std::make_shared<LIElement>()},
    {"p", std::make_shared<DIVElement>()},
    {"span", std::make_shared<SPANElement>()},
    {"aside", std::make_shared<SPANElement>()},
    {"div", std::make_shared<DIVElement>()},
    {"br", std::make_shared<DIVElement>()},
    {"strong", std::make_shared<STRONGElement>()},
    {"input", std::make_shared<INPUTElement>()},
    {"textarea", std::make_shared<TEXTAREAElement>()},
    {"button", std::make_shared<BUTTONElement>()},
    {"b", std::make_shared<STRONGElement>()},
    {"i", std::make_shared<SPANElement>()}
};

std::shared_ptr<Component> ComponentBuilder::build(const std::shared_ptr<Node> node, const std::shared_ptr<Component> &parentComponent, const std::shared_ptr<Window> win, DocumentComponent *docComponent) {
    std::shared_ptr<Component> component;
    std::string tag;
    if (node == nullptr) {
        std::cout << "ComponentBuilder::build - node is null" << std::endl;
        return nullptr;
    }

    if (node->nodeType == NodeType::TAG) {
        TagNode *tagNode = dynamic_cast<TagNode*>(node.get());
        if (tagNode) {
            tag = tagNode->tag;
        }
    } else if (node->nodeType == NodeType::TEXT) {
        TagNode *tagNode = dynamic_cast<TagNode*>(node->parent.get());
        if (tagNode) {
            tag = tagNode->tag;
        }
    }

    bool isInline = false;

    std::unordered_map<std::string, std::shared_ptr<Element>>::const_iterator elementPair = elementMap.find(tag);
    if (elementPair != elementMap.end()) {
        std::shared_ptr<Element> element = elementPair->second;
        isInline = parentComponent->isInline || element->isInline;
        // also it's Inline if it's a TextNode
        if (dynamic_cast<TextNode*>(node.get())) {
            isInline = true;
        }
        //std::tie(x,y) = getPos(parentComponent, isInline, windowWidth);
        //auto [x, y]=getPos(parentComponent, isInline);

        //component = element->renderer(node, 0, 0, windowWidth, windowHeight); // doesn't always make a component
        ElementRenderRequest request;
        request.node = node;
        request.parentComponent = parentComponent;
        request.docComponent = docComponent;
        component = element->renderer(request); // doesn't always make a component
    } else {
        //std::cout << "Unknown tag: " << tag << std::endl;
    }

    if (!component) {
        //std::cout << "tag [" << tag << "] didn't yeild any component" << std::endl;
        component = std::make_unique<Component>();
    }
    TextComponent *textComponent = dynamic_cast<TextComponent*>(component.get());
    if (textComponent) {
        //std::cout << "compositing [" << textComponent->text << "]" << std::endl;
        // FIXME: put in textcomponent
        if (!textComponent->onMousedown) {
            textComponent->onMousedown =  [textComponent, win](int x, int y) {
                if (win->selectionList.size()) {
                    //std::cout << "unselecting text" << std::endl;
                    for(std::vector<std::shared_ptr<Component>>::iterator it = win->selectionList.begin(); it != win->selectionList.end(); ++it) {
                        TextComponent *selectedTextComponent = dynamic_cast<TextComponent*>(it->get());
                        if (selectedTextComponent) {
                            selectedTextComponent->textSelected = false;
                            selectedTextComponent->updateHighlight();
                        }
                    }
                    win->selectionList.clear();
                }
                //std::cout << "TextSelection starting at " << x << "," << y << std::endl;
                // 0 to -640 (windowHeight)
                // so if y=640 , f(y)=-640
                //int ny = -y;
                //std::cout << "TextSelection adjusted " << x << "," << ny << std::endl;
                win->highlightStartX = x;
                win->highlightStartY = y;
                //std::cout << "Component at " << static_cast<int>(textComponent->x) << "," << static_cast<int>(textComponent->y) << std::endl;
            };
            // mouseover to update selection renderer
            textComponent->onMouseup =  [textComponent, parentComponent, win](int x, int y) {
                //std::cout << "TextSelection ending at " << x << "," << y << std::endl;
                //int ny = -y;
                //std::cout << "TextSelection adjusted " << x << "," << ny << std::endl;
                // find all components in that range
                //std::cout << "TextSelection started at " << win->highlightStartX << "," << win->highlightStartY << std::endl;
                int minX = std::min(win->highlightStartX, x);
                int maxX = std::max(win->highlightStartX, x);
                int minY = std::min(win->highlightStartY, y);
                int maxY = std::max(win->highlightStartY, y);
                // FIXME: find root component and search there
                // can't use win because we maybe in a sub component
                for(int cx = minX; cx < maxX; ++cx) {
                    for(int cy = minY; cy < maxY; ++cy) {
                        //std::cout << "textComponent inside " << cx << "," << cy << std::endl;
                        std::shared_ptr<Component> cp = searchComponentTree(parentComponent, cx, cy);
                        TextComponent *selectedTextComponent = dynamic_cast<TextComponent*>(cp.get());
                        if (selectedTextComponent) {
                            std::vector<std::shared_ptr<Component>>::iterator it = std::find(win->selectionList.begin(), win->selectionList.end(), cp);
                            if (it == win->selectionList.end()) {
                                selectedTextComponent->textSelected = true;
                                selectedTextComponent->updateHighlight();
                                win->renderDirty = true;
                                win->selectionList.push_back(cp);
                            }
                        }
                    }
                }
                //std::cout << "selected " << win->selectionList.size() << " components" << std::endl;
            };
        }
    }
    InputComponent *inputComponent = dynamic_cast<InputComponent*>(component.get());
    ButtonComponent *buttonComponent = dynamic_cast<ButtonComponent*>(component.get());
    //if (inputComponent) {
        // any input set up we need to do?
        // boundToPage defaults to true for components, InputComponent doesn't have it overridded
        //std::cout << "Just built an inputComponent" << std::endl;
        //inputComponent->win = win;
    //}
    //std::cout << "composting component, initial: " << (int)component->width << "x" << (int)component->height << std::endl;

    // set our available dimensions
    component->win = win;
    component->windowWidth = win->windowWidth;
    component->windowHeight = win->windowHeight;
    // set our type
    component->isInline = isInline;

    // need to bind it to the page
    component->boundToPage = true;

    // place us in tree
    component->setParent(parentComponent);
    if (parentComponent) {
        parentComponent->children.push_back(component);
    } else {
        std::cout << "componentBuilder::build - no parentComponent for " << typeOfComponent(component) << std::endl;
    }

    // figure out our position, size, texture
    component->layout();

    // not sure why only this component needs this but it fixes it
    if (inputComponent) {
        //std::cout << "ComponentBuilder::build - Just laid out input component" << std::endl;
        //std::cout << "ComponentBuilder::build - inputComponent at " << (int)inputComponent->x << "," << (int)inputComponent->y << std::endl;
        inputComponent->updateParentSize();
    }
    if (buttonComponent) {
        //std::cout << "ComponentBuilder::build - buttonComponent at " << (int)buttonComponent->x << "," << (int)buttonComponent->y << std::endl;
        // since win is set
        buttonComponent->updateText();
        buttonComponent->updateParentSize();
    }
    //std::cout << "post layout placed: " << (int)component->x << "x" << (int)component->y << " w/h: " << (int)component->width << "x" << (int)component->height << std::endl;
    return component;
}

// these aren't used in any elements yet
#include "DocumentComponent.h"
#include "TabbedComponent.h"
#include "ImageComponent.h"

std::string typeOfComponent(const std::shared_ptr<Component> &component) {
    return typeOfComponent(component.get());
}

std::string typeOfComponent(Component *component) {
    TabbedComponent *tabComponent = dynamic_cast<TabbedComponent*>(component);
    if (tabComponent) return "tab";
    DocumentComponent *docComponent = dynamic_cast<DocumentComponent*>(component);
    if (docComponent) return "doc";
    TextComponent *textComponent = dynamic_cast<TextComponent*>(component);
    if (textComponent) return "text";
    InputComponent *inputComponent = dynamic_cast<InputComponent*>(component);
    if (inputComponent) return "input";
    ButtonComponent *buttonComponent = dynamic_cast<ButtonComponent*>(component);
    if (buttonComponent) return "button";
    ImageComponent *imageComponent = dynamic_cast<ImageComponent*>(component);
    if (imageComponent) return "image";
    BoxComponent *boxComponent = dynamic_cast<BoxComponent*>(component);
    if (boxComponent) return "box";
    return "";
}

// used for picking for text selection
std::shared_ptr<Component> searchComponentTree(const std::shared_ptr<Component> &component, const int passedX, const int passedY) {
    if (component->children.empty()) {
        //std::cout << "MultiComponent::searchComponentTree - component at " << static_cast<int>(component->x) << "," << static_cast<int>(component->y) << " size " << static_cast<int>(component->width) << "," << static_cast<int>(component->height) << std::endl;
        //std::cout << "MultiComponent::searchComponentTree - y search: " << static_cast<int>(-component->y) << "<" << static_cast<int>(passedY) << "<" << static_cast<int>(-component->y + component->height) << std::endl;
        if (-component->y < passedY && -component->y + component->height > passedY) {
            //std::cout << "DocumentComponent::searchComponentTree - x search: " << static_cast<int>(component->x) << "<" << static_cast<int>(passedX) << "<" << static_cast<int>(component->x + component->width) << std::endl;
            if (component->x < passedX && component->x + component->width > passedX) {
                //std::cout << "MultiComponent::searchComponentTree - hit " << typeOfComponent(component) << std::endl;
                return component;
            }
        }
    }
    else {
        for (std::shared_ptr<Component> child : component->children) {
            std::shared_ptr<Component> found = searchComponentTree(child, passedX, passedY);
            if (found) {
                return found;
            }
        }
    }
    return nullptr;
}

repo_name: vaartis/netrunner

license: mit


code:
#ifndef COMPONENTBUILDER_H
#define COMPONENTBUILDER_H

#include <algorithm>
#include <memory>
#include <unordered_map>
#include "Component.h"
//#include "DocumentComponent.h"
//#include "../elements/Element.h"
#include "../../parsers/markup/TagNode.h"
#include "../../parsers/markup/TextNode.h"
#include "../../parsers/markup/Node.h"

class DocumentComponent;
class Element;

typedef std::unordered_map<std::string, std::function<std::unique_ptr<Component>(const Node &node, int y, int windowWidth, int windowHeight)>> ElementRendererMap;

class ComponentBuilder {
private:
    const static std::unordered_map<std::string, std::shared_ptr<Element>> elementMap;
public:
    // FIXME: consider passing the documentComponent we're bound too
    std::shared_ptr<Component> build(const std::shared_ptr<Node> node, const std::shared_ptr<Component> &parentComponent, const std::shared_ptr<Window> win, DocumentComponent *docComponent);
};

// getComponentType
std::string typeOfComponent(const std::shared_ptr<Component> &component);
std::string typeOfComponent(Component *component);

#endif


code:
#include "DocumentComponent.h"
#include <cmath>
#include <iostream>
#include "../../Log.h"
#include "InputComponent.h"
#include "ButtonComponent.h"
#include "TabbedComponent.h"
#include <ctime>

#include "../graphical/renderers/glfw/Shader.h"
#include "../../WebResource.h"
#include "../../browser.h"
#include "../../parsers/markup/html/HTMLParser.h"

void deleteComponent(std::shared_ptr<Component> &component);
void deleteNode(std::shared_ptr<Node> node);

extern std::unique_ptr<Browser> browser;

#include <utility>

// well we need to know how many chars in a lines
// we also need to know where a line starts (pos)
// lines start at 0 for first line
std::pair<size_t, size_t> getLine(std::string text, int findLine) {
    //std::cout << "DocumentComponent.getLine [" << text << "] findLine: " << findLine << std::endl;
    size_t pos = 0;
    size_t lPos = 0;
    pos = text.find("\r");
    size_t line = 0;
    while(pos != std::string::npos) {
        lPos = pos;
        pos = text.find("\r", lPos + 1);
        if (line == static_cast<unsigned int>(findLine)) {
            //std::cout << "lPos: " << lPos << " pos: " << pos << " line: " << line << std::endl;
            //std::cout << "DocumentComponent.getLine start " << (lPos + line + 1) << " end " << (pos == std::string::npos ? text.length() : (lPos + pos)) << std::endl;
            return std::make_pair(lPos + line + 1, pos == std::string::npos ? text.length() : (lPos + pos));
        }
        line++;
    }
    //std::cout << "end lPos: " << lPos << " pos: " << pos << " line: " << line << std::endl;
    //std::cout << "DocumentComponent.getLine result end of text" << findLine << std::endl;
    return std::make_pair(lPos + line, text.size());
}

size_t getNumberOfLines(std::string text) {
    size_t pos = text.find("\r");
    size_t lines = 0;
    while(pos != std::string::npos) {
        lines++;
        pos = text.find("\r", pos + 1);
    }
    return lines;
}

DocumentComponent::DocumentComponent(const float rawX, const float rawY, const float rawWidth, const float rawHeight, const int passedWindowWidth, const int passedWindowHeight) : MultiComponent(rawX, rawY, rawWidth, rawHeight, passedWindowWidth, passedWindowHeight) {
    //std::cout << "DocumentComponent::DocumentComponent" << std::endl;

    windowWidth = passedWindowWidth;
    windowHeight = passedWindowHeight;
    //std::cout << "DocumentComponent::DocumentComponent - window size: " << windowWidth << "x" << windowHeight << std::endl;

    std::shared_ptr<Component> rootComponent = std::make_shared<Component>();
    this->layers.push_back(rootComponent);

    x = rawX;
    y = rawY;
    width = rawWidth;
    height = rawHeight;

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

    //std::cout << "DocumentComponent::DocumentComponent - our size" << static_cast<int>(width) << "x" << static_cast<int>(height) << std::endl;
    onMousemove=[this](int passedX, int passedY) {
        // set hover component
        static int lx = 0;
        static int ly = 0;
        //std::cout << "DocumentComponent::DocumentComponent:onMousemove - at " << passedX << "," << passedY << std::endl;
        if (lx == passedX && ly == passedY) {
            return;
        }
        lx = passedX;
        ly = passedY;
        //std::cout << "DocumentComponent::DocumentComponent:onMousemove - size " << this->windowWidth << "," << this->windowHeight << std::endl;
        //std::cout << "DocumentComponent::DocumentComponent:onMousemove - at " << passedX << "," << passedY << " scroll: " << (int)(((this->transformMatrix[13] / 2) - 1) * this->windowHeight) << std::endl;
        std::shared_ptr<Component> newHover = nullptr;
        // FIXME: we may need to iterator backwards here (top layer to back layer)
        for(auto layer: this->layers) {
            //this->hoverComponent = this->searchComponentTree(this->rootComponent, passedX, passedY + (((this->transformMatrix[13] / 2) - 1) * this->windowHeight));
            // apply scroll transformation
            std::shared_ptr<Component> found = this->searchComponentTree(layer, passedX, passedY + (((this->transformMatrix[13] / 2) - 1) * this->windowHeight));
            if (found) {
                newHover = found;
                break;
            }
        }
        // FIXME: no mouseOver or out?
        this->hoverComponent = newHover;
        if (this->hoverComponent) {
            //std::cout << "DocumentComponent::DocumentComponent:onMousemove - hovering over " << typeOfComponent(this->hoverComponent) << " component" << std::endl;
            if (this->hoverComponent->onMousemove) {
                // this could communicate the cursor to use
                this->hoverComponent->onMousemove(passedX, passedY);
            }
            if (this->hoverComponent->onClick || typeOfComponent(this->hoverComponent) == "button") {
                glfwSetCursor(this->win->window, this->win->cursorHand);
            } else {
                glfwSetCursor(this->win->window, this->win->cursorIbeam);
            }
        } else {
            glfwSetCursor(this->win->window, this->win->cursorArrow);
        }
    };
    onWheel=[this](int passedX, int passedY) {
        //std::cout << "DocumentComponent::DocumentComponent:::onWheel - scroll yDelta: " << passedY << std::endl;
        this->scrollY(passedY);
    };
    onMousedown=[this](int passedX, int passedY) {
        //std::cout << "document left press" << std::endl;
        if (this->hoverComponent) {
            // if we're hovering over a component that's not focused
            if (this->focusedComponent != this->hoverComponent) {
                // blur old component
                if (this->focusedComponent) {
                    if (this->focusedComponent->onBlur) {
                        this->focusedComponent->onBlur();
                    }
                }
                // focus new component
                if (this->hoverComponent->onFocus) {
                    this->hoverComponent->onFocus();
                }
            }
            // set hover component as focused
            this->focusedComponent = this->hoverComponent;
            if (this->focusedComponent->onMousedown) {
                //std::cout << "click event" << std::endl;
                this->focusedComponent->onMousedown(passedX, passedY);
            }
        }
    };
    onMouseup=[this](int passedX, int passedY) {
        //std::cout << "document left release" << std::endl;
        if (this->hoverComponent) {
            //std::cout << "DocumentComponent::DocumentComponent:onMouseup - hovering over " << typeOfComponent(this->hoverComponent) << " component" << std::endl;
            if (this->focusedComponent != this->hoverComponent) {
                // blur old component
                if (this->focusedComponent) {
                    if (this->focusedComponent->onBlur) {
                        this->focusedComponent->onBlur();
                    }
                }
                // focus new component
                if (this->hoverComponent->onFocus) {
                    this->hoverComponent->onFocus();
                }
            }
            this->focusedComponent = this->hoverComponent;
            //std::cout << "DocumentComponent::DocumentComponent:onMouseup - hovering over " << typeOfComponent(this->hoverComponent) << " component, focused on " << typeOfComponent(this->focusedComponent) << std::endl;
            if (this->focusedComponent->onMouseup) {
                //std::cout << "click event" << std::endl;
                this->focusedComponent->onMouseup(passedX, passedY);
            }
            if (this->focusedComponent && this->focusedComponent->onClick) {
                //std::cout << "click event" << std::endl;
                this->focusedComponent->onClick();
            }
        }
    };
    onKeyUp=[this](int key, int scancode, int action, int mods) {
        ///std::cout << "DocumentComponent::DocumentComponent:onKeyup - focus: " << typeOfComponent(this->focusedComponent) << " key: " << key << " scancode: " << scancode << " mods: " << mods << std::endl;
        InputComponent *focusedInputComponent = dynamic_cast<InputComponent*>(this->focusedComponent.get());
        if (focusedInputComponent) {
            //std::cout << "inputComponent is focused, key pressed " << key << " action: " <<action << std::endl;
            // FIXME: not going to get repeat events here...
            // action 1 is down, 0 is up, 2 is a repeat
            if (action == 0 || action == 2) {
                // key up
                // it's always uppercase...
                // FIXME: switch statement this
                if (key == GLFW_KEY_BACKSPACE) {
                    focusedInputComponent->backSpace();
                    return;
                }
                if (key == GLFW_KEY_UP) {
                    focusedInputComponent->updateCursor(0, -1);
                    return;
                }
                if (key == GLFW_KEY_DOWN) {
                    focusedInputComponent->updateCursor(0, 1);
                    return;
                }
                if (key == GLFW_KEY_LEFT) {
                    focusedInputComponent->updateCursor(-1, 0);
                    return;
                }
                if (key == GLFW_KEY_RIGHT) {
                    focusedInputComponent->updateCursor(1, 0);
                    return;
                }
                if (key == GLFW_KEY_ENTER) {
                    //std::cout << "DocumentComponent::onKeyUp - enter!" << std::endl;
                    if (focusedInputComponent->multiLine) {
                        // harfbuzz or freetype2 (something?) doesn't like \n                        //focusedInputComponent->value += "\r";
                        focusedInputComponent->addChar('\r');
                    } else {
                        std::cout << "should submit form!" << std::endl;
                        if (focusedInputComponent->onEnter) {
                            focusedInputComponent->onEnter(focusedInputComponent->getValue());
                        }
                        return;
                    }
                } else {
                    if (key < 256) {
                        if (mods & GLFW_MOD_SHIFT) {
                            // SHIFT
                            if (key >= '1' && key <= '9') {
                                key -= 16;
                            }
                            switch(key) {
                                case GLFW_KEY_SLASH: key='?'; break;
                                case GLFW_KEY_APOSTROPHE: key='"'; break;
                                case GLFW_KEY_COMMA: key='<'; break;
                                case GLFW_KEY_MINUS: key='_'; break;
                                case GLFW_KEY_PERIOD: key='>'; break;
                                case GLFW_KEY_SEMICOLON: key=':'; break;
                                case GLFW_KEY_EQUAL: key='+'; break;
                                case GLFW_KEY_LEFT_BRACKET: key='{'; break;
                                case GLFW_KEY_BACKSLASH: key='|'; break;
                                case GLFW_KEY_RIGHT_BRACKET: key='}'; break;
                                case GLFW_KEY_GRAVE_ACCENT: key='~'; break;
                                case GLFW_KEY_0: key=')'; break;
                                default:
                                    // a key we don't care about atm
                                    break;
                            }
                        } else {
                            // no shift or caplocks
                            // basically: when SHIFT isn't pressed but key is in A-Z range, add ascii offset to make it lower case
                            if (key >= 'A' && key <= 'Z') {
                                key += 'a' - 'A';
                            }
                        }
                        focusedInputComponent->addChar(key);
                    } // otherwise I think it's some weird control char
                }
            }
        }
        //std::cout << "nothing focused " << GLFW_RELEASE << "==" << action << std::endl;
        // was press but we don't get those events here
        if (key == GLFW_KEY_Q && action == GLFW_RELEASE) {
            std::cout << "Q was pressed. Exiting." << std::endl;
            exit(0);
        }
        if (key == GLFW_KEY_E && action == GLFW_RELEASE) {
            printf("Printing NodeTree\n\n");
            printNode(this->domRootNode, 1);
            printf("\n\n");
        }
        if (key == GLFW_KEY_D && action == GLFW_RELEASE) {
            printf("Printing ComponentTree\n\n");
            Component::printComponentTree(this->layers[0], 0);
            printf("\n\n");
        }
        if (key == GLFW_KEY_F && action == GLFW_RELEASE) {
            printf("Printing UI ComponentTree\n\n");
            for(auto layer: this->win->ui->layers) {
                Component::printComponentTree(layer, 0);
            }
            printf("\n\n");
        }
        if (key == GLFW_KEY_T && action == GLFW_RELEASE) {
            browser->NextTheme();
        }
        // FIXME: probably should just direct set the scroll
        // we should have a function for setting scrollY position
        if (key == GLFW_KEY_HOME && (action == GLFW_RELEASE || action == GLFW_REPEAT)) {
            std::cout << "Home is/was pressed. Scrolling up." << std::endl;
            this->scrollY(INT_MAX);
        }
        if (key == GLFW_KEY_END && (action == GLFW_RELEASE || action == GLFW_REPEAT)) {
            std::cout << "End is/was pressed. Scrolling down." << std::endl;
            this->scrollY(-INT_MAX);
        }
        if (key == GLFW_KEY_PAGE_UP && (action == GLFW_RELEASE || action == GLFW_REPEAT)) {
            std::cout << "PgUp is/was pressed. Scrolling up." << std::endl;
            this->scrollY(170);
        }
        if (key == GLFW_KEY_PAGE_DOWN && (action == GLFW_RELEASE || action == GLFW_REPEAT)) {
            std::cout << "PgDn is/was pressed. Scrolling down." << std::endl;
            this->scrollY(-170);
        }
        if ((key == GLFW_KEY_J || key == GLFW_KEY_UP) && (action == GLFW_RELEASE || action == GLFW_REPEAT)) {
            std::cout << "J is/was pressed. Scrolling up." << std::endl;
            this->scrollY(4);
        }
        if ((key == GLFW_KEY_K || key == GLFW_KEY_DOWN) && (action == GLFW_RELEASE || action == GLFW_REPEAT)) {
            std::cout << "K is/was pressed. Scrolling down." << std::endl;
            this->scrollY(-4);
        }
    };
    onKeyPress=[this](int key, int scancode, int action, int mods) {
        InputComponent *focusedInputComponent = dynamic_cast<InputComponent*>(this->focusedComponent.get());
        if (focusedInputComponent) {
            return;
        }
        std::cout << "DocumentComponent onKeyPresss - no input component focused" << std::endl;
        int yOffsetScroll = 1;
        if (key == GLFW_KEY_PAGE_UP && (action == GLFW_PRESS || action == GLFW_REPEAT)) {
            std::cout << "PgUp is/was pressed. Scrolling down." << std::endl;
            this->scrollY(-yOffsetScroll*0.1);

        }
        if (key == GLFW_KEY_PAGE_DOWN && (action == GLFW_PRESS || action == GLFW_REPEAT)) {
            std::cout << "PgDn is/was pressed. Scrolling up." << std::endl;
            this->scrollY(yOffsetScroll*0.1);
        }

        // FIXME Scrolling with this can scroll past boundary whereas the same doesn't happen with
        // scrolling wheel
        if (key == GLFW_KEY_J && (action == GLFW_PRESS || action == GLFW_REPEAT)) {
            std::cout << "J is/was pressed. Scrolling down." << std::endl;
            this->scrollY(-yOffsetScroll*0.1);

        }
        // FIXME Scrolling with this can scroll past boundary whereas the same doesn't happen with
        // scrolling wheel
        if (key == GLFW_KEY_K && (action == GLFW_PRESS || action == GLFW_REPEAT)) {
            std::cout << "K is/was pressed. Scrolling up." << std::endl;
            this->scrollY(yOffsetScroll*0.1);
        }
    };
}

void DocumentComponent::scrollY(int py) {
    //std::cout << "DocumentComponent::scrollY - adjusting Y by " << (py * 0.01) << std::endl;
    // we're going to reduce here, so we can get the full signal all the way here
    this->transformMatrix[13] -= py * 0.01;
    this->textureTransformMatrix[13] -= py * 0.01;
    //std::cout << "transformMatrix       : " << this->transformMatrix[13] << std::endl;
    //std::cout << "textureTransformMatrix: " << this->textureTransformMatrix[13] << std::endl;

    // check bounds
    if (this->transformMatrix[13] < 2) {
        this->transformMatrix[13] = 2;
    }
    if (this->textureTransformMatrix[13] < 0) {
        this->textureTransformMatrix[13] = 0;
    }
    std::shared_ptr<Component> rootComponent = this->layers[0]; // we only have one layer (currently)
    // calculate scroll max by calculating how many screens are in the rootComponent's Height
    if (this->transformMatrix[13] > std::max( (rootComponent->height - rootComponent->y) / this->windowHeight * 2.0f, 2.0f)) {
        this->transformMatrix[13] = std::max( (rootComponent->height - rootComponent->y) / this->windowHeight * 2.0f, 2.0f);
    }
    if (this->textureTransformMatrix[13] > std::max( (rootComponent->height - rootComponent->y) / this->windowHeight * 2.0f, 2.0f) - 2.0) {
        this->textureTransformMatrix[13] = std::max( (rootComponent->height - rootComponent->y) / this->windowHeight * 2.0f, 2.0f) - 2.0;
    }
    this->transformMatrixDirty = true;
    this->win->renderDirty = true;
}

void deleteComponent(std::shared_ptr<Component> &component) {
    // delete all my child first
    for (std::shared_ptr<Component> child : component->children) {
        deleteComponent(child);
    }
    component->parent=nullptr;
    component->previous=nullptr;
    component->children.clear();
    component.reset();
    // now delete self
}

void deleteNode(std::shared_ptr<Node> node) {
    for (std::shared_ptr<Node> child : node->children) {
        deleteNode(child);
    }
    node->parent=nullptr;
    node->children.clear();
    node->component=nullptr; // disassociate component
    node.reset();
}

void DocumentComponent::setDOM(const std::shared_ptr<Node> rootNode) {
    std::shared_ptr<Component> rootComponent = this->layers[0]; // we only have one layer (currently)
    // reset rootComponent
    if (rootComponent) {
        deleteComponent(rootComponent);
    }
    if (domRootNode) {
        deleteNode(domRootNode);
    }
    // reset scroll position
    //transformMatrix[13] = 2;
    //transformMatrixDirty = true;
    this->scrollY(0);

    // new root component
    rootComponent = std::make_shared<Component>();
    rootComponent->name = "rootComponent of " + name;
    rootComponent->y = y;

    domRootNode = rootNode;
    //std::cout << "DocumentComponent::setDOM - printing nodes" << endl; printNode(domRootNode, 0);
    domDirty = true;
}

void DocumentComponent::render() {
    //std::cout << "DocumentComponent::render" << std::endl;
    std::shared_ptr<Component> rootComponent = this->layers[0]; // we only have one layer (currently)
    if (domDirty) {
        const std::clock_t begin = clock();
        createComponentTree(domRootNode, rootComponent);
        const std::clock_t end = clock();
        // root component here doesn't have any children...
        std::cout << "built & laid out document components in: " << std::fixed << ((static_cast<double>(end - begin)) / CLOCKS_PER_SEC) << std::scientific << " seconds" << std::endl;

        //Component::printComponentTree(rootComponent, 0);
        domDirty = false;
        //std::cout << "root Height: " << static_cast<int>(rootComponent->height) << " window Height: " << windowHeight << " y " << static_cast<int>(this->y) << std::endl;
        //scrollHeight = std::max(0, static_cast<int>(rootComponent->height - (windowHeight + (this->y * 2))));

        // FIXME recalculate both right?
        // recalculate scroll max by calculating how many screens are in the rootComponent's Height
        if (transformMatrix[13]>std::max((rootComponent->height)/(windowHeight)*2.0f, 2.0f)) {
            transformMatrix[13]=std::max((rootComponent->height)/(windowHeight)*2.0f, 2.0f);
            transformMatrixDirty = true;
        }

        // after we load in the document, allow scroll to work
        this->updateMouse();
    }

    // we have to do this each time because window resets it
    //if (transformMatrixDirty) {
        //const std::clock_t begin = clock();

		Shader *fontShader = this->win->shaderLoader.getShader(VertexShader("FontShader.vert"),
				FragmentShader("FontShader.frag"));
		GLint transformLocation = fontShader->uniform("transform");
        glUniformMatrix4fv(transformLocation, 1, GL_FALSE, transformMatrix);
        //const std::clock_t end = clock();
        //std::cout << "Updated font matrix in: " << std::fixed << ((static_cast<double>(end - begin)) / CLOCKS_PER_SEC) << std::scientific << " seconds" << std::endl;

        Shader *textureShader = this->win->shaderLoader.getShader(VertexShader("TextureShader.vert"),
                                                              FragmentShader("TextureShader.frag"));

        transformMatrixDirty = false;
    //}
    //std::cout << "DocumentComponent::render - renderDirty" << std::endl;
	textureShader->bind();
    GLint transformLocation2 = textureShader->uniform("transform");
    glUniformMatrix4fv(transformLocation2, 1, GL_FALSE, textureTransformMatrix);

    //std::cout << "DocumentComponent::render - start Box components" << std::endl;
    //renderBoxComponents(rootComponent);
    this->renderComponentType("input", rootComponent);
    this->renderComponentType("button", rootComponent);
    //std::cout << "DocumentComponent::render - end Box components" << std::endl;
	//textureShader->release(); // just set it to 0, bind is better call

	fontShader->bind();
    //std::cout << "DocumentComponent::render - start components" << std::endl;
    //renderComponents(rootComponent);
    this->renderComponentType("text", rootComponent);

    //std::cout << "DocumentComponent::render - end components" << std::endl;
	fontShader->release();
}

// create this component and all it's children
void DocumentComponent::createComponentTree(const std::shared_ptr<Node> node, const std::shared_ptr<Component> &parentComponent) {
    std::shared_ptr<Component> component = componentBuilder.build(node, parentComponent, this->win, this);
    //std::cout << "DocumentComponent::createComponentTree" << std::endl;
    if (!component) {
        //std::cout << "DocumentComponent::createComponentTree - no component" << std::endl;
        return;
    }
    // don't build text node of button component because it'll mess with the picking
    if (typeOfComponent(component) == "button") {
        // 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) {
                //std::cout << "Button text: " << textNode->text << std::endl;
                ButtonComponent *buttonComponent = dynamic_cast<ButtonComponent*>(component.get());
                if (buttonComponent) {
                    // FIXME: strip out more than 1 new line
                    std::string line(textNode->text);
                    line.erase(line.find_last_not_of(" \t\n\r\f\v") + 1);
                    buttonComponent->value = line;
                    buttonComponent->resizeToTextSize();
                    buttonComponent->updateText();
                }
            }
        }
        return;
    }
    if (node==domRootNode) {
        // if this is the root node
        component->reqWidth = windowWidth;
        component->reqHeight = windowHeight;
    }
    // create children elements
    for (std::shared_ptr<Node> child : node->children) {
        createComponentTree(child, component);
    }
}

// used for picking
std::shared_ptr<Component> DocumentComponent::searchComponentTree(const std::shared_ptr<Component> &component, const int passedX, const int passedY) {
    // only get hits on leaves (and not to hit the rootComponent every time)
    if (component->children.empty()) {
        //std::cout << "DocumentComponent::searchComponentTree - component at " << static_cast<int>(component->x) << "," << static_cast<int>(component->y) << " size " << static_cast<int>(component->width) << "," << static_cast<int>(component->height) << std::endl;
        //std::cout << "DocumentComponent::searchComponentTree - y search: " << static_cast<int>(-component->y) << "<" << static_cast<int>(passedY) << "<" << static_cast<int>(-component->y + component->height) << std::endl;
        if (-component->y < passedY && -component->y + component->height > passedY) {
            //std::cout << "DocumentComponent::searchComponentTree - x search: " << static_cast<int>(component->x) << "<" << static_cast<int>(passedX) << "<" << static_cast<int>(component->x + component->width) << std::endl;
            if (component->x < passedX && component->x + component->width > passedX) {
                //std::cout << "DocumentComponent::searchComponentTree - hit " << typeOfComponent(component) << " name: " << component->name << std::endl;
                return component;
            }
        }
    } else {
        for (std::shared_ptr<Component> child : component->children) {
            std::shared_ptr<Component> found = searchComponentTree(child, passedX, passedY);
            if (found) {
                return found;
            }
        }
    }
    return nullptr;
}

// moving naviagtion closer to window, as window is now the owner of currentURL
// preparation for multiple HTML documents
void DocumentComponent::navTo(const std::string url) {
    logDebug() << "DocumentComponent::navTo(" << url << ")" << std::endl;

    this->currentURL = currentURL.merge(URL(url));
    logDebug() << "DocumentComponent::navTo - go to: " << currentURL << std::endl;
    //setWindowContent(currentURL);
    logDebug() << "main::setWindowContent - " << url << std::endl;

    // integrity check
    TabbedComponent *tabComponent = dynamic_cast<TabbedComponent*>(this->win->tabComponent.get());
    if (!tabComponent->tabs.size()) {
        std::cout << "DocumentComponent::navTo - There's a document when there's not tabs" << std::endl;
    }

    // download URL
    WebResource res = getWebResource(this->currentURL);
    this->handleResource(res, currentURL.toString());
}

void DocumentComponent::handleResource(WebResource &res, std::string url) {
    if (res.resourceType == ResourceType::INVALID) {
        logError() << "DocumentComponent::handleResource - Invalid resource type: " << res.raw << std::endl;

        std::shared_ptr<Node> rootNode = std::make_shared<Node>(NodeType::ROOT);
        std::shared_ptr<TagNode> tagNode = std::make_shared<TagNode>();
        tagNode->tag="p";
        // bind tag to root
        tagNode->parent = rootNode;
        rootNode->children.push_back(tagNode);

        std::shared_ptr<TextNode> textNode = std::make_shared<TextNode>();
        textNode->text = "Invalid Resource Type, HTTP Result Status: " + res.raw;

        // bind text to tag
        textNode->parent = tagNode;
        tagNode->children.push_back(textNode);

        // send NodeTree to window
        //this->win->setDOM(rootNode);
        this->setDOM(rootNode);
    }

    //std::cout << "body: " << res.raw << std::endl;
    //std::cout << "type: " << res.resourceType << std::endl;

    // parse HTML
    if (res.resourceType == ResourceType::HTML) {
        HTMLParser parser;
        const std::clock_t begin = clock();
        std::shared_ptr<Node> rootNode = parser.parse(res.raw);
        const std::clock_t end = clock();
        logDebug() << "DocumentComponent::handleResource - Parsed document in: " << std::fixed << ((static_cast<double>(end - begin)) / CLOCKS_PER_SEC) << std::scientific << " seconds" << std::endl;

        // send NodeTree to window
        //this->win->setDOM(rootNode);
        //printNode(rootNode, 0);

        // we need a way to communicate with our tabComponent
        // maybe an event is best
        if (this->onBeforeLoad) {
            this->onBeforeLoad(url);
        }
        this->setDOM(rootNode);
    } else if (res.resourceType == ResourceType::TXT) {
        std::cout << "DocumentComponent::handleResource - Rendering text document" << std::endl;
        std::shared_ptr<Node> rootNode = std::make_shared<Node>(NodeType::ROOT);
        std::shared_ptr<TagNode> tagNode = std::make_shared<TagNode>();
        tagNode->tag="p";
        // bind tag to root
        tagNode->parent = rootNode;
        rootNode->children.push_back(tagNode);

        std::shared_ptr<TextNode> textNode = std::make_shared<TextNode>();
        textNode->text = res.raw;

        // bind text to tag
        textNode->parent = tagNode;
        tagNode->children.push_back(textNode);

        // send NodeTree to window
        //this->win->setDOM(rootNode);
        this->setDOM(rootNode);
    } else {
        std::cout << "DocumentComponent::handleResource - I don't know how to render non-html files" << std::endl;
    }
    if (this->win) {
        this->win->renderDirty = true;
    }
}


code:
#ifndef DOCUMENTCOMPONENT_H
#define DOCUMENTCOMPONENT_H

#include <GL/glew.h>
#include "Component.h"
#include "MultiComponent.h"
#include "ComponentBuilder.h"
#include "../../parsers/markup/Node.h"
#include "../../URL.h"
#include "../../networking/HTTPResponse.h"
#include "../../WebResource.h"

std::pair<size_t, size_t> getLine(std::string text, int findLine);
size_t getNumberOfLines(std::string text);

// document is scrollable unlike multicomponent
class DocumentComponent : public MultiComponent {
public:
    DocumentComponent(const float rawX, const float rawY, const float rawWidth, const float rawHeight, const int passedWindowWidth, const int passedWindowHeight);
    void render();
    void scrollY(int y);
    void createComponentTree(const std::shared_ptr<Node> node, const std::shared_ptr<Component> &parentComponent);
    std::shared_ptr<Component> searchComponentTree(const std::shared_ptr<Component> &component, const int x, const int y);
    void navTo(const std::string url);
    void handleResource(WebResource &res, std::string url);
    void setDOM(const std::shared_ptr<Node> rootNode);
    std::shared_ptr<Node> domRootNode = nullptr;
    ComponentBuilder componentBuilder;
    URL currentURL;
    bool domDirty = false;
    //
    //int scrollY = 0;
    //int scrollHeight = 0;

    // FIXME: I don't think we need two matrixes (they scroll in the same direction)
    float textureTransformMatrix[16] = {
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    };
    float transformMatrix[16] = {
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 2, 0, 1
    };
    bool transformMatrixDirty = true;
    std::function<void(std::string url)> onBeforeLoad = nullptr;
};

#endif


code:
#include "ImageComponent.h"
#include <cmath>
#include <iostream>
#include <cstring>
#include "../../parsers/images/netpbm/pnm.h"

ImageComponent::ImageComponent(std::string filename, const float rawX, const float rawY, const float rawWidth, const float rawHeight, const int passedWindowWidth, const int passedWindowHeight) : BoxComponent(rawX, rawY, rawWidth, rawHeight, 0x00000000, passedWindowWidth, passedWindowHeight) {
    //std::cout << "ImageComponent::ImageComponent" << std::endl;

    windowWidth = passedWindowWidth;
    windowHeight = passedWindowHeight;

    x = rawX;
    y = rawY;
    width = rawWidth;
    height = rawHeight;

    RGBAPNMObject *anime = readPPM(filename.c_str());
    unsigned int loadWidth = 1024, loadHeight = 1024;
    if (!anime) {
        std::cout << "Can't set up image component, couldn't read " << filename << std::endl;
    } else {
        loadWidth = anime->width;
        loadHeight = anime->height;
    }
    if (loadWidth > 1024 || loadHeight > 1024) {
        std::cout << "Can't set up image component, " << filename << " resolution too large " << std::endl;
        loadWidth = 1024;
        loadHeight = 1024;
        tlsf_free(anime->m_Ptr);
        delete anime;
        anime = nullptr;
    }
    std::cout << "loading " << filename << " at " << loadWidth << "," << loadHeight << std::endl;

    /*
    response->textureWidth = pow(2, ceil(log(response->width) / log(2)));
    response->textureHeight = pow(2, ceil(log(response->height) / log(2)));
    */

    for (unsigned int py = 0; py < loadHeight; py++) {
        for (unsigned int px = 0; px < loadWidth; px++) {
            if (anime) {
                for (unsigned int i = 0; i < 4; i++) {
                    //data[1023 - py][px][i] = anime.pixel_data[((px * 4) + (py * 4 * 1024)) + i];
                    data[loadWidth - 1 - py][px][i] = anime->m_Ptr[((px * 4) + (py * 4 * loadWidth)) + i];
                }
            } else {
                data[loadWidth - 1 - py][px][3] = 0x00; // just make it all transparent for now
            }
        }
    }

    float vx = rawX;
    float vy = rawY;
    float vWidth = rawWidth;
    float vHeight = rawHeight;
    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;

    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, static_cast<int>(loadWidth), static_cast<int>(loadHeight), 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);

    if (anime) {
        tlsf_free(anime->m_Ptr);
        delete anime;
    }
}

// I guess we need this to change the image verticies
// FIXME: remove overrides for box and image componetns for resize
void ImageComponent::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 IMAGECOMPONENT_H
#define IMAGECOMPONENT_H

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

class ImageComponent : public BoxComponent {
public:
    ImageComponent(std::string filename, const float rawX, const float rawY, const float rawWidth, const float rawHeight, const int passedWindowWidth, const int passedWindowHeight);
    void resize(const int passedWindowWidth, const int passedWindowHeight);
    // FIXME: dynamically allocate this
    // Soon. -despair
    unsigned char data[1024][1024][4];
};

#endif


code:
#include "InputComponent.h"
#include "DocumentComponent.h"
#include <iostream>
#include "../graphical/font/TextRasterizerCache.h"
#include "../../scheduler.h"

#include "../graphical/renderers/glfw/Shader.h"

extern TextRasterizerCache *rasterizerCache;
extern std::unique_ptr<Scheduler> scheduler;

// : BoxComponent(rawX, rawY, rawWidth, rawHeight, passedWindowWidth, passedWindowHeight)
InputComponent::InputComponent(const float rawX, const float rawY, const float rawWidth, const float rawHeight, const int passedWindowWidth, const int passedWindowHeight) {

    //std::cout << "InputComponent::InputComponent - create, boundToPage" << boundToPage << std::endl;
    //std::cout << "InputComponent::InputComponent - window: " << windowWidth << "x" << windowHeight << " passed " << passedWindowWidth << "x" << passedWindowHeight << std::endl;

    //boundToPage = true;
    useBoxShader = true;

    // set up state
    windowWidth = passedWindowWidth;
    windowHeight = passedWindowHeight;
    // lets not set, like resize set, and then maybe parent width will be correct
    // didn't really changed anything
    width = rawWidth;
    height = rawHeight;
    // ugh
    x = rawX;
    y = rawY;
    lastRenderedWindowHeight = windowHeight;

    // const float rawX, const float rawY, const float rawWidth, const float rawHeight, const unsigned int hexColor, const int passedWindowWidth, const int passedWindowHeight
    // we only want it one line high (esp for text area)
    this->cursorBox = new BoxComponent(x, y, 2, 13, 0x000000FF, windowWidth, windowHeight);
    this->cursorBox->boundToPage = this->boundToPage;

    this->cursorBox->x = x;
    this->cursorBox->y = y;
    if (boundToPage) {
        this->cursorBox->y = this->windowHeight + y - 13;
    }
    this->cursorBox->resize(this->windowWidth, this->windowHeight);
    this->updateText();

    onFocus=[this]() {
        this->focused = true;
        //std::cout << "focus input" << std::endl;
        // blink cursor
        this->cursorTimer = scheduler->setInterval([this]() {
            this->showCursor = !this->showCursor;
            //std::cout << "showCursor " << this->showCursor << std::endl;
            if (this->win) {
                this->win->renderDirty = true;
            }
        }, 500);
        this->updateCursor(0, 0);
        this->win->renderDirty = true;
    };
    onBlur=[this]() {
        //std::cout << "blur input" << std::endl;
        if (this->cursorTimer != nullptr) {
            scheduler->clearInterval(this->cursorTimer);
        }
        this->focused = false;
        this->win->renderDirty = true;
    };

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

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

    data[0][0][0] = 0xf0; // set R color
    data[0][0][1] = 0xf0; // set G color
    data[0][0][2] = 0xf0; // set B color
    data[0][0][3] = 0xff; // set alpha

    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 = rawWidth;
    float vHeight = rawHeight;
    pointToViewport(vx, vy);

    // 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 << "InputComponent::InputComponent - placing box at GL " << vx << "," << vy << " to " << vx+vWidth << "," << vy+vHeight << " size: " << vWidth << "x" << vHeight << 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
}

void InputComponent::render() {
    //std::cout << "InputComponent::render - at " << (int)x << "," << (int)y << std::endl;
    //std::cout << "InputComponent::render - boundToPage " << boundToPage << std::endl;
    GLenum glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "InputComponent::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 << "InputComponent::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 << "InputComponent::render - glBufferData not ok: " << glErr << std::endl;
        }
        verticesDirty = false;
    }

    glBindVertexArray(vertexArrayObject);
    glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "InputComponent::render - glBindVertexArray not ok: " << glErr << std::endl;
    }
    glBindTexture(GL_TEXTURE_2D, texture);
    glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "InputComponent::render - glBindTexture not ok: " << glErr << std::endl;
    }
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
    glErr=glGetError();
    if(glErr != GL_NO_ERROR) {
        std::cout << "InputComponent::render - glDrawElements not ok: " << glErr << std::endl;
    }
    glBindVertexArray(0);
    // can actuall delete vertices here
    if (userInputText) {
        // make sure we're using win's transformMatrix
		Shader *fontShader = this->win->shaderLoader.getShader(VertexShader("FontShader.vert"),
				FragmentShader("FontShader.frag"));
		fontShader->bind();
        if (!boundToPage) {
            GLint transformLocation = fontShader->uniform("transform");
            glErr=glGetError();
            if(glErr != GL_NO_ERROR) {
                std::cout << "InputComponent::render - glGetUniformLocation not ok: " << glErr << std::endl;
            }
            // it's about the document transformMatrix
            glUniformMatrix4fv(transformLocation, 1, GL_FALSE, this->win->transformMatrix);
            glErr=glGetError();
            if(glErr != GL_NO_ERROR) {
                std::cout << "InputComponent::render - glUniformMatrix4fv not ok: " << glErr << std::endl;
            }
        }
        //std::cout << "rendering some text" << std::endl;
        userInputText->render();
		Shader *textureShader = this->win->shaderLoader.getShader(VertexShader("TextureShader.vert"),
				FragmentShader("TextureShader.frag"));
		textureShader->bind();
    }
    if (focused) {
        //std::cout << "Rendering cursor" << std::endl;

        // render it if we need to
        if (showCursor) {
            cursorBox->render();
        }
    }
}

void InputComponent::resize(const int passedWindowWidth, const int passedWindowHeight) {
    //std::cout << "InputComponent::resize" << std::endl;
    //std::cout << "InputComponent::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 << "InputComponent::resize - boxShader: " << useBoxShader << " boundToPage: " << boundToPage << std::endl;

    /*
    if (!boundToPage) {
        // ok because box shader is anchored to the bottom of the screen
        // if we resize Y (increase height), we need to rebind
        int yDiff = windowHeight - lastRenderedWindowHeight;
        //std::cout << "InputComponent::resize - Adjusting y: " << y << " by " << yDiff << std::endl;
        this->y += yDiff;
        lastRenderedWindowHeight = windowHeight;
    }
    */

    // turning this off breaks coordinates
    //boundToPage = true;
    /*
    useBoxShader = true;

    for (int i = 0; i < 3; i++) {
        data[0][0][i] = 0xf0; // set RGB color
    }
    data[0][0][3] = 0xff; // set alpha
     */

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

    float vx = x;
    float vy = y;
    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;

    //std::cout << "InputComponent::resize - placing box at GL " << vx << "," << vy << " to " << vx1 << "," << vy1 << " size: " << vWidth << "x" << vHeight << std::endl;

    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 (userInputText) {
        // do we need updateText here?
        userInputText->resize(passedWindowWidth, passedWindowHeight);

    }
}

std::string InputComponent::getValue() {
    //return this->value;
    return this->text.getValue();
}

void InputComponent::setValue(std::string newValue) {
    //this->value = newValue;
    this->text.setValue(newValue);
    if (!this->multiLine) {
        this->cursorCharX = static_cast<int>(newValue.size() & INT_MAX);
    }
    // if we pasted in a middle of block
}

void InputComponent::addChar(char c) {
    this->text.insertAt(std::string(1, c), static_cast<size_t>(this->cursorCharX), static_cast<size_t>(this->cursorCharY));
    //this->value += c;
    //std::cout << "InputComponent::addChar - was cursor at " << this->cursorCharX << "," << this->cursorCharY << std::endl;
    if (c == '\r') {
        if (this->multiLine) {
            this->cursorCharY++;
            this->cursorCharX = 0;
        }
    } else {
        this->cursorCharX++;
    }
    //std::cout << "InputComponent::addChar - at " << (int)x << "," << (int)y << std::endl;
    this->updateCursor(0, 0);
    this->updateText();
}

void InputComponent::backSpace() {
    char last = '\0';
    if (this->text.getValue().length()) {
        last = this->text.getValue().at(this->text.getValue().length() - 1);
    }
    //std::cout << "InputComponent::backSpace - was cursor at " << this->cursorCharX << "," << this->cursorCharY << std::endl;
    //value = value.substr(0, value.length() - 1);
    if (this->cursorCharX) {
        // since this backSpace, we want to remove the character before this cursor
        // 0,A,1,S,2,D,3,F,4
        //             ^ means remove char 2 (starting at 0)
        //
        this->text.deleteAt(static_cast<size_t>(this->cursorCharX - 1), static_cast<size_t>(this->cursorCharY), 1);
    } else {
        // maybe delete line
        this->text.deleteAt(static_cast<size_t>(this->cursorCharX), static_cast<size_t>(this->cursorCharY), 1);
    }
    //std::cout << "InputComponent::backSpace - removed [" << last << "]" << std::endl;
    if (last == '\r') {
        if (this->multiLine) {
            this->cursorCharY--;
            if (this->cursorCharY < 0) {
                this->cursorCharY = 0;
                this->cursorCharX = 0;
            } else {
                this->cursorCharX = this->text.lineLength(static_cast<size_t>(this->cursorCharY));
            }
            //std::pair<int, int> lineData = getLine(this->text.getValue(), this->cursorCharY);
            //this->cursorCharX = lineData.second - lineData.first + 1;
        }
    } else {
        this->cursorCharX--;
        if (this->cursorCharX < 0) this->cursorCharX = 0;
    }
    //std::cout << "InputComponent::backSpace - at " << (int)x << "," << (int)y << std::endl;
    this->updateCursor(0, 0);
    this->updateText();
}

void InputComponent::updateCursor(int mX, int mY) {
    //std::cout << "InputComponent::updateCursor - at " << this->cursorCharX << "," << this->cursorCharY << " move: " << mX << "," << mY << std::endl;
    bool moved = false;
    if (mX || mY) {
        if (this->multiLine) {
            if (mY < 0) {
                // up
                this->cursorCharY--;
                if (this->cursorCharY < 0) {
                    this->cursorCharX = 0;
                    this->cursorCharY = 0;
                }
            } else if (mY > 0) {
                // down
                this->cursorCharY++;
                if (static_cast<size_t>(this->cursorCharY) >= this->text.lines.size()) {
                    this->cursorCharY = this->text.lines.size() - 1;
                    this->cursorCharX = this->text.lineLength(static_cast<size_t>(this->cursorCharY));
                }
            }
        } else {
            if (mY < 0) {
                // up
                this->cursorCharX = 0;
            } else if (mY > 0) {
                // down
                this->cursorCharX = this->text.lineLength(0);
            }
        }
        if (mX < 0) {
            // left
            this->cursorCharX--;
            if (this->cursorCharX < 0) {
                this->cursorCharX = 0;
                this->cursorCharY--;
                if (this->cursorCharY < 0) {
                    this->cursorCharY = 0;
                } else {
                    this->cursorCharX = this->text.lineLength(static_cast<size_t>(this->cursorCharY));
                }
            }
        } else if (mX > 0) {
            // right
            this->cursorCharX++;
            if (static_cast<size_t>(this->cursorCharX) > this->text.lineLength(static_cast<size_t>(this->cursorCharY))) {
                this->cursorCharY++;
                if (static_cast<size_t>(this->cursorCharY) >= this->text.lines.size()) {
                    this->cursorCharY = this->text.lines.size() - 1;
                    this->cursorCharX = this->text.lineLength(static_cast<size_t>(this->cursorCharY));
                } else {
                    this->cursorCharX = 0;
                }
            }
        }
        //std::cout << "InputComponent::updateCursor - cursor now at " << this->cursorCharX << "," << this->        cursorCharY << std::endl;
    }
    if (this->cursorLastX != this->cursorCharX || this->cursorLastY != this->cursorCharY) {
        moved = true;
    }
    //std::cout << "InputComponent::updateCursor - text[" << this->value << "]" << std::endl;
    //std::pair<int, int> lineData = getLine(this->text.getValue(), this->cursorCharY);
    //std::cout << "InputComponent::updateCursor - line " << this->cursorCharY << " starts at " << lineData.first << std::endl;
    //size_t pos = static_cast<size_t>(lineData.first + this->cursorCharX);
    //std::cout << "InputComponent::updateCursor - cx " << this->cursorCharX << " starts at value pos " << pos << std::endl;

    //if (pos < this->text.getValue().size()) {
        //std::cout << "InputComponent::updateCursor - Cursor at " << this->cursorCharX << "," << this->cursorCharY << " " << this->value.at(pos) << std::endl;
    //}

    const std::shared_ptr<TextRasterizer> textRasterizer = rasterizerCache->loadFont(12, false); // fontSize, bold
    rasterizationRequest request;
    //request.text = text.getValue().substr(0, pos);
    request.text = this->text.getValueUpTo(static_cast<size_t>(this->cursorCharX), static_cast<size_t>(this->cursorCharY));
    //std::cout << "InputComponent::updateCursor - sizeText[" << request.text << "]" << std::endl;
    request.startX = x;
    request.availableWidth = windowWidth;
    request.sourceStartX = 0;
    request.sourceStartY = 0;
    if (this->multiLine) {
        request.noWrap = false;
    } else {
        request.noWrap = true;
    }
    std::unique_ptr<sizeResponse> textInfo = textRasterizer->size(request);
    //std::cout << "InputComponent::updateCursor - response at [" << textInfo->endingX << "," << textInfo->endingY << "]" << std::endl;
    int textEndingX = textInfo->endingX;
    int textEndingY = textInfo->endingY;

    // adjust text crop
    //std::cout << "c.endingY: " << textInfo->endingY << " c.y:" << endingY << "" << std::endl;
    this->textCropX = std::max(textInfo->endingX, static_cast<int>(this->width));
    this->textCropY = std::max(-textInfo->endingY, static_cast<int>(this->height));
    this->textScrollY = 0; // reset scroll
    //this->cursorStartAtX = textInfo->endingX;
    //this->cursorStartAtY = textInfo->endingY;

    // adjust our scroll
    //std::cout << "cursor height" << (int)textInfo->height << " > " << (int)this->height << " (" << this->textScrollY << ")" << std::endl;
    if (textInfo->height > this->height) {
        textEndingY += textInfo->height - this->height;
        this->textScrollY = textInfo->height - this->height;
    }

    // this is texture shader coordinates now (not text shader coords)
    cursorBox->x = x + textEndingX;
    if (boundToPage) {
        cursorBox->y = this->windowHeight + this->y + textEndingY;
    } else {
        // + this->cursorStartAtY
        cursorBox->y = this->y + 5;
    }
    //std::cout << "placing cursor at " << (int)cursorBox->x << "," << (int)cursorBox->y << std::endl;
    cursorBox->resize(this->windowWidth, this->windowHeight);

    // ok placement of the cursor may trigger the text to scroll and need to be re-rasterized
    if (moved) {
        /*
        if (userInputText) {
            std::cout << "cursor at " << (int)textInfo->endingY << " to " << textInfo->endingY << " textScrollY: " << this->textScrollY << " inputHeight: " << (int)this->height << " textHeight: " << (int)this->userInputText->height << " adjHeight: " << (int)(this->height - this->textScrollY) << std::endl;
            // if we're at the top of input comp moving up
            // or at the bottom of the input comp going down
            if (-endingY > this->height - this->textScrollY) {
                //this->textScrollY = this->height + endingY;
                std::cout << "set textScrollY: " << this->textScrollY << std::endl;
            }
        }
         */
        this->updateText();
        // we're updated
        this->cursorLastX = this->cursorCharX;
        this->cursorLastY = this->cursorCharY;
    }

    // if we move the cursor, redraw immediately
    this->showCursor = true;
    if (this->win) {
        this->win->renderDirty = true;
    }
}

void InputComponent::updateText() {
    //std::cout << "InputComponent::updateText - input value is now: " << this->text.getValue() << std::endl;

    if (this->node) {
        std::map<std::string, std::string>::iterator it = this->node->properties.find("value");
        if (it == this->node->properties.end()) {
            this->node->properties.insert(std::make_pair("value", this->text.getValue()));
        } else {
            it->second = this->text.getValue();
        }
    }

    //std::cout << "placed userInputText at " << static_cast<int>(x) << "," << static_cast<int>(y - windowHeight) << std::endl;
    // 125 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 = text.getValue();
    request.startX = x;
    request.availableWidth = windowWidth;
    request.sourceStartX = 0;
    request.sourceStartY = 0;
    // don't wrap if ML, otherwise do
    if (this->multiLine) {
        request.noWrap = false;
    } else {
        request.noWrap = true;
    }
    std::unique_ptr<sizeResponse> textInfo = textRasterizer->size(request);
    if (textInfo.get() == nullptr) {
        std::cout << "InputComponent::updateText - couldn't estimate value[" << this->text.getValue() << "] size" << std::endl;
        return;
    }
    //int textWidth = textInfo->first;
    //int estWidth = textInfo->width;
    //int estHeight = textInfo->height;
    //this->cursorStartAtX = textInfo->endingX;
    //this->cursorStartAtY = textInfo->endingY;

    // if textHeight is bigger than window, adjust cursor too
    /*
    if (estHeight > this->height) {
        std::cout << "InputComponent::updateText - used to adj cursorStartAtY += " << (int)(estHeight - this->height) << " (" << estHeight << " - " << (int)this->height << ")" << std::endl;
        //this->cursorStartAtY += estHeight - this->height;
    }
    */
    //this->updateCursor(0, 0);

    // why does changing the width mess shit up?
    //std::cout << "InputComponent::updateText - our width: " << static_cast<int>(width) << " windowWidth: " << windowWidth << std::endl;
    if (this->win) {
        // maybe tie the fontsize to height
        if (this->userInputText) {
            delete this->userInputText;
        }
        this->userInputText = new TextComponent(this->text.getValue(), 0, 0, 12, false, 0x000000FF, this->windowWidth, this->windowHeight);
        if (!userInputText->win && this->win) {
            userInputText->win = this->win;
        }
        //std::cout << "placing userInputText at " << static_cast<int>(x) << "," << static_cast<int>(y) << std::endl;
        userInputText->x = this->x;
        //std::cout << "InputComponent::updateText - y: " << static_cast<int>(this->y) << " boundToPage: " << this->boundToPage << std::endl;
        //if (this->y < 0) {
        if (this->boundToPage) {
            userInputText->y = this->y;
        } else {
            userInputText->y = this->y - this->windowHeight + 16;
        }
        userInputText->boundToPage = this->boundToPage;

        userInputText->rasterStartX = this->textScrollX;
        userInputText->rasterStartY = this->textScrollY;
        userInputText->rasterCropX = this->textCropX;
        userInputText->rasterCropY = this->textCropY;
        //std::cout << "InputComponent::updateText - estWidth: " << estWidth << " width: " << static_cast<int>(this->width) << " eX: " << textInfo->endingX << std::endl;
        // no longer scroll to end of text
        /*
        if (estWidth > this->width) {
            //std::cout << "scrolling text" << std::endl;
            userInputText->rasterStartX = estWidth - this->width;
        }
        //std::cout << "InputComponent::updateText - estHeight: " << estHeight << " height: " << static_cast<int>(this->height) << " eY: " << textInfo->endingY << std::endl;
        if (this->multiLine && estHeight > this->height) {
            //std::cout << "scrolling text" << std::endl;
            userInputText->rasterStartY = estHeight - this->height; // start at one widget height from the bottom
        }
        */
        //
        /*
        // translate cursor in scroll
        std::cout << "cursor at " << this->cursorStartAtX << "," << this->cursorStartAtY << " size: " << (int)this->width << "," << (int)this->height << std::endl;
        // so we only care about cursorStartAtY here atm
        // we get values like -58, -43, -28, -13
        if (this->cursorStartAtX > this->width) {
            userInputText->rasterStartX = this->cursorStartAtX - this->width;
        }
        if (this->multiLine && -(this->cursorStartAtY + userInputText->height) > this->height) {
            std::cout << "scrolling y start: " << (-(this->cursorStartAtY + userInputText->height) - this->height) << std::endl;
            userInputText->rasterStartY = -this->cursorStartAtY - this->height;
        }
        */
        //std::cout << "InputComponent::updateText - textHeight: " << estHeight << " inputHeight: " << static_cast<int>(height) << " eY: " << textInfo->endingY << " y0max:" << textInfo->y0max << " rasterStartY: " << userInputText->rasterStartY << " lines: " << textInfo->lines << std::endl;
        // don't wrap if multiLine, otherwise wrap
        userInputText->noWrap = !this->multiLine;

        userInputText->resize(this->windowWidth, this->windowHeight, this->width); // need to make sure there's a texture
        this->win->renderDirty = true;
    } // else it was called from cstr, and window isn't set up yet but no worrites, render will be dirty
}


code:
#ifndef INPUTCOMPONENT_H
#define INPUTCOMPONENT_H

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

class Window;

class InputComponent : public BoxComponent {
public:
    //: BoxComponent(rawX, rawY, rawWidth, rawHeight, passedWindowWidth, passedWindowHeight) { }
    InputComponent(const float rawX, const float rawY, const float rawWidth, const float rawHeight, const int passedWindowWidth, const int passedWindowHeight);
    // or
    //using BoxComponent::BoxComponent;
    void resize(const int passedWindowWidth, const int passedWindowHeight);
    void render();
    std::string getValue();
    void setValue(std::string newValue);
    // FIXME: change to using std::string
    void addChar(char c);
    void backSpace();
    void updateCursor(int x, int y);
    void updateText();
    TextComponent *userInputText = nullptr;
    BoxComponent *cursorBox = nullptr;
    std::function<void(std::string value)> onEnter = nullptr;
    // needed for our shader's resizing
    int lastRenderedWindowHeight;
    bool focused = false;
    bool showCursor = true;
    std::shared_ptr<timer_handle> cursorTimer = nullptr;
    // store for cursor
    int textScrollX = 0;
    int textScrollY = 0;
    int textCropX = 0;
    int textCropY = 0;
    int cursorLastX = 0;
    int cursorLastY = 0;
    int cursorCharX = 0;
    int cursorCharY = 0;
    TextBlock text;
    TagNode *node = nullptr;
    bool multiLine = false; // textarea control
//protected:
    //std::string value="";
};

#endif