Improve TextFieldElement

This commit is contained in:
MrLetsplay 2023-10-15 12:40:43 +02:00
parent 3b03637cd4
commit 94c2d9b8bd
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
8 changed files with 145 additions and 75 deletions

View File

@ -2,9 +2,11 @@
#include <GL/glew.h> #include <GL/glew.h>
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include <algorithm>
#include <glm/gtc/type_ptr.hpp> #include <glm/gtc/type_ptr.hpp>
#include <iostream> #include <iostream>
#include <stb_image_write.h> #include <stb_image_write.h>
#include <string>
#include "constants.h" #include "constants.h"
#include "engine.h" #include "engine.h"
@ -16,7 +18,7 @@ namespace kek {
Shader *fontShader = nullptr; Shader *fontShader = nullptr;
TextObject::TextObject(Font *font, std::string text) { TextObject::TextObject(std::shared_ptr<Font> font, std::string text) {
assert(text.length() > 0); assert(text.length() > 0);
this->font = font; this->font = font;
@ -52,7 +54,7 @@ TextObject &TextObject::operator=(TextObject &&other) {
return *this; return *this;
} }
Font *TextObject::getFont() { std::shared_ptr<Font> TextObject::getFont() {
return font; return font;
} }
@ -65,26 +67,17 @@ std::string TextObject::getText() {
return text; return text;
} }
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 * metrics.offsetX), (int) (sizeRatio * metrics.offsetY), (int) (sizeRatio * metrics.width), (int) (sizeRatio * metrics.height));
}
TextMetrics TextObject::getMetrics(int sizePixels) { TextMetrics TextObject::getMetrics(int sizePixels) {
float sizeRatio = (float) sizePixels / KEK_FONT_RESOLUTION; 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 * unscaledMetrics.offsetX), (int) (sizeRatio * unscaledMetrics.offsetY), (int) (sizeRatio * unscaledMetrics.width), (int) (sizeRatio * unscaledMetrics.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];
}
};
void TextObject::allocateBuffer(TextBlock *block, int numChars) { 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 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
block->bufferSize = targetSize; block->bufferSize = targetSize;
@ -106,9 +99,11 @@ void TextObject::allocateBuffer(TextBlock *block, int numChars) {
glBindVertexArray(0); glBindVertexArray(0);
} }
void TextObject::loadChars() { TextMetrics TextObject::measureChars(std::u32string str, unsigned int offset, unsigned int length, std::map<unsigned int, std::vector<RenderChar>> *chars) {
std::map<unsigned int, std::vector<RenderChar>> chars; if(offset != 0 || length != str.length()) {
std::u32string str = Unicode::convertStdToU32(text); length = std::min(length, (unsigned int) (str.length() - offset));
str = str.substr(offset, length);
}
float x = 0, y = 0; float x = 0, y = 0;
int lineHeight = font->getDefaultMetrics().lineHeight; int lineHeight = font->getDefaultMetrics().lineHeight;
@ -132,6 +127,9 @@ void TextObject::loadChars() {
float h = ch.size.y; float h = ch.size.y;
if(h > maxH) maxH = h; if(h > maxH) maxH = h;
if(chars != nullptr) {
auto &_chars = *chars;
float texL = (ch.textureX) / (float) KEK_FONT_BITMAP_WIDTH; float texL = (ch.textureX) / (float) KEK_FONT_BITMAP_WIDTH;
float texR = (ch.textureX + ch.size.x) / (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 texU = (ch.textureY) / (float) KEK_FONT_BITMAP_HEIGHT;
@ -139,22 +137,28 @@ void TextObject::loadChars() {
RenderChar rCh = RenderChar(xPos, yPos, w, h, texL, texR, texU, texD); RenderChar rCh = RenderChar(xPos, yPos, w, h, texL, texR, texU, texD);
auto charsForBlock = chars.find(charBlock); auto charsForBlock = _chars.find(charBlock);
if(charsForBlock != chars.end()) { if(charsForBlock != _chars.end()) {
charsForBlock->second.push_back(rCh); charsForBlock->second.push_back(rCh);
} else { } else {
std::vector<RenderChar> chs; std::vector<RenderChar> chs;
chs.push_back(rCh); chs.push_back(rCh);
chars[charBlock] = chs; _chars[charBlock] = chs;
}
} }
x += (ch.advance >> 6); // == ch.advance / 64 x += (ch.advance >> 6); // == ch.advance / 64
} }
this->offsetX = offX; return TextMetrics(offX, offY, x, maxH);
this->offsetY = offY; }
this->width = x;
this->height = maxH; void TextObject::loadChars() {
std::map<unsigned int, std::vector<RenderChar>> chars;
std::u32string str = Unicode::convertStdToU32(text);
TextMetrics metrics = measureChars(str, 0, str.length(), &chars);
this->unscaledMetrics = metrics;
auto it = chars.begin(); auto it = chars.begin();
while(it != chars.end()) { while(it != chars.end()) {
@ -284,10 +288,6 @@ CharacterBlock Font::generateCharacterBlock(unsigned int block) {
return chBlock; return chBlock;
} }
TextObject *Font::createText(std::string text) {
return new TextObject(this, text);
}
FontMetrics Font::getDefaultMetrics() { FontMetrics Font::getDefaultMetrics() {
return FontMetrics(lineHeight >> 6, ascender >> 6, descender >> 6); 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); Font::drawText(textObject, projection, x - metrics.offsetX + metrics.width / 2, y + metrics.offsetY - metrics.height / 2, sizePixels, color);
} }
std::shared_ptr<Font> Font::load(std::string fontPath) {
std::shared_ptr<Font> ret = nullptr;
auto iter = kekData.loadedFonts.find(fontPath);
if(iter != kekData.loadedFonts.end()) ret = iter->second.lock();
if(!ret) {
ret = std::make_shared<Font>(fontPath);
kekData.loadedFonts.emplace(fontPath, ret);
}
return ret;
}
} }

View File

@ -56,7 +56,7 @@ void Texture::use(GLenum texture) {
} }
std::shared_ptr<Texture> Texture::load(std::string texturePath) { std::shared_ptr<Texture> Texture::load(std::string texturePath) {
std::shared_ptr<Texture> ret; std::shared_ptr<Texture> ret = nullptr;
auto iter = kekData.loadedTextures.find(texturePath); auto iter = kekData.loadedTextures.find(texturePath);
if(iter != kekData.loadedTextures.end()) ret = iter->second.lock(); if(iter != kekData.loadedTextures.end()) ret = iter->second.lock();

View File

@ -244,12 +244,12 @@ UIElement *UIElement::dragEnterAll(UIPoint pos, UIPoint screenPos) {
void UI::init() { void UI::init() {
kekData.ui = new UIData(); 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"); kekData.ui->rectangleShader = new Shader("shader/rectangle/vertex.glsl", "shader/rectangle/fragment.glsl");
} }
void UI::destroy() { void UI::destroy() {
delete kekData.ui->defaultFont; kekData.ui->defaultFont = nullptr;
delete kekData.ui->rectangleShader; delete kekData.ui->rectangleShader;
delete kekData.ui; delete kekData.ui;
} }

View File

@ -2,8 +2,10 @@
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include <glm/gtc/type_ptr.hpp> #include <glm/gtc/type_ptr.hpp>
#include <string>
#include "constants.h" #include "constants.h"
#include "fonts.h"
#include "input.h" #include "input.h"
#include "internal.h" #include "internal.h"
#include "internal/ui.h" #include "internal/ui.h"
@ -13,7 +15,7 @@
namespace kek { namespace kek {
TextElement::TextElement(UIValue x, UIValue y, Font *font) TextElement::TextElement(UIValue x, UIValue y, std::shared_ptr<Font> font)
: UIElement(x, y) { : UIElement(x, y) {
this->text = new TextObject(font, "Text"); this->text = new TextObject(font, "Text");
this->sizePixels = KEK_DEFAULT_FONT_SIZE_PIXELS; this->sizePixels = KEK_DEFAULT_FONT_SIZE_PIXELS;
@ -195,7 +197,7 @@ void ButtonElement::draw(UIPoint screenPos, glm::mat4 projection) {
RectangleElement::draw(screenPos, 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> font)
: RectangleElement(x, y, w, uiPx(0)) { : RectangleElement(x, y, w, uiPx(0)) {
this->enableClipping = true; this->enableClipping = true;
this->focusable = true; this->focusable = true;
@ -233,6 +235,14 @@ TextFieldElement::~TextFieldElement() {
delete cursor; 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() { UIElementType TextFieldElement::getType() {
return UIElementType::TEXT_FIELD; return UIElementType::TEXT_FIELD;
} }
@ -247,23 +257,43 @@ void TextFieldElement::focusEnter() {
std::u32string str = Unicode::convertStdToU32(_this->text); std::u32string str = Unicode::convertStdToU32(_this->text);
if(event.codepoint == KEK_INPUT_DELETE) { if(event.codepoint == KEK_INPUT_DELETE) {
if(str.length() == 0) return; if(_this->cursorPos == str.length()) return;
str = str.substr(0, str.length() - 1); str = str.erase(str.length() - _this->cursorPos - 1, 1);
} else { } 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->lastCharTyped = glfwGetTime();
_this->cursor->x = uiPx(_this->textElement->getBounds().w); _this->updateText(str);
}, },
this), this),
KeyCallback([](KeyEvent event, void *data) { KeyCallback([](KeyEvent event, void *data) {
TextFieldElement *_this = (TextFieldElement *) data; TextFieldElement *_this = (TextFieldElement *) data;
if(event.key == GLFW_KEY_ENTER && event.action == GLFW_PRESS) {
if(event.action == GLFW_PRESS || event.action == GLFW_REPEAT) {
switch(event.key) {
case GLFW_KEY_ENTER:
Input::uncaptureKeyboardInput(_this->capture); 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), this),

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <ft2build.h> #include <ft2build.h>
#include <memory>
#include FT_FREETYPE_H #include FT_FREETYPE_H
#include <glm/glm.hpp> #include <glm/glm.hpp>
@ -31,12 +32,17 @@ struct TextMetrics {
int width; int width;
int height; int height;
TextMetrics(int offsetX, int offsetY, unsigned int width, unsigned int height) { TextMetrics()
this->offsetX = offsetX; : offsetX(0),
this->offsetY = offsetY; offsetY(0),
this->width = width; width(0),
this->height = height; height(0) {}
}
TextMetrics(int offsetX, int offsetY, unsigned int width, unsigned int height)
: offsetX(offsetX),
offsetY(offsetY),
width(width),
height(height) {}
}; };
struct FontMetrics { struct FontMetrics {
@ -62,21 +68,33 @@ struct TextBlock {
unsigned int vbo; 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 { class TextObject {
private: private:
Font *font; std::shared_ptr<Font> font;
std::string text; std::string text;
int offsetX; TextMetrics unscaledMetrics;
int offsetY;
int width;
int height;
public: public:
std::map<unsigned int, TextBlock> blocks; std::map<unsigned int, TextBlock> blocks;
TextObject(Font *font, std::string text); TextObject(std::shared_ptr<Font> font, std::string text);
~TextObject(); ~TextObject();
@ -88,12 +106,14 @@ class TextObject {
TextObject &operator=(TextObject &&other); TextObject &operator=(TextObject &&other);
Font *getFont(); std::shared_ptr<Font> getFont();
void setText(std::string text); void setText(std::string text);
std::string getText(); std::string getText();
TextMetrics getMetrics(unsigned int offset, unsigned int length, int sizePixels);
TextMetrics getMetrics(int sizePixels); TextMetrics getMetrics(int sizePixels);
private: private:
@ -101,6 +121,8 @@ class TextObject {
void allocateBuffer(TextBlock *block, int numChars); void allocateBuffer(TextBlock *block, int numChars);
TextMetrics measureChars(std::u32string str, unsigned int offset, unsigned int length, std::map<unsigned int, std::vector<RenderChar>> *chars);
void loadChars(); void loadChars();
}; };
@ -123,8 +145,6 @@ class Font {
CharacterBlock getCharacterBlock(unsigned int block); CharacterBlock getCharacterBlock(unsigned int block);
TextObject *createText(std::string text);
FontMetrics getDefaultMetrics(); FontMetrics getDefaultMetrics();
FontMetrics getMetrics(int sizePixels); FontMetrics getMetrics(int sizePixels);
@ -149,6 +169,9 @@ class Font {
// Draws text centered around (x,y) // Draws text centered around (x,y)
void drawTextCentered(TextObject *textObject, glm::mat4 projection, int x, int y, int sizePixels, Color color); void drawTextCentered(TextObject *textObject, glm::mat4 projection, int x, int y, int sizePixels, Color color);
// Loads a font from resources
static std::shared_ptr<Font> load(std::string path);
private: private:
CharacterBlock generateCharacterBlock(unsigned int block); CharacterBlock generateCharacterBlock(unsigned int block);
}; };

View File

@ -33,6 +33,7 @@ struct KekData {
float lastFrameTime; float lastFrameTime;
std::map<std::string, std::weak_ptr<Texture>> loadedTextures; std::map<std::string, std::weak_ptr<Texture>> loadedTextures;
std::map<std::string, std::weak_ptr<Font>> loadedFonts;
FT_Library freetype; FT_Library freetype;

View File

@ -7,7 +7,7 @@ namespace kek {
struct UIData { struct UIData {
std::vector<UIElement *> elements; std::vector<UIElement *> elements;
UIElement *focusedElement; UIElement *focusedElement;
Font *defaultFont; std::shared_ptr<Font> defaultFont;
Shader *rectangleShader; Shader *rectangleShader;
}; };

View File

@ -30,16 +30,15 @@ enum class TextBounds {
class TextElement: public UIElement { class TextElement: public UIElement {
protected: public:
TextObject *text; TextObject *text;
public:
int sizePixels; int sizePixels;
Color color; Color color;
TextMode textMode; TextMode textMode;
TextBounds textBounds; TextBounds textBounds;
TextElement(UIValue x, UIValue y, Font *font); TextElement(UIValue x, UIValue y, std::shared_ptr<Font> font);
TextElement(UIValue x, UIValue y); TextElement(UIValue x, UIValue y);
@ -109,9 +108,12 @@ class TextFieldElement: public RectangleElement {
KeyboardCapture capture; KeyboardCapture capture;
float lastCharTyped = 0; float lastCharTyped = 0;
unsigned int cursorPos = 0; // Cursor offset from the back of the string
void updateText(std::u32string newText);
public: public:
TextFieldElement(UIValue x, UIValue y, UIValue w, Font *font); TextFieldElement(UIValue x, UIValue y, UIValue w, std::shared_ptr<Font> font);
TextFieldElement(UIValue x, UIValue y, UIValue w); TextFieldElement(UIValue x, UIValue y, UIValue w);