multiline labels

This commit is contained in:
MihailRis 2024-02-25 21:03:21 +03:00
parent fcc5d11814
commit d189bdc107
7 changed files with 239 additions and 95 deletions

View File

@ -20,7 +20,8 @@ namespace gui {
using onnumberchange = std::function<void(GUI*, double)>;
enum class Align {
left, center, right
left, center, right,
top=left, bottom=right,
};
class UINode {

View File

@ -1,5 +1,7 @@
#include "controls.h"
#include <queue>
#include <sstream>
#include <iostream>
#include "../../window/Events.h"
@ -30,9 +32,16 @@ Label::Label(std::wstring text, std::string fontName)
void Label::setText(std::wstring text) {
this->text = text;
lines = 1;
for (size_t i = 0; i < text.length(); i++) {
if (text[i] == L'\n') {
lines++;
}
}
lines = std::max(lines, 1U);
}
std::wstring Label::getText() const {
const std::wstring& Label::getText() const {
return text;
}
@ -44,18 +53,45 @@ const std::string& Label::getFontName() const {
return fontName;
}
void Label::setVerticalAlign(Align align) {
this->valign = align;
}
Align Label::getVerticalAlign() const {
return valign;
}
float Label::getLineInterval() const {
return lineInterval;
}
void Label::setLineInterval(float interval) {
lineInterval = interval;
}
int Label::getTextYOffset() const {
return textYOffset;
}
int Label::getLineYOffset(uint line) const {
return line * totalLineHeight + textYOffset;
}
void Label::draw(const GfxContext* pctx, Assets* assets) {
if (supplier) {
setText(supplier());
}
auto batch = pctx->getBatch2D();
auto font = assets->getFont(fontName);
batch->color = getColor();
Font* font = assets->getFont(fontName);
uint lineHeight = font->getLineHeight();
glm::vec2 size = getSize();
glm::vec2 newsize (
font->calcWidth(text),
font->getLineHeight()+font->getYOffset()
(lines == 1 ? lineHeight : lineHeight*lineInterval)*lines + font->getYOffset()
);
glm::vec2 coord = calcCoord();
@ -69,14 +105,48 @@ void Label::draw(const GfxContext* pctx, Assets* assets) {
coord.x += size.x-newsize.x;
break;
}
coord.y += (size.y-newsize.y)*0.5f;
font->draw(batch, text, coord.x, coord.y);
switch (valign) {
case Align::top:
break;
case Align::center:
coord.y += (size.y-newsize.y)*0.5f;
break;
case Align::bottom:
coord.y += size.y-newsize.y;
break;
}
textYOffset = coord.y;
totalLineHeight = lineHeight * lineInterval;
if (multiline) {
size_t offset = 0;
for (uint i = 0; i < lines; i++) {
std::wstring_view view(text.c_str()+offset, text.length()-offset);
size_t end = view.find(L'\n');
if (end != std::wstring::npos) {
view = std::wstring_view(text.c_str()+offset, end);
offset += end + 1;
}
font->draw(batch, view, coord.x, coord.y + i * totalLineHeight, FontStyle::none);
}
} else {
font->draw(batch, text, coord.x, coord.y, FontStyle::none);
}
}
void Label::textSupplier(wstringsupplier supplier) {
this->supplier = supplier;
}
void Label::setMultiline(bool multiline) {
this->multiline = multiline;
}
bool Label::isMultiline() const {
return multiline;
}
// ================================= Image ====================================
Image::Image(std::string texture, glm::vec2 size) : UINode(glm::vec2(), size), texture(texture) {
setInteractive(false);
@ -264,7 +334,7 @@ void TextBox::draw(const GfxContext* pctx, Assets* assets) {
if (!isFocused())
return;
const int yoffset = 2;
const int yoffset = 0;
const int lineHeight = font->getLineHeight();
glm::vec2 lcoord = label->calcCoord();
auto batch = pctx->getBatch2D();
@ -390,6 +460,16 @@ bool TextBox::isValid() const {
return valid;
}
void TextBox::setMultiline(bool multiline) {
this->multiline = multiline;
label->setMultiline(multiline);
label->setVerticalAlign(multiline ? Align::top : Align::center);
}
bool TextBox::isMultiline() const {
return multiline;
}
void TextBox::setOnEditStart(runnable oneditstart) {
onEditStart = oneditstart;
}

View File

@ -24,19 +24,40 @@ namespace gui {
std::wstring text;
std::string fontName;
wstringsupplier supplier = nullptr;
uint lines = 1;
float lineInterval = 1.5f;
Align valign = Align::center;
bool multiline = false;
// runtime values
int textYOffset = 0;
int totalLineHeight = 1;
public:
Label(std::string text, std::string fontName="normal");
Label(std::wstring text, std::string fontName="normal");
virtual void setText(std::wstring text);
std::wstring getText() const;
const std::wstring& getText() const;
virtual void setFontName(std::string name);
const std::string& getFontName() const;
virtual const std::string& getFontName() const;
virtual void setVerticalAlign(Align align);
virtual Align getVerticalAlign() const;
virtual float getLineInterval() const;
virtual void setLineInterval(float interval);
virtual int getTextYOffset() const;
virtual int getLineYOffset(uint line) const;
virtual void draw(const GfxContext* pctx, Assets* assets) override;
virtual void textSupplier(wstringsupplier supplier);
virtual void setMultiline(bool multiline);
virtual bool isMultiline() const;
};
class Image : public UINode {
@ -121,6 +142,8 @@ namespace gui {
size_t selectionEnd = 0;
size_t selectionOrigin = 0;
bool multiline = false;
size_t normalizeIndex(int index);
int calcIndexAt(int x) const;
@ -134,16 +157,9 @@ namespace gui {
TextBox(std::wstring placeholder,
glm::vec4 padding=glm::vec4(4.0f));
virtual std::shared_ptr<UINode> getAt(glm::vec2 pos, std::shared_ptr<UINode> self) override;
virtual void draw(const GfxContext* pctx, Assets* assets) override;
virtual void drawBackground(const GfxContext* pctx, Assets* assets) override;
virtual void typed(unsigned int codepoint) override;
virtual void keyPressed(keycode key) override;
virtual void setTextSupplier(wstringsupplier supplier);
virtual void setTextConsumer(wstringconsumer consumer);
virtual void setTextValidator(wstringchecker validator);
virtual bool isFocuskeeper() const override {return true;}
virtual void setFocusedColor(glm::vec4 color);
virtual glm::vec4 getFocusedColor() const;
virtual void setErrorColor(glm::vec4 color);
@ -161,11 +177,22 @@ namespace gui {
virtual bool validate();
virtual void setValid(bool valid);
virtual bool isValid() const;
// multiline mode is in development
virtual void setMultiline(bool multiline);
virtual bool isMultiline() const;
virtual void setOnEditStart(runnable oneditstart);
virtual void focus(GUI*) override;
virtual void refresh() override;
virtual void click(GUI*, int, int) override;
virtual void mouseMove(GUI*, int x, int y) override;
virtual bool isFocuskeeper() const override {return true;}
virtual void draw(const GfxContext* pctx, Assets* assets) override;
virtual void drawBackground(const GfxContext* pctx, Assets* assets) override;
virtual void typed(unsigned int codepoint) override;
virtual void keyPressed(keycode key) override;
virtual std::shared_ptr<UINode> getAt(glm::vec2 pos, std::shared_ptr<UINode> self) override;
};
class InputBindBox : public Panel {

View File

@ -17,6 +17,8 @@ static Align align_from_string(const std::string& str, Align def) {
if (str == "left") return Align::left;
if (str == "center") return Align::center;
if (str == "right") return Align::right;
if (str == "top") return Align::top;
if (str == "bottom") return Align::bottom;
return def;
}
@ -138,6 +140,11 @@ static std::shared_ptr<UINode> readLabel(UiXmlReader& reader, xml::xmlelement el
std::wstring text = readAndProcessInnerText(element);
auto label = std::make_shared<Label>(text);
_readUINode(reader, element, *label);
if (element->has("valign")) {
label->setVerticalAlign(
align_from_string(element->attr("valign").getText(), label->getVerticalAlign())
);
}
return label;
}
@ -163,7 +170,7 @@ static std::shared_ptr<UINode> readButton(UiXmlReader& reader, xml::xmlelement e
auto callback = scripting::create_runnable(
reader.getEnvironment().getId(),
element->attr("onclick").getText(),
reader.getFilename()+".lua"
reader.getFilename()
);
button->listenAction([callback](GUI*) {
callback();
@ -188,7 +195,7 @@ static std::shared_ptr<UINode> readCheckBox(UiXmlReader& reader, xml::xmlelement
auto consumer = scripting::create_bool_consumer(
reader.getEnvironment().getId(),
element->attr("consumer").getText(),
reader.getFilename()+".lua"
reader.getFilename()
);
checkbox->setConsumer(consumer);
}
@ -197,7 +204,7 @@ static std::shared_ptr<UINode> readCheckBox(UiXmlReader& reader, xml::xmlelement
auto supplier = scripting::create_bool_supplier(
reader.getEnvironment().getId(),
element->attr("supplier").getText(),
reader.getFilename()+".lua"
reader.getFilename()
);
checkbox->setSupplier(supplier);
}
@ -210,12 +217,16 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement
auto textbox = std::make_shared<TextBox>(placeholder, glm::vec4(0.0f));
_readPanel(reader, element, *textbox);
textbox->setText(text);
if (element->has("multiline")) {
textbox->setMultiline(element->attr("multiline").asBool());
}
if (element->has("consumer")) {
auto consumer = scripting::create_wstring_consumer(
reader.getEnvironment().getId(),
element->attr("consumer").getText(),
reader.getFilename()+".lua"
reader.getFilename()
);
textbox->setTextConsumer(consumer);
}
@ -224,7 +235,7 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement
auto supplier = scripting::create_wstring_supplier(
reader.getEnvironment().getId(),
element->attr("consumer").getText(),
reader.getFilename()+".lua"
reader.getFilename()
);
textbox->setTextSupplier(supplier);
}
@ -238,7 +249,7 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement
auto validator = scripting::create_wstring_validator(
reader.getEnvironment().getId(),
element->attr("validator").getText(),
reader.getFilename()+".lua"
reader.getFilename()
);
textbox->setTextValidator(validator);
}
@ -265,7 +276,7 @@ static std::shared_ptr<UINode> readTrackBar(UiXmlReader& reader, xml::xmlelement
auto consumer = scripting::create_number_consumer(
reader.getEnvironment().getId(),
element->attr("consumer").getText(),
reader.getFilename()+".lua"
reader.getFilename()
);
bar->setConsumer(consumer);
}
@ -273,7 +284,7 @@ static std::shared_ptr<UINode> readTrackBar(UiXmlReader& reader, xml::xmlelement
auto supplier = scripting::create_number_supplier(
reader.getEnvironment().getId(),
element->attr("supplier").getText(),
reader.getFilename()+".lua"
reader.getFilename()
);
bar->setSupplier(supplier);
}

View File

@ -2,7 +2,9 @@
#include "Texture.h"
#include "Batch2D.h"
using glm::vec4;
inline constexpr uint GLYPH_SIZE = 16;
inline constexpr uint MAX_CODEPAGES = 10000; // idk ho many codepages unicode has
inline constexpr glm::vec4 SHADOW_TINT(0.0f, 0.0f, 0.0f, 1.0f);
Font::Font(std::vector<std::unique_ptr<Texture>> pages, int lineHeight, int yoffset)
: lineHeight(lineHeight), yoffset(yoffset), pages(std::move(pages)) {
@ -16,71 +18,78 @@ int Font::getYOffset() const {
}
int Font::getLineHeight() const {
return lineHeight;
return lineHeight;
}
bool Font::isPrintableChar(int c) {
switch (c){
case ' ':
case '\t':
case '\n':
case '\f':
case '\r':
return false;
default:
return true;
}
bool Font::isPrintableChar(uint codepoint) const {
switch (codepoint){
case ' ':
case '\t':
case '\n':
case '\f':
case '\r':
return false;
default:
return true;
}
}
const int RES = 16;
int Font::calcWidth(std::wstring text, size_t length) {
return std::min(text.length(), length) * 8;
return std::min(text.length(), length) * 8;
}
void Font::draw(Batch2D* batch, std::wstring text, int x, int y) {
draw(batch, text, x, y, STYLE_NONE);
draw(batch, text, x, y, FontStyle::none);
}
void Font::draw(Batch2D* batch, std::wstring text, int x, int y, int style) {
int page = 0;
int next = 10000;
int init_x = x;
do {
for (unsigned c : text){
if (isPrintableChar(c)){
int charpage = c >> 8;
if (charpage == page){
Texture* texture = pages[charpage].get();
if (texture == nullptr){
texture = pages[0].get();
}
batch->texture(texture);
switch (style){
case STYLE_SHADOW:
batch->sprite(x+1, y+1, RES, RES, 16, c, vec4(0.0f, 0.0f, 0.0f, 1.0f));
break;
case STYLE_OUTLINE:
for (int oy = -1; oy <= 1; oy++){
for (int ox = -1; ox <= 1; ox++){
if (ox || oy)
batch->sprite(x+ox, y+oy, RES, RES, 16, c, vec4(0.0f, 0.0f, 0.0f, 1.0f));
}
}
break;
}
batch->sprite(x, y, RES, RES, 16, c, batch->color);
}
else if (charpage > page && charpage < next){
next = charpage;
}
}
x += 8;//getGlyphWidth(c);
}
page = next;
next = 10000;
x = init_x;
} while (page < 10000);
static inline void drawGlyph(Batch2D* batch, int x, int y, uint c, FontStyle style) {
switch (style){
case FontStyle::shadow:
batch->sprite(x+1, y+1, GLYPH_SIZE, GLYPH_SIZE, 16, c, SHADOW_TINT);
break;
case FontStyle::outline:
for (int oy = -1; oy <= 1; oy++){
for (int ox = -1; ox <= 1; ox++){
if (ox || oy) {
batch->sprite(x+ox, y+oy, GLYPH_SIZE, GLYPH_SIZE, 16, c, SHADOW_TINT);
}
}
}
break;
}
batch->sprite(x, y, GLYPH_SIZE, GLYPH_SIZE, 16, c, batch->color);
}
void Font::draw(Batch2D* batch, std::wstring text, int x, int y, FontStyle style) {
draw(batch, std::wstring_view(text.c_str(), text.length()), x, y, style);
}
void Font::draw(Batch2D* batch, std::wstring_view text, int x, int y, FontStyle style) {
uint page = 0;
uint next = MAX_CODEPAGES;
int init_x = x;
do {
for (uint c : text){
if (!isPrintableChar(c)) {
x += 8;
continue;
}
int charpage = c >> 8;
if (charpage == page){
Texture* texture = pages[charpage].get();
if (texture == nullptr){
texture = pages[0].get();
}
batch->texture(texture);
drawGlyph(batch, x, y, c, style);
}
else if (charpage > page && charpage < next){
next = charpage;
}
x += 8;//getGlyphWidth(c);
}
page = next;
next = MAX_CODEPAGES;
x = init_x;
} while (page < MAX_CODEPAGES);
}

View File

@ -9,25 +9,27 @@
class Texture;
class Batch2D;
const uint STYLE_NONE = 0;
const uint STYLE_SHADOW = 1;
const uint STYLE_OUTLINE = 2;
enum class FontStyle {
none,
shadow,
outline
};
class Font {
int lineHeight;
int lineHeight;
int yoffset;
public:
std::vector<std::unique_ptr<Texture>> pages;
Font(std::vector<std::unique_ptr<Texture>> pages, int lineHeight, int yoffset);
~Font();
std::vector<std::unique_ptr<Texture>> pages;
Font(std::vector<std::unique_ptr<Texture>> pages, int lineHeight, int yoffset);
~Font();
int getLineHeight() const;
int getLineHeight() const;
int getYOffset() const;
int calcWidth(std::wstring text, size_t length=-1);
// int getGlyphWidth(char c);
bool isPrintableChar(int c);
void draw(Batch2D* batch, std::wstring text, int x, int y);
void draw(Batch2D* batch, std::wstring text, int x, int y, int style);
int calcWidth(std::wstring text, size_t length=-1);
bool isPrintableChar(uint codepoint) const;
void draw(Batch2D* batch, std::wstring text, int x, int y);
void draw(Batch2D* batch, std::wstring text, int x, int y, FontStyle style);
void draw(Batch2D* batch, std::wstring_view text, int x, int y, FontStyle style);
};
#endif /* GRAPHICS_FONT_H_ */

View File

@ -292,6 +292,10 @@ double util::parse_double(const std::string& str, size_t offset, size_t len) {
return parse_double(str.substr(offset, len));
}
/// @brief Split string by delimiter
/// @param str source string
/// @param delimiter split delimiter size
/// @return vector of string parts, containing at least one string
std::vector<std::string> util::split(const std::string& str, char delimiter) {
std::vector<std::string> result;
std::stringstream ss(str);
@ -299,9 +303,16 @@ std::vector<std::string> util::split(const std::string& str, char delimiter) {
while (std::getline(ss, tmp, delimiter)) {
result.push_back(tmp);
}
if (result.empty()) {
result.push_back("");
}
return result;
}
/// @brief Split wstring by delimiter
/// @param str source string
/// @param delimiter split delimiter size
/// @return vector of string parts, containing at least one string
std::vector<std::wstring> util::split(const std::wstring& str, char delimiter) {
std::vector<std::wstring> result;
std::wstringstream ss(str);
@ -309,5 +320,8 @@ std::vector<std::wstring> util::split(const std::wstring& str, char delimiter) {
while (std::getline(ss, tmp, static_cast<wchar_t>(delimiter))) {
result.push_back(tmp);
}
if (result.empty()) {
result.push_back(L"");
}
return result;
}