#include "app.h" #include "../tools/Scheduler.h" #include "../interfaces/components/TabbedComponent.h" #include "../parsers/markup/ntrml/NTRMLParser.h" #include "../parsers/markup/html/HTMLParser.h" #include "../interfaces/components/DocumentComponent.h" //#include "interfaces/components/ComponentBuilder.h" #include "../interfaces/components/InputComponent.h" #include "../interfaces/components/ImageComponent.h" #include "../interfaces/components/TabbedComponent.h" #include "../interfaces/components/ButtonComponent.h" #include #ifndef _WIN32 #include #else #include "../win32-glob.h" #endif // why can't I const this? std::unique_ptr scheduler = std::make_unique(); void doOnClick(std::string type, Component *component, Window *win) { if (type=="back") { component->onClick=[win]() { std::cout << "Browser::doOnClick.navBackButton->onClick - Back" << std::endl; TabbedComponent *pTabComponent = dynamic_cast(win->tabComponent.get()); if (pTabComponent) { if (pTabComponent->selectedTabId) { pTabComponent->selectedTab->get()->history->print(); pTabComponent->selectedTab->get()->history->back(); } else { std::cout << "Browser::doOnClick.navBackButton->onClick - no tab selected" << std::endl; } } else { std::cout << "Browser::doOnClick.navBackButton->onClick - no tabbed component" << std::endl; } }; } else if (type=="forward") { component->onClick=[win]() { //std::cout << "Forward" << std::endl; TabbedComponent *pTabComponent = dynamic_cast(win->tabComponent.get()); if (pTabComponent) { if (pTabComponent->selectedTabId) { pTabComponent->selectedTab->get()->history->forward(); } } }; } else if (type=="refresh") { component->onClick=[win]() { std::shared_ptr docComponent = win->getActiveDocumentComponent(); if (docComponent) { std::cout << "Refreshing " << docComponent->currentURL << std::endl; // now tell it to navigate somewhere docComponent->navTo(docComponent->currentURL.toString()); } }; } else if (type=="go") { component->onClick=[win]() { // get address bar value and nav away std::string value; InputComponent *p_addressComponent = dynamic_cast(win->addressComponent.get()); if (!p_addressComponent) { std::cout << "Browser:zdoOnClick.navAddressBar->onClick - can't find address bar in windows " << std::endl; return; } //std::cout << "Browser:zdoOnClick.navAddressBar->onClick - got " << value << std::endl; TabbedComponent *p_tabComponent = dynamic_cast(win->tabComponent.get()); if (p_tabComponent) { if (!p_tabComponent->tabs.size()) { p_tabComponent->addTab("Loading..."); p_tabComponent->selectTab(p_tabComponent->tabs.back()); win->renderDirty = true; win->render(); // display loading tab before IO } } std::shared_ptr docComponent = win->getActiveDocumentComponent(); if (docComponent) { //std::cout << "Browser:zdoOnClick.navAddressBar->onClick - Found an active document component" << std::endl; // now tell it to navigate somewhere docComponent->navTo(value); } else { std::cout << "Browser:zdoOnClick.navAddressBar->onClick - No active document component" << std::endl; } }; } else if (type=="nextTab") { component->onClick=[win]() { // get tab value and nav away }; } else if (type=="prevTab") { component->onClick=[win]() { // get tab value and nav away }; } } std::shared_ptr App::loadTheme(std::string filename) { std::string ntrml, line; std::ifstream myfile(filename); if (myfile.is_open()) { while(getline(myfile, line)) { ntrml += line; } myfile.close(); } else { std::cout << "Couldnt read " << filename << std::endl; } NTRMLParser uiParser; //std::cout << "Browser read [" << ntrml << "]" << std::endl; return uiParser.parse(ntrml); } void App::destroyTheme() { for(auto win: this->windows) { if (win->tabComponent) { TabbedComponent *tabbedComponent = dynamic_cast(win->tabComponent.get()); for(auto tab: tabbedComponent->tabs) { tabbedComponent->removeTab(tab->id); } } win->addressComponent = nullptr; win->tabComponent = nullptr; for(auto layer: win->ui->layers) { layer.reset(); } win->ui->layers.clear(); } } void App::rebuildTheme() { for(auto win: this->windows) { // convert uiRootNode into a component list std::shared_ptr rootComponent = std::make_shared(); rootComponent->name = "rootComponent of browser"; // we build one global tree that each layer has a child node in this->layers.clear(); // nuke any layers we have this->createComponentTree(this->uiRootNode, rootComponent, win); //Component::printComponentTree(rootComponent, 0); // these probably should be fatal if (!win->addressComponent) { std::cout << "ERROR: no addressComponent" << std::endl; } if (!win->tabComponent) { std::cout << "ERROR: no tabComponent" << std::endl; } // we want each window to has it's own component tree, so each address bar can have different values size_t layerCount = 0; for(auto layer: this->layers) { // set proper vertices layer->resize(win->windowWidth, win->windowHeight); // transfer layer win->ui->layers.push_back(layer); // debug /* std::cout << "layer: " << layerCount << std::endl; Component::printComponentTree(layer, 0); */ layerCount++; layer.reset(); // deallocate our local layers } rootComponent.reset(); //newWindow->rootComponent = rootComponent; //newWindow->ui->rootComponent = rootComponent; win->renderDirty = true; } } void App::transferTheme(std::string filename) { // assuming we don't need the NodeTree to get our values this->uiRootNode.reset(); this->uiRootNode = this->loadTheme(filename); for(auto win: this->windows) { glfwMakeContextCurrent(win->window); TabbedComponent *oldTabbedComponent = nullptr; if (win->tabComponent) { oldTabbedComponent = dynamic_cast(win->tabComponent.get()); } win->tabComponent = nullptr; // release to release win->addressComponent = nullptr; // I think this is ok to release now // convert uiRootNode into a component list std::shared_ptr rootComponent = std::make_shared(); rootComponent->name = "rootComponent of browser"; this->layers.clear(); // nuke any layers we had (doesn't nuke window layers) // we build one global tree that each layer has a child node in this->createComponentTree(this->uiRootNode, rootComponent, win); //Component::printComponentTree(rootComponent, 0); // these probably should be fatal if (!win->addressComponent) { std::cout << "ERROR: no addressComponent" << std::endl; } if (!win->tabComponent) { std::cout << "ERROR: no tabComponent" << std::endl; } // ok now we need to transfer the tabs over if (oldTabbedComponent) { // should have been set in createComponentTree TabbedComponent *newTabbedComponent = dynamic_cast(win->tabComponent.get()); if (newTabbedComponent) { size_t oldSelectedTabId = oldTabbedComponent->selectedTabId; std::shared_ptr newSelectedTab = nullptr; std::cout << "going to copy " << oldTabbedComponent->tabs.size() << " tab(s) over to new theme" << std::endl; for(auto tab: oldTabbedComponent->tabs) { DocumentComponent *oldDocComponent = dynamic_cast(tab->contents.get()); if (oldDocComponent) { //std::cout << "Reading DOM" << std::endl; //printNode(oldDocComponent->domRootNode, 0); std::shared_ptr nTab = newTabbedComponent->addTab(tab->titleBox->text); newTabbedComponent->selectTab(nTab); // maybe faster if we can skip the component tree rebuild newTabbedComponent->loadDomIntoTab(oldDocComponent->domRootNode, tab->titleBox->text); //newTabbedComponent->documentComponent.get() DocumentComponent *newDocComponent = dynamic_cast(newTabbedComponent->tabs.back()->contents.get()); if (newDocComponent) { // really slow and we don't need to do this for all tabs // not sure why this is required... //newDocComponent->setDOM(oldDocComponent->domRootNode); //std::cout << "Checking DOM" << std::endl; //printNode(newTabbedComponent->documentComponent->, 0); newDocComponent->render(); // flush out components //newDocComponent->domDirty = true; // is most accurate } else { std::cout << "new tab had no doc" << std::endl; } if (tab->id == oldSelectedTabId) { // translate old selected id into new id newSelectedTab = nTab; } } else { std::cout << "old tab had no doc" << std::endl; } // this breaks everything //oldTabbedComponent->removeTab(tab->id); } if (newSelectedTab) { newTabbedComponent->selectTab(newSelectedTab); /* DocumentComponent *newDocComponent = dynamic_cast(newTabbedComponent->documentComponent.get()); if (newDocComponent) { // FIXME: can only in render active window/ogl context newDocComponent->render(); // flush out components } else { std::cout << "new selected tab had no doc" << std::endl; } */ } else { std::cout << "no new selected tab" << std::endl; } /* std::cout << "new tabbed component has " << newTabbedComponent->tabs.size() << " tab(s) over from old theme" << std::endl; for(auto tab: newTabbedComponent->tabs) { DocumentComponent *newDocComponent = dynamic_cast(tab->contents.get()); std::cout << "checking tab DOM" << std::endl; printNode(newDocComponent->domRootNode, 0); } if (newTabbedComponent->documentComponent) { DocumentComponent *newDocComponent = dynamic_cast(newTabbedComponent->documentComponent.get()); std::cout << "checking selected DOM" << std::endl; printNode(newDocComponent->domRootNode, 0); } */ } else { std::cout << "new theme didnt have tab component" << std::endl; } } else { std::cout << "old theme didnt have tab component" << std::endl; } // now we're officially done with old layer, nuked them for(auto layer: win->ui->layers) { layer.reset(); } win->ui->layers.clear(); // we want each window to has it's own component tree, so each address bar can have different values size_t layerCount = 0; for(auto layer: this->layers) { // set proper vertices layer->resize(win->windowWidth, win->windowHeight); // transfer layer win->ui->layers.push_back(layer); // debug /* std::cout << "layer: " << layerCount << std::endl; Component::printComponentTree(layer, 0); */ layerCount++; layer.reset(); // deallocate our local layers } rootComponent.reset(); //newWindow->rootComponent = rootComponent; //newWindow->ui->rootComponent = rootComponent; win->renderDirty = true; } } void App::NextTheme() { glob_t glob_result; static unsigned int t = 0; #ifndef _WIN32 glob("*.ntrml", GLOB_TILDE, nullptr, &glob_result); #else glob("*.ntrml", GLOB_NOCHECK, nullptr, &glob_result); #endif t++; if (t == glob_result.gl_pathc) t = 0; /* for(unsigned int i = 0; i < glob_result.gl_pathc; ++i){ if (t == i) std::cout << "*"; std::cout << glob_result.gl_pathv[i] << std::endl; } */ // this works decently, but only destroys first window /* this->destroyTheme(); //deleteNode(this->uiRootNode); this->uiRootNode.reset(); this->uiRootNode = this->loadTheme(std::string(glob_result.gl_pathv[t])); this->rebuildTheme(); */ this->transferTheme(glob_result.gl_pathv[t]); // start with removing existing theme // and replacing with new version of existing theme } // Previously defined components with functionality // 1+ image (no functionality) // 1+ tabbed document // 1+ back/fwd/reload/stop components // 1+ address bar void App::createComponentTree(const std::shared_ptr node, std::shared_ptr &parentComponent, std::shared_ptr win) { // FIXME: remove these 2 vars int winWidth = win->windowWidth; int winHeight = win->windowHeight; std::string tag; if (node == nullptr) { std::cout << "ComponentBuilder::build - node is null" << std::endl; return; } TagNode *tagNode = nullptr; if (node->nodeType == NodeType::TAG) { tagNode = dynamic_cast(node.get()); if (tagNode) { tag = tagNode->tag; } } else if (node->nodeType == NodeType::TEXT) { // just ignore text blocks return; tagNode = dynamic_cast(node->parent.get()); if (tagNode) { tag = tagNode->tag; } } std::shared_ptr component = nullptr; if (!tagNode) { // usually the root node... //std::cout << "!tagnode" << std::endl; component = std::make_shared(); for (std::shared_ptr child : node->children) { createComponentTree(child, component, win); } return; } //std::cout << "Looking at tag[" << tag << "]" << std::endl; if (tag == "layer") { // the layering only works for default layout due to the ordering we're doign in window.render component = std::make_shared(); this->layers.push_back(component); //parentComponent = component; //std::swap(component, parentComponent); //std::cout << "there are now " << this->layers.size() << " layers" << std::endl; } else if (tag == "body") { if (tagNode->properties.find("bgcolor") != tagNode->properties.end()) { std::stringstream ss; ss << std::hex << tagNode->properties["bgcolor"]; ss >> win->clearColor; //std::cout << "set clear color " << std::hex << win->clearColor << std::dec << std::endl; float r = (static_cast((win->clearColor >> 24) & 0xFF)) / 255; float g = (static_cast((win->clearColor >> 16) & 0xFF)) / 255; float b = (static_cast((win->clearColor >> 8) & 0xFF)) / 255; float a = (static_cast((win->clearColor >> 0) & 0xFF)) / 255; glClearColor(r, g, b, a); } } else if (tag == "img") { std::string src = "anime.pnm"; if (tagNode->properties.find("src") != tagNode->properties.end()) { src = tagNode->properties["src"]; } //std::shared_ptr img = std::make_unique(src, winWidth * imgSetup.left.pct * 0.01 + imgSetup.left.px, winHeight * imgSetup.top.pct * 0.01 + imgSetup.top.px, imgSetup.width.px, imgSetup.height.px, winWidth, winHeight); std::shared_ptr img = std::make_unique(src, 0, 0, 512, 512, winWidth, winHeight); img->boundToPage = false; // have to set this before setUpUI img->setUpUI(tagNode->properties, win.get()); // set up interactivity if (tagNode->properties.find("onClick") != tagNode->properties.end()) { img->isPickable = true; //doOnClick(std::string type, Component &component, Window *win) doOnClick(tagNode->properties["onClick"], img.get(), win.get()); } img->isPickable = false; img->name = "img"; if (tagNode->properties.find("name") != tagNode->properties.end()) { img->name = tagNode->properties["name"]; } component = img; //std::cout << "Added img component to ui" << std::endl; } else if (tag == "font") { // TextComponent(const std::string &rawText, const int rawX, const int rawY, const unsigned int size, const bool bolded, const unsigned int hexColor, const int passedWindowWidth, const int passedWindowHeight); std::string label = "NeTRunner"; if (node->children.size()) { TextNode *textNode = dynamic_cast(node->children.front().get()); if (textNode) { label = textNode->text; } } std::shared_ptr text = std::make_unique(label, 0, 0, 12, false, 0x000000FF, winWidth, winHeight); text->boundToPage = false; // have to set this before setUpUI text->win = win; // this may not work for the text shader... yea doesn't seem to text->setUpUI(tagNode->properties, win.get()); text->isPickable = false; //text->resize(win->windowWidth, win->windowHeight); // force a re-raster // set up interactivity if (tagNode->properties.find("onClick") != tagNode->properties.end()) { text->isPickable = true; //doOnClick(std::string type, Component &component, Window *win) doOnClick(tagNode->properties["onClick"], text.get(), win.get()); } component = text; } else if (tag == "box") { unsigned int color = 0x888888FF; if (tagNode->properties.find("color") != tagNode->properties.end()) { std::stringstream ss; ss << std::hex << tagNode->properties["color"]; ss >> color; //std::cout << "read color " << tagNode->properties["color"] << " as " << std::hex << color << std::dec << std::endl; } // winHeight minus because box coordinates? yes //std::cout << "placing box at " << cX << "," << cY << " " << cW << "x" << cH << " color: " << std::hex << color << std::dec << std::endl; //std::shared_ptr box = std::make_unique(cX, winHeight - cH - cY, cW, cH, color, winWidth, winHeight); std::shared_ptr box = nullptr; if (node->children.size()) { box = std::make_unique(0, 0, 1, 1, color, winWidth, winHeight); } else { box = std::make_unique(0, 0, 1, 1, color, winWidth, winHeight); } box->boundToPage = false; // have to set this before setUpUI box->setUpUI(tagNode->properties, win.get()); box->isPickable = false; box->name = "box"; if (tagNode->properties.find("name") != tagNode->properties.end()) { box->name = tagNode->properties["name"]; } // set up interactivity if (tagNode->properties.find("hover") != tagNode->properties.end()) { box->isPickable = true; unsigned int hoverColor = 0; std::stringstream ss; ss << std::hex << tagNode->properties["hover"]; ss >> hoverColor; //std::cout << "setHover Color " << std::hex << hoverColor << std::dec << std::endl; box->onMouseover = [box, win, hoverColor]() { //std::cout << "box->onMouseover" << std::endl; box->changeColor(hoverColor); win->renderDirty = true; }; box->onMouseout = [box, win, color]() { //std::cout << "box->onMouseout" << std::endl; box->changeColor(color); win->renderDirty = true; }; } if (tagNode->properties.find("onClick") != tagNode->properties.end()) { box->isPickable = true; //doOnClick(std::string type, Component &component, Window *win) doOnClick(tagNode->properties["onClick"], box.get(), win.get()); } component = box; // if this button node has children, extract the text before discarding it if (node->children.size()) { TextNode *textNode = dynamic_cast(node->children.front().get()); if (textNode) { // so now cast back to a ButtonComponent and set it std::cout << "Box text: " << textNode->text << std::endl; /* ButtonComponent *buttonComponent = dynamic_cast(component.get()); if (buttonComponent) { buttonComponent->value = textNode->text; buttonComponent->resizeToTextSize(); buttonComponent->updateText(); } */ } } // manual quick add before we bail component->setParent(parentComponent); parentComponent->children.push_back(component); // skip my children return; } else if (tag == "input") { //std::shared_ptr navAddressBar = std::make_unique(192.0f, winHeight - 48.0f, winWidth - 384.0f, 24.0f, winWidth, winHeight); std::shared_ptr navAddressBar = std::make_unique(0, 0, 384.0f, 24.0f, winWidth, winHeight); navAddressBar->boundToPage = false; // have to set this before setUpUI navAddressBar->setUpUI(tagNode->properties, win.get()); navAddressBar->name = "navAddressBar"; navAddressBar->onEnter=[win](std::string value) { std::cout << "navAddressBar::onEnter got " << value << std::endl; TabbedComponent *p_tabComponent = dynamic_cast(win->tabComponent.get()); if (p_tabComponent) { if (!p_tabComponent->tabs.size()) { p_tabComponent->addTab("Loading..."); p_tabComponent->selectTab(p_tabComponent->tabs.back()); win->renderDirty = true; win->render(); // display loading tab before IO } } std::shared_ptr docComponent = win->getActiveDocumentComponent(); if (docComponent) { std::cout << "Found an active document component" << std::endl; // now tell it to navigate somewhere docComponent->navTo(value); } else { std::cout << "No active document component" << std::endl; } }; navAddressBar->win = win; win->addressComponent = navAddressBar; component = navAddressBar; } else if (tag == "tabselector") { std::shared_ptr tabbedComponent = std::make_shared(0, 0, static_cast(winWidth), static_cast(winHeight - 64), winWidth, winHeight); tabbedComponent->win = win; win->tabComponent = tabbedComponent; tabbedComponent->boundToPage = false; // have to set this before setUpUI tabbedComponent->setUpUI(tagNode->properties, win.get()); tabbedComponent->name = "tabbedComponent"; /* tabbedComponent->y = -64; tabbedComponent->uiControl.x = { 0 , 0 }; // 0 tabbedComponent->uiControl.y = { 0 , -64 }; // -64px tabbedComponent->uiControl.w = { 100, 0 }; // 100% tabbedComponent->uiControl.h = { 100, -64 }; // 100% - 64px tabbedComponent->boundToPage = false; */ component = tabbedComponent; // make the UI focus on it to relay keyboard events win->ui->relayKeyboardComponent = tabbedComponent; } else { std::cout << "Browser::createComponentTree - unknown tag[" << tag << "]" << std::endl; } if (!component) { component = std::make_shared(); } // add it to our parent component->setParent(parentComponent); parentComponent->children.push_back(component); // , children count: " << parentComponent->children.size() // create children elements for (std::shared_ptr child : node->children) { createComponentTree(child, component, win); } component.reset(); } void App::addJSDebuggerWindow() { windowCounter++; std::shared_ptr newWindow = std::make_shared(); newWindow->id = windowCounter; newWindow->windowWidth = 1024; newWindow->windowHeight = 640; newWindow->init(); // load our UI into it std::shared_ptr p_rootComponent = std::make_shared(); p_rootComponent->name = "rootComponent of jsconsole"; std::shared_ptr outputComp = std::make_shared("NeTRunner JavaScript console", 0, 0, 12, false, 0x000000FF, newWindow->windowWidth, newWindow->windowHeight); outputComp->win = newWindow; newWindow->ui->layers.push_back(outputComp); outputComp.reset(); /* std::shared_ptr inputComp = std::make_shared(0, 0, 1024, 12, newWindow->windowWidth, newWindow->windowHeight); inputComp->win = newWindow; newWindow->ui->layers.push_back(inputComp); inputComp.reset(); */ this->windows.push_back(newWindow); newWindow.reset(); } void App::addWindow() { windowCounter++; std::shared_ptr newWindow = std::make_shared(); newWindow->id = windowCounter; newWindow->windowWidth = 1024; newWindow->windowHeight = 640; newWindow->init(); // load our UI into it newWindow->openglWindow->app = this; // convert uiRootNode into a component list std::shared_ptr p_rootComponent = std::make_shared(); p_rootComponent->name = "rootComponent of browser"; // we build one global tree that each layer has a child node in this->createComponentTree(this->uiRootNode, p_rootComponent, newWindow); //Component::printComponentTree(p_rootComponent, 0); // these probably should be fatal if (!newWindow->addressComponent) { std::cout << "ERROR: no addressComponent" << std::endl; } if (!newWindow->tabComponent) { std::cout << "ERROR: no tabComponent" << std::endl; } // we want each window to has it's own component tree, so each address bar can have different values size_t layerCount = 0; for(auto layer: this->layers) { // set proper vertices layer->resize(newWindow->windowWidth, newWindow->windowHeight); // transfer layer newWindow->ui->layers.push_back(layer); // debug /* std::cout << "layer: " << layerCount << std::endl; Component::printComponentTree(layer, 0); */ layerCount++; layer.reset(); // deallocate our local layers } this->layers.clear(); // nuke any layers we have p_rootComponent.reset(); //newWindow->rootComponent = rootComponent; //newWindow->ui->rootComponent = rootComponent; newWindow->renderDirty = true; this->windows.push_back(newWindow); newWindow.reset(); if (!activeWindow) { //std::cout << "Browser::addWindow - setting active window" << std::endl; activeWindow = this->windows.back().get(); } } // FIXME: put quit check in here and clean up windows void App::render() { // guessing during render this iterator can be invalidated size_t w = this->windows.size(); //for(std::vector>::iterator it = this->windows.begin(); it != this->windows.end(); ++it) { //it->get()->render(); // definitely safer for(size_t i = 0; i < w; ++i) { glfwMakeContextCurrent(this->windows[i]->window); this->windows[i]->render(); } } void App::loop() { bool shouldQuit = false; while (!shouldQuit) { //const std::clock_t begin = clock(); this->render(); scheduler->fireTimers(); // render may have taken some time double next = scheduler->getNext(); //if (!next) std::cout << "timer starvation, refiring" << std::endl; /* while(!next) { std::cout << "timer starvation, refiring" << std::endl; scheduler->fireTimers(); // render may have taken some time next = scheduler->getNext(); } */ //std::cout << "Browser::loop - next timer at " << next << std::endl; if (next == LONG_MAX) { glfwWaitEvents(); // block until something changes } else { glfwWaitEventsTimeout(next / 1000); } scheduler->fireTimers(); // check before we go into render again //glfwWaitEventsTimeout(1.0 / 60.0); // increase the cpu from 0% to 2% on idle //const std::clock_t end = clock(); //std::cout << '\r' << std::fixed << (((static_cast(end - begin)) / CLOCKS_PER_SEC) * 1000) << std::scientific << " ms/f " << std::flush; // FIXME: one close shouldn't close the app for(std::vector>::iterator it = this->windows.begin(); it != this->windows.end(); ++it) { if (glfwWindowShouldClose(it->get()->window)) { // FIXME: just remove window from stack shouldQuit = true; break; } } } }