diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e432b8..0ac2015 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ set(BUILD_SHARED_LIBS OFF) set(BUILD_TESTS OFF) set(BUILD_SAMPLES OFF) set(BUILD_WITH_ASSIMP OFF) +set(BUILD_GUI_ELEMENTS ON) set(LIB_NAME vb01) set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -25,6 +26,10 @@ if(BUILD_WITH_ASSIMP) set(ASSET_READERS ${ASSET_READERS} assimpModelReader.cpp) endif() +if(BUILD_GUI_ELEMENTS) + set(GUI ${GUI} button.cpp listbox.cpp textbox.cpp checkbox.cpp slider.cpp) +endif() + set(LIB_SRC ${RENDER} ${MATH} ${UTILS} ${GUI} ${ARMATURE} ${ASSET_MANAGER} ${ASSET_READERS} ${ANIM}) include_directories(external/tinydir) diff --git a/button.cpp b/button.cpp new file mode 100644 index 0000000..4de82b7 --- /dev/null +++ b/button.cpp @@ -0,0 +1,156 @@ +#include +#include +#include + +#include "root.h" +#include "quad.h" +#include "node.h" +#include "material.h" +#include "text.h" +#include "texture.h" +#include "util.h" +#include "button.h" + +using namespace std; +using namespace vb01; + +namespace vb01Gui{ + Button::Button(Vector2 pos, Vector2 size, string name, string fontPath, int trigger, bool separate, string imagePath){ + this->pos = pos; + this->size = size; + this->name = name; + this->separate = separate; + this->imagePath = imagePath; + this->trigger = trigger; + + Root *root = Root::getSingleton(); + guiNode = root->getGuiNode(); + rect = new Quad(Vector3(size.x, size.y, 0), false); + rectNode = new Node(Vector3(pos.x, pos.y, 0)); + Material *mat = new Material(root->getLibPath() + "gui"); + + if(imagePath == ""){ + mat->addBoolUniform(texUni, false); + mat->addVec4Uniform(diffColUni, color); + } + else{ + mat->addBoolUniform(texUni, true); + string frame[]{imagePath}; + Texture *tex = new Texture(frame, 1, false); + mat->addTexUniform("diffuseMap", tex, false); + textures.push_back(tex); + } + + rect->setMaterial(mat); + rectNode->attachMesh(rect); + guiNode->attachChild(rectNode); + + if(separate){ + text = new Text(fontPath, stringToWstring(name)); + text->setScale(.2); + + textNode = new Node(Vector3(pos.x, pos.y + size.y, - .1)); + textNode->addText(text); + + Material *textMat = new Material(root->getLibPath() + "text"); + textMat->addBoolUniform(texUni, false); + textMat->addVec4Uniform(diffColUni, Vector4::VEC_IJKL); + text->setMaterial(textMat); + + guiNode->attachChild(textNode); + } + + initWindowSize[0] = Root::getSingleton()->getWidth(); + initWindowSize[1] = Root::getSingleton()->getHeight(); + } + + Button::~Button(){ + if(textNode){ + guiNode->dettachChild(textNode); + delete textNode; + } + + guiNode->dettachChild(rectNode); + delete rectNode; + } + + void Button::update(){ + int width, height; + GLFWwindow *window = Root::getSingleton()->getWindow(); + glfwGetWindowSize(window, &width, &height); + + float posRatio[] = {pos.x / initWindowSize[0], pos.y / initWindowSize[1]}; + float sizeRatio[] = {size.x / initWindowSize[0], size.y / initWindowSize[1]}; + + pos = Vector2(width * posRatio[0], height * posRatio[1]); + size = Vector2(width * sizeRatio[0], height * sizeRatio[1]); + + initWindowSize[0] = width; + initWindowSize[1] = height; + + if(textNode) + textNode->setVisible(active); + + rectNode->setVisible(active); + } + + void Button::onMouseOver(){ + mouseOver = true; + } + + void Button::onMouseOff(){ + mouseOver = false; + } + + void Button::setPos(Vector2 pos){ + this->pos = pos; + float z1 = rectNode->getPosition().z; + rectNode->setPosition(Vector3(pos.x, pos.y, z1)); + + if(textNode){ + float z2 = textNode->getPosition().z; + textNode->setPosition(Vector3(pos.x, pos.y + size.y, z2)); + } + } + + void Button::setSize(Vector2 size){ + this->size = size; + rect->setSize(Vector3(size.x, size.y, 0)); + } + + void Button::setColor(Vector4 c){ + this->color = c; + ((Material::Vector4Uniform*)rect->getMaterial()->getUniform(diffColUni))->value = color; + } + + void Button::setZOrder(float zOrder){ + Vector3 rectPos = rectNode->getPosition(); + rectNode->setPosition(Vector3(rectPos.x, rectPos.y, zOrder)); + + if(textNode){ + Vector3 textPos = textNode->getPosition(); + textNode->setPosition(Vector3(textPos.x, textPos.y, zOrder - .1)); + } + } + + void Button::setImage(string image){ + int texId = -1; + Material *mat = rect->getMaterial(); + + for(int i = 0; i < textures.size(); i++) + if(textures[i]->getPath()[0] == image) + texId = i; + + /* + if(texId == -1){ + string p[]{image}; + textures.push_back(new Texture(p, 1)); + mat->setDiffuseMap(textures[textures.size() - 1], 0); + } + else + mat->setDiffuseMap(textures[texId], 0); + */ + + this->imagePath = image; + } +} diff --git a/button.h b/button.h new file mode 100644 index 0000000..6a99bfd --- /dev/null +++ b/button.h @@ -0,0 +1,53 @@ +#ifndef BUTTON_H +#define BUTTON_H + +#include +#include + +#include "vector.h" + +class GLFWwindow; + +namespace vb01{ + class Quad; + class Node; + class Text; + class Texture; +} + +namespace vb01Gui{ + class Button{ + public: + Button(vb01::Vector2, vb01::Vector2, std::string, std::string, int = -1, bool = true, std::string = ""); + virtual ~Button(); + void update(); + virtual void onMouseOver(); + virtual void onMouseOff(); + virtual void onClick(){} + inline vb01::Vector2 getPos(){return pos;} + void setPos(vb01::Vector2); + inline vb01::Vector2 getSize(){return size;} + void setSize(vb01::Vector2); + inline std::string getName(){return name;} + inline void setActive(bool active){this->active = active;} + inline bool isSeparate(){return separate;} + inline bool isActive(){return active;} + inline vb01::Vector4 getColor(){return color;} + inline int getTrigger(){return trigger;} + void setColor(vb01::Vector4); + void setZOrder(float); + void setImage(std::string); + protected: + int trigger, initWindowSize[2]; + bool separate, active = true, mouseOver = false; + vb01::Quad *rect; + vb01::Node *rectNode, *textNode = nullptr, *guiNode; + vb01::Vector2 pos, size; + vb01::Text *text = nullptr; + std::string name, imagePath, texUni = "texturingEnabled", diffColUni = "diffuseColor"; + std::vector textures; + vb01::Vector4 color = vb01::Vector4(.6, .6, .6, 1); + }; +} + +#endif diff --git a/checkbox.cpp b/checkbox.cpp new file mode 100644 index 0000000..95dfdc9 --- /dev/null +++ b/checkbox.cpp @@ -0,0 +1,47 @@ +#include "checkbox.h" +#include "text.h" +#include "node.h" +#include "material.h" + +using namespace vb01; +using namespace std; + +namespace vb01Gui{ + Checkbox::CheckboxButton::CheckboxButton(Checkbox *ch, Vector2 pos, Vector2 size, string name, bool separate) : Button(pos, size, name, "", -1, separate) { + checkbox = ch; + } + + void Checkbox::CheckboxButton::onClick() { + checkbox->check(); + float col = (checkbox->isChecked() ? .6 : .2); + setColor(Vector4(col, col, col, 1)); + } + + Checkbox::Checkbox(Vector2 pos, string fontPath){ + this->pos = pos; + checkboxButton = new CheckboxButton(this, pos, Vector2(length,length), "CheckboxButton", false); + + /* + Root *root = Root::getSingleton(); + checkNode = new Node(); + Text *check = new Text(fontPath, L"X"); + Material *mat = new Material(root->getLibPath() + "text"); + mat->addBoolUniform("texturingEnabled", false); + mat->addVec4Uniform("diffuseColor", Vector4::VEC_IJKL); + check->setMaterial(mat); + checkNode->addText(check); + checkNode->setVisible(false); + root->getGuiNode()->attachChild(checkNode); + checkNode->setPosition(Vector3(pos.x, pos.y, 0.1f)); + */ + } + + Checkbox::~Checkbox() { + + } + + void Checkbox::check() { + checked = !checked; + //checkNode->setVisible(checked); + } +} diff --git a/checkbox.h b/checkbox.h new file mode 100644 index 0000000..e1d8bd6 --- /dev/null +++ b/checkbox.h @@ -0,0 +1,39 @@ +#ifndef CHECKBOX_H +#define CHECKBOX_H + +#include "button.h" + +namespace vb01{ + class Quad; + class Node; + class Text; +} + +namespace vb01Gui{ + class Checkbox { + public: + Checkbox(vb01::Vector2, std::string); + ~Checkbox(); + void update(){} + virtual void check(); + virtual bool isChecked(){return checked;} + private: + class CheckboxButton : public Button { + public: + CheckboxButton(Checkbox*, vb01::Vector2, vb01::Vector2, std::string, bool); + void onClick(); + private: + Checkbox *checkbox = nullptr; + }; + + const int length = 15; + bool checked = false; + vb01::Vector2 pos; + CheckboxButton *checkboxButton; + vb01::Node *checkNode = nullptr; + public: + inline CheckboxButton* getCheckboxButton(){return checkboxButton;} + }; +} + +#endif diff --git a/listbox.cpp b/listbox.cpp new file mode 100644 index 0000000..d37c347 --- /dev/null +++ b/listbox.cpp @@ -0,0 +1,234 @@ +#include + +#include "listbox.h" +#include "util.h" +#include "node.h" +#include "text.h" +#include "quad.h" +#include "root.h" +#include "material.h" + +using namespace vb01; +using namespace std; + +namespace vb01Gui{ + Listbox::ListboxButton::ListboxButton(Listbox *l, Vector2 pos, Vector2 size, string name) : Button(pos, size, name, "", -1, false){listbox = l;} + + void Listbox::ListboxButton::onClick(){ + if(!listbox->isOpen()) + listbox->openUp(); + else{ + Vector2 cursorPos = getCursorPos(); + + if(cursorPos.x < listbox->getScrollingButton()->getPos().x){ + if(listbox->isCloseable()) + listbox->close(); + } + else + listbox->scrollToHeight(cursorPos.y); + } + } + + Listbox::ScrollingButton::ScrollingButton(Vector2 pos, Vector2 size, string name) : Button(pos, size, name, "", -1, false){} + + void Listbox::ScrollingButton::onClick(){} + + Listbox::Listbox(Vector2 pos, Vector2 size, std::vector &lines, int maxDisplay, string fontPath, bool closeable){ + this->pos = pos; + this->size = size; + this->maxDisplay = maxDisplay; + this->fontPath = fontPath; + this->lineHeight = size.y; + this->closeable = closeable; + + Root *root = Root::getSingleton(); + Vector2 mousePos = getCursorPos(); + guiNode = Root::getSingleton()->getGuiNode(); + string texUni = "texturingEnabled", diffColUni = "diffuseColor"; + + for(int i = 0; i < lines.size(); i++){ + Text *text = new Text(fontPath, stringToWstring(lines[i])); + text->setScale(.2); + + Material *textMat = new Material(root->getLibPath() + "text"); + textMat->addBoolUniform(texUni, false); + textMat->addVec4Uniform(diffColUni, Vector4::VEC_IJKL); + text->setMaterial(textMat); + + Node *node = new Node(Vector3(pos.x, pos.y + size.y * (i + 1), textZCoord)); + node->addText(text); + guiNode->attachChild(node); + node->setVisible(false); + + this->lines.push_back(text); + } + + this->lines[0]->getNode()->setVisible(true); + + listboxButton = new ListboxButton(this, pos, size, "ListboxButton"); + + scrollingButton = new ScrollingButton(Vector2(pos.x + size.x - 20, pos.y + lineHeight), Vector2(20, lineHeight * (maxDisplay - 2) / (maxDisplay + 1)), "scrollingButton"); + scrollingButton->setColor(Vector4(.2, .2, .2, 1)); + scrollingButton->setZOrder(-.05); + scrollingButton->setActive(false); + + Quad *selRect = new Quad(Vector3(size.x, size.y, 0), false); + selRectNode = new Node(Vector3(size.x, size.y, -.05)); + Material *mat = new Material(root->getLibPath() + "gui"); + mat->addBoolUniform(texUni, false); + mat->addVec4Uniform(diffColUni, Vector4(.6, .35, .05, 1)); + selRect->setMaterial(mat); + selRectNode->attachMesh(selRect); + selRectNode->setVisible(false); + guiNode->attachChild(selRectNode); + + if(!closeable) + openUp(); + } + + Listbox::~Listbox(){ + while(!lines.empty()){ + int id = lines.size() - 1; + Node *textNode = lines[id]->getNode(); + guiNode->dettachChild(textNode); + delete textNode; + lines.pop_back(); + } + + guiNode->dettachChild(selRectNode); + delete selRectNode; + } + + void Listbox::update(){ + scrollingButton->update(); + listboxButton->update(); + + if(open){ + Vector2 mousePos = getCursorPos(); + Vector2 p = pos; + + if(mousePos.y > pos.y && mousePos.y < pos.y + size.y * maxDisplay){ + selectedOption = scrollOffset + ((int)(mousePos.y - pos.y) / ((int)size.y)); + p.y = pos.y + size.y * ((int)(mousePos.y - pos.y) / ((int)size.y)); + } + else if(mousePos.y < pos.y){ + selectedOption = 0; + p.y = pos.y; + } + else{ + selectedOption = (maxDisplay + scrollOffset) - 1; + p.y = pos.y + size.y * (maxDisplay - 1); + } + + selRectNode->setPosition(Vector3(p.x, p.y, -.05)); + + Vector2 scrollButtonPos = Vector2(pos.x + size.x - 20, pos.y + lineHeight + lineHeight * scrollOffset * (double(maxDisplay - 2) / (maxDisplay + 1))); + scrollingButton->setPos(scrollButtonPos); + } + } + + void Listbox::scrollToHeight(float clickPos){ + float clickHeight = clickPos - (pos.y + lineHeight); + int newOffset = clickHeight / (lineHeight * (maxDisplay - 2) / (maxDisplay + 1)); + int diffOffset = scrollOffset - newOffset; + + for(int i = 0; i < abs(diffOffset); ++i){ + if(diffOffset > 0) + scrollUp(); + else + scrollDown(); + } + } + + void Listbox::openUp(){ + open = true; + lines[selectedOption]->getNode()->setPosition(Vector3(pos.x, pos.y + size.y * (selectedOption-scrollOffset + 1), textZCoord)); + lines[selectedOption]->getNode()->setVisible(false); + Vector2 size = listboxButton->getSize(); + size.y *= maxDisplay; + listboxButton->setSize(size); + selRectNode->setVisible(true); + scrollingButton->setActive(true); + + for(int i = scrollOffset; i < scrollOffset + maxDisplay; i++) + lines[i]->getNode()->setVisible(true); + + onOpen(); + } + + void Listbox::close(){ + open = false; + Vector2 size = listboxButton->getSize(); + size.y /= maxDisplay; + listboxButton->setSize(size); + selRectNode->setVisible(false); + scrollingButton->setActive(false); + + for(int i = scrollOffset; i < scrollOffset + maxDisplay; i++) + lines[i]->getNode()->setVisible(false); + + lines[selectedOption]->getNode()->setPosition(Vector3(pos.x, pos.y + size.y, textZCoord)); + lines[selectedOption]->getNode()->setVisible(true); + + onClose(); + } + + void Listbox::scrollUp(){ + if(scrollOffset > 0){ + scrollOffset--; + + for(Text *l : lines){ + Node *node = l->getNode(); + Vector3 p = node->getPosition(); + node->setPosition(Vector3(p.x, p.y + size.y, p.z)); + + if(node->getPosition().y > pos.y + size.y * maxDisplay) + node->setVisible(false); + else if(node->getPosition().y > pos.y) + node->setVisible(true); + } + } + } + + void Listbox::scrollDown(){ + if (scrollOffset < lines.size() - maxDisplay){ + scrollOffset++; + + for(Text *l : lines){ + Node *node = l->getNode(); + Vector3 p = node->getPosition(); + node->setPosition(Vector3(p.x, p.y - size.y, p.z)); + + if(node->getPosition().y - size.y < pos.y) + node->setVisible(false); + else if(node->getPosition().y < pos.y + size.y * (maxDisplay + 1)) + node->setVisible(true); + } + } + } + + void Listbox::changeLine(int id, wstring change){ + lines[id]->setText(change); + } + + std::vector Listbox::getContents(){ + std::vector lines; + + for(Text *t : this->lines) + lines.push_back(t->getText()); + + return lines; + } + + void Listbox::addLine(wstring line){ + Text *t = new Text(fontPath, line); + Node *node = new Node(Vector3(pos.x, pos.y - size.y * lines.size(), textZCoord)); + node->addText(t); + guiNode->attachChild(node); + lines.push_back(t); + } + + string Listbox::convert(string str){ + return str; + } +} diff --git a/listbox.h b/listbox.h new file mode 100644 index 0000000..49c361c --- /dev/null +++ b/listbox.h @@ -0,0 +1,75 @@ +#ifndef LISTBOX_H +#define LISTBOX_H + +#include + +#include "button.h" +#include "vector.h" + +namespace vb01{ + class Node; + class Text; +} + +namespace vb01Gui{ + class Listbox{ + public: + class ListboxButton : public Button{ + public: + ListboxButton(Listbox*, vb01::Vector2, vb01::Vector2, std::string); + void onClick(); + private: + Listbox *listbox = nullptr; + }; + + Listbox(vb01::Vector2, vb01::Vector2, std::vector&, int, std::string, bool = true); + ~Listbox(); + void update(); + void openUp(); + void close(); + void scrollUp(); + void scrollDown(); + void addLine(std::wstring); + void changeLine(int, std::wstring); + void scrollToHeight(float); + virtual void onOpen(){} + virtual void onClose(){} + inline int getScrollOffset(){return scrollOffset;} + inline float getLineHeight(){return lineHeight;} + inline bool isOpen(){return open;} + inline bool isCloseable(){return closeable;} + void appendLines(std::vector&); + inline int getSelectedOption(){return selectedOption;} + inline int getMaxDisplay(){return maxDisplay;} + inline int getNumLines(){return lines.size();} + std::vector getContents(); + inline vb01::Vector2 getPos(){return pos;} + inline vb01::Vector2 getSize(){return size;} + private: + class ScrollingButton : public Button{ + public: + ScrollingButton(vb01::Vector2, vb01::Vector2, std::string); + void onClick(); + private: + }; + + std::string convert(std::string); + + float textZCoord = -.3, lineHeight; + int maxDisplay, scrollOffset = 0; + bool open = false, closeable = false; + std::string fontPath; + vb01::Vector2 pos, size; + ListboxButton *listboxButton; + ScrollingButton *scrollingButton; + vb01::Node *selRectNode, *guiNode; + std::vector lines; + protected: + int selectedOption = 0; + public: + inline ListboxButton* getListboxButton(){return listboxButton;} + inline ScrollingButton* getScrollingButton(){return scrollingButton;} + }; +} + +#endif diff --git a/slider.cpp b/slider.cpp new file mode 100644 index 0000000..077e34e --- /dev/null +++ b/slider.cpp @@ -0,0 +1,78 @@ +#include + +#include +#include + +#include "slider.h" +#include "root.h" +#include "util.h" + + +using namespace std; +using namespace vb01; + +namespace vb01Gui{ + Slider::MovableSliderButton::MovableSliderButton(Slider *slider, Vector2 pos, Vector2 size, string name, bool separate) : Button(pos, size, name, "", -1, separate) { + this->slider = slider; + } + + void Slider::MovableSliderButton::onClick() { + clicked = !clicked; + } + + void Slider::MovableSliderButton::update() { + Vector2 cursorPos; + + if (clicked && cursorPos.y > slider->getPos().y && cursorPos.y < slider->getPos().y + slider->getSize().y) { + if (cursorPos.x < slider->getPos().x) + cursorPos.x = slider->getPos().x; + else if (cursorPos.x > slider->getPos().x + slider->getSize().x) + cursorPos.x = slider->getPos().x + slider->getSize().x; + + slider->setValue((((double) cursorPos.x - slider->getPos().x) / slider->getSize().x) * slider->getMaxValue()); + } + } + + Slider::StaticSliderButton::StaticSliderButton(Slider *slider, Vector2 pos, Vector2 size, string name, bool separate) : Button(pos, size, name, "", -1, separate) { + this->slider = slider; + } + + void Slider::StaticSliderButton::onClick() { + Vector2 cursorPos = getCursorPos(); + s16 buttonClickPoint = -slider->getPos().x + cursorPos.x; + slider->setValue(((double) buttonClickPoint / slider->getSize().x) * slider->getMaxValue()); + } + + Slider::Slider(Vector2 pos, Vector2 size, double minValue, double maxValue) { + this->pos = pos; + this->size = size; + this->minValue = minValue; + this->maxValue = maxValue; + + staticSliderButton = new StaticSliderButton(this, pos, size, "StaticSliderButton", false); + movableSliderButton = new MovableSliderButton(this, Vector2(pos.x + staticSliderButton->getSize().x / 1, pos.y - 15), Vector2(10, 40), "MovableSliderButton", false); + + value = (maxValue - minValue) / 2; + } + + Slider::~Slider() { + } + + void Slider::update() { + if (value < minValue) + value = minValue; + else if (value > maxValue) + value = maxValue; + + Vector2 p = movableSliderButton->getPos(), s = movableSliderButton->getSize(); + p.x = pos.x + (double) size.x / maxValue * value; + movableSliderButton->update(); + movableSliderButton->setPos(p); + + if(textbox && !textbox->isEnabled()){ + ostringstream ss; + ss << value; + textbox->setEntry(stringToWstring(ss.str())); + } + } +} diff --git a/slider.h b/slider.h new file mode 100644 index 0000000..ce629b5 --- /dev/null +++ b/slider.h @@ -0,0 +1,52 @@ +#ifndef SLIDER_H +#define SLIDER_H + +#include "vector.h" +#include "button.h" +#include "textbox.h" + +namespace vb01Gui{ + class Slider { + public: + Slider(vb01::Vector2, vb01::Vector2, double, double); + ~Slider(); + void update(); + inline void setTextbox(Textbox *t){this->textbox=t;} + inline double getMinValue(){return minValue;} + inline double getValue(){return value;} + inline double getMaxValue(){return maxValue;} + inline void setValue(double v){this->value=v;} + inline vb01::Vector2 getPos(){return pos;} + inline vb01::Vector2 getSize(){return size;} + inline Textbox* getTextbox(){return textbox;} + private: + class MovableSliderButton : public Button { + public: + MovableSliderButton(Slider*, vb01::Vector2, vb01::Vector2, std::string, bool); + void onClick(); + void update(); + private: + Slider *slider; + bool clicked = false; + }; + + class StaticSliderButton : public Button { + public: + StaticSliderButton(Slider*, vb01::Vector2, vb01::Vector2, std::string, bool); + void onClick(); + private: + Slider *slider; + }; + + MovableSliderButton *movableSliderButton = nullptr; + StaticSliderButton *staticSliderButton = nullptr; + double minValue, value, maxValue; + vb01::Vector2 pos, size; + Textbox *textbox = nullptr; + public: + inline MovableSliderButton* getMovableSliderButton(){return movableSliderButton;} + inline StaticSliderButton* getStaticSliderButton(){return staticSliderButton;} + }; +} + +#endif diff --git a/textbox.cpp b/textbox.cpp new file mode 100644 index 0000000..c90846e --- /dev/null +++ b/textbox.cpp @@ -0,0 +1,131 @@ +#include +#include +#include + +#include "textbox.h" +#include "slider.h" +#include "root.h" +#include "node.h" +#include "quad.h" +#include "material.h" +#include "util.h" + +using namespace vb01; +using namespace std; + +namespace vb01Gui{ + Textbox::TextboxButton::TextboxButton(Textbox *t, Vector2 pos, Vector2 size, string name) : Button(pos, size, name, "", -1, false) {textbox = t;} + + void Textbox::TextboxButton::onClick(){ + if(!textbox->isEnabled()) + textbox->enable(); + else + textbox->disable(); + } + + Textbox::Textbox(Vector2 pos, Vector2 size, string fontPath, wstring entry){ + this->pos = pos; + this->size = size; + this->fontPath = fontPath; + + textboxButton = new TextboxButton(this, pos, size, "TextboxButton"); + string libPath = Root::getSingleton()->getLibPath(); + + guiNode = Root::getSingleton()->getGuiNode(); + text = new Text(fontPath, entry); + text->setScale(.5); + Material *textMat = new Material(libPath + "text"); + textMat->addBoolUniform("texturingEnabled", false); + textMat->addVec4Uniform("diffuseColor", Vector4(1, 1, 1, 1)); + text->setMaterial(textMat); + textNode = new Node(Vector3(pos.x, pos.y + size.y, -.1)); + textNode->addText(text); + guiNode->attachChild(textNode); + + cursorPosOffset = entry.length(); + cursorRect = new Quad(Vector3(cursorWidth, size.y, 0), false); + cursorNode = new Node(Vector3(pos.x + text->getLength(), pos.y, cursorZCoord)); + Material *mat = new Material(libPath + "gui"); + mat->addBoolUniform("texturingEnabled", false); + mat->addVec4Uniform("diffuseColor", Vector4(1, 1, 1, 1)); + cursorRect->setMaterial(mat); + cursorNode->attachMesh(cursorRect); + cursorNode->setVisible(false); + guiNode->attachChild(cursorNode); + } + + Textbox::~Textbox(){ + guiNode->dettachChild(textNode); + guiNode->dettachChild(cursorNode); + delete textNode; + delete cursorNode; + } + + void Textbox::update(){ + if(enabled){ + if(canChangeCursor()){ + canShowCursor = !canShowCursor; + lastBlinkTime = getTime(); + cursorNode->setVisible(canShowCursor); + } + } + } + + void Textbox::type(u32 c, bool capitalLetters){ + entry += c; + text->setText(entry); + moveCursor(false, text->getGlyph(c)->size.x); + } + + void Textbox::moveCursor(bool left, float charWidth){ + Vector3 p = cursorNode->getPosition(); + + if(left && entry.size() - cursorPosOffset > 0){ + cursorPosOffset--; + cursorNode->setPosition(Vector3(p.x - charWidth, p.y, p.z)); + } + else if(!left){ + cursorPosOffset++; + cursorNode->setPosition(Vector3(p.x + charWidth, p.y, p.z)); + } + } + + void Textbox::deleteCharacter(){ + if(entry.length() > 0 && canDeleteChar()){ + char c = entry.c_str()[entry.length() - 1]; + entry = entry.substr(0, entry.length() - 1); + text->setText(entry); + moveCursor(true, text->getGlyph(c)->size.x); + lastDeleteTime = getTime(); + } + } + + void Textbox::enable(){ + enabled = true; + lastBlinkTime = getTime(); + cursorNode->setVisible(true); + } + + void Textbox::disable(){ + enabled = false; + cursorNode->setVisible(false); + + if(slider){ + float val = strtof(wstringToString(entry).c_str(), nullptr); + + if(val < slider->getMinValue()) + val = slider->getMinValue(); + else if(val > slider->getMaxValue()) + val = slider->getMaxValue(); + + slider->setValue(val); + } + } + + void Textbox::setEntry(wstring entry){ + this->entry = entry; + text->setText(entry); + cursorNode->setPosition(Vector3(pos.x + text->getLength(), pos.y, cursorZCoord)); + cursorPosOffset = entry.length(); + } +} diff --git a/textbox.h b/textbox.h new file mode 100644 index 0000000..a6d86d1 --- /dev/null +++ b/textbox.h @@ -0,0 +1,59 @@ +#ifndef TEXTBOX_H +#define TEXTBOX_H + +#include "util.h" +#include "text.h" +#include "button.h" + +namespace vb01{ + class Node; + class Quad; +} + +namespace vb01Gui{ + class Slider; + + class Textbox{ + public: + Textbox(vb01::Vector2, vb01::Vector2, std::string, std::wstring = L""); + ~Textbox(); + void update(); + void enable(); + void disable(); + void type(vb01::u32, bool = false); + void moveCursor(bool, float); + void deleteCharacter(); + void setSlider(Slider *s){this->slider = s;} + void setEntry(std::wstring); + inline bool isEnabled(){return enabled;} + inline std::wstring getText(){return text->getText();} + void remove(); + private: + class TextboxButton : public Button{ + public: + TextboxButton(Textbox*, vb01::Vector2, vb01::Vector2, std::string); + void onClick(); + private: + Textbox *textbox; + }; + inline bool canChangeCursor(){return vb01::getTime() - lastBlinkTime > 250;} + inline bool canDeleteChar(){return vb01::getTime() - lastDeleteTime > 50;} + + float cursorZCoord = -.2; + const int cursorWidth = 5; + vb01::Vector2 pos, size; + std::wstring entry = L""; + std::string fontPath = ""; + bool enabled = false, canShowCursor = false, capitalLeters = false; + vb01::s64 lastBlinkTime = 0, lastDeleteTime = 0, cursorPosOffset = 0; + vb01::Quad *cursorRect; + vb01::Text *text; + vb01::Node *guiNode, *textNode, *cursorNode; + TextboxButton *textboxButton; + Slider *slider = nullptr; + public: + inline TextboxButton* getTextboxButton(){return textboxButton;} + }; +} + +#endif