text-wrapping + fix
This commit is contained in:
parent
37e089169e
commit
56a8226028
@ -34,8 +34,12 @@ bool Font::isPrintableChar(uint codepoint) const {
|
||||
}
|
||||
}
|
||||
|
||||
int Font::calcWidth(std::wstring text, size_t length) {
|
||||
return std::min(text.length(), length) * 8;
|
||||
int Font::calcWidth(const std::wstring& text, size_t length) {
|
||||
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) {
|
||||
|
||||
@ -28,9 +28,16 @@ public:
|
||||
|
||||
/// @brief Calculate text width in pixels
|
||||
/// @param text selected text
|
||||
/// @param length max text chunk length (default: no limit)
|
||||
/// @return pixel width of the text
|
||||
int calcWidth(std::wstring text, size_t length=-1);
|
||||
/// @param length max substring length (default: no limit)
|
||||
/// @return pixel width of the substring
|
||||
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)
|
||||
/// @param codepoint character unicode codepoint
|
||||
|
||||
@ -7,30 +7,50 @@
|
||||
|
||||
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;
|
||||
lines.clear();
|
||||
lines.push_back(LineScheme {0, false});
|
||||
|
||||
if (font == nullptr) {
|
||||
wrap = false;
|
||||
}
|
||||
size_t len = 0;
|
||||
if (multiline) {
|
||||
lines.push_back(LineScheme {0});
|
||||
for (size_t i = 0; i < text.length(); i++) {
|
||||
for (size_t i = 0; i < text.length(); i++, len++) {
|
||||
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)
|
||||
: UINode(glm::vec2(text.length() * 8, 15)),
|
||||
: UINode(glm::vec2(text.length() * 8, 15)),
|
||||
text(util::str2wstr_utf8(text)),
|
||||
fontName(fontName)
|
||||
{
|
||||
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)
|
||||
{
|
||||
setInteractive(false);
|
||||
cache.update(this->text, multiline);
|
||||
cache.update(this->text, multiline, textWrap);
|
||||
}
|
||||
|
||||
void Label::setText(std::wstring text) {
|
||||
@ -48,7 +68,7 @@ void Label::setText(std::wstring text) {
|
||||
return;
|
||||
}
|
||||
this->text = text;
|
||||
cache.update(this->text, multiline);
|
||||
cache.update(this->text, multiline, textWrap);
|
||||
}
|
||||
|
||||
const std::wstring& Label::getText() const {
|
||||
@ -88,6 +108,11 @@ size_t Label::getTextLineOffset(size_t line) const {
|
||||
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 {
|
||||
return line * totalLineHeight + textYOffset;
|
||||
}
|
||||
@ -113,12 +138,16 @@ uint Label::getLinesNumber() const {
|
||||
}
|
||||
|
||||
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) {
|
||||
setText(supplier());
|
||||
}
|
||||
|
||||
auto batch = pctx->getBatch2D();
|
||||
auto font = assets->getFont(fontName);
|
||||
if (cache.resetFlag) {
|
||||
cache.update(text, multiline, textWrap);
|
||||
}
|
||||
|
||||
batch->setColor(getColor());
|
||||
|
||||
@ -157,13 +186,12 @@ void Label::draw(const GfxContext* pctx, Assets* assets) {
|
||||
totalLineHeight = lineHeight;
|
||||
|
||||
if (multiline) {
|
||||
size_t offset = 0;
|
||||
for (uint i = 0; i < cache.lines.size(); i++) {
|
||||
for (size_t 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);
|
||||
size_t end = view.find(L'\n');
|
||||
if (end != std::wstring::npos) {
|
||||
view = std::wstring_view(text.c_str()+offset, end);
|
||||
offset += end + 1;
|
||||
if (i < cache.lines.size()-1) {
|
||||
view = std::wstring_view(text.c_str()+offset, cache.lines.at(i+1).offset-offset);
|
||||
}
|
||||
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 {
|
||||
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"
|
||||
|
||||
class Font;
|
||||
|
||||
namespace gui {
|
||||
struct LineScheme {
|
||||
size_t offset;
|
||||
bool fake;
|
||||
};
|
||||
|
||||
struct LabelCache {
|
||||
Font* font = nullptr;
|
||||
std::vector<LineScheme> lines;
|
||||
/// @brief Reset cache flag
|
||||
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 {
|
||||
@ -29,8 +35,11 @@ namespace gui {
|
||||
/// @brief Vertical alignment (only when multiline is set to false)
|
||||
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;
|
||||
|
||||
/// @brief Text wrapping (works only if multiline is enabled)
|
||||
bool textWrap = true;
|
||||
|
||||
/// @brief Text Y offset relative to label position
|
||||
/// (last calculated alignment)
|
||||
@ -80,6 +89,7 @@ namespace gui {
|
||||
virtual uint getLineByYOffset(int offset) const;
|
||||
virtual uint getLineByTextIndex(size_t index) const;
|
||||
virtual uint getLinesNumber() const;
|
||||
virtual bool isFakeLine(size_t line) const;
|
||||
|
||||
virtual void draw(const GfxContext* pctx, Assets* assets) override;
|
||||
|
||||
@ -87,6 +97,9 @@ namespace gui {
|
||||
|
||||
virtual void setMultiline(bool multiline);
|
||||
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));
|
||||
glm::vec2 lcoord = label->calcPos();
|
||||
lcoord.y -= 2;
|
||||
|
||||
uint line = label->getLineByTextIndex(caret);
|
||||
int lineY = label->getLineYOffset(line);
|
||||
int lineHeight = font->getLineHeight() * label->getLineInterval();
|
||||
batch->rect(lcoord.x, lcoord.y+lineY, label->getSize().x, 1);
|
||||
batch->rect(lcoord.x, lcoord.y+lineY+lineHeight-2, label->getSize().x, 1);
|
||||
while (label->isFakeLine(line)) {
|
||||
line--;
|
||||
}
|
||||
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));
|
||||
@ -225,6 +233,14 @@ void TextBox::setMultiline(bool multiline) {
|
||||
bool TextBox::isMultiline() const {
|
||||
return multiline;
|
||||
}
|
||||
|
||||
void TextBox::setTextWrapping(bool flag) {
|
||||
label->setTextWrapping(flag);
|
||||
}
|
||||
|
||||
bool TextBox::isTextWrapping() const {
|
||||
return label->isTextWrapping();
|
||||
}
|
||||
|
||||
void TextBox::setEditable(bool editable) {
|
||||
this->editable = editable;
|
||||
|
||||
@ -124,6 +124,12 @@ namespace gui {
|
||||
/// @brief Check if multiline mode is enabled
|
||||
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
|
||||
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) {
|
||||
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));
|
||||
|
||||
// TODO: implement built-in text wrapping
|
||||
const int wrap_length = 60;
|
||||
if (text.length() > wrap_length) {
|
||||
size_t offset = 0;
|
||||
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));
|
||||
}
|
||||
auto label = std::make_shared<Label>(text);
|
||||
label->setMultiline(true);
|
||||
label->setSize(glm::vec2(1, 80));
|
||||
panel->add(label);
|
||||
panel->add(std::make_shared<Button>(
|
||||
langs::get(L"Ok"), glm::vec4(10.f),
|
||||
[=](GUI* gui) {
|
||||
|
||||
@ -215,6 +215,12 @@ static std::shared_ptr<UINode> readLabel(UiXmlReader& reader, xml::xmlelement el
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -291,6 +297,9 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement
|
||||
if (element->has("multiline")) {
|
||||
textbox->setMultiline(element->attr("multiline").asBool());
|
||||
}
|
||||
if (element->has("text-wrap")) {
|
||||
textbox->setTextWrapping(element->attr("text-wrap").asBool());
|
||||
}
|
||||
|
||||
if (element->has("editable")) {
|
||||
textbox->setEditable(element->attr("editable").asBool());
|
||||
|
||||
@ -85,9 +85,10 @@ static void show_content_missing(
|
||||
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 {
|
||||
engine->loadWorldContent(folder);
|
||||
return true;
|
||||
} catch (const contentpack_error& error) {
|
||||
engine->setScreen(std::make_shared<MenuScreen>(engine));
|
||||
// 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": "+
|
||||
util::str2wstr_utf8(error.getPackId())
|
||||
);
|
||||
return;
|
||||
return false;
|
||||
} catch (const std::runtime_error& error) {
|
||||
engine->setScreen(std::make_shared<MenuScreen>(engine));
|
||||
guiutil::alert(
|
||||
engine->getGUI(), langs::get(L"Content Error", L"menu")+L": "+
|
||||
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) {
|
||||
auto paths = engine->getPaths();
|
||||
auto folder = paths->getWorldsFolder()/fs::u8path(name);
|
||||
loadWorldContent(engine, folder);
|
||||
if (!loadWorldContent(engine, folder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* content = engine->getContent();
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user