text-wrapping + fix
This commit is contained in:
@@ -34,8 +34,12 @@ bool Font::isPrintableChar(uint codepoint) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int Font::calcWidth(std::wstring text, size_t length) {
|
int Font::calcWidth(const std::wstring& text, size_t length) {
|
||||||
return std::min(text.length(), length) * 8;
|
return calcWidth(text, 0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Font::calcWidth(const std::wstring& text, size_t offset, size_t length) {
|
||||||
|
return std::min(text.length()-offset, length) * 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Font::draw(Batch2D* batch, std::wstring text, int x, int y) {
|
void Font::draw(Batch2D* batch, std::wstring text, int x, int y) {
|
||||||
|
|||||||
@@ -28,9 +28,16 @@ public:
|
|||||||
|
|
||||||
/// @brief Calculate text width in pixels
|
/// @brief Calculate text width in pixels
|
||||||
/// @param text selected text
|
/// @param text selected text
|
||||||
/// @param length max text chunk length (default: no limit)
|
/// @param length max substring length (default: no limit)
|
||||||
/// @return pixel width of the text
|
/// @return pixel width of the substring
|
||||||
int calcWidth(std::wstring text, size_t length=-1);
|
int calcWidth(const std::wstring& text, size_t length=-1);
|
||||||
|
|
||||||
|
/// @brief Calculate text width in pixels
|
||||||
|
/// @param text selected text
|
||||||
|
/// @param offset start of the substring
|
||||||
|
/// @param length max substring length
|
||||||
|
/// @return pixel width of the substring
|
||||||
|
int calcWidth(const std::wstring& text, size_t offset, size_t length);
|
||||||
|
|
||||||
/// @brief Check if character is visible (non-whitespace)
|
/// @brief Check if character is visible (non-whitespace)
|
||||||
/// @param codepoint character unicode codepoint
|
/// @param codepoint character unicode codepoint
|
||||||
|
|||||||
@@ -7,21 +7,41 @@
|
|||||||
|
|
||||||
using namespace gui;
|
using namespace gui;
|
||||||
|
|
||||||
void LabelCache::update(const std::wstring& text, bool multiline) {
|
void LabelCache::prepare(Font* font, size_t wrapWidth) {
|
||||||
|
if (font != this->font) {
|
||||||
|
resetFlag = true;
|
||||||
|
this->font = font;
|
||||||
|
}
|
||||||
|
if (wrapWidth != this->wrapWidth) {
|
||||||
|
resetFlag = true;
|
||||||
|
this->wrapWidth = wrapWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LabelCache::update(const std::wstring& text, bool multiline, bool wrap) {
|
||||||
resetFlag = false;
|
resetFlag = false;
|
||||||
lines.clear();
|
lines.clear();
|
||||||
|
lines.push_back(LineScheme {0, false});
|
||||||
|
|
||||||
|
if (font == nullptr) {
|
||||||
|
wrap = false;
|
||||||
|
}
|
||||||
|
size_t len = 0;
|
||||||
if (multiline) {
|
if (multiline) {
|
||||||
lines.push_back(LineScheme {0});
|
for (size_t i = 0; i < text.length(); i++, len++) {
|
||||||
for (size_t i = 0; i < text.length(); i++) {
|
|
||||||
if (text[i] == L'\n') {
|
if (text[i] == L'\n') {
|
||||||
lines.push_back(LineScheme {i+1});
|
lines.push_back(LineScheme {i+1, false});
|
||||||
|
len = 0;
|
||||||
|
} else if (i > 0 && wrap && text[i+1] != L'\n') {
|
||||||
|
size_t width = font->calcWidth(text, i-len-1, i-(i-len)+2);
|
||||||
|
if (width >= wrapWidth) {
|
||||||
|
// starting a fake line
|
||||||
|
lines.push_back(LineScheme {i+1, true});
|
||||||
|
len = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (lines.empty()) {
|
|
||||||
lines.push_back(LineScheme {0});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Label::Label(std::string text, std::string fontName)
|
Label::Label(std::string text, std::string fontName)
|
||||||
@@ -30,7 +50,7 @@ Label::Label(std::string text, std::string fontName)
|
|||||||
fontName(fontName)
|
fontName(fontName)
|
||||||
{
|
{
|
||||||
setInteractive(false);
|
setInteractive(false);
|
||||||
cache.update(this->text, multiline);
|
cache.update(this->text, multiline, textWrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -40,7 +60,7 @@ Label::Label(std::wstring text, std::string fontName)
|
|||||||
fontName(fontName)
|
fontName(fontName)
|
||||||
{
|
{
|
||||||
setInteractive(false);
|
setInteractive(false);
|
||||||
cache.update(this->text, multiline);
|
cache.update(this->text, multiline, textWrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Label::setText(std::wstring text) {
|
void Label::setText(std::wstring text) {
|
||||||
@@ -48,7 +68,7 @@ void Label::setText(std::wstring text) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->text = text;
|
this->text = text;
|
||||||
cache.update(this->text, multiline);
|
cache.update(this->text, multiline, textWrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::wstring& Label::getText() const {
|
const std::wstring& Label::getText() const {
|
||||||
@@ -88,6 +108,11 @@ size_t Label::getTextLineOffset(size_t line) const {
|
|||||||
return cache.lines.at(line).offset;
|
return cache.lines.at(line).offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Label::isFakeLine(size_t line) const {
|
||||||
|
line = std::min(cache.lines.size()-1, line);
|
||||||
|
return cache.lines.at(line).fake;
|
||||||
|
}
|
||||||
|
|
||||||
int Label::getLineYOffset(uint line) const {
|
int Label::getLineYOffset(uint line) const {
|
||||||
return line * totalLineHeight + textYOffset;
|
return line * totalLineHeight + textYOffset;
|
||||||
}
|
}
|
||||||
@@ -113,12 +138,16 @@ uint Label::getLinesNumber() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Label::draw(const GfxContext* pctx, Assets* assets) {
|
void Label::draw(const GfxContext* pctx, Assets* assets) {
|
||||||
|
auto batch = pctx->getBatch2D();
|
||||||
|
auto font = assets->getFont(fontName);
|
||||||
|
cache.prepare(font, static_cast<size_t>(glm::abs(getSize().x)));
|
||||||
|
|
||||||
if (supplier) {
|
if (supplier) {
|
||||||
setText(supplier());
|
setText(supplier());
|
||||||
}
|
}
|
||||||
|
if (cache.resetFlag) {
|
||||||
auto batch = pctx->getBatch2D();
|
cache.update(text, multiline, textWrap);
|
||||||
auto font = assets->getFont(fontName);
|
}
|
||||||
|
|
||||||
batch->setColor(getColor());
|
batch->setColor(getColor());
|
||||||
|
|
||||||
@@ -157,13 +186,12 @@ void Label::draw(const GfxContext* pctx, Assets* assets) {
|
|||||||
totalLineHeight = lineHeight;
|
totalLineHeight = lineHeight;
|
||||||
|
|
||||||
if (multiline) {
|
if (multiline) {
|
||||||
size_t offset = 0;
|
for (size_t i = 0; i < cache.lines.size(); i++) {
|
||||||
for (uint i = 0; i < cache.lines.size(); i++) {
|
auto& line = cache.lines.at(i);
|
||||||
|
size_t offset = line.offset;
|
||||||
std::wstring_view view(text.c_str()+offset, text.length()-offset);
|
std::wstring_view view(text.c_str()+offset, text.length()-offset);
|
||||||
size_t end = view.find(L'\n');
|
if (i < cache.lines.size()-1) {
|
||||||
if (end != std::wstring::npos) {
|
view = std::wstring_view(text.c_str()+offset, cache.lines.at(i+1).offset-offset);
|
||||||
view = std::wstring_view(text.c_str()+offset, end);
|
|
||||||
offset += end + 1;
|
|
||||||
}
|
}
|
||||||
font->draw(batch, view, pos.x, pos.y + i * totalLineHeight, FontStyle::none);
|
font->draw(batch, view, pos.x, pos.y + i * totalLineHeight, FontStyle::none);
|
||||||
}
|
}
|
||||||
@@ -187,3 +215,12 @@ void Label::setMultiline(bool multiline) {
|
|||||||
bool Label::isMultiline() const {
|
bool Label::isMultiline() const {
|
||||||
return multiline;
|
return multiline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Label::setTextWrapping(bool flag) {
|
||||||
|
this->textWrap = flag;
|
||||||
|
cache.resetFlag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Label::isTextWrapping() const {
|
||||||
|
return textWrap;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,17 +3,23 @@
|
|||||||
|
|
||||||
#include "UINode.hpp"
|
#include "UINode.hpp"
|
||||||
|
|
||||||
|
class Font;
|
||||||
|
|
||||||
namespace gui {
|
namespace gui {
|
||||||
struct LineScheme {
|
struct LineScheme {
|
||||||
size_t offset;
|
size_t offset;
|
||||||
|
bool fake;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LabelCache {
|
struct LabelCache {
|
||||||
|
Font* font = nullptr;
|
||||||
std::vector<LineScheme> lines;
|
std::vector<LineScheme> lines;
|
||||||
/// @brief Reset cache flag
|
/// @brief Reset cache flag
|
||||||
bool resetFlag = true;
|
bool resetFlag = true;
|
||||||
|
size_t wrapWidth = -1;
|
||||||
|
|
||||||
void update(const std::wstring& text, bool multiline);
|
void prepare(Font* font, size_t wrapWidth);
|
||||||
|
void update(const std::wstring& text, bool multiline, bool wrap);
|
||||||
};
|
};
|
||||||
|
|
||||||
class Label : public UINode {
|
class Label : public UINode {
|
||||||
@@ -29,9 +35,12 @@ namespace gui {
|
|||||||
/// @brief Vertical alignment (only when multiline is set to false)
|
/// @brief Vertical alignment (only when multiline is set to false)
|
||||||
Align valign = Align::center;
|
Align valign = Align::center;
|
||||||
|
|
||||||
/// @brief Line separators will be ignored if set to false
|
/// @brief Line separators and wrapping will be ignored if set to false
|
||||||
bool multiline = false;
|
bool multiline = false;
|
||||||
|
|
||||||
|
/// @brief Text wrapping (works only if multiline is enabled)
|
||||||
|
bool textWrap = true;
|
||||||
|
|
||||||
/// @brief Text Y offset relative to label position
|
/// @brief Text Y offset relative to label position
|
||||||
/// (last calculated alignment)
|
/// (last calculated alignment)
|
||||||
int textYOffset = 0;
|
int textYOffset = 0;
|
||||||
@@ -80,6 +89,7 @@ namespace gui {
|
|||||||
virtual uint getLineByYOffset(int offset) const;
|
virtual uint getLineByYOffset(int offset) const;
|
||||||
virtual uint getLineByTextIndex(size_t index) const;
|
virtual uint getLineByTextIndex(size_t index) const;
|
||||||
virtual uint getLinesNumber() const;
|
virtual uint getLinesNumber() const;
|
||||||
|
virtual bool isFakeLine(size_t line) const;
|
||||||
|
|
||||||
virtual void draw(const GfxContext* pctx, Assets* assets) override;
|
virtual void draw(const GfxContext* pctx, Assets* assets) override;
|
||||||
|
|
||||||
@@ -87,6 +97,9 @@ namespace gui {
|
|||||||
|
|
||||||
virtual void setMultiline(bool multiline);
|
virtual void setMultiline(bool multiline);
|
||||||
virtual bool isMultiline() const;
|
virtual bool isMultiline() const;
|
||||||
|
|
||||||
|
virtual void setTextWrapping(bool flag);
|
||||||
|
virtual bool isTextWrapping() const;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,11 +101,19 @@ void TextBox::drawBackground(const GfxContext* pctx, Assets* assets) {
|
|||||||
batch->setColor(glm::vec4(1, 1, 1, 0.1f));
|
batch->setColor(glm::vec4(1, 1, 1, 0.1f));
|
||||||
glm::vec2 lcoord = label->calcPos();
|
glm::vec2 lcoord = label->calcPos();
|
||||||
lcoord.y -= 2;
|
lcoord.y -= 2;
|
||||||
|
|
||||||
uint line = label->getLineByTextIndex(caret);
|
uint line = label->getLineByTextIndex(caret);
|
||||||
int lineY = label->getLineYOffset(line);
|
while (label->isFakeLine(line)) {
|
||||||
int lineHeight = font->getLineHeight() * label->getLineInterval();
|
line--;
|
||||||
batch->rect(lcoord.x, lcoord.y+lineY, label->getSize().x, 1);
|
}
|
||||||
batch->rect(lcoord.x, lcoord.y+lineY+lineHeight-2, label->getSize().x, 1);
|
batch->setColor(glm::vec4(1, 1, 1, 0.05f));
|
||||||
|
do {
|
||||||
|
int lineY = label->getLineYOffset(line);
|
||||||
|
int lineHeight = font->getLineHeight() * label->getLineInterval();
|
||||||
|
|
||||||
|
batch->rect(lcoord.x, lcoord.y+lineY, label->getSize().x, lineHeight);
|
||||||
|
line++;
|
||||||
|
} while (line < label->getLinesNumber() && label->isFakeLine(line));
|
||||||
}
|
}
|
||||||
|
|
||||||
label->setColor(glm::vec4(input.empty() ? 0.5f : 1.0f));
|
label->setColor(glm::vec4(input.empty() ? 0.5f : 1.0f));
|
||||||
@@ -226,6 +234,14 @@ bool TextBox::isMultiline() const {
|
|||||||
return multiline;
|
return multiline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextBox::setTextWrapping(bool flag) {
|
||||||
|
label->setTextWrapping(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TextBox::isTextWrapping() const {
|
||||||
|
return label->isTextWrapping();
|
||||||
|
}
|
||||||
|
|
||||||
void TextBox::setEditable(bool editable) {
|
void TextBox::setEditable(bool editable) {
|
||||||
this->editable = editable;
|
this->editable = editable;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,12 @@ namespace gui {
|
|||||||
/// @brief Check if multiline mode is enabled
|
/// @brief Check if multiline mode is enabled
|
||||||
virtual bool isMultiline() const;
|
virtual bool isMultiline() const;
|
||||||
|
|
||||||
|
/// @brief Enable/disable text wrapping
|
||||||
|
virtual void setTextWrapping(bool flag);
|
||||||
|
|
||||||
|
/// @brief Check if text wrapping is enabled
|
||||||
|
virtual bool isTextWrapping() const;
|
||||||
|
|
||||||
/// @brief Enable/disable text editing feature
|
/// @brief Enable/disable text editing feature
|
||||||
virtual void setEditable(bool editable);
|
virtual void setEditable(bool editable);
|
||||||
|
|
||||||
|
|||||||
@@ -42,27 +42,13 @@ std::shared_ptr<gui::UINode> guiutil::create(const std::string& source, scripten
|
|||||||
|
|
||||||
void guiutil::alert(GUI* gui, const std::wstring& text, runnable on_hidden) {
|
void guiutil::alert(GUI* gui, const std::wstring& text, runnable on_hidden) {
|
||||||
auto menu = gui->getMenu();
|
auto menu = gui->getMenu();
|
||||||
auto panel = std::make_shared<Panel>(glm::vec2(500, 200), glm::vec4(8.0f), 8.0f);
|
auto panel = std::make_shared<Panel>(glm::vec2(500, 300), glm::vec4(8.0f), 8.0f);
|
||||||
panel->setColor(glm::vec4(0.0f, 0.0f, 0.0f, 0.5f));
|
panel->setColor(glm::vec4(0.0f, 0.0f, 0.0f, 0.5f));
|
||||||
|
|
||||||
// TODO: implement built-in text wrapping
|
auto label = std::make_shared<Label>(text);
|
||||||
const int wrap_length = 60;
|
label->setMultiline(true);
|
||||||
if (text.length() > wrap_length) {
|
label->setSize(glm::vec2(1, 80));
|
||||||
size_t offset = 0;
|
panel->add(label);
|
||||||
int extra;
|
|
||||||
while ((extra = text.length() - offset) > 0) {
|
|
||||||
size_t endline = text.find(L'\n', offset);
|
|
||||||
if (endline != std::string::npos) {
|
|
||||||
extra = std::min(extra, int(endline-offset)+1);
|
|
||||||
}
|
|
||||||
extra = std::min(extra, wrap_length);
|
|
||||||
std::wstring part = text.substr(offset, extra);
|
|
||||||
panel->add(std::make_shared<Label>(part));
|
|
||||||
offset += extra;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panel->add(std::make_shared<Label>(text));
|
|
||||||
}
|
|
||||||
panel->add(std::make_shared<Button>(
|
panel->add(std::make_shared<Button>(
|
||||||
langs::get(L"Ok"), glm::vec4(10.f),
|
langs::get(L"Ok"), glm::vec4(10.f),
|
||||||
[=](GUI* gui) {
|
[=](GUI* gui) {
|
||||||
|
|||||||
@@ -215,6 +215,12 @@ static std::shared_ptr<UINode> readLabel(UiXmlReader& reader, xml::xmlelement el
|
|||||||
);
|
);
|
||||||
label->textSupplier(supplier);
|
label->textSupplier(supplier);
|
||||||
}
|
}
|
||||||
|
if (element->has("multiline")) {
|
||||||
|
label->setMultiline(element->attr("multiline").asBool());
|
||||||
|
}
|
||||||
|
if (element->has("text-wrap")) {
|
||||||
|
label->setTextWrapping(element->attr("text-wrap").asBool());
|
||||||
|
}
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,6 +297,9 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement
|
|||||||
if (element->has("multiline")) {
|
if (element->has("multiline")) {
|
||||||
textbox->setMultiline(element->attr("multiline").asBool());
|
textbox->setMultiline(element->attr("multiline").asBool());
|
||||||
}
|
}
|
||||||
|
if (element->has("text-wrap")) {
|
||||||
|
textbox->setTextWrapping(element->attr("text-wrap").asBool());
|
||||||
|
}
|
||||||
|
|
||||||
if (element->has("editable")) {
|
if (element->has("editable")) {
|
||||||
textbox->setEditable(element->attr("editable").asBool());
|
textbox->setEditable(element->attr("editable").asBool());
|
||||||
|
|||||||
@@ -85,9 +85,10 @@ static void show_content_missing(
|
|||||||
menus::show(engine, "reports/missing_content", std::move(args));
|
menus::show(engine, "reports/missing_content", std::move(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loadWorldContent(Engine* engine, fs::path folder) {
|
static bool loadWorldContent(Engine* engine, fs::path folder) {
|
||||||
try {
|
try {
|
||||||
engine->loadWorldContent(folder);
|
engine->loadWorldContent(folder);
|
||||||
|
return true;
|
||||||
} catch (const contentpack_error& error) {
|
} catch (const contentpack_error& error) {
|
||||||
engine->setScreen(std::make_shared<MenuScreen>(engine));
|
engine->setScreen(std::make_shared<MenuScreen>(engine));
|
||||||
// could not to find or read pack
|
// could not to find or read pack
|
||||||
@@ -95,14 +96,14 @@ static void loadWorldContent(Engine* engine, fs::path folder) {
|
|||||||
engine->getGUI(), langs::get(L"error.pack-not-found")+L": "+
|
engine->getGUI(), langs::get(L"error.pack-not-found")+L": "+
|
||||||
util::str2wstr_utf8(error.getPackId())
|
util::str2wstr_utf8(error.getPackId())
|
||||||
);
|
);
|
||||||
return;
|
return false;
|
||||||
} catch (const std::runtime_error& error) {
|
} catch (const std::runtime_error& error) {
|
||||||
engine->setScreen(std::make_shared<MenuScreen>(engine));
|
engine->setScreen(std::make_shared<MenuScreen>(engine));
|
||||||
guiutil::alert(
|
guiutil::alert(
|
||||||
engine->getGUI(), langs::get(L"Content Error", L"menu")+L": "+
|
engine->getGUI(), langs::get(L"Content Error", L"menu")+L": "+
|
||||||
util::str2wstr_utf8(error.what())
|
util::str2wstr_utf8(error.what())
|
||||||
);
|
);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +127,9 @@ static void loadWorld(Engine* engine, fs::path folder) {
|
|||||||
void EngineController::openWorld(std::string name, bool confirmConvert) {
|
void EngineController::openWorld(std::string name, bool confirmConvert) {
|
||||||
auto paths = engine->getPaths();
|
auto paths = engine->getPaths();
|
||||||
auto folder = paths->getWorldsFolder()/fs::u8path(name);
|
auto folder = paths->getWorldsFolder()/fs::u8path(name);
|
||||||
loadWorldContent(engine, folder);
|
if (!loadWorldContent(engine, folder)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto* content = engine->getContent();
|
auto* content = engine->getContent();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user