diff --git a/src/kekengine/cpp/render/fonts.cpp b/src/kekengine/cpp/render/fonts.cpp index 6e2d54d..ac48239 100644 --- a/src/kekengine/cpp/render/fonts.cpp +++ b/src/kekengine/cpp/render/fonts.cpp @@ -2,9 +2,11 @@ #include #include +#include #include #include #include +#include #include "constants.h" #include "engine.h" @@ -16,7 +18,7 @@ namespace kek { Shader *fontShader = nullptr; -TextObject::TextObject(Font *font, std::string text) { +TextObject::TextObject(std::shared_ptr font, std::string text) { assert(text.length() > 0); this->font = font; @@ -52,7 +54,7 @@ TextObject &TextObject::operator=(TextObject &&other) { return *this; } -Font *TextObject::getFont() { +std::shared_ptr TextObject::getFont() { return font; } @@ -65,25 +67,16 @@ std::string TextObject::getText() { return text; } -TextMetrics TextObject::getMetrics(int sizePixels) { +TextMetrics TextObject::getMetrics(unsigned int offset, unsigned int length, int sizePixels) { + TextMetrics metrics = measureChars(Unicode::convertStdToU32(text), offset, length, nullptr); float sizeRatio = (float) sizePixels / KEK_FONT_RESOLUTION; - return TextMetrics((int) (sizeRatio * offsetX), (int) (sizeRatio * offsetY), (int) (sizeRatio * width), (int) (sizeRatio * height)); + return TextMetrics((int) (sizeRatio * metrics.offsetX), (int) (sizeRatio * metrics.offsetY), (int) (sizeRatio * metrics.width), (int) (sizeRatio * metrics.height)); } -struct RenderChar { - float data[24]; - - RenderChar(float xPos, float yPos, float w, float h, float texL, float texR, float texU, float texD) { - float coords[] = { - xPos, yPos, texL, texU, - xPos, yPos + h, texL, texD, - xPos + w, yPos, texR, texU, - xPos + w, yPos, texR, texU, - xPos, yPos + h, texL, texD, - xPos + w, yPos + h, texR, texD}; - for(int i = 0; i < 24; i++) data[i] = coords[i]; - } -}; +TextMetrics TextObject::getMetrics(int sizePixels) { + float sizeRatio = (float) sizePixels / KEK_FONT_RESOLUTION; + return TextMetrics((int) (sizeRatio * unscaledMetrics.offsetX), (int) (sizeRatio * unscaledMetrics.offsetY), (int) (sizeRatio * unscaledMetrics.width), (int) (sizeRatio * unscaledMetrics.height)); +} void TextObject::allocateBuffer(TextBlock *block, int numChars) { unsigned int targetSize = (numChars + KEK_TEXT_BLOCK_SIZE - 1) / KEK_TEXT_BLOCK_SIZE * KEK_TEXT_BLOCK_SIZE; // ceil(numChars / KEK_TEXT_BLOCK_SIZE) * KEK_TEXT_BLOCK_SIZE @@ -106,9 +99,11 @@ void TextObject::allocateBuffer(TextBlock *block, int numChars) { glBindVertexArray(0); } -void TextObject::loadChars() { - std::map> chars; - std::u32string str = Unicode::convertStdToU32(text); +TextMetrics TextObject::measureChars(std::u32string str, unsigned int offset, unsigned int length, std::map> *chars) { + if(offset != 0 || length != str.length()) { + length = std::min(length, (unsigned int) (str.length() - offset)); + str = str.substr(offset, length); + } float x = 0, y = 0; int lineHeight = font->getDefaultMetrics().lineHeight; @@ -132,29 +127,38 @@ void TextObject::loadChars() { float h = ch.size.y; if(h > maxH) maxH = h; - float texL = (ch.textureX) / (float) KEK_FONT_BITMAP_WIDTH; - float texR = (ch.textureX + ch.size.x) / (float) KEK_FONT_BITMAP_WIDTH; - float texU = (ch.textureY) / (float) KEK_FONT_BITMAP_HEIGHT; - float texD = (ch.textureY + ch.size.y) / (float) KEK_FONT_BITMAP_HEIGHT; + if(chars != nullptr) { + auto &_chars = *chars; - RenderChar rCh = RenderChar(xPos, yPos, w, h, texL, texR, texU, texD); + float texL = (ch.textureX) / (float) KEK_FONT_BITMAP_WIDTH; + float texR = (ch.textureX + ch.size.x) / (float) KEK_FONT_BITMAP_WIDTH; + float texU = (ch.textureY) / (float) KEK_FONT_BITMAP_HEIGHT; + float texD = (ch.textureY + ch.size.y) / (float) KEK_FONT_BITMAP_HEIGHT; - auto charsForBlock = chars.find(charBlock); - if(charsForBlock != chars.end()) { - charsForBlock->second.push_back(rCh); - } else { - std::vector chs; - chs.push_back(rCh); - chars[charBlock] = chs; + RenderChar rCh = RenderChar(xPos, yPos, w, h, texL, texR, texU, texD); + + auto charsForBlock = _chars.find(charBlock); + if(charsForBlock != _chars.end()) { + charsForBlock->second.push_back(rCh); + } else { + std::vector chs; + chs.push_back(rCh); + _chars[charBlock] = chs; + } } x += (ch.advance >> 6); // == ch.advance / 64 } - this->offsetX = offX; - this->offsetY = offY; - this->width = x; - this->height = maxH; + return TextMetrics(offX, offY, x, maxH); +} + +void TextObject::loadChars() { + std::map> chars; + std::u32string str = Unicode::convertStdToU32(text); + + TextMetrics metrics = measureChars(str, 0, str.length(), &chars); + this->unscaledMetrics = metrics; auto it = chars.begin(); while(it != chars.end()) { @@ -284,10 +288,6 @@ CharacterBlock Font::generateCharacterBlock(unsigned int block) { return chBlock; } -TextObject *Font::createText(std::string text) { - return new TextObject(this, text); -} - FontMetrics Font::getDefaultMetrics() { return FontMetrics(lineHeight >> 6, ascender >> 6, descender >> 6); } @@ -340,4 +340,18 @@ void Font::drawTextCentered(TextObject *textObject, glm::mat4 projection, int x, Font::drawText(textObject, projection, x - metrics.offsetX + metrics.width / 2, y + metrics.offsetY - metrics.height / 2, sizePixels, color); } +std::shared_ptr Font::load(std::string fontPath) { + std::shared_ptr ret = nullptr; + + auto iter = kekData.loadedFonts.find(fontPath); + if(iter != kekData.loadedFonts.end()) ret = iter->second.lock(); + + if(!ret) { + ret = std::make_shared(fontPath); + kekData.loadedFonts.emplace(fontPath, ret); + } + + return ret; +} + } diff --git a/src/kekengine/cpp/render/texture.cpp b/src/kekengine/cpp/render/texture.cpp index b258b78..48636a2 100644 --- a/src/kekengine/cpp/render/texture.cpp +++ b/src/kekengine/cpp/render/texture.cpp @@ -56,7 +56,7 @@ void Texture::use(GLenum texture) { } std::shared_ptr Texture::load(std::string texturePath) { - std::shared_ptr ret; + std::shared_ptr ret = nullptr; auto iter = kekData.loadedTextures.find(texturePath); if(iter != kekData.loadedTextures.end()) ret = iter->second.lock(); diff --git a/src/kekengine/cpp/ui/ui.cpp b/src/kekengine/cpp/ui/ui.cpp index 592d1b7..9331465 100644 --- a/src/kekengine/cpp/ui/ui.cpp +++ b/src/kekengine/cpp/ui/ui.cpp @@ -244,12 +244,12 @@ UIElement *UIElement::dragEnterAll(UIPoint pos, UIPoint screenPos) { void UI::init() { kekData.ui = new UIData(); - kekData.ui->defaultFont = new Font(KEK_DEFAULT_FONT); + kekData.ui->defaultFont = Font::load(KEK_DEFAULT_FONT); kekData.ui->rectangleShader = new Shader("shader/rectangle/vertex.glsl", "shader/rectangle/fragment.glsl"); } void UI::destroy() { - delete kekData.ui->defaultFont; + kekData.ui->defaultFont = nullptr; delete kekData.ui->rectangleShader; delete kekData.ui; } diff --git a/src/kekengine/cpp/ui/uielements.cpp b/src/kekengine/cpp/ui/uielements.cpp index 3cf4a6e..6d68dc8 100644 --- a/src/kekengine/cpp/ui/uielements.cpp +++ b/src/kekengine/cpp/ui/uielements.cpp @@ -2,8 +2,10 @@ #include #include +#include #include "constants.h" +#include "fonts.h" #include "input.h" #include "internal.h" #include "internal/ui.h" @@ -13,7 +15,7 @@ namespace kek { -TextElement::TextElement(UIValue x, UIValue y, Font *font) +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; @@ -195,7 +197,7 @@ void ButtonElement::draw(UIPoint screenPos, glm::mat4 projection) { RectangleElement::draw(screenPos, projection); } -TextFieldElement::TextFieldElement(UIValue x, UIValue y, UIValue w, Font *font) +TextFieldElement::TextFieldElement(UIValue x, UIValue y, UIValue w, std::shared_ptr font) : RectangleElement(x, y, w, uiPx(0)) { this->enableClipping = true; this->focusable = true; @@ -233,6 +235,14 @@ TextFieldElement::~TextFieldElement() { delete cursor; } +void TextFieldElement::updateText(std::u32string newText) { + this->text = Unicode::convertU32ToStd(newText); + this->textElement->setText(this->text); + + TextMetrics m = textElement->text->getMetrics(0, newText.length() - cursorPos, textElement->sizePixels); + this->cursor->x = uiPx(m.width); +} + UIElementType TextFieldElement::getType() { return UIElementType::TEXT_FIELD; } @@ -247,23 +257,43 @@ void TextFieldElement::focusEnter() { std::u32string str = Unicode::convertStdToU32(_this->text); if(event.codepoint == KEK_INPUT_DELETE) { - if(str.length() == 0) return; - str = str.substr(0, str.length() - 1); + if(_this->cursorPos == str.length()) return; + str = str.erase(str.length() - _this->cursorPos - 1, 1); } else { - str.push_back(event.codepoint); + str.insert(str.length() - _this->cursorPos, 1, event.codepoint); } - _this->text = Unicode::convertU32ToStd(str); - _this->textElement->setText(_this->text); - _this->lastCharTyped = glfwGetTime(); - _this->cursor->x = uiPx(_this->textElement->getBounds().w); + _this->updateText(str); }, this), KeyCallback([](KeyEvent event, void *data) { TextFieldElement *_this = (TextFieldElement *) data; - if(event.key == GLFW_KEY_ENTER && event.action == GLFW_PRESS) { - Input::uncaptureKeyboardInput(_this->capture); + + if(event.action == GLFW_PRESS || event.action == GLFW_REPEAT) { + switch(event.key) { + case GLFW_KEY_ENTER: + Input::uncaptureKeyboardInput(_this->capture); + 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), diff --git a/src/kekengine/include/fonts.h b/src/kekengine/include/fonts.h index 0fd4b96..b740c10 100644 --- a/src/kekengine/include/fonts.h +++ b/src/kekengine/include/fonts.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include FT_FREETYPE_H #include @@ -31,12 +32,17 @@ struct TextMetrics { int width; int height; - TextMetrics(int offsetX, int offsetY, unsigned int width, unsigned int height) { - this->offsetX = offsetX; - this->offsetY = offsetY; - this->width = width; - this->height = height; - } + TextMetrics() + : offsetX(0), + offsetY(0), + width(0), + height(0) {} + + TextMetrics(int offsetX, int offsetY, unsigned int width, unsigned int height) + : offsetX(offsetX), + offsetY(offsetY), + width(width), + height(height) {} }; struct FontMetrics { @@ -62,21 +68,33 @@ struct TextBlock { unsigned int vbo; }; +struct RenderChar { + float data[24]; + + RenderChar(float xPos, float yPos, float w, float h, float texL, float texR, float texU, float texD) { + float coords[] = { + xPos, yPos, texL, texU, + xPos, yPos + h, texL, texD, + xPos + w, yPos, texR, texU, + xPos + w, yPos, texR, texU, + xPos, yPos + h, texL, texD, + xPos + w, yPos + h, texR, texD}; + for(int i = 0; i < 24; i++) data[i] = coords[i]; + } +}; + class TextObject { private: - Font *font; + std::shared_ptr font; std::string text; - int offsetX; - int offsetY; - int width; - int height; + TextMetrics unscaledMetrics; public: std::map blocks; - TextObject(Font *font, std::string text); + TextObject(std::shared_ptr font, std::string text); ~TextObject(); @@ -88,12 +106,14 @@ class TextObject { TextObject &operator=(TextObject &&other); - Font *getFont(); + std::shared_ptr getFont(); void setText(std::string text); std::string getText(); + TextMetrics getMetrics(unsigned int offset, unsigned int length, int sizePixels); + TextMetrics getMetrics(int sizePixels); private: @@ -101,6 +121,8 @@ class TextObject { void allocateBuffer(TextBlock *block, int numChars); + TextMetrics measureChars(std::u32string str, unsigned int offset, unsigned int length, std::map> *chars); + void loadChars(); }; @@ -123,8 +145,6 @@ class Font { CharacterBlock getCharacterBlock(unsigned int block); - TextObject *createText(std::string text); - FontMetrics getDefaultMetrics(); FontMetrics getMetrics(int sizePixels); @@ -149,6 +169,9 @@ class Font { // Draws text centered around (x,y) void drawTextCentered(TextObject *textObject, glm::mat4 projection, int x, int y, int sizePixels, Color color); + // Loads a font from resources + static std::shared_ptr load(std::string path); + private: CharacterBlock generateCharacterBlock(unsigned int block); }; diff --git a/src/kekengine/include/internal.h b/src/kekengine/include/internal.h index b1e911a..5a8f0bf 100644 --- a/src/kekengine/include/internal.h +++ b/src/kekengine/include/internal.h @@ -33,6 +33,7 @@ struct KekData { float lastFrameTime; std::map> loadedTextures; + std::map> loadedFonts; FT_Library freetype; diff --git a/src/kekengine/include/internal/ui.h b/src/kekengine/include/internal/ui.h index 3195da0..b9aa74b 100644 --- a/src/kekengine/include/internal/ui.h +++ b/src/kekengine/include/internal/ui.h @@ -7,7 +7,7 @@ namespace kek { struct UIData { std::vector elements; UIElement *focusedElement; - Font *defaultFont; + std::shared_ptr defaultFont; Shader *rectangleShader; }; diff --git a/src/kekengine/include/uielements.h b/src/kekengine/include/uielements.h index 9eea97a..f7ea8d2 100644 --- a/src/kekengine/include/uielements.h +++ b/src/kekengine/include/uielements.h @@ -30,16 +30,15 @@ enum class TextBounds { class TextElement: public UIElement { - protected: + public: TextObject *text; - public: int sizePixels; Color color; TextMode textMode; TextBounds textBounds; - TextElement(UIValue x, UIValue y, Font *font); + TextElement(UIValue x, UIValue y, std::shared_ptr font); TextElement(UIValue x, UIValue y); @@ -109,9 +108,12 @@ class TextFieldElement: public RectangleElement { KeyboardCapture capture; float lastCharTyped = 0; + unsigned int cursorPos = 0; // Cursor offset from the back of the string + + void updateText(std::u32string newText); public: - TextFieldElement(UIValue x, UIValue y, UIValue w, Font *font); + TextFieldElement(UIValue x, UIValue y, UIValue w, std::shared_ptr font); TextFieldElement(UIValue x, UIValue y, UIValue w);