#include "uielements.h" #include #include #include #include #include "constants.h" #include "fonts.h" #include "input.h" #include "internal.h" #include "internal/ui.h" #include "ui.h" #include "unicode.h" #include "utils.h" namespace kek { TextElement::TextElement(UIValue x, UIValue y, std::shared_ptr font) : UIElement(x, y) { this->text = new TextObject(font, "Text"); this->sizePixels = KEK_DEFAULT_FONT_SIZE_PIXELS; this->color = Colors::WHITE; this->textMode = TextMode::ORIGIN; this->textBounds = TextBounds::SMALLEST; } TextElement::TextElement(UIValue x, UIValue y) : TextElement(x, y, kekData.ui->defaultFont) {} TextElement::~TextElement() { delete text; } UIElementType TextElement::getType() { return UIElementType::TEXT; } UIBounds TextElement::getBounds() { TextMetrics metrics = text->getMetrics(sizePixels); int w = metrics.width, h; switch(textBounds) { case TextBounds::SMALLEST: default: h = metrics.height; break; case TextBounds::LINE: FontMetrics metrics = text->getFont()->getMetrics(sizePixels); h = metrics.lineHeight; break; } switch(textMode) { case TextMode::BASELINE: return UIBounds(offsetX(w, origin), 0, w, h); case TextMode::ORIGIN: default: return offsetUIBounds(w, h, origin); } } void TextElement::setText(std::string text) { this->text->setText(text); } std::string TextElement::getText() { return text->getText(); } void TextElement::draw(UIPoint screenPos, glm::mat4 projection) { switch(textMode) { case TextMode::BASELINE: text->getFont()->drawText(text, projection, screenPos.x, screenPos.y, sizePixels, color); break; case TextMode::ORIGIN: switch(textBounds) { case TextBounds::SMALLEST: default: text->getFont()->drawTextFromOrigin(text, projection, screenPos.x, screenPos.y, sizePixels, color); break; case TextBounds::LINE: FontMetrics metrics = text->getFont()->getMetrics(sizePixels); int offsetY = metrics.ascender; text->getFont()->drawText(text, projection, screenPos.x, screenPos.y + offsetY, sizePixels, color); break; } break; } } UIWindow::UIWindow(UIValue x, UIValue y) : UIElement(x, y) { addChild(new TextElement(x, y)); } UIWindow::~UIWindow() { } UIElementType UIWindow::getType() { return UIElementType::WINDOW; } void UIWindow::draw(UIPoint screenPos, glm::mat4 projection) { } static float rectangleVerts[] = { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; RectangleElement::RectangleElement(UIValue x, UIValue y, UIValue w, UIValue h) : UIElement(x, y) { this->w = w; this->h = h; color = Colors::BLACK; glCreateVertexArrays(1, &vao); glCreateBuffers(1, &vbo); glNamedBufferData(vbo, sizeof(rectangleVerts), rectangleVerts, GL_STATIC_DRAW); glVertexArrayVertexBuffer(vao, KEK_UI_VERTEX_BUFFER_BINDING, vbo, 0, 2 * sizeof(float)); glEnableVertexArrayAttrib(vao, KEK_UI_RECT_VERTEX_SHADER_IN_POSITION); glVertexArrayAttribFormat(vao, KEK_UI_RECT_VERTEX_SHADER_IN_POSITION, 2, GL_FLOAT, GL_FALSE, 0); glVertexArrayAttribBinding(vao, KEK_UI_RECT_VERTEX_SHADER_IN_POSITION, KEK_UI_VERTEX_BUFFER_BINDING); } RectangleElement::~RectangleElement() { glDeleteVertexArrays(1, &vao); glDeleteBuffers(1, &vbo); } UIElementType RectangleElement::getType() { return UIElementType::RECTANGLE; } UIBounds RectangleElement::getBounds() { return offsetUIBounds(uiToScreen(w), uiToScreen(h), origin); } void RectangleElement::draw(UIPoint screenPos, glm::mat4 projection) { kekData.ui->rectangleShader->use(); UIBounds offset = getBounds(); glUniformMatrix4fv(glGetUniformLocation(kekData.ui->rectangleShader->id, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); glm::vec4 bounds = glm::vec4(screenPos.x, screenPos.y, offset.w, offset.h); glUniform4fv(glGetUniformLocation(kekData.ui->rectangleShader->id, "bounds"), 1, glm::value_ptr(bounds)); glUniform4fv(glGetUniformLocation(kekData.ui->rectangleShader->id, "rectColor"), 1, color.valuePointer()); glBindVertexArray(vao); glDrawArrays(GL_TRIANGLES, 0, 6); } ButtonElement::ButtonElement(UIValue x, UIValue y, UIValue w, UIValue h) : RectangleElement(x, y, w, h) { clickable = true; color = Colors::CYAN; hoverColor = Colors::CYAN.darker(); text = new TextElement(uiPw(0.5), uiPh(0.5)); text->origin = Origin::CENTER; addChild(text); } ButtonElement::~ButtonElement() { delete text; } UIElementType ButtonElement::getType() { return UIElementType::BUTTON; } void ButtonElement::click(UIPoint pos, UIPoint screenPos, GLFWMouseButton button) { onClick(); } void ButtonElement::draw(UIPoint screenPos, glm::mat4 projection) { RectangleElement::color = hovering ? hoverColor : color; RectangleElement::draw(screenPos, projection); } TextFieldElement::TextFieldElement(UIValue x, UIValue y, UIValue w, std::shared_ptr font) : RectangleElement(x, y, w, uiPx(0)) { this->enableClipping = true; this->focusable = true; this->color = Colors::WHITE; this->focusColor = Colors::GRAY; this->text = ""; this->textElement = new TextElement(uiPx(0), uiPx(0)); textElement->textBounds = TextBounds::LINE; textElement->color = Colors::BLACK; textElement->setText(text); addChild(textElement); int textH = textElement->getBounds().h; h = uiPx(textH); this->cursor = new RectangleElement(uiPx(0), uiPx(2), uiPx(1), uiPx(textH - 4)); cursor->color = Colors::BLACK; addChild(cursor); this->capture = KEK_INVALID_ID; } TextFieldElement::TextFieldElement(UIValue x, UIValue y, UIValue w) : TextFieldElement(x, y, w, kekData.ui->defaultFont) { } TextFieldElement::~TextFieldElement() { Input::uncaptureKeyboardInput(capture); capture = KEK_INVALID_ID; delete textElement; delete cursor; } void TextFieldElement::updateText(std::u32string newText) { this->text = Unicode::convertU32ToStd(newText); this->textElement->setText(this->text); UIBounds bounds = getBounds(); TextMetrics m = textElement->text->getMetrics(0, newText.length() - cursorPos, textElement->sizePixels); TextMetrics fullM = textElement->text->getMetrics(0, newText.length(), textElement->sizePixels); // Set offset to keep cursor visible float offsetX = -this->textElement->x.pixels; float minOffsetX = std::max(m.width - bounds.w + 1, 0); float maxOffsetX = m.width; // Adjust offset to make sure the text field is always fully utilized (if possible) float adjust = bounds.w - (fullM.width - offsetX); if(adjust > 0) offsetX -= adjust; offsetX = clamp(offsetX, minOffsetX, maxOffsetX); this->cursor->x = uiPx(m.width - offsetX); this->textElement->x = uiPx(-offsetX); } UIElementType TextFieldElement::getType() { return UIElementType::TEXT_FIELD; } void TextFieldElement::focusEnter() { cursor->visible = true; lastCharTyped = glfwGetTime(); capture = Input::captureKeyboardInput( KeyCharCallback([](KeyCharEvent event, void *data) { TextFieldElement *_this = (TextFieldElement *) data; std::u32string str = Unicode::convertStdToU32(_this->text); switch(event.codepoint) { case KEK_INPUT_DELETE: if(_this->cursorPos == str.length()) return; str = str.erase(str.length() - _this->cursorPos - 1, 1); break; case KEK_INPUT_DELETE_FORWARD: if(_this->cursorPos == 0) return; str = str.erase(str.length() - _this->cursorPos, 1); _this->cursorPos--; break; default: str.insert(str.length() - _this->cursorPos, 1, event.codepoint); break; } _this->lastCharTyped = glfwGetTime(); _this->updateText(str); }, this), KeyCallback([](KeyEvent event, void *data) { TextFieldElement *_this = (TextFieldElement *) data; if(event.action == GLFW_PRESS || event.action == GLFW_REPEAT) { switch(event.key) { case GLFW_KEY_ENTER: Input::uncaptureKeyboardInput(_this->capture); _this->onSubmit(_this->text); break; case GLFW_KEY_LEFT: { std::u32string str = Unicode::convertStdToU32(_this->text); if(_this->cursorPos < str.length()) { _this->cursorPos++; _this->updateText(str); _this->lastCharTyped = glfwGetTime(); } break; } case GLFW_KEY_RIGHT: { std::u32string str = Unicode::convertStdToU32(_this->text); if(_this->cursorPos > 0) { _this->cursorPos--; _this->updateText(str); _this->lastCharTyped = glfwGetTime(); } break; } } } }, this), Callable([](void *data) { TextFieldElement *_this = (TextFieldElement *) data; UI::unfocusElement(_this); }, this)); } void TextFieldElement::focusExit() { cursor->visible = false; Input::uncaptureKeyboardInput(capture); capture = KEK_INVALID_ID; cursorPos = 0; updateText(Unicode::convertStdToU32(this->text)); } void TextFieldElement::draw(UIPoint screenPos, glm::mat4 projection) { RectangleElement::color = focused ? focusColor : color; RectangleElement::draw(screenPos, projection); double time = glfwGetTime(); cursor->visible = focused ? (time - lastCharTyped < 0.5 || (int) time % 2 == 0) : false; } void TextFieldElement::setText(std::string text) { this->cursorPos = 0; updateText(Unicode::convertStdToU32(text)); } }