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 <GLFW/glfw3.h>
#include <algorithm>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <stb_image_write.h>
#include <string>
#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> 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<Font> 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<unsigned int, std::vector<RenderChar>> chars;
std::u32string str = Unicode::convertStdToU32(text);
TextMetrics TextObject::measureChars(std::u32string str, unsigned int offset, unsigned int length, std::map<unsigned int, std::vector<RenderChar>> *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<RenderChar> 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<RenderChar> 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<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();
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> 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> ret;
std::shared_ptr<Texture> ret = nullptr;
auto iter = kekData.loadedTextures.find(texturePath);
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() {
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;
}

View File

@ -2,8 +2,10 @@
#include <GLFW/glfw3.h>
#include <glm/gtc/type_ptr.hpp>
#include <string>
#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> 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> 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),

View File

@ -1,6 +1,7 @@
#pragma once
#include <ft2build.h>
#include <memory>
#include FT_FREETYPE_H
#include <glm/glm.hpp>
@ -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> font;
std::string text;
int offsetX;
int offsetY;
int width;
int height;
TextMetrics unscaledMetrics;
public:
std::map<unsigned int, TextBlock> blocks;
TextObject(Font *font, std::string text);
TextObject(std::shared_ptr<Font> font, std::string text);
~TextObject();
@ -88,12 +106,14 @@ class TextObject {
TextObject &operator=(TextObject &&other);
Font *getFont();
std::shared_ptr<Font> 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<unsigned int, std::vector<RenderChar>> *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<Font> load(std::string path);
private:
CharacterBlock generateCharacterBlock(unsigned int block);
};

View File

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

View File

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

View File

@ -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> 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> font);
TextFieldElement(UIValue x, UIValue y, UIValue w);