RichText and ScrollView enhancements (#1696)

* Add id tag to several RichText elements to allow locating the nodes in RichText
Add font related styling to paragraph tags

* Move functionality out of ListView and into ScrollView to allow scrolling to a specific child node within a ScrollView

* Add function to allow finding protected child node by name

* Example of anchor tags to local RichText content in a ScrollView

* Fix string storage type
This commit is contained in:
RH 2024-02-18 23:09:54 +11:00 committed by GitHub
parent 8e2e577e2f
commit 978d15db78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 528 additions and 163 deletions

View File

@ -128,6 +128,17 @@ Node* ProtectedNode::getProtectedChildByTag(int tag)
return nullptr;
}
Node* ProtectedNode::getProtectedChildByName(std::string_view name)
{
// AXASSERT(!name.empty(), "Invalid name");
for (auto&& child : _protectedChildren)
{
if (child && child->getName() == name)
return child;
}
return nullptr;
}
/* "remove" logic MUST only be on this method
* If a class want's to extend the 'removeChild' behavior it only needs
* to override this method

View File

@ -94,6 +94,15 @@ public:
*/
virtual Node* getProtectedChildByTag(int tag);
/**
* Gets a child from the container with its name.
*
* @param name An identifier to find the child node.
*
* @return a Node object whose name equals to the input parameter.
*/
virtual Node* getProtectedChildByName(std::string_view name);
////// REMOVES //////
/**

View File

@ -1,6 +1,7 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md).
https://axmolengine.github.io/
@ -28,8 +29,6 @@ THE SOFTWARE.
NS_AX_BEGIN
static const float DEFAULT_TIME_IN_SEC_FOR_SCROLL_TO_ITEM = 1.0f;
namespace ui
{
@ -45,7 +44,6 @@ ListView::ListView()
, _topPadding(0.0f)
, _rightPadding(0.0f)
, _bottomPadding(0.0f)
, _scrollTime(DEFAULT_TIME_IN_SEC_FOR_SCROLL_TO_ITEM)
, _curSelectedIndex(-1)
, _innerContainerDoLayoutDirty(true)
, _eventCallback(nullptr)
@ -523,17 +521,6 @@ float ListView::getBottomPadding() const
return _bottomPadding;
}
void ListView::setScrollDuration(float time)
{
if (time >= 0)
_scrollTime = time;
}
float ListView::getScrollDuration() const
{
return _scrollTime;
}
void ListView::setDirection(Direction dir)
{
switch (dir)
@ -642,13 +629,6 @@ void ListView::interceptTouchEvent(TouchEventType event, Widget* sender, Touch*
}
}
static Vec2 calculateItemPositionWithAnchor(Widget* item, const Vec2& itemAnchorPoint)
{
Vec2 origin(item->getLeftBoundary(), item->getBottomBoundary());
Vec2 size = item->getContentSize();
return origin + Vec2(size.width * itemAnchorPoint.x, size.height * itemAnchorPoint.y);
}
static Widget* findClosestItem(const Vec2& targetPosition,
const Vector<Widget*>& items,
const Vec2& itemAnchorPoint,
@ -676,7 +656,7 @@ static Widget* findClosestItem(const Vec2& targetPosition,
// Binary search
ssize_t midIndex = (firstIndex + lastIndex) / 2;
Vec2 itemPosition = calculateItemPositionWithAnchor(items.at(midIndex), itemAnchorPoint);
Vec2 itemPosition = ListView::calculateItemPositionWithAnchor(items.at(midIndex), itemAnchorPoint);
float distanceFromMid = (targetPosition - itemPosition).length();
if (distanceFromFirst <= distanceFromLast)
{
@ -830,17 +810,6 @@ void ListView::jumpToPercentBothDirection(const Vec2& percent)
ScrollView::jumpToPercentBothDirection(percent);
}
Vec2 ListView::calculateItemDestination(const Vec2& positionRatioInView, Widget* item, const Vec2& itemAnchorPoint)
{
const Vec2& contentSize = getContentSize();
Vec2 positionInView;
positionInView.x += contentSize.width * positionRatioInView.x;
positionInView.y += contentSize.height * positionRatioInView.y;
Vec2 itemPosition = calculateItemPositionWithAnchor(item, itemAnchorPoint);
return -(itemPosition - positionInView);
}
void ListView::jumpToItem(ssize_t itemIndex, const Vec2& positionRatioInView, const Vec2& itemAnchorPoint)
{
Widget* item = getItem(itemIndex);
@ -875,8 +844,7 @@ void ListView::scrollToItem(ssize_t itemIndex,
{
return;
}
Vec2 destination = calculateItemDestination(positionRatioInView, item, itemAnchorPoint);
startAutoScrollToDestination(destination, timeInSec, true);
ScrollView::scrollToItem(item, positionRatioInView, itemAnchorPoint, timeInSec);
}
ssize_t ListView::getCurSelectedIndex() const
@ -920,12 +888,12 @@ void ListView::copyClonedWidgetChildren(Widget* model)
}
}
void ListView::copySpecialProperties(Widget* widget)
void ListView::copySpecialProperties(Widget* model)
{
ListView* listViewEx = dynamic_cast<ListView*>(widget);
ListView* listViewEx = dynamic_cast<ListView*>(model);
if (listViewEx)
{
ScrollView::copySpecialProperties(widget);
ScrollView::copySpecialProperties(model);
setItemModel(listViewEx->_model);
setItemsMargin(listViewEx->_itemsMargin);
setGravity(listViewEx->_gravity);

View File

@ -1,6 +1,7 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md).
https://axmolengine.github.io/
@ -106,7 +107,7 @@ public:
* @js NA
* @lua NA
*/
virtual ~ListView();
~ListView() override;
/**
* Create an empty ListView.
@ -288,31 +289,16 @@ public:
*/
float getBottomPadding() const;
/**
* Set the time in seconds to scroll between items.
* Subsequent calls of function 'scrollToItem', will take 'time' seconds for scrolling.
* @param time The seconds needed to scroll between two items. 'time' must be >= 0
* @see scrollToItem(ssize_t, const Vec2&, const Vec2&)
*/
void setScrollDuration(float time);
/**
* Get the time in seconds to scroll between items.
* @return The time in seconds to scroll between items
* @see setScrollDuration(float)
*/
float getScrollDuration() const;
// override methods
virtual void doLayout() override;
virtual void requestDoLayout() override;
virtual void addChild(Node* child) override;
virtual void addChild(Node* child, int localZOrder) override;
virtual void addChild(Node* child, int zOrder, int tag) override;
virtual void addChild(Node* child, int zOrder, std::string_view name) override;
virtual void removeAllChildren() override;
virtual void removeAllChildrenWithCleanup(bool cleanup) override;
virtual void removeChild(Node* child, bool cleanup = true) override;
void doLayout() override;
void requestDoLayout() override;
void addChild(Node* child) override;
void addChild(Node* child, int localZOrder) override;
void addChild(Node* child, int zOrder, int tag) override;
void addChild(Node* child, int zOrder, std::string_view name) override;
void removeAllChildren() override;
void removeAllChildrenWithCleanup(bool cleanup) override;
void removeChild(Node* child, bool cleanup = true) override;
/**
* @brief Query the closest item to a specific position in inner container.
@ -367,17 +353,17 @@ public:
/**
* Override functions
*/
virtual void jumpToBottom() override;
virtual void jumpToTop() override;
virtual void jumpToLeft() override;
virtual void jumpToRight() override;
virtual void jumpToTopLeft() override;
virtual void jumpToTopRight() override;
virtual void jumpToBottomLeft() override;
virtual void jumpToBottomRight() override;
virtual void jumpToPercentVertical(float percent) override;
virtual void jumpToPercentHorizontal(float percent) override;
virtual void jumpToPercentBothDirection(const Vec2& percent) override;
void jumpToBottom() override;
void jumpToTop() override;
void jumpToLeft() override;
void jumpToRight() override;
void jumpToTopLeft() override;
void jumpToTopRight() override;
void jumpToBottomLeft() override;
void jumpToBottomRight() override;
void jumpToPercentVertical(float percent) override;
void jumpToPercentHorizontal(float percent) override;
void jumpToPercentBothDirection(const Vec2& percent) override;
/**
* @brief Jump to specific item
@ -391,15 +377,20 @@ public:
* @brief Scroll to specific item
* @param positionRatioInView Specifies the position with ratio in list view's content size.
* @param itemAnchorPoint Specifies an anchor point of each item for position to calculate distance.
* @param timeInSec Scroll time
*/
void scrollToItem(ssize_t itemIndex, const Vec2& positionRatioInView, const Vec2& itemAnchorPoint);
/**
* @brief Scroll to specific item
* @param positionRatioInView Specifies the position with ratio in list view's content size.
* @param itemAnchorPoint Specifies an anchor point of each item for position to calculate distance.
* @param timeInSec Scroll time
*/
void scrollToItem(ssize_t itemIndex, const Vec2& positionRatioInView, const Vec2& itemAnchorPoint, float timeInSec);
/**
* @brief Query current selected widget's index.
*
* @return An index of a selected item.
*/
ssize_t getCurSelectedIndex() const;
@ -423,14 +414,14 @@ public:
* Direction Direction::VERTICAL means vertical scroll, Direction::HORIZONTAL means horizontal scroll.
* @param dir Set the list view's scroll direction.
*/
virtual void setDirection(Direction dir) override;
void setDirection(Direction dir) override;
virtual std::string getDescription() const override;
std::string getDescription() const override;
virtual bool init() override;
bool init() override;
protected:
virtual void handleReleaseLogic(Touch* touch) override;
void handleReleaseLogic(Touch* touch) override;
virtual void onItemListChanged();
@ -439,19 +430,18 @@ protected:
void remedyVerticalLayoutParameter(LinearLayoutParameter* layoutParameter, ssize_t itemIndex);
void remedyHorizontalLayoutParameter(LinearLayoutParameter* layoutParameter, ssize_t itemIndex);
virtual void onSizeChanged() override;
virtual Widget* createCloneInstance() override;
virtual void copySpecialProperties(Widget* model) override;
virtual void copyClonedWidgetChildren(Widget* model) override;
void onSizeChanged() override;
Widget* createCloneInstance() override;
void copySpecialProperties(Widget* model) override;
void copyClonedWidgetChildren(Widget* model) override;
void selectedItemEvent(TouchEventType event);
virtual void interceptTouchEvent(Widget::TouchEventType event, Widget* sender, Touch* touch) override;
void interceptTouchEvent(Widget::TouchEventType event, Widget* sender, Touch* touch) override;
virtual Vec2 getHowMuchOutOfBoundary(const Vec2& addition = Vec2::ZERO) override;
Vec2 getHowMuchOutOfBoundary(const Vec2& addition = Vec2::ZERO) override;
virtual void startAttenuatingAutoScroll(const Vec2& deltaMove, const Vec2& initialVelocity) override;
void startAttenuatingAutoScroll(const Vec2& deltaMove, const Vec2& initialVelocity) override;
void startMagneticScroll();
Vec2 calculateItemDestination(const Vec2& positionRatioInView, Widget* item, const Vec2& itemAnchorPoint);
protected:
Widget* _model;
@ -470,8 +460,6 @@ protected:
float _rightPadding;
float _bottomPadding;
float _scrollTime;
ssize_t _curSelectedIndex;
bool _innerContainerDoLayoutDirty;

View File

@ -151,11 +151,12 @@ RichElementText* RichElementText::create(int tag,
const Color3B& shadowColor,
const Vec2& shadowOffset,
int shadowBlurRadius,
const Color3B& glowColor)
const Color3B& glowColor,
std::string_view id)
{
RichElementText* element = new RichElementText();
if (element->init(tag, color, opacity, text, fontName, fontSize, flags, url, outlineColor, outlineSize, shadowColor,
shadowOffset, shadowBlurRadius, glowColor))
shadowOffset, shadowBlurRadius, glowColor, id))
{
element->autorelease();
return element;
@ -177,7 +178,8 @@ bool RichElementText::init(int tag,
const Color3B& shadowColor,
const Vec2& shadowOffset,
int shadowBlurRadius,
const Color3B& glowColor)
const Color3B& glowColor,
std::string_view id)
{
if (RichElement::init(tag, color, opacity))
{
@ -192,6 +194,7 @@ bool RichElementText::init(int tag,
_shadowOffset = shadowOffset;
_shadowBlurRadius = shadowBlurRadius;
_glowColor = glowColor;
_id = id;
return true;
}
return false;
@ -202,10 +205,11 @@ RichElementImage* RichElementImage::create(int tag,
uint8_t opacity,
std::string_view filePath,
std::string_view url,
Widget::TextureResType texType)
Widget::TextureResType texType,
std::string_view id)
{
RichElementImage* element = new RichElementImage();
if (element->init(tag, color, opacity, filePath, url, texType))
if (element->init(tag, color, opacity, filePath, url, texType, id))
{
element->autorelease();
return element;
@ -219,7 +223,8 @@ bool RichElementImage::init(int tag,
uint8_t opacity,
std::string_view filePath,
std::string_view url,
Widget::TextureResType texType)
Widget::TextureResType texType,
std::string_view id)
{
if (RichElement::init(tag, color, opacity))
{
@ -230,6 +235,7 @@ bool RichElementImage::init(int tag,
_scaleY = 1.0f;
_url = url;
_textureType = texType;
_id = id;
return true;
}
return false;
@ -250,13 +256,19 @@ void RichElementImage::setUrl(std::string_view url)
_url = url;
}
void RichElementImage::setId(std::string_view id)
{
_id = id;
}
RichElementCustomNode* RichElementCustomNode::create(int tag,
const Color3B& color,
uint8_t opacity,
ax::Node* customNode)
ax::Node* customNode,
std::string_view id)
{
RichElementCustomNode* element = new RichElementCustomNode();
if (element->init(tag, color, opacity, customNode))
if (element->init(tag, color, opacity, customNode, id))
{
element->autorelease();
return element;
@ -265,11 +277,16 @@ RichElementCustomNode* RichElementCustomNode::create(int tag,
return nullptr;
}
bool RichElementCustomNode::init(int tag, const Color3B& color, uint8_t opacity, ax::Node* customNode)
bool RichElementCustomNode::init(int tag,
const Color3B& color,
uint8_t opacity,
ax::Node* customNode,
std::string_view id)
{
if (RichElement::init(tag, color, opacity))
{
_customNode = customNode;
_id = id;
_customNode->retain();
return true;
}
@ -339,6 +356,7 @@ public:
Vec2 shadowOffset; /*!< shadow effect offset value */
int shadowBlurRadius; /*!< the shadow effect blur radius */
Color3B glowColor; /*!< the glow effect color value */
std::string name; /*!< the name of this element */
void setColor(const Color3B& acolor)
{
@ -398,6 +416,8 @@ public:
std::tuple<bool, Color3B> getGlow() const;
std::string getName() const;
void startElement(void* ctx, const char* name, const char** atts) override;
void endElement(void* ctx, const char* name) override;
@ -569,6 +589,11 @@ MyXMLVisitor::MyXMLVisitor(RichText* richText) : _fontElements(20), _richText(ri
elementImg->setScaleX(scaleX);
elementImg->setScaleY(scaleY);
if (tagAttrValueMap.find("id") != tagAttrValueMap.end())
{
elementImg->setId(tagAttrValueMap.at("id").asString());
}
}
return make_pair(ValueMap(), elementImg);
});
@ -581,6 +606,12 @@ MyXMLVisitor::MyXMLVisitor(RichText* richText) : _fontElements(20), _richText(ri
{
attrValueMap[RichText::KEY_URL] = tagAttrValueMap.at("href").asString();
}
if (tagAttrValueMap.find("id") != tagAttrValueMap.end())
{
attrValueMap[RichText::KEY_ID] = tagAttrValueMap.at("id").asString();
}
return make_pair(attrValueMap, nullptr);
});
@ -589,8 +620,33 @@ MyXMLVisitor::MyXMLVisitor(RichText* richText) : _fontElements(20), _richText(ri
return make_pair(ValueMap(), richElement);
});
MyXMLVisitor::setTagDescription("p", false, nullptr,
[] { return RichElementNewLine::create(0, 2, Color3B::WHITE, 255); });
MyXMLVisitor::setTagDescription(
"p", true,
[](const ValueMap& tagAttrValueMap) -> std::pair<ValueMap, RichElement*> {
ValueMap attrValueMap;
if (tagAttrValueMap.find("size") != tagAttrValueMap.end())
{
attrValueMap[RichText::KEY_FONT_SIZE] = tagAttrValueMap.at("size").asString();
}
if (tagAttrValueMap.find("color") != tagAttrValueMap.end())
{
attrValueMap[RichText::KEY_FONT_COLOR_STRING] = tagAttrValueMap.at("color").asString();
}
if (tagAttrValueMap.find("face") != tagAttrValueMap.end())
{
attrValueMap[RichText::KEY_FONT_FACE] = tagAttrValueMap.at("face").asString();
}
if (tagAttrValueMap.find("id") != tagAttrValueMap.end())
{
attrValueMap[RichText::KEY_ID] = tagAttrValueMap.at("id").asString();
}
return make_pair(attrValueMap, nullptr);
},
[] { return RichElementNewLine::create(0, 2, Color3B::WHITE, 255); });
constexpr auto headerTagEnterHandler = [](const ValueMap& tagAttrValueMap,
float defaultFontSize) -> std::pair<ValueMap, RichElement*> {
@ -616,6 +672,11 @@ MyXMLVisitor::MyXMLVisitor(RichText* richText) : _fontElements(20), _richText(ri
attrValueMap[RichText::KEY_TEXT_BOLD] = true;
if (tagAttrValueMap.find("id") != tagAttrValueMap.end())
{
attrValueMap[RichText::KEY_ID] = tagAttrValueMap.at("id").asString();
}
return make_pair(attrValueMap, nullptr);
};
@ -817,6 +878,16 @@ std::tuple<bool, Color3B> MyXMLVisitor::getGlow() const
return std::make_tuple(false, Color3B::WHITE);
}
std::string MyXMLVisitor::getName() const
{
for (auto i = _fontElements.rbegin(), iRend = _fontElements.rend(); i != iRend; ++i)
{
if (!i->name.empty())
return i->name;
}
return "";
}
void MyXMLVisitor::startElement(void* /*ctx*/, const char* elementName, const char** atts)
{
auto it = _tagTables.find(elementName);
@ -960,7 +1031,49 @@ void MyXMLVisitor::startElement(void* /*ctx*/, const char* elementName, const ch
}
}
}
if (attrValueMap.find(RichText::KEY_URL) != attrValueMap.end())
{
attributes.url = attrValueMap.at(RichText::KEY_URL).asString();
attributes.setColor(_richText->getAnchorFontColor3B());
if (_richText->isAnchorTextBoldEnabled())
{
attributes.bold = true;
}
if (_richText->isAnchorTextItalicEnabled())
{
attributes.italics = true;
}
if (_richText->isAnchorTextUnderlineEnabled())
{
attributes.line = StyleLine::UNDERLINE;
}
if (_richText->isAnchorTextDelEnabled())
{
attributes.line = StyleLine::STRIKETHROUGH;
}
if (_richText->isAnchorTextOutlineEnabled())
{
attributes.effect = StyleEffect::OUTLINE;
attributes.outlineColor = _richText->getAnchorTextOutlineColor3B();
attributes.outlineSize = _richText->getAnchorTextOutlineSize();
}
if (_richText->isAnchorTextShadowEnabled())
{
attributes.effect = StyleEffect::SHADOW;
attributes.shadowColor = _richText->getAnchorTextShadowColor3B();
attributes.shadowOffset = _richText->getAnchorTextShadowOffset();
attributes.shadowBlurRadius = _richText->getAnchorTextShadowBlurRadius();
}
if (_richText->isAnchorTextGlowEnabled())
{
attributes.effect = StyleEffect::GLOW;
attributes.glowColor = _richText->getAnchorTextGlowColor3B();
}
}
if (attrValueMap.find(RichText::KEY_ID) != attrValueMap.end())
{
attributes.name = attrValueMap.at(RichText::KEY_ID).asString();
}
pushBackFontElement(attributes);
}
if (richElement)
@ -1017,6 +1130,7 @@ void MyXMLVisitor::textHandler(void* /*ctx*/, const char* str, size_t len)
auto outline = getOutline();
auto shadow = getShadow();
auto glow = getGlow();
auto name = getName();
uint32_t flags = 0;
if (italics)
@ -1038,7 +1152,7 @@ void MyXMLVisitor::textHandler(void* /*ctx*/, const char* str, size_t len)
auto element = RichElementText::create(0, color, 255, text, face, fontSize, flags, url, std::get<1>(outline),
std::get<2>(outline), std::get<1>(shadow), std::get<2>(shadow),
std::get<3>(shadow), std::get<1>(glow));
std::get<3>(shadow), std::get<1>(glow), name);
_richText->pushBackElement(element);
}
@ -1125,6 +1239,7 @@ const std::string RichText::KEY_ANCHOR_TEXT_SHADOW_OFFSET_WIDTH("KEY_ANCHOR_TEXT
const std::string RichText::KEY_ANCHOR_TEXT_SHADOW_OFFSET_HEIGHT("KEY_ANCHOR_TEXT_SHADOW_OFFSET_HEIGHT");
const std::string RichText::KEY_ANCHOR_TEXT_SHADOW_BLUR_RADIUS("KEY_ANCHOR_TEXT_SHADOW_BLUR_RADIUS");
const std::string RichText::KEY_ANCHOR_TEXT_GLOW_COLOR("KEY_ANCHOR_TEXT_GLOW_COLOR");
const std::string RichText::KEY_ID("KEY_ID");
RichText::RichText() : _formatTextDirty(true), _leftSpaceWidth(0.0f)
{
@ -1702,6 +1817,9 @@ void RichText::formatText(bool force)
label->enableGlow(Color4B(elmtText->_glowColor));
}
label->setTextColor(Color4B(elmtText->_color));
label->setName(elmtText->_id);
elementRenderer = label;
break;
}
@ -1732,6 +1850,7 @@ void RichText::formatText(bool force)
UrlTouchListenerComponent::create(elementRenderer, elmtImage->_url,
std::bind(&RichText::openUrl, this, std::placeholders::_1)));
elementRenderer->setColor(element->_color);
elementRenderer->setName(elmtImage->_id);
}
break;
}
@ -1774,7 +1893,7 @@ void RichText::formatText(bool force)
handleTextRenderer(elmtText->_text, elmtText->_fontName, elmtText->_fontSize, elmtText->_color,
elmtText->_opacity, elmtText->_flags, elmtText->_url, elmtText->_outlineColor,
elmtText->_outlineSize, elmtText->_shadowColor, elmtText->_shadowOffset,
elmtText->_shadowBlurRadius, elmtText->_glowColor);
elmtText->_shadowBlurRadius, elmtText->_glowColor, elmtText->_id);
break;
}
case RichElement::Type::IMAGE:
@ -1782,13 +1901,13 @@ void RichText::formatText(bool force)
RichElementImage* elmtImage = static_cast<RichElementImage*>(element);
handleImageRenderer(elmtImage->_filePath, elmtImage->_textureType, elmtImage->_color,
elmtImage->_opacity, elmtImage->_width, elmtImage->_height, elmtImage->_url,
elmtImage->_scaleX, elmtImage->_scaleY);
elmtImage->_scaleX, elmtImage->_scaleY, elmtImage->_id);
break;
}
case RichElement::Type::CUSTOM:
{
RichElementCustomNode* elmtCustom = static_cast<RichElementCustomNode*>(element);
handleCustomRenderer(elmtCustom->_customNode);
handleCustomRenderer(elmtCustom->_customNode, elmtCustom->_id);
break;
}
case RichElement::Type::NEWLINE:
@ -1972,7 +2091,8 @@ void RichText::handleTextRenderer(std::string_view text,
const Color3B& shadowColor,
const Vec2& shadowOffset,
int shadowBlurRadius,
const Color3B& glowColor)
const Color3B& glowColor,
std::string_view id)
{
bool fileExist = FileUtils::getInstance()->isFileExist(fontName);
RichText::WrapMode wrapMode = static_cast<RichText::WrapMode>(_defaults.at(KEY_WRAP_MODE).asInt());
@ -1982,6 +2102,7 @@ void RichText::handleTextRenderer(std::string_view text,
ss << text;
std::string currentText;
size_t realLines = 0;
auto isFirstLabel = true;
while (std::getline(ss, currentText, '\n'))
{
if (realLines > 0)
@ -2026,6 +2147,12 @@ void RichText::handleTextRenderer(std::string_view text,
textRenderer->setTextColor(Color4B(color));
textRenderer->setOpacity(opacity);
if (isFirstLabel && !id.empty())
{
textRenderer->setName(id);
isFirstLabel = false;
}
// textRendererWidth will get 0.0f, when we've got glError: 0x0501 in Label::getContentSize
// It happens when currentText is very very long so that can't generate a texture
const float textRendererWidth = textRenderer->getContentSize().width;
@ -2094,7 +2221,8 @@ void RichText::handleImageRenderer(std::string_view filePath,
int height,
std::string_view url,
float scaleX,
float scaleY)
float scaleY,
std::string_view id)
{
Sprite* imageRenderer;
if (textureType == Widget::TextureResType::LOCAL)
@ -2110,6 +2238,8 @@ void RichText::handleImageRenderer(std::string_view filePath,
if (height != -1)
imageRenderer->setScaleY(height / currentSize.height);
imageRenderer->setName(id);
imageRenderer->setScaleX(imageRenderer->getScaleX() * scaleX);
imageRenderer->setScaleY(imageRenderer->getScaleY() * scaleY);
@ -2122,9 +2252,15 @@ void RichText::handleImageRenderer(std::string_view filePath,
}
}
void RichText::handleCustomRenderer(ax::Node* renderer)
void RichText::handleCustomRenderer(ax::Node* renderer, std::string_view id)
{
Vec2 imgSize = renderer->getContentSize();
if (!id.empty())
{
renderer->setName(id);
}
_leftSpaceWidth -= imgSize.width;
if (_leftSpaceWidth < 0.0f)
{

View File

@ -121,6 +121,7 @@ public:
* @param shadowOffset shadow effect offset value
* @param shadowBlurRadius the shadow effect blur radius
* @param glowColor glow color
* @param id ID of element
* @return True if initialize success, false otherwise.
*/
bool init(int tag,
@ -136,7 +137,8 @@ public:
const Color3B& shadowColor = Color3B::BLACK,
const Vec2& shadowOffset = Vec2(2.0, -2.0),
int shadowBlurRadius = 0,
const Color3B& glowColor = Color3B::WHITE);
const Color3B& glowColor = Color3B::WHITE,
std::string_view id = ""sv);
/**
* @brief Create a RichElementText with various arguments.
@ -155,6 +157,7 @@ public:
* @param shadowOffset shadow effect offset value
* @param shadowBlurRadius the shadow effect blur radius
* @param glowColor glow color
* @param id ID of element
* @return RichElementText instance.
*/
static RichElementText* create(int tag,
@ -170,7 +173,8 @@ public:
const Color3B& shadowColor = Color3B::BLACK,
const Vec2& shadowOffset = Vec2(2.0, -2.0),
int shadowBlurRadius = 0,
const Color3B& glowColor = Color3B::WHITE);
const Color3B& glowColor = Color3B::WHITE,
std::string_view id = ""sv);
protected:
std::string _text;
@ -184,6 +188,7 @@ protected:
Vec2 _shadowOffset; /*!< shadow effect offset value */
int _shadowBlurRadius; /*!< the shadow effect blur radius */
Color3B _glowColor; /*!< attributes of glow tag */
std::string _id; /*!< ID of this text field */
friend class RichText;
};
@ -210,6 +215,7 @@ public:
* @param filePath A image file name.
* @param url uniform resource locator
* @param texType texture type, may be a valid file path, or a sprite frame name
* @param id ID of element
* @return True if initialize success, false otherwise.
*/
bool init(int tag,
@ -217,7 +223,8 @@ public:
uint8_t opacity,
std::string_view filePath,
std::string_view url = "",
Widget::TextureResType texType = Widget::TextureResType::LOCAL);
Widget::TextureResType texType = Widget::TextureResType::LOCAL,
std::string_view id = ""sv);
/**
* @brief Create a RichElementImage with various arguments.
@ -228,6 +235,7 @@ public:
* @param filePath A image file name.
* @param url uniform resource locator
* @param texType texture type, may be a valid file path, or a sprite frame name
* @param id ID of element
* @return A RichElementImage instance.
*/
static RichElementImage* create(int tag,
@ -235,7 +243,8 @@ public:
uint8_t opacity,
std::string_view filePath,
std::string_view url = "",
Widget::TextureResType texType = Widget::TextureResType::LOCAL);
Widget::TextureResType texType = Widget::TextureResType::LOCAL,
std::string_view id = ""sv);
void setWidth(int width);
void setHeight(int height);
@ -243,6 +252,7 @@ public:
void setScaleX(float scaleX) { _scaleX = scaleX; }
void setScaleY(float scaleY) { _scaleY = scaleY; }
void setUrl(std::string_view url);
void setId(std::string_view id);
protected:
std::string _filePath;
@ -254,6 +264,7 @@ protected:
float _scaleX;
float _scaleY;
std::string _url; /*!< attributes of anchor tag */
std::string _id; /*!< attributes of anchor tag */
};
/**
@ -283,9 +294,10 @@ public:
* @param color A color in Color3B.
* @param opacity A opacity in GLubyte.
* @param customNode A custom node pointer.
* @param id ID of element
* @return True if initialize success, false otherwise.
*/
bool init(int tag, const Color3B& color, uint8_t opacity, Node* customNode);
bool init(int tag, const Color3B& color, uint8_t opacity, Node* customNode, std::string_view id = ""sv);
/**
* @brief Create a RichElementCustomNode with various arguments.
@ -294,12 +306,19 @@ public:
* @param color A color in Color3B.
* @param opacity A opacity in GLubyte.
* @param customNode A custom node pointer.
* @param id ID of element
* @return A RichElementCustomNode instance.
*/
static RichElementCustomNode* create(int tag, const Color3B& color, uint8_t opacity, Node* customNode);
static RichElementCustomNode* create(int tag,
const Color3B& color,
uint8_t opacity,
Node* customNode,
std::string_view id = ""sv);
protected:
Node* _customNode{};
std::string _id;
friend class RichText;
};
@ -394,7 +413,7 @@ public:
static const std::string KEY_VERTICAL_SPACE; /*!< key of vertical space */
static const std::string KEY_WRAP_MODE; /*!< key of per word, or per char */
static const std::string KEY_HORIZONTAL_ALIGNMENT; /*!< key of left, right, or center */
static const std::string KEY_VERTICAL_ALIGNMENT; /*!< key of left, right, or center */
static const std::string KEY_VERTICAL_ALIGNMENT; /*!< key of left, right, or center */
static const std::string KEY_FONT_COLOR_STRING; /*!< key of font color */
static const std::string KEY_FONT_SIZE; /*!< key of font size */
static const std::string KEY_FONT_SMALL; /*!< key of font size small */
@ -431,6 +450,7 @@ public:
static const std::string KEY_ANCHOR_TEXT_SHADOW_OFFSET_HEIGHT; /*!< key of shadow offset (height) of anchor tag */
static const std::string KEY_ANCHOR_TEXT_SHADOW_BLUR_RADIUS; /*!< key of shadow blur radius of anchor tag */
static const std::string KEY_ANCHOR_TEXT_GLOW_COLOR; /*!< key of glow color of anchor tag */
static const std::string KEY_ID; /*!< key of id */
/**
* @brief Default constructor.
@ -611,7 +631,8 @@ protected:
const Color3B& shadowColor = Color3B::BLACK,
const Vec2& shadowOffset = Vec2(2.0, -2.0),
int shadowBlurRadius = 0,
const Color3B& glowColor = Color3B::WHITE);
const Color3B& glowColor = Color3B::WHITE,
std::string_view id = ""sv);
void handleImageRenderer(std::string_view filePath,
Widget::TextureResType textureType,
const Color3B& color,
@ -619,9 +640,10 @@ protected:
int width,
int height,
std::string_view url,
float scaleX = 1.f,
float scaleY = 1.f);
void handleCustomRenderer(Node* renderer);
float scaleX = 1.f,
float scaleY = 1.f,
std::string_view id = ""sv);
void handleCustomRenderer(Node* renderer, std::string_view id = ""sv);
void formatRenderers();
void addNewLine(int quantity = 1);
void doHorizontalAlignment(const Vector<Node*>& row, float rowWidth);

View File

@ -1,6 +1,7 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md).
https://axmolengine.github.io/
@ -32,19 +33,23 @@ THE SOFTWARE.
#include "2d/Camera.h"
NS_AX_BEGIN
static const int NUMBER_OF_GATHERED_TOUCHES_FOR_MOVE_SPEED = 5;
static const float OUT_OF_BOUNDARY_BREAKING_FACTOR = 0.05f;
static const float BOUNCE_BACK_DURATION = 1.0f;
namespace
{
constexpr float DEFAULT_TIME_IN_SEC_FOR_SCROLL_TO_ITEM = 1.0f;
constexpr int NUMBER_OF_GATHERED_TOUCHES_FOR_MOVE_SPEED = 5;
constexpr float OUT_OF_BOUNDARY_BREAKING_FACTOR = 0.05f;
constexpr float BOUNCE_BACK_DURATION = 1.0f;
#define MOVE_INCH 7.0f / 160.0f
static float convertDistanceFromPointToInch(const Vec2& dis)
float convertDistanceFromPointToInch(const Vec2& dis)
{
auto glView = Director::getInstance()->getGLView();
int dpi = Device::getDPI();
float distance = Vec2(dis.x * glView->getScaleX() / dpi, dis.y * glView->getScaleY() / dpi).getLength();
return distance;
}
} // namespace
#define MOVE_INCH (7.0f / 160.0f)
namespace ui
{
@ -78,6 +83,7 @@ ScrollView::ScrollView()
, _horizontalScrollBar(nullptr)
, _scrollViewEventListener(nullptr)
, _eventCallback(nullptr)
, _scrollTime(DEFAULT_TIME_IN_SEC_FOR_SCROLL_TO_ITEM)
{
setTouchEnabled(true);
_propagateTouchEvents = false;
@ -1587,6 +1593,51 @@ Widget* ScrollView::findNextFocusedWidget(ax::ui::Widget::FocusDirection directi
return Widget::findNextFocusedWidget(direction, current);
}
}
void ScrollView::setScrollDuration(float time)
{
if (time >= 0)
_scrollTime = time;
}
float ScrollView::getScrollDuration() const
{
return _scrollTime;
}
Vec2 ScrollView::calculateItemPositionWithAnchor(const Node* node, const Vec2& itemAnchorPoint)
{
auto boundingBox = node->getBoundingBox();
Vec2 origin(boundingBox.origin.x, boundingBox.origin.y);
Vec2 size = node->getContentSize();
return origin + Vec2(size.width * itemAnchorPoint.x, size.height * itemAnchorPoint.y);
}
Vec2 ScrollView::calculateItemDestination(const Vec2& positionRatioInView, const Node* item, const Vec2& itemAnchorPoint)
{
const Vec2& contentSize = getContentSize();
Vec2 positionInView;
positionInView.x += contentSize.width * positionRatioInView.x;
positionInView.y += contentSize.height * positionRatioInView.y;
Vec2 itemPosition = calculateItemPositionWithAnchor(item, itemAnchorPoint);
return -(itemPosition - positionInView);
}
void ScrollView::scrollToItem(Node* item, const Vec2& positionRatioInView, const Vec2& itemAnchorPoint)
{
scrollToItem(item, positionRatioInView, itemAnchorPoint, _scrollTime);
}
void ScrollView::scrollToItem(Node* item, const Vec2& positionRatioInView, const Vec2& itemAnchorPoint, float timeInSec)
{
if (item == nullptr)
{
return;
}
auto destination = calculateItemDestination(positionRatioInView, item, itemAnchorPoint);
startAutoScrollToDestination(destination, timeInSec, true);
}
} // namespace ui
NS_AX_END

View File

@ -1,6 +1,7 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md).
https://axmolengine.github.io/
@ -101,7 +102,7 @@ public:
* @js NA
* @lua NA
*/
virtual ~ScrollView();
~ScrollView() override;
/**
* Create an empty ScrollView.
@ -340,24 +341,24 @@ public:
virtual void addEventListener(const ccScrollViewCallback& callback);
// override functions
virtual void addChild(Node* child) override;
virtual void addChild(Node* child, int localZOrder) override;
virtual void addChild(Node* child, int localZOrder, int tag) override;
virtual void addChild(Node* child, int localZOrder, std::string_view name) override;
virtual void removeAllChildren() override;
virtual void removeAllChildrenWithCleanup(bool cleanup) override;
virtual void removeChild(Node* child, bool cleanup = true) override;
virtual Vector<Node*>& getChildren() override;
virtual const Vector<Node*>& getChildren() const override;
virtual ssize_t getChildrenCount() const override;
virtual Node* getChildByTag(int tag) const override;
virtual Node* getChildByName(std::string_view name) const override;
void addChild(Node* child) override;
void addChild(Node* child, int localZOrder) override;
void addChild(Node* child, int localZOrder, int tag) override;
void addChild(Node* child, int localZOrder, std::string_view name) override;
void removeAllChildren() override;
void removeAllChildrenWithCleanup(bool cleanup) override;
void removeChild(Node* child, bool cleanup = true) override;
Vector<Node*>& getChildren() override;
const Vector<Node*>& getChildren() const override;
ssize_t getChildrenCount() const override;
Node* getChildByTag(int tag) const override;
Node* getChildByName(std::string_view name) const override;
// touch event callback
virtual bool onTouchBegan(Touch* touch, Event* unusedEvent) override;
virtual void onTouchMoved(Touch* touch, Event* unusedEvent) override;
virtual void onTouchEnded(Touch* touch, Event* unusedEvent) override;
virtual void onTouchCancelled(Touch* touch, Event* unusedEvent) override;
virtual void update(float dt) override;
bool onTouchBegan(Touch* touch, Event* unusedEvent) override;
void onTouchMoved(Touch* touch, Event* unusedEvent) override;
void onTouchEnded(Touch* touch, Event* unusedEvent) override;
void onTouchCancelled(Touch* touch, Event* unusedEvent) override;
void update(float dt) override;
/**
* @brief Toggle bounce enabled when scroll to the edge.
@ -453,7 +454,7 @@ public:
/**
* @brief Set the scroll bar's color
*
* @param the scroll bar's color
* @param color the scroll bar's color
*/
void setScrollBarColor(const Color3B& color);
@ -467,7 +468,7 @@ public:
/**
* @brief Set the scroll bar's opacity
*
* @param the scroll bar's opacity
* @param opacity the scroll bar's opacity
*/
void setScrollBarOpacity(uint8_t opacity);
@ -481,7 +482,7 @@ public:
/**
* @brief Set scroll bar auto hide state
*
* @param scroll bar auto hide state
* @param autoHideEnabled scroll bar auto hide state
*/
void setScrollBarAutoHideEnabled(bool autoHideEnabled);
@ -495,7 +496,7 @@ public:
/**
* @brief Set scroll bar auto hide time
*
* @param scroll bar auto hide time
* @param autoHideTime bar auto hide time
*/
void setScrollBarAutoHideTime(float autoHideTime);
@ -509,7 +510,7 @@ public:
/**
* @brief Set the touch total time threshold
*
* @param the touch total time threshold
* @param touchTotalTimeThreshold the touch total time threshold
*/
void setTouchTotalTimeThreshold(float touchTotalTimeThreshold);
@ -526,7 +527,7 @@ public:
* @see `Layout::Type`
* @param type Layout type enum.
*/
virtual void setLayoutType(Type type) override;
void setLayoutType(Type type) override;
/**
* Get the layout type for scrollview.
@ -534,22 +535,22 @@ public:
* @see `Layout::Type`
* @return LayoutType
*/
virtual Type getLayoutType() const override;
Type getLayoutType() const override;
/**
* Return the "class name" of widget.
*/
virtual std::string getDescription() const override;
std::string getDescription() const override;
/**
* @lua NA
*/
virtual void onEnter() override;
void onEnter() override;
/**
* @lua NA
*/
virtual void onExit() override;
void onExit() override;
/**
* When a widget is in a layout, you could call this method to get the next focused widget within a specified
@ -558,7 +559,22 @@ public:
*@param current the current focused widget
*@return the next focused widget in a layout
*/
virtual Widget* findNextFocusedWidget(FocusDirection direction, Widget* current) override;
Widget* findNextFocusedWidget(FocusDirection direction, Widget* current) override;
/**
* Set the time in seconds to scroll between items.
* Subsequent calls of function 'scrollToItem', will take 'time' seconds for scrolling.
* @param time The seconds needed to scroll between two items. 'time' must be >= 0
* @see scrollToItem(ssize_t, const Vec2&, const Vec2&)
*/
void setScrollDuration(float time);
/**
* Get the time in seconds to scroll between items.
* @return The time in seconds to scroll between items
* @see setScrollDuration(float)
*/
float getScrollDuration() const;
/**
* @return Whether the user is currently dragging the ScrollView to scroll it
@ -569,7 +585,26 @@ public:
*/
bool isAutoScrolling() const { return _autoScrolling; }
virtual bool init() override;
bool init() override;
/**
* @brief Scroll to specific item
* @param item Item to scroll to
* @param positionRatioInView Specifies the position with ratio in list view's content size.
* @param itemAnchorPoint Specifies an anchor point of each item for position to calculate distance.
*/
void scrollToItem(Node* item, const Vec2& positionRatioInView, const Vec2& itemAnchorPoint);
/**
* @brief Scroll to specific item
* @param item Item to scroll to
* @param positionRatioInView Specifies the position with ratio in list view's content size.
* @param itemAnchorPoint Specifies an anchor point of each item for position to calculate distance.
* @param timeInSec Scroll time
*/
void scrollToItem(Node* item, const Vec2& positionRatioInView, const Vec2& itemAnchorPoint, float timeInSec);
static Vec2 calculateItemPositionWithAnchor(const Node* node, const Vec2& itemAnchorPoint);
protected:
enum class MoveDirection
@ -580,14 +615,14 @@ protected:
RIGHT,
};
virtual void initRenderer() override;
void initRenderer() override;
virtual void onSizeChanged() override;
virtual void doLayout() override;
void onSizeChanged() override;
void doLayout() override;
virtual Widget* createCloneInstance() override;
virtual void copySpecialProperties(Widget* model) override;
virtual void copyClonedWidgetChildren(Widget* model) override;
Widget* createCloneInstance() override;
void copySpecialProperties(Widget* model) override;
void copyClonedWidgetChildren(Widget* model) override;
virtual void initScrollBar();
virtual void removeScrollBar();
@ -622,7 +657,7 @@ protected:
virtual void handleMoveLogic(Touch* touch);
virtual void handleReleaseLogic(Touch* touch);
virtual void interceptTouchEvent(Widget::TouchEventType event, Widget* sender, Touch* touch) override;
void interceptTouchEvent(Widget::TouchEventType event, Widget* sender, Touch* touch) override;
void processScrollEvent(MoveDirection dir, bool bounce);
void processScrollingEvent();
@ -631,6 +666,8 @@ protected:
void updateScrollBar(const Vec2& outOfBoundary);
Vec2 calculateItemDestination(const Vec2& positionRatioInView, const Node* item, const Vec2& itemAnchorPoint);
protected:
virtual float getAutoScrollStopEpsilon() const;
bool fltEqualZero(const Vec2& point) const;
@ -678,6 +715,8 @@ protected:
Ref* _scrollViewEventListener;
ccScrollViewCallback _eventCallback;
float _scrollTime;
};
} // namespace ui

View File

@ -53,6 +53,7 @@ UIRichTextTests::UIRichTextTests()
ADD_TEST_CASE(UIRichTextNewline);
ADD_TEST_CASE(UIRichTextHeaders);
ADD_TEST_CASE(UIRichTextParagraph);
ADD_TEST_CASE(UIRichTextScrollTo);
}
//
@ -1053,3 +1054,130 @@ bool UIRichTextParagraph::init()
}
return false;
}
bool UIRichTextScrollTo::init()
{
if (UIRichTextTestBase::init())
{
auto& widgetSize = _widget->getContentSize();
// Add the alert
Text* alert = Text::create("Paragraph Tag", "fonts/Marker Felt.ttf", 30);
alert->setColor(Color3B(159, 168, 176));
alert->setPosition(
Vec2(widgetSize.width / 2.0f, widgetSize.height / 2.0f - alert->getContentSize().height * 3.125));
_widget->addChild(alert);
createButtonPanel();
#ifdef AX_PLATFORM_PC
_defaultContentSize = Size(290, 150);
#endif
// ScrollView
_scrollView = ScrollView::create();
_scrollView->setContentSize(_defaultContentSize);
_scrollView->setInnerContainerSize(_defaultContentSize);
_scrollView->setBounceEnabled(true);
_scrollView->setDirection(ScrollView::Direction::VERTICAL);
_scrollView->setLayoutType(Layout::Type::VERTICAL);
_scrollView->setScrollBarEnabled(true);
_scrollView->setScrollBarWidth(4);
_scrollView->setScrollBarPositionFromCorner(Vec2(2, 2));
_scrollView->setScrollBarColor(Color3B::WHITE);
_scrollView->setScrollBarAutoHideEnabled(false);
_scrollView->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
_scrollView->setPosition(_widget->getContentSize() / 2);
_scrollView->setLocalZOrder(10);
_widget->addChild(_scrollView);
ValueMap valMap;
valMap[RichText::KEY_ANCHOR_FONT_COLOR_STRING] = "#00ffdd";
// RichText
_richText = RichText::createWithXML(
R"(<h1>Quick Links</h1>
<a href="#fancy_header">Jump To Fancy Header</a><br/>
<a href="#paragraph_2">Jump To Second Paragraph</a><br/>
<a href="#some_link">Jump To Web Search</a><br/>
<br/>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua.
Purus faucibus ornare suspendisse sed nisi. Viverra aliquet eget sit amet tellus cras adipiscing.
Ut tellus elementum sagittis vitae.
Risus feugiat in ante metus dictum at. Semper eget duis at tellus at. Iaculis eu non diam phasellus vestibulum
lorem sed risus.
Sed vulputate odio ut enim. Morbi tristique senectus et netus et malesuada fames.</p>
<h1 id="fancy_header">Fancy Header</h1>
<p id="paragraph_2">This is the second paragraph! Cras sed felis eget velit aliquet sagittis id consectetur purus.
Turpis nunc eget lorem dolor sed viverra ipsum nunc.
Ultrices tincidunt arcu non sodales neque sodales ut etiam sit. Risus feugiat in ante metus dictum at tempor.
Id neque aliquam vestibulum morbi blandit cursus risus.</p>
<p>Tortor condimentum lacinia quis vel eros donec ac. Molestie ac feugiat sed lectus.
Aliquam id diam maecenas ultricies mi eget mauris.
Ullamcorper malesuada proin libero nunc consequat interdum varius.
Sollicitudin nibh sit amet commodo nulla facilisi nullam vehicula ipsum.
Diam quam nulla porttitor massa id neque aliquam vestibulum morbi. Sed velit dignissim sodales ut.
Morbi leo urna molestie at elementum eu facilisis.
Cursus metus aliquam eleifend mi in. Euismod lacinia at quis risus sed vulputate odio.
Sit amet mattis vulputate enim nulla aliquet porttitor lacus luctus.</p>
<a id="some_link" href="https://google.com">Google!</a>)",
valMap);
_richText->ignoreContentAdaptWithSize(false);
_richText->setContentSize(Size(_defaultContentSize.width, 0));
_richText->setAnchorPoint(Vec2::ANCHOR_MIDDLE_TOP);
_richText->setOpenUrlHandler([this](std::string_view url) {
// Check if the href starts with a "#" character
if (url.starts_with('#'))
{
auto* node = _richText->getProtectedChildByName(url.substr(1));
if (node)
{
// Scroll to the location of that node, and the reason it works is because
// the ScrollView inner container is the same height as the RichText
_scrollView->scrollToItem(node, Vec2::ANCHOR_MIDDLE_TOP, Vec2::ANCHOR_MIDDLE_TOP);
}
}
else if (!url.empty())
{
Application::getInstance()->openURL(url);
}
});
const auto contentSize = _scrollView->getInnerContainerSize();
_richText->setPosition(Vec2(contentSize.width / 2, contentSize.height));
_richText->setLocalZOrder(10);
_richText->formatText();
_scrollView->addChild(_richText);
updateScrollViewSize();
// test remove all children, this call won't effect the test
_richText->removeAllChildren();
return true;
}
return false;
}
void UIRichTextScrollTo::updateScrollViewSize()
{
auto newHeight = 0.f;
auto&& children = _scrollView->getChildren();
for (auto&& child : children)
{
auto&& contentSize = child->getContentSize();
newHeight += contentSize.height;
if (const auto* widget = dynamic_cast<Widget*>(child))
{
if (const auto* layoutParam = widget->getLayoutParameter())
{
auto&& margin = layoutParam->getMargin();
newHeight += margin.top + margin.bottom;
}
}
}
_scrollView->setInnerContainerSize(Size(_scrollView->getInnerContainerSize().width, newHeight));
_scrollView->scrollToTop(0.f, false);
}

View File

@ -220,4 +220,17 @@ public:
bool init() override;
};
class UIRichTextScrollTo : public UIRichTextTestBase
{
public:
CREATE_FUNC(UIRichTextScrollTo);
bool init() override;
protected:
void updateScrollViewSize();
ax::ui::ScrollView* _scrollView;
};
#endif /* defined(__TestCpp__UIRichTextTest__) */