mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-27 09:55:34 -05:00
Added a text area remake to replace the old and unreliable SimpleTextArea
This commit is contained in:
parent
7aa9e774c4
commit
047d55f263
10 changed files with 632 additions and 14 deletions
|
@ -18,5 +18,6 @@
|
|||
#include "ui/ScrollLayer.hpp"
|
||||
#include "ui/SelectList.hpp"
|
||||
#include "ui/Scrollbar.hpp"
|
||||
#include "ui/TextArea.hpp"
|
||||
#include "ui/TextAreaV2.hpp"
|
||||
#include "ui/SimpleTextArea.hpp"
|
||||
#include "ui/TextRenderer.hpp"
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace geode {
|
|||
*
|
||||
* Contact me on Discord (\@smjs) if you have any questions, suggestions or bugs.
|
||||
*/
|
||||
class GEODE_DLL SimpleTextArea : public cocos2d::CCNode {
|
||||
class GEODE_DLL [[deprecated("Use TextArea instead")]] SimpleTextArea : public cocos2d::CCNode {
|
||||
public:
|
||||
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);
|
241
loader/include/Geode/ui/TextAreaV2.hpp
Normal file
241
loader/include/Geode/ui/TextAreaV2.hpp
Normal file
|
@ -0,0 +1,241 @@
|
|||
#pragma once
|
||||
|
||||
#include <cocos2d.h>
|
||||
#include "PaddingNode.hpp"
|
||||
|
||||
namespace geode {
|
||||
class GEODE_DLL TextAreaV2 : public PaddingNode {
|
||||
public:
|
||||
enum class WrappingMode {
|
||||
// Doesn't wrap the text and completely ignores bounds
|
||||
NoWrap,
|
||||
// Wraps the text on the last special character (spaces included) before the width is exceeded
|
||||
WordWrap,
|
||||
// Wraps the text on the last space before the width is exceeded
|
||||
SpaceWrap,
|
||||
// Wraps the text on the exact character that exceeds the width
|
||||
CutoffWrap
|
||||
};
|
||||
|
||||
enum class Alignment {
|
||||
// Aligns the text to the left
|
||||
Left,
|
||||
// Aligns the text in the center
|
||||
Center,
|
||||
// Aligns the text to the right
|
||||
Right
|
||||
};
|
||||
|
||||
struct Line {
|
||||
std::string text;
|
||||
std::string overflow;
|
||||
cocos2d::CCLabelBMFont* label;
|
||||
int lineNumber;
|
||||
float currentHeight;
|
||||
bool isLastLine;
|
||||
};
|
||||
|
||||
static TextAreaV2* create(const std::string& text, const std::string& font, const float scale = 1, const float width = -1, const float height = -1, const bool deferUpdates = false);
|
||||
|
||||
/**
|
||||
* Sets the font of the text area
|
||||
*/
|
||||
void setFont(const std::string& font);
|
||||
/**
|
||||
* Gets the font of the text area
|
||||
*/
|
||||
std::string getFont() const;
|
||||
/**
|
||||
* Sets the text of the text area
|
||||
*/
|
||||
void setText(const std::string& text);
|
||||
/**
|
||||
* Gets the text of the text area
|
||||
*/
|
||||
std::string getText() const;
|
||||
/**
|
||||
* Sets the color of the text in the text area
|
||||
*/
|
||||
void setTextColor(const cocos2d::ccColor4B& color);
|
||||
/**
|
||||
* Gets the color of the text in the text area
|
||||
*/
|
||||
cocos2d::ccColor4B getTextColor() const;
|
||||
/**
|
||||
* Sets horizontal the alignment of the text in the text area
|
||||
*/
|
||||
void setAlignment(const Alignment alignment);
|
||||
/**
|
||||
* Gets the horizontal alignment of the text in the text area
|
||||
*/
|
||||
Alignment getAlignment() const;
|
||||
/**
|
||||
* Sets the wrapping mode of the text in the text area
|
||||
*/
|
||||
void setWrappingMode(const WrappingMode mode);
|
||||
/**
|
||||
* Gets the wrapping mode of the text in the text area
|
||||
*/
|
||||
WrappingMode getWrappingMode() const;
|
||||
/**
|
||||
* Sets the maximum number of lines in the text area
|
||||
*
|
||||
* @note If this is set to a value smaller than 0, it will stop keeping track of the lines and leave either all the lines or as many as the max height allows
|
||||
*/
|
||||
void setMaxLines(const int maxLines);
|
||||
/**
|
||||
* Gets the maximum number of lines in the text area
|
||||
*/
|
||||
int getMaxLines() const;
|
||||
/**
|
||||
* Sets the scale of the text in the text area
|
||||
*/
|
||||
void setTextScale(const float scale);
|
||||
/**
|
||||
* Gets the scale of the text in the text area
|
||||
*/
|
||||
float getTextScale() const;
|
||||
/**
|
||||
* Sets the padding between lines in the text area
|
||||
*/
|
||||
void setLinePadding(const float padding);
|
||||
/**
|
||||
* Gets the padding between lines in the text area
|
||||
*/
|
||||
float getLinePadding() const;
|
||||
/**
|
||||
* Sets a minimum bound for the width of the text area
|
||||
*
|
||||
* @note If this is set to a value smaller than 0, it will stop rescaling the container and leave it at the max width
|
||||
*/
|
||||
void setMinWidth(const float width);
|
||||
/**
|
||||
* Resets the minimum width to prevent it from rescaling the container
|
||||
*/
|
||||
void resetMinWidth();
|
||||
/**
|
||||
* Gets the min width of the text area
|
||||
*/
|
||||
float getMinWidth() const;
|
||||
/**
|
||||
* Sets the max width of the text area to define the wrapping bounds
|
||||
*
|
||||
* @note If this is set to a value smaller than 0, it will stop rescaling the container and leave it as big as the text or the min width
|
||||
*/
|
||||
void setMaxWidth(const float width);
|
||||
/**
|
||||
* Resets the maximum width to prevent it from rescaling the container
|
||||
*/
|
||||
void resetMaxWidth();
|
||||
/**
|
||||
* Gets the max width of the text area
|
||||
*/
|
||||
float getMaxWidth() const;
|
||||
/**
|
||||
* Sets a minimum bound for the height of the text area
|
||||
*
|
||||
* @note If this is set to a value smaller than 0, it will stop rescaling the container and leave it at the max height
|
||||
*/
|
||||
void setMinHeight(const float height);
|
||||
/**
|
||||
* Resets the minimum height to prevent it from rescaling the container
|
||||
*/
|
||||
void resetMinHeight();
|
||||
/**
|
||||
* Gets the min height of the text area
|
||||
*/
|
||||
float getMinHeight() const;
|
||||
/**
|
||||
* Sets the max height of the text area to define the maximum number of lines through a height limit
|
||||
*
|
||||
* @note If this is set to a value smaller than 0, it will stop rescaling the container and leave it as big as the lines or the min height
|
||||
*/
|
||||
void setMaxHeight(const float height);
|
||||
/**
|
||||
* Resets the maximum height to prevent it from rescaling the container
|
||||
*/
|
||||
void resetMaxHeight();
|
||||
/**
|
||||
* Gets the max height of the text area
|
||||
*/
|
||||
float getMaxHeight() const;
|
||||
/**
|
||||
* Resets the minimum bounds for the width and height
|
||||
*/
|
||||
void resetMinBounds();
|
||||
/**
|
||||
* Resets the maximum bounds for the width and height
|
||||
*/
|
||||
void resetMaxBounds();
|
||||
/**
|
||||
* Sets whether the text should be hyphenated
|
||||
*/
|
||||
void setHyphenate(const bool hyphenate);
|
||||
/**
|
||||
* Gets whether the text should be hyphenated
|
||||
*/
|
||||
bool getHyphenate() const;
|
||||
/**
|
||||
* Sets whether the text area should show an ellipsis when the text overflows
|
||||
*
|
||||
* @note This will only show an ellipsis if the last line doesn't end with an ellipsis already
|
||||
*/
|
||||
void setEllipsis(const bool ellipsis);
|
||||
/**
|
||||
* Gets whether the text area should show an ellipsis when the text overflows
|
||||
*/
|
||||
bool getEllipsis() const;
|
||||
/**
|
||||
* Sets whether the text area should defer updates until the next frame
|
||||
*
|
||||
* @warning If set to true, all nodes won't immediately update and will only update on the next frame. This can cause side effects if not accounted for
|
||||
*/
|
||||
void setDeferUpdates(const bool defer);
|
||||
/**
|
||||
* Gets whether the text area should defer updates until the next frame
|
||||
*/
|
||||
bool getDeferUpdates() const;
|
||||
/**
|
||||
* Gets the height of a line including padding
|
||||
*/
|
||||
float getLineHeight() const;
|
||||
/**
|
||||
* Gets the labels of the text area
|
||||
*/
|
||||
std::vector<Line> getLines() const;
|
||||
protected:
|
||||
std::string m_font;
|
||||
std::string m_text;
|
||||
cocos2d::ccColor4B m_textColor;
|
||||
Alignment m_alignment;
|
||||
WrappingMode m_wrappingMode;
|
||||
int m_maxLines;
|
||||
float m_textScale;
|
||||
float m_linePadding;
|
||||
float m_minWidth;
|
||||
float m_maxWidth;
|
||||
float m_minHeight;
|
||||
float m_maxHeight;
|
||||
bool m_hyphenate;
|
||||
bool m_ellipsis;
|
||||
bool m_deferUpdates;
|
||||
bool m_deferred;
|
||||
std::vector<Line> m_lines;
|
||||
|
||||
TextAreaV2(const std::string& text, const std::string& font, const float scale, const float width, const float height, const bool deferUpdates);
|
||||
bool init() override;
|
||||
void update();
|
||||
void updatePadding() override;
|
||||
void updateContainer(const float dt = 0);
|
||||
void updateLineAlignment(cocos2d::CCLabelBMFont* line);
|
||||
void addEllipsis(Line& line);
|
||||
bool isWidthOverflowing(const cocos2d::CCLabelBMFont* line);
|
||||
size_t getOverflowAmount(cocos2d::CCLabelBMFont* line, const size_t lineSize);
|
||||
Line createLine(const std::string& text, Line& previousLine);
|
||||
std::vector<Line> createLines();
|
||||
std::vector<Line> createNotWrap(const Line& reference);
|
||||
std::vector<Line> createCutoffWrap(const std::string& text, Line& reference);
|
||||
std::vector<Line> createDelimitedWrap(const std::string& text, Line& reference, const std::string& delimiters);
|
||||
std::vector<Line> wrapper(const std::string& text, Line& reference, const std::function<std::vector<Line>(Line& currentLine)>& onOverflow);
|
||||
};
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include <Geode/ui/General.hpp>
|
||||
#include <Geode/ui/ScrollLayer.hpp>
|
||||
#include <Geode/ui/TextArea.hpp>
|
||||
#include <Geode/ui/TextAreaV2.hpp>
|
||||
#include <Geode/ui/IconButtonSprite.hpp>
|
||||
#include <Geode/binding/SetTextPopupDelegate.hpp>
|
||||
#include <Geode/binding/SetIDPopupDelegate.hpp>
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/Log.hpp>
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
#include <Geode/ui/TextArea.hpp>
|
||||
#include <Geode/utils/cocos.hpp>
|
||||
#include <Geode/utils/ColorProvider.hpp>
|
||||
#include <GUI/CCControlExtension/CCScale9Sprite.h>
|
||||
|
|
|
@ -323,9 +323,9 @@ bool ModList::init(ModListSource* src, CCSize const& size) {
|
|||
m_statusDetailsBtn->setID("status-details-button");
|
||||
m_statusContainer->addChild(m_statusDetailsBtn);
|
||||
|
||||
m_statusDetails = SimpleTextArea::create("", "chatFont.fnt", .6f);
|
||||
m_statusDetails = TextAreaV2::create("", "chatFont.fnt", .6f);
|
||||
m_statusDetails->setID("status-details-input");
|
||||
m_statusDetails->setAlignment(kCCTextAlignmentCenter);
|
||||
m_statusDetails->setAlignment(TextAreaV2::Alignment::Center);
|
||||
m_statusContainer->addChild(m_statusDetails);
|
||||
|
||||
m_statusLoadingCircle = createLoadingCircle(50);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include <Geode/ui/General.hpp>
|
||||
#include <Geode/ui/ScrollLayer.hpp>
|
||||
#include <Geode/ui/TextArea.hpp>
|
||||
#include <Geode/ui/TextAreaV2.hpp>
|
||||
#include <Geode/ui/TextInput.hpp>
|
||||
#include <Geode/ui/IconButtonSprite.hpp>
|
||||
#include <Geode/binding/TextArea.hpp>
|
||||
|
@ -26,7 +26,7 @@ protected:
|
|||
ScrollLayer* m_list;
|
||||
CCMenu* m_statusContainer;
|
||||
CCLabelBMFont* m_statusTitle;
|
||||
SimpleTextArea* m_statusDetails;
|
||||
TextAreaV2* m_statusDetails;
|
||||
CCMenuItemSpriteExtra* m_statusDetailsBtn;
|
||||
CCNode* m_statusLoadingCircle;
|
||||
Slider* m_statusLoadingBar;
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/Log.hpp>
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
#include <Geode/ui/TextArea.hpp>
|
||||
#include <Geode/ui/TextAreaV2.hpp>
|
||||
#include <Geode/utils/cocos.hpp>
|
||||
#include <Geode/utils/ColorProvider.hpp>
|
||||
#include <GUI/CCControlExtension/CCScale9Sprite.h>
|
||||
|
@ -57,15 +57,15 @@ bool ModProblemItem::init(Mod* source, LoadProblem problem, CCSize const& size)
|
|||
CCPoint { 10.0f, 0.0f }
|
||||
);
|
||||
|
||||
auto label = SimpleTextArea::create(
|
||||
auto label = TextAreaV2::create(
|
||||
message.c_str(),
|
||||
"bigFont.fnt"
|
||||
);
|
||||
label->setWrappingMode(WrappingMode::SPACE_WRAP);
|
||||
label->setWrappingMode(TextAreaV2::WrappingMode::SpaceWrap);
|
||||
label->setAnchorPoint({ 0.0f, 0.5f });
|
||||
label->setMaxLines(4);
|
||||
if (this->showFixButton() || this->showInfoButton()) {
|
||||
label->setWidth(size.width * 0.7f);
|
||||
label->setMaxWidth(size.width * 0.7f);
|
||||
|
||||
auto helpMenu = CCMenu::create();
|
||||
helpMenu->setAnchorPoint({ 1.0f, 0.5f });
|
||||
|
@ -92,7 +92,7 @@ bool ModProblemItem::init(Mod* source, LoadProblem problem, CCSize const& size)
|
|||
// Left + Right + Space between
|
||||
constexpr float paddings = 30.0f;
|
||||
float calc = size.width - paddings - icon->getScaledContentWidth();
|
||||
label->setWidth(calc);
|
||||
label->setMaxWidth(calc);
|
||||
}
|
||||
label->setScale(0.4f);
|
||||
this->addChildAtPosition(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include <Geode/ui/TextArea.hpp>
|
||||
#include <Geode/ui/SimpleTextArea.hpp>
|
||||
|
||||
using namespace geode::prelude;
|
||||
|
377
loader/src/ui/nodes/TextAreaV2.cpp
Normal file
377
loader/src/ui/nodes/TextAreaV2.cpp
Normal file
|
@ -0,0 +1,377 @@
|
|||
#include <Geode/ui/TextAreaV2.hpp>
|
||||
#include <Geode/DefaultInclude.hpp>
|
||||
|
||||
using namespace geode::prelude;
|
||||
|
||||
#define IMPL_GETTER(type, name, methodName) \
|
||||
type geode::TextAreaV2::get##methodName() const { return m_##name; }
|
||||
#define IMPL_GETTER_SETTER(type, paramType, name, methodName) \
|
||||
void geode::TextAreaV2::set##methodName(const paramType name) { m_##name = name; this->update(); } \
|
||||
IMPL_GETTER(type, name, methodName)
|
||||
|
||||
geode::TextAreaV2* geode::TextAreaV2::create(const std::string& text, const std::string& font, const float scale, const float width, const float height, const bool deferUpdates) {
|
||||
TextAreaV2* area = new TextAreaV2(text, font, scale, width, height, deferUpdates);
|
||||
|
||||
if (area && area->init()) {
|
||||
area->autorelease();
|
||||
|
||||
return area;
|
||||
} else {
|
||||
CC_SAFE_DELETE(area);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
geode::TextAreaV2::TextAreaV2(const std::string& text, const std::string& font, const float scale, const float width, const float height, const bool deferUpdates) :
|
||||
PaddingNode(CCNode::create()),
|
||||
m_text(text),
|
||||
m_font(font),
|
||||
m_textColor({ 255, 255, 255, 255 }),
|
||||
m_alignment(Alignment::Left),
|
||||
m_wrappingMode(WrappingMode::WordWrap),
|
||||
m_maxLines(-1),
|
||||
m_textScale(scale),
|
||||
m_linePadding(0),
|
||||
m_minWidth(-1),
|
||||
m_maxWidth(width),
|
||||
m_minHeight(-1),
|
||||
m_maxHeight(height),
|
||||
m_hyphenate(true),
|
||||
m_ellipsis(true),
|
||||
m_deferUpdates(deferUpdates),
|
||||
m_deferred(false) { }
|
||||
|
||||
bool geode::TextAreaV2::init() {
|
||||
if (!PaddingNode::init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->update();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPL_GETTER_SETTER(std::string, std::string&, font, Font)
|
||||
IMPL_GETTER_SETTER(std::string, std::string&, text, Text)
|
||||
IMPL_GETTER_SETTER(ccColor4B, ccColor4B&, textColor, TextColor)
|
||||
IMPL_GETTER_SETTER(geode::TextAreaV2::Alignment, Alignment, alignment, Alignment)
|
||||
IMPL_GETTER_SETTER(geode::TextAreaV2::WrappingMode, WrappingMode, wrappingMode, WrappingMode)
|
||||
IMPL_GETTER_SETTER(int, int, maxLines, MaxLines)
|
||||
IMPL_GETTER_SETTER(float, float, textScale, TextScale)
|
||||
IMPL_GETTER_SETTER(float, float, linePadding, LinePadding)
|
||||
IMPL_GETTER_SETTER(float, float, minWidth, MinWidth)
|
||||
IMPL_GETTER_SETTER(float, float, maxWidth, MaxWidth)
|
||||
IMPL_GETTER_SETTER(float, float, minHeight, MinHeight)
|
||||
IMPL_GETTER_SETTER(float, float, maxHeight, MaxHeight)
|
||||
IMPL_GETTER_SETTER(bool, bool, hyphenate, Hyphenate)
|
||||
IMPL_GETTER_SETTER(bool, bool, ellipsis, Ellipsis)
|
||||
IMPL_GETTER(bool, deferUpdates, DeferUpdates)
|
||||
IMPL_GETTER(std::vector<geode::TextAreaV2::Line>, lines, Lines)
|
||||
|
||||
void geode::TextAreaV2::resetMinWidth() {
|
||||
this->setMinWidth(-1);
|
||||
}
|
||||
|
||||
void geode::TextAreaV2::resetMaxWidth() {
|
||||
this->setMaxWidth(-1);
|
||||
}
|
||||
|
||||
void geode::TextAreaV2::resetMinHeight() {
|
||||
this->setMinHeight(-1);
|
||||
}
|
||||
|
||||
void geode::TextAreaV2::resetMaxHeight() {
|
||||
this->setMaxHeight(-1);
|
||||
}
|
||||
|
||||
void geode::TextAreaV2::resetMinBounds() {
|
||||
this->resetMinWidth();
|
||||
this->resetMinHeight();
|
||||
}
|
||||
|
||||
void geode::TextAreaV2::resetMaxBounds() {
|
||||
this->resetMaxWidth();
|
||||
this->resetMaxHeight();
|
||||
}
|
||||
|
||||
void geode::TextAreaV2::setDeferUpdates(const bool defer) {
|
||||
m_deferUpdates = defer;
|
||||
|
||||
// If already deferred, update the container immediately and unschedule the deferred update
|
||||
if (m_deferred) {
|
||||
this->unschedule(schedule_selector(TextAreaV2::updateContainer));
|
||||
|
||||
this->updateContainer();
|
||||
}
|
||||
}
|
||||
|
||||
float geode::TextAreaV2::getLineHeight() const {
|
||||
return m_lines.empty() ? 0 : m_lines.front().label->getScaledContentHeight() + m_linePadding;
|
||||
}
|
||||
|
||||
void geode::TextAreaV2::update() {
|
||||
if (!m_deferred) {
|
||||
if (m_deferUpdates) {
|
||||
m_deferred = true;
|
||||
|
||||
this->scheduleOnce(schedule_selector(TextAreaV2::updateContainer), 0);
|
||||
} else {
|
||||
this->updateContainer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void geode::TextAreaV2::updatePadding() {
|
||||
this->update();
|
||||
}
|
||||
|
||||
void geode::TextAreaV2::updateContainer(const float dt) {
|
||||
PaddingNode::updatePadding();
|
||||
m_container->removeAllChildren();
|
||||
|
||||
m_deferred = false;
|
||||
m_lines = this->createLines();
|
||||
|
||||
const bool upperBoundWidth = m_maxWidth >= 0;
|
||||
const bool upperBoundHeight = m_maxHeight >= 0;
|
||||
const bool artificialWidth = m_minWidth >= 0 || upperBoundWidth;
|
||||
const bool artificialHeight = m_minHeight >= 0 || upperBoundHeight;
|
||||
const bool inheritedNodeSize = this->getContentSize() == this->getPaddedContainerSize();
|
||||
float height = m_lines.empty() ? 0 : m_lines.back().currentHeight;
|
||||
float width = 0;
|
||||
|
||||
// First determine the container size before manipulating the node anchors and true positions
|
||||
for (const Line& line : m_lines) {
|
||||
width = std::max(width, line.label->getScaledContentWidth());
|
||||
}
|
||||
|
||||
if (artificialWidth) {
|
||||
width = std::max(m_minWidth, width);
|
||||
|
||||
m_container->setContentWidth(upperBoundWidth ? std::min(m_maxWidth, width) : width);
|
||||
} else {
|
||||
m_container->setContentWidth(width);
|
||||
}
|
||||
|
||||
if (artificialHeight) {
|
||||
height = std::max(m_minHeight, height);
|
||||
|
||||
m_container->setContentHeight(upperBoundHeight ? std::min(m_maxHeight, height) : height);
|
||||
} else {
|
||||
m_container->setContentHeight(height);
|
||||
}
|
||||
|
||||
if (inheritedNodeSize) {
|
||||
this->setContentSize(this->getPaddedContainerSize());
|
||||
}
|
||||
|
||||
for (const Line& line : m_lines) {
|
||||
this->updateLineAlignment(line.label);
|
||||
|
||||
// Correct the Y position to be relative to the container height
|
||||
line.label->setPositionY(m_container->getContentHeight() - line.currentHeight + line.label->getScaledContentHeight());
|
||||
m_container->addChild(line.label);
|
||||
}
|
||||
}
|
||||
|
||||
void geode::TextAreaV2::updateLineAlignment(CCLabelBMFont* line) {
|
||||
switch (m_alignment) {
|
||||
case Alignment::Left:
|
||||
line->setAnchorPoint({ 0, 1 });
|
||||
line->setPositionX(0);
|
||||
break;
|
||||
case Alignment::Center:
|
||||
line->setAnchorPoint({ 0.5f, 1 });
|
||||
line->setPositionX(m_container->getContentWidth() / 2);
|
||||
break;
|
||||
case Alignment::Right:
|
||||
line->setAnchorPoint({ 1, 1 });
|
||||
line->setPositionX(m_container->getContentWidth());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void geode::TextAreaV2::addEllipsis(Line& line) {
|
||||
if (!m_ellipsis || line.lineNumber == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
line.text = line.text.find_first_not_of(' ') == std::string::npos ?
|
||||
"..." :
|
||||
line.text + std::string("...").substr(std::min<size_t>(3, line.text.size() - line.text.find_last_not_of('.') - 1));
|
||||
|
||||
line.label->setString(line.text.c_str());
|
||||
|
||||
if (this->isWidthOverflowing(line.label)) {
|
||||
const size_t lineSize = line.text.size();
|
||||
const size_t overflow = this->getOverflowAmount(line.label, lineSize + 3);
|
||||
|
||||
line.overflow = line.text.substr(lineSize - overflow - 3, overflow) + line.overflow;
|
||||
|
||||
line.label->setString((line.text = line.text.substr(0, lineSize - overflow - 3) + "...").c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool geode::TextAreaV2::isWidthOverflowing(const CCLabelBMFont* line) {
|
||||
return m_wrappingMode != WrappingMode::NoWrap && m_maxWidth >= 0 && line->getScaledContentWidth() + this->getTotalPaddingX() > m_maxWidth;
|
||||
}
|
||||
|
||||
size_t geode::TextAreaV2::getOverflowAmount(CCLabelBMFont* line, const size_t lineSize) {
|
||||
for (size_t overflow = 1; overflow < lineSize; overflow++) {
|
||||
CCNode* character = cocos::getChild(line, lineSize - overflow);
|
||||
|
||||
if ((character->getPositionX() - character->getContentWidth() / 2) * m_textScale <= m_maxWidth) {
|
||||
return overflow;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
geode::TextAreaV2::Line geode::TextAreaV2::createLine(const std::string& text, Line& previousLine) {
|
||||
CCLabelBMFont* line = CCLabelBMFont::create(text.c_str(), m_font.c_str());
|
||||
|
||||
line->setScale(m_textScale);
|
||||
line->setColor({ m_textColor.r, m_textColor.g, m_textColor.b });
|
||||
line->setOpacity(m_textColor.a);
|
||||
|
||||
const size_t lineSize = text.size();
|
||||
const float currentHeight = m_linePadding + previousLine.currentHeight + line->getScaledContentHeight();
|
||||
const size_t overflow = this->isWidthOverflowing(line) ? this->getOverflowAmount(line, lineSize) : 0;
|
||||
const size_t overflowStart = lineSize - overflow;
|
||||
const std::string finalText = text.substr(0, overflowStart);
|
||||
const bool exceededLines = (m_maxLines != -1 && previousLine.lineNumber + 1 >= m_maxLines) ||
|
||||
(m_maxHeight != -1 && currentHeight + this->getTotalPaddingY() > m_maxHeight);
|
||||
|
||||
if (overflow > 0) {
|
||||
line->setString(finalText.c_str());
|
||||
}
|
||||
|
||||
previousLine.isLastLine = exceededLines;
|
||||
|
||||
return {
|
||||
.text = finalText,
|
||||
.overflow = text.substr(overflowStart, overflow),
|
||||
.label = line,
|
||||
.lineNumber = previousLine.lineNumber + 1,
|
||||
.currentHeight = currentHeight,
|
||||
.isLastLine = false
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<geode::TextAreaV2::Line> geode::TextAreaV2::createLines() {
|
||||
Line placeholderLine = {
|
||||
.lineNumber = -1,
|
||||
.currentHeight = -m_linePadding
|
||||
};
|
||||
|
||||
switch (m_wrappingMode) {
|
||||
case WrappingMode::NoWrap: return this->createNotWrap(placeholderLine);
|
||||
case WrappingMode::WordWrap: return this->createDelimitedWrap(m_text, placeholderLine, " `~!@#$%^&*()-_=+[{}];:'\",<.>/?\\|");
|
||||
case WrappingMode::SpaceWrap: return this->createDelimitedWrap(m_text, placeholderLine, " ");
|
||||
case WrappingMode::CutoffWrap: return this->createCutoffWrap(m_text, placeholderLine);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<geode::TextAreaV2::Line> geode::TextAreaV2::createNotWrap(const Line& reference) {
|
||||
Line previousLine = reference;
|
||||
std::stringstream stream(m_text);
|
||||
std::vector<Line> lines;
|
||||
std::string line;
|
||||
|
||||
while (std::getline(stream, line)) {
|
||||
const Line currentLine = this->createLine(line, previousLine);
|
||||
|
||||
if (previousLine.isLastLine) {
|
||||
this->addEllipsis(previousLine);
|
||||
|
||||
break;
|
||||
} else {
|
||||
lines.push_back(previousLine = currentLine);
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
std::vector<geode::TextAreaV2::Line> geode::TextAreaV2::createCutoffWrap(const std::string& text, Line& reference) {
|
||||
return this->wrapper(text, reference, [this](Line& currentLine) {
|
||||
if (currentLine.text.empty()) return std::vector<Line>();
|
||||
|
||||
if (m_hyphenate && currentLine.text.back() != '-') {
|
||||
currentLine.overflow = currentLine.text.back() + currentLine.overflow;
|
||||
currentLine.text.pop_back();
|
||||
currentLine.text = currentLine.text.substr(0, currentLine.text.find_last_not_of(' ') + 1) + "-";
|
||||
|
||||
currentLine.label->setString(currentLine.text.c_str());
|
||||
}
|
||||
|
||||
return this->createCutoffWrap(currentLine.overflow.erase(0, currentLine.overflow.find_first_not_of(' ')), currentLine);
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<geode::TextAreaV2::Line> geode::TextAreaV2::createDelimitedWrap(const std::string& text, Line& reference, const std::string& delimiters) {
|
||||
return this->wrapper(text, reference, [this, delimiters](Line& currentLine) {
|
||||
if (currentLine.text.empty()) return std::vector<Line>();
|
||||
|
||||
const size_t lineSize = currentLine.text.size();
|
||||
size_t additionalOverflow = 0;
|
||||
|
||||
while (additionalOverflow < lineSize && delimiters.find(currentLine.text[lineSize - additionalOverflow - 1]) == std::string::npos) {
|
||||
additionalOverflow++;
|
||||
}
|
||||
|
||||
if (additionalOverflow == lineSize) additionalOverflow = 0;
|
||||
|
||||
currentLine.overflow = currentLine.text.substr(lineSize - additionalOverflow) + currentLine.overflow;
|
||||
currentLine.text = currentLine.text.substr(0, lineSize - additionalOverflow);
|
||||
|
||||
if (m_hyphenate && currentLine.text.back() != '-') {
|
||||
if (additionalOverflow == 0) {
|
||||
currentLine.overflow = currentLine.text.back() + currentLine.overflow;
|
||||
currentLine.text.pop_back();
|
||||
}
|
||||
|
||||
currentLine.text = currentLine.text.substr(0, currentLine.text.find_last_not_of(' ') + 1) + "-";
|
||||
}
|
||||
|
||||
currentLine.label->setString(currentLine.text.c_str());
|
||||
|
||||
return this->createDelimitedWrap(currentLine.overflow.erase(0, currentLine.overflow.find_first_not_of(' ')), currentLine, delimiters);
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<geode::TextAreaV2::Line> geode::TextAreaV2::wrapper(const std::string& text, Line& reference, const std::function<std::vector<Line>(Line& currentLine)>& onOverflow) {
|
||||
Line previousLine = reference;
|
||||
std::stringstream stream(text);
|
||||
std::vector<Line> lines;
|
||||
std::string line;
|
||||
|
||||
while (std::getline(stream, line)) {
|
||||
Line currentLine = this->createLine(line, previousLine);
|
||||
|
||||
if (previousLine.isLastLine) {
|
||||
if (previousLine.lineNumber == reference.lineNumber) {
|
||||
reference.isLastLine = true;
|
||||
|
||||
this->addEllipsis(reference);
|
||||
} else {
|
||||
this->addEllipsis(previousLine);
|
||||
}
|
||||
|
||||
break;
|
||||
} else if (currentLine.overflow.empty()) {
|
||||
lines.push_back(previousLine = currentLine);
|
||||
} else {
|
||||
const std::vector<Line> overflowedLines = onOverflow(currentLine);
|
||||
|
||||
lines.push_back(currentLine);
|
||||
lines.insert(lines.end(), overflowedLines.begin(), overflowedLines.end());
|
||||
|
||||
if ((previousLine = lines.back()).isLastLine) break;
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
Loading…
Reference in a new issue