From 0e8865546c8ab96cbb182ce13b316df40fbfc72c Mon Sep 17 00:00:00 2001 From: SMJS <38814077+SMJSGaming@users.noreply.github.com> Date: Tue, 3 Oct 2023 22:28:15 +0200 Subject: [PATCH 1/2] Fixed alignment issues caused by unallocated memory and bad anchor points Simply put, alignment had no default assignment, causing undefined behavior, this also revealed some bad anchor points which could be optimized --- loader/src/ui/nodes/TextArea.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/loader/src/ui/nodes/TextArea.cpp b/loader/src/ui/nodes/TextArea.cpp index 4af0518b5..9dce696fa 100644 --- a/loader/src/ui/nodes/TextArea.cpp +++ b/loader/src/ui/nodes/TextArea.cpp @@ -30,6 +30,7 @@ SimpleTextArea::SimpleTextArea(const std::string& font, const std::string& text, m_maxLines = 0; m_scale = scale; m_linePadding = 0; + m_alignment = kCCTextAlignmentLeft; m_artificialWidth = artificialWidth; m_container = CCMenu::create(); @@ -129,7 +130,6 @@ CCLabelBMFont* SimpleTextArea::createLabel(const std::string& text, const float CCLabelBMFont* label = CCLabelBMFont::create(text.c_str(), m_font.c_str()); label->setScale(m_scale); - label->setAnchorPoint({ 0, 0 }); label->setPosition({ 0, top }); return label; @@ -203,26 +203,24 @@ void SimpleTextArea::updateContents() { m_container->setContentSize(this->getContentSize()); m_container->removeAllChildren(); - height -= m_lineHeight; - for (CCLabelBMFont* line : m_lines) { const float y = height + line->getPositionY(); switch (m_alignment) { case kCCTextAlignmentLeft: { - line->setAnchorPoint({ 0, 0 }); + line->setAnchorPoint({ 0, 1 }); line->setPosition({ 0, y }); } break; case kCCTextAlignmentCenter: { - line->setAnchorPoint({ 0.5f, 0 }); + line->setAnchorPoint({ 0.5f, 1 }); line->setPosition({ width / 2, y }); } break; case kCCTextAlignmentRight: { - line->setAnchorPoint({ 1, 0 }); + line->setAnchorPoint({ 1, 1 }); line->setPosition({ width, y }); } break; } m_container->addChild(line); } -} \ No newline at end of file +} From 42a1a33c532a4c86546ee0b0eb55dcfd3b9f0382 Mon Sep 17 00:00:00 2001 From: SMJSGaming Date: Tue, 10 Oct 2023 19:19:12 +0200 Subject: [PATCH 2/2] Added word wrappers, colors and optimizations to text area --- loader/include/Geode/ui/TextArea.hpp | 33 ++++-- loader/src/ui/nodes/TextArea.cpp | 164 +++++++++++++++++++-------- 2 files changed, 144 insertions(+), 53 deletions(-) diff --git a/loader/include/Geode/ui/TextArea.hpp b/loader/include/Geode/ui/TextArea.hpp index 0ac0cf313..c923d4e4e 100644 --- a/loader/include/Geode/ui/TextArea.hpp +++ b/loader/include/Geode/ui/TextArea.hpp @@ -4,36 +4,47 @@ #include namespace geode { + enum WrappingMode { + NO_WRAP, + WORD_WRAP, + CUTOFF_WRAP + }; + /** * A class which provides a textarea with proper alignment and some extra features like: * * - Max lines * - Changing all aspects after creation * - Custom text alignment - * - Automatic line wrapping and cutoff + * - Configurable and automatic word wrapping * - Line padding * * Contact me on Discord (\@smjs) if you have any questions, suggestions or bugs. */ class GEODE_DLL SimpleTextArea : public cocos2d::CCNode { + static SimpleTextArea* create(const std::string& text, const std::string& font = "chatFont.fnt", const float scale = 1); + static SimpleTextArea* create(const std::string& text, const std::string& font, const float scale, const float width); + cocos2d::CCMenu* m_container; std::string m_font; std::string m_text; std::vector m_lines; + cocos2d::ccColor4B m_color; cocos2d::CCTextAlignment m_alignment; + WrappingMode m_wrappingMode; size_t m_maxLines; float m_scale; float m_lineHeight; float m_linePadding; - bool m_artificialWidth; - public: - static SimpleTextArea* create(const std::string& font, const std::string& text, const float scale); - static SimpleTextArea* create(const std::string& font, const std::string& text, const float scale, const float width); void setFont(const std::string& font); std::string getFont(); + void setColor(const cocos2d::ccColor4B& color); + cocos2d::ccColor4B getColor(); void setAlignment(const cocos2d::CCTextAlignment alignment); cocos2d::CCTextAlignment getAlignment(); + void setWrappingMode(const WrappingMode mode); + WrappingMode getWrappingMode(); void setText(const std::string& text); std::string getText(); void setMaxLines(const size_t maxLines); @@ -50,11 +61,17 @@ namespace geode { private: static SimpleTextArea* create(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth); + bool m_shouldUpdate; + bool m_artificialWidth; + SimpleTextArea(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth); cocos2d::CCLabelBMFont* createLabel(const std::string& text, const float top); - cocos2d::CCLabelBMFont* moveOverflow(cocos2d::CCLabelBMFont* line, const char c, const float top); float calculateOffset(cocos2d::CCLabelBMFont* label); - void updateLines(); - void updateContents(); + void charIteration(const std::function& overflowHandling); + void updateLinesNoWrap(); + void updateLinesWordWrap(); + void updateLinesCutoffWrap(); + void updateContainer(); + virtual void draw() override; }; } \ No newline at end of file diff --git a/loader/src/ui/nodes/TextArea.cpp b/loader/src/ui/nodes/TextArea.cpp index 9dce696fa..a2b747175 100644 --- a/loader/src/ui/nodes/TextArea.cpp +++ b/loader/src/ui/nodes/TextArea.cpp @@ -2,11 +2,11 @@ using namespace geode::prelude; -SimpleTextArea* SimpleTextArea::create(const std::string& font, const std::string& text, const float scale = 1) { - return SimpleTextArea::create(font, text, scale, 500, false); +SimpleTextArea* SimpleTextArea::create(const std::string& text, const std::string& font, const float scale) { + return SimpleTextArea::create(font, text, scale, CCDirector::sharedDirector()->getWinSize().width / 2, false); } -SimpleTextArea* SimpleTextArea::create(const std::string& font, const std::string& text, const float scale, const float width) { +SimpleTextArea* SimpleTextArea::create(const std::string& text, const std::string& font, const float scale, const float width) { return SimpleTextArea::create(font, text, scale, width, true); } @@ -30,9 +30,12 @@ SimpleTextArea::SimpleTextArea(const std::string& font, const std::string& text, m_maxLines = 0; m_scale = scale; m_linePadding = 0; + m_color = { 0xFF, 0xFF, 0xFF, 0xFF }; m_alignment = kCCTextAlignmentLeft; + m_wrappingMode = WORD_WRAP; m_artificialWidth = artificialWidth; m_container = CCMenu::create(); + m_shouldUpdate = true; this->setAnchorPoint({ 0.5f, 0.5f }); m_container->setPosition({ 0, 0 }); @@ -40,33 +43,47 @@ SimpleTextArea::SimpleTextArea(const std::string& font, const std::string& text, m_container->setContentSize({ width, 0 }); this->addChild(m_container); - this->updateContents(); } void SimpleTextArea::setFont(const std::string& font) { m_font = font; - - this->updateContents(); + m_shouldUpdate = true; } std::string SimpleTextArea::getFont() { return m_font; } +void SimpleTextArea::setColor(const ccColor4B& color) { + m_color = color; + m_shouldUpdate = true; +} + +ccColor4B SimpleTextArea::getColor() { + return m_color; +} + void SimpleTextArea::setAlignment(const CCTextAlignment alignment) { m_alignment = alignment; - - this->updateContents(); + m_shouldUpdate = true; } CCTextAlignment SimpleTextArea::getAlignment() { return m_alignment; } +void SimpleTextArea::setWrappingMode(const WrappingMode mode) { + m_wrappingMode = mode; + m_shouldUpdate = true; +} + +WrappingMode SimpleTextArea::getWrappingMode() { + return m_wrappingMode; +} + void SimpleTextArea::setText(const std::string& text) { m_text = text; - - this->updateContents(); + m_shouldUpdate = true; } std::string SimpleTextArea::getText() { @@ -75,8 +92,7 @@ std::string SimpleTextArea::getText() { void SimpleTextArea::setMaxLines(const size_t maxLines) { m_maxLines = maxLines; - - this->updateContents(); + m_shouldUpdate = true; } size_t SimpleTextArea::getMaxLines() { @@ -85,6 +101,7 @@ size_t SimpleTextArea::getMaxLines() { void SimpleTextArea::setWidth(const float width) { m_artificialWidth = true; + m_shouldUpdate = true; this->setContentSize({ width, this->getContentSize().height }); m_container->setContentSize(this->getContentSize()); @@ -96,8 +113,7 @@ float SimpleTextArea::getWidth() { void SimpleTextArea::setScale(const float scale) { m_scale = scale; - - this->updateContents(); + m_shouldUpdate = true; } float SimpleTextArea::getScale() { @@ -106,8 +122,7 @@ float SimpleTextArea::getScale() { void SimpleTextArea::setLinePadding(const float padding) { m_linePadding = padding; - - this->updateContents(); + m_shouldUpdate = true; } float SimpleTextArea::getLinePadding() { @@ -131,34 +146,17 @@ CCLabelBMFont* SimpleTextArea::createLabel(const std::string& text, const float label->setScale(m_scale); label->setPosition({ 0, top }); + label->setColor({ m_color.r, m_color.g, m_color.b }); + label->setOpacity(m_color.a); return label; } -CCLabelBMFont* SimpleTextArea::moveOverflow(CCLabelBMFont* line, const char c, const float top) { - const std::string text = line->getString(); - const char back = text.back(); - const bool lastIsSpace = back == ' '; - CCLabelBMFont* newLine = this->createLabel(std::string(!lastIsSpace, back).append(std::string(c != ' ', c)), top); - - if (!lastIsSpace) { - if (text[text.size() - 2] == ' ') { - line->setString(text.substr(0, text.size() - 1).c_str()); - } else { - line->setString((text.substr(0, text.size() - 1) + '-').c_str()); - } - } - - m_lines.push_back(newLine); - - return newLine; -} - float SimpleTextArea::calculateOffset(CCLabelBMFont* label) { return m_linePadding + label->getContentSize().height * m_scale; } -void SimpleTextArea::updateLines() { +void SimpleTextArea::charIteration(const std::function& overflowHandling) { float top = 0; CCLabelBMFont* line = this->createLabel("", top); m_lines = { line }; @@ -173,21 +171,87 @@ void SimpleTextArea::updateLines() { break; } else if (c == '\n') { - line = this->createLabel("", top -= this->calculateOffset(line)); + m_lines.push_back(line = this->createLabel("", top -= this->calculateOffset(line))); + } else if (m_artificialWidth && line->getContentSize().width * m_scale >= this->getWidth()) { + m_lines.push_back(line = overflowHandling(line, c, top -= this->calculateOffset(line))); + } else { + line->setString((std::string(line->getString()) + c).c_str()); + } + } +} - m_lines.push_back(line); - } else if (m_artificialWidth && line->getContentSize().width >= this->getWidth()) { - line = this->moveOverflow(line, c, top -= this->calculateOffset(line)); +void SimpleTextArea::updateLinesNoWrap() { + std::stringstream stream(m_text); + std::string part; + float top = 0; + + while (std::getline(stream, part)) { + if (m_maxLines && m_lines.size() >= m_maxLines) { + CCLabelBMFont* last = m_lines.at(m_maxLines - 1); + const std::string text = last->getString(); + + last->setString(text.substr(0, text.size() - 3).append("...").c_str()); + + break; } else { - const std::string text = line->getString(); + CCLabelBMFont* line = this->createLabel(part, 0); + + top -= this->calculateOffset(line); - line->setString((text + c).c_str()); + m_lines.push_back(line); } } } -void SimpleTextArea::updateContents() { - this->updateLines(); +void SimpleTextArea::updateLinesWordWrap() { + this->charIteration([this](CCLabelBMFont* line, const char c, const float top) { + static std::string delimiters(" `~!@#$%^&*()-_=+[{}];:'\",<.>/?\\|"); + + if (delimiters.find(c) == std::string_view::npos) { + const std::string text = line->getString(); + const size_t position = text.find_last_of(delimiters) + 1; + + line->setString(text.substr(0, position).c_str()); + + return this->createLabel(text.substr(position) + c, top); + } else { + return this->createLabel(std::string(c, c != ' '), top); + } + }); +} + +void SimpleTextArea::updateLinesCutoffWrap() { + this->charIteration([this](CCLabelBMFont* line, const char c, const float top) { + const std::string text = line->getString(); + const char back = text.back(); + const bool lastIsSpace = back == ' '; + CCLabelBMFont* newLine = this->createLabel(std::string(!lastIsSpace, back).append(std::string(c != ' ', c)), top); + + if (!lastIsSpace) { + if (text[text.size() - 2] == ' ') { + line->setString(text.substr(0, text.size() - 1).c_str()); + } else { + line->setString((text.substr(0, text.size() - 1) + '-').c_str()); + } + } + + return newLine; + }); +} + +void SimpleTextArea::updateContainer() { + switch (m_wrappingMode) { + case NO_WRAP: { + this->updateLinesNoWrap(); + } break; + case WORD_WRAP: { + this->updateLinesWordWrap(); + } break; + case CUTOFF_WRAP: { + this->updateLinesCutoffWrap(); + } break; + } + const size_t lineCount = m_lines.size(); const float width = this->getWidth(); @@ -212,7 +276,7 @@ void SimpleTextArea::updateContents() { line->setPosition({ 0, y }); } break; case kCCTextAlignmentCenter: { - line->setAnchorPoint({ 0.5f, 1 }); + line->setAnchorPoint({ 0, 1 }); line->setPosition({ width / 2, y }); } break; case kCCTextAlignmentRight: { @@ -224,3 +288,13 @@ void SimpleTextArea::updateContents() { m_container->addChild(line); } } + +void SimpleTextArea::draw() { + CCNode::draw(); + + if (m_shouldUpdate) { + this->updateContainer(); + + m_shouldUpdate = false; + } +} \ No newline at end of file