You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

581 lines
28 KiB

#include "DocumentComponent.h"
#include <cmath>
#include <iostream>
#include "../../Log.h"
#include "InputComponent.h"
#include "ButtonComponent.h"
#include "TabbedComponent.h"
#include <ctime>
#include "../opengl/Shader.h"
#include "../../WebResource.h"
#include "../../html/HTMLParser.h"
void deleteComponent(std::shared_ptr<Component> &component);
void deleteNode(std::shared_ptr<Node> node);
#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;
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;
this->hoverComponent = this->searchComponentTree(this->rootComponent, passedX, passedY + (((this->transformMatrix[13] / 2) - 1) * this->windowHeight));
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->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" << typeOfComponent(this->focusedComponent) << 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...
if (key == GLFW_KEY_BACKSPACE) {
focusedInputComponent->backSpace();
} else if (key == GLFW_KEY_UP) {
/*
std::pair<int, int> lineData = getLine(focusedInputComponent->value, 0);
std::cout << "line 0 is " << focusedInputComponent->value.substr(lineData.first, lineData.second) << " start: " << lineData.first << " end: " << lineData.second << std::endl;
lineData = getLine(focusedInputComponent->value, 1);
std::cout << "line 1 is " << focusedInputComponent->value.substr(lineData.first, lineData.second) << " start: " << lineData.first << " end: " << lineData.second << std::endl;
*/
if (focusedInputComponent->multiLine) {
//std::pair<int, int> lineData = getLine(focusedInputComponent->value, 0);
focusedInputComponent->cursorCharY--;
if (focusedInputComponent->cursorCharY < 0) {
focusedInputComponent->cursorCharY = 0;
focusedInputComponent->cursorCharX = 0;
}
} else {
focusedInputComponent->cursorCharX = 0;
}
focusedInputComponent->updateCursor();
} else if (key == GLFW_KEY_DOWN) {
if (focusedInputComponent->multiLine) {
//std::pair<int, int> lineData = getLine(focusedInputComponent->value, 0);
focusedInputComponent->cursorCharY++;
size_t lines = getNumberOfLines(focusedInputComponent->getValue());
if (focusedInputComponent->cursorCharY > static_cast<int>(lines)) {
focusedInputComponent->cursorCharY = lines;
focusedInputComponent->cursorCharX = focusedInputComponent->getValue().length();
}
} else {
focusedInputComponent->cursorCharX = focusedInputComponent->getValue().size();
}
focusedInputComponent->updateCursor();
} else if (key == GLFW_KEY_LEFT) {
focusedInputComponent->cursorCharX--;
if (focusedInputComponent->cursorCharX < 0) {
if (focusedInputComponent->multiLine) {
focusedInputComponent->cursorCharY--;
if (focusedInputComponent->cursorCharY < 0) {
focusedInputComponent->cursorCharY = 0;
// if was already at first line, no need to adjust x
} else {
std::pair<int, int> lineData = getLine(focusedInputComponent->getValue(), focusedInputComponent->cursorCharY);
// adjust Y
focusedInputComponent->cursorCharX = lineData.second; // end of this line
}
}
focusedInputComponent->cursorCharX = 0;
}
focusedInputComponent->updateCursor();
} else if (key == GLFW_KEY_RIGHT) {
focusedInputComponent->cursorCharX++;
if (focusedInputComponent->cursorCharX > static_cast<int>(focusedInputComponent->getValue().size())) {
if (focusedInputComponent->multiLine) {
focusedInputComponent->cursorCharY++;
size_t lines = getNumberOfLines(focusedInputComponent->getValue());
if (focusedInputComponent->cursorCharY > static_cast<int>(lines)) {
focusedInputComponent->cursorCharY = lines;
} else {;
focusedInputComponent->cursorCharX = 0;
}
} else {
focusedInputComponent->cursorCharX = focusedInputComponent->getValue().size();
}
}
focusedInputComponent->updateCursor();
} else 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;
}
} else {
if (key < 256) {
if (mods & GLFW_MOD_SHIFT) {
// SHIFT
if (key == GLFW_KEY_SLASH) key='?';
if (key == GLFW_KEY_APOSTROPHE) key='"';
if (key == GLFW_KEY_COMMA) key='<';
if (key == GLFW_KEY_MINUS) key='_';
if (key == GLFW_KEY_PERIOD) key='>';
if (key == GLFW_KEY_SEMICOLON) key=':';
if (key == GLFW_KEY_EQUAL) key='+';
if (key == GLFW_KEY_LEFT_BRACKET) key='{';
if (key == GLFW_KEY_BACKSLASH) key='|';
if (key == GLFW_KEY_RIGHT_BRACKET) key='}';
if (key == GLFW_KEY_GRAVE_ACCENT) key='~';
} else {
// no shift or caplocks
// basically: when SHIFT isn't pressed but key is in A-Z range, add ascii offset to make it lower case
if (key >= 'A' && key <= 'Z') {
key += 'a' - 'A';
}
}
focusedInputComponent->addChar(key);
} // otherwise I think it's some weird control char
}
}
}
};
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) {
// 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;
}
// calculate scroll max by calculating how many screens are in the rootComponent's Height
if (this->transformMatrix[13] > std::max( (this->rootComponent->height - this->rootComponent->y) / this->windowHeight * 2.0f, 2.0f)) {
this->transformMatrix[13] = std::max( (this->rootComponent->height - this->rootComponent->y) / this->windowHeight * 2.0f, 2.0f);
}
if (this->textureTransformMatrix[13] > std::max( (this->rootComponent->height - this->rootComponent->y) / this->windowHeight * 2.0f, 2.0f) - 2.0) {
this->textureTransformMatrix[13] = std::max( (this->rootComponent->height - this->rootComponent->y) / this->windowHeight * 2.0f, 2.0f) - 2.0;
}
this->transformMatrixDirty = 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) {
// reset rootComponent
if (rootComponent) {
deleteComponent(rootComponent);
}
if (domRootNode) {
deleteNode(domRootNode);
}
// reset scroll position
transformMatrix[13] = 2;
transformMatrixDirty = true;
// 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;
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))));
// 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);
//std::cout << "DocumentComponent::render - end Box components" << std::endl;
textureShader->release();
fontShader->bind();
//std::cout << "DocumentComponent::render - start components" << std::endl;
renderComponents(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) {
buttonComponent->value = textNode->text;
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;
}
}