mirror of https://github.com/axmolengine/axmol.git
1056 lines
32 KiB
C++
1056 lines
32 KiB
C++
/****************************************************************************
|
|
Copyright (c) 2013 cocos2d-x.org
|
|
|
|
http://www.cocos2d-x.org
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
****************************************************************************/
|
|
|
|
#include "ui/UIRichText.h"
|
|
|
|
#include <vector>
|
|
#include <locale>
|
|
|
|
#include "tinyxml2/tinyxml2.h"
|
|
#include "platform/CCFileUtils.h"
|
|
#include "platform/CCApplication.h"
|
|
#include "base/CCEventListenerTouch.h"
|
|
#include "base/CCEventDispatcher.h"
|
|
#include "base/CCDirector.h"
|
|
#include "2d/CCLabel.h"
|
|
#include "2d/CCSprite.h"
|
|
#include "base/ccUTF8.h"
|
|
#include "ui/UIHelper.h"
|
|
|
|
NS_CC_BEGIN
|
|
|
|
namespace ui {
|
|
class ListenerComponent : public Component
|
|
{
|
|
public:
|
|
static ListenerComponent* create(Label* parent, const std::string& url)
|
|
{
|
|
auto component = new (std::nothrow) ListenerComponent(parent, url);
|
|
component->autorelease();
|
|
return component;
|
|
}
|
|
|
|
explicit ListenerComponent(Label* parent, const std::string& url)
|
|
: _parent(parent)
|
|
, _url(url)
|
|
{
|
|
_touchListener = cocos2d::EventListenerTouchAllAtOnce::create();
|
|
_touchListener->onTouchesEnded = CC_CALLBACK_2(ListenerComponent::onTouchesEnded, this);
|
|
|
|
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(_touchListener, _parent);
|
|
_touchListener->retain();
|
|
}
|
|
|
|
virtual ~ListenerComponent()
|
|
{
|
|
Director::getInstance()->getEventDispatcher()->removeEventListener(_touchListener);
|
|
_touchListener->release();
|
|
}
|
|
|
|
void onTouchesEnded(const std::vector<Touch*>& touches, Event *event)
|
|
{
|
|
for (const auto& touch: touches)
|
|
{
|
|
// FIXME: Node::getBoundBox() doesn't return it in local coordinates... so create one manually.
|
|
Rect localRect = Rect(Vec2::ZERO, _parent->getContentSize());
|
|
if (localRect.containsPoint(_parent->convertTouchToNodeSpace(touch)))
|
|
Application::getInstance()->openURL(_url);
|
|
}
|
|
}
|
|
|
|
private:
|
|
Label* _parent; // weak ref.
|
|
std::string _url;
|
|
EventDispatcher* _eventDispatcher; // weak ref.
|
|
EventListenerTouchAllAtOnce* _touchListener; // strong ref.
|
|
};
|
|
|
|
|
|
bool RichElement::init(int tag, const Color3B &color, GLubyte opacity)
|
|
{
|
|
_tag = tag;
|
|
_color = color;
|
|
_opacity = opacity;
|
|
return true;
|
|
}
|
|
|
|
|
|
RichElementText* RichElementText::create(int tag, const Color3B &color, GLubyte opacity, const std::string& text, const std::string& fontName, float fontSize, uint32_t flags, const std::string& url)
|
|
{
|
|
RichElementText* element = new (std::nothrow) RichElementText();
|
|
if (element && element->init(tag, color, opacity, text, fontName, fontSize, flags, url))
|
|
{
|
|
element->autorelease();
|
|
return element;
|
|
}
|
|
CC_SAFE_DELETE(element);
|
|
return nullptr;
|
|
}
|
|
|
|
bool RichElementText::init(int tag, const Color3B &color, GLubyte opacity, const std::string& text, const std::string& fontName, float fontSize, uint32_t flags, const std::string& url)
|
|
{
|
|
if (RichElement::init(tag, color, opacity))
|
|
{
|
|
_text = text;
|
|
_fontName = fontName;
|
|
_fontSize = fontSize;
|
|
_flags = flags;
|
|
_url = url;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
RichElementImage* RichElementImage::create(int tag, const Color3B &color, GLubyte opacity, const std::string& filePath)
|
|
{
|
|
RichElementImage* element = new (std::nothrow) RichElementImage();
|
|
if (element && element->init(tag, color, opacity, filePath))
|
|
{
|
|
element->autorelease();
|
|
return element;
|
|
}
|
|
CC_SAFE_DELETE(element);
|
|
return nullptr;
|
|
}
|
|
|
|
bool RichElementImage::init(int tag, const Color3B &color, GLubyte opacity, const std::string& filePath)
|
|
{
|
|
if (RichElement::init(tag, color, opacity))
|
|
{
|
|
_filePath = filePath;
|
|
_width = -1;
|
|
_height = -1;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
void RichElementImage::setWidth(int width)
|
|
{
|
|
_width = width;
|
|
}
|
|
|
|
void RichElementImage::setHeight(int height)
|
|
{
|
|
_height = height;
|
|
}
|
|
|
|
RichElementCustomNode* RichElementCustomNode::create(int tag, const Color3B &color, GLubyte opacity, cocos2d::Node *customNode)
|
|
{
|
|
RichElementCustomNode* element = new (std::nothrow) RichElementCustomNode();
|
|
if (element && element->init(tag, color, opacity, customNode))
|
|
{
|
|
element->autorelease();
|
|
return element;
|
|
}
|
|
CC_SAFE_DELETE(element);
|
|
return nullptr;
|
|
}
|
|
|
|
bool RichElementCustomNode::init(int tag, const Color3B &color, GLubyte opacity, cocos2d::Node *customNode)
|
|
{
|
|
if (RichElement::init(tag, color, opacity))
|
|
{
|
|
_customNode = customNode;
|
|
_customNode->retain();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
RichElementNewLine* RichElementNewLine::create(int tag, const Color3B& color, GLubyte opacity)
|
|
{
|
|
RichElementNewLine* element = new (std::nothrow) RichElementNewLine();
|
|
if (element && element->init(tag, color, opacity))
|
|
{
|
|
element->autorelease();
|
|
return element;
|
|
}
|
|
CC_SAFE_DELETE(element);
|
|
return nullptr;
|
|
}
|
|
|
|
RichText::RichText()
|
|
: _formatTextDirty(true)
|
|
, _leftSpaceWidth(0.0f)
|
|
, _verticalSpace(0.0f)
|
|
, _wrapMode(WRAP_PER_WORD)
|
|
{
|
|
}
|
|
|
|
RichText::~RichText()
|
|
{
|
|
_richElements.clear();
|
|
}
|
|
|
|
RichText* RichText::create()
|
|
{
|
|
RichText* widget = new (std::nothrow) RichText();
|
|
if (widget && widget->init())
|
|
{
|
|
widget->autorelease();
|
|
return widget;
|
|
}
|
|
CC_SAFE_DELETE(widget);
|
|
return nullptr;
|
|
}
|
|
|
|
RichText* RichText::createWithXML(const std::string& xml)
|
|
{
|
|
RichText* widget = new (std::nothrow) RichText();
|
|
if (widget && widget->initWithXML(xml))
|
|
{
|
|
widget->autorelease();
|
|
return widget;
|
|
}
|
|
CC_SAFE_DELETE(widget);
|
|
return nullptr;
|
|
}
|
|
|
|
bool RichText::init()
|
|
{
|
|
if (Widget::init())
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class MyXMLVisitor: public tinyxml2::XMLVisitor
|
|
{
|
|
struct Attributes
|
|
{
|
|
std::string face;
|
|
std::string url;
|
|
float fontSize;
|
|
Color3B color;
|
|
bool hasColor;
|
|
bool bold;
|
|
bool italics;
|
|
bool underline;
|
|
bool strikethrough;
|
|
|
|
void setColor(const Color3B& acolor)
|
|
{
|
|
color = acolor;
|
|
hasColor = true;
|
|
}
|
|
Attributes()
|
|
: bold(false)
|
|
, italics(false)
|
|
, underline(false)
|
|
, strikethrough(false)
|
|
, hasColor(false)
|
|
, fontSize(-1)
|
|
{
|
|
}
|
|
};
|
|
|
|
std::vector<Attributes> _fontElements;
|
|
|
|
RichText* _richText;
|
|
|
|
Color3B getColor() const
|
|
{
|
|
for (auto i = _fontElements.rbegin(); i != _fontElements.rend(); ++i)
|
|
{
|
|
if (i->hasColor)
|
|
return i->color;
|
|
}
|
|
return Color3B::WHITE;
|
|
}
|
|
|
|
float getFontSize() const
|
|
{
|
|
for (auto i = _fontElements.rbegin(); i != _fontElements.rend(); ++i)
|
|
{
|
|
if (i->fontSize != -1)
|
|
return i->fontSize;
|
|
}
|
|
return 12;
|
|
}
|
|
|
|
std::string getFace() const
|
|
{
|
|
for (auto i = _fontElements.rbegin(); i != _fontElements.rend(); ++i)
|
|
{
|
|
if (i->face.size() != 0)
|
|
return i->face;
|
|
}
|
|
return "fonts/Marker Felt.ttf";
|
|
}
|
|
|
|
std::string getURL() const
|
|
{
|
|
for (auto i = _fontElements.rbegin(); i != _fontElements.rend(); ++i)
|
|
{
|
|
if (i->url.size() != 0)
|
|
return i->url;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
bool getBold() const
|
|
{
|
|
for (auto i = _fontElements.rbegin(); i != _fontElements.rend(); ++i)
|
|
{
|
|
if (i->bold)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool getItalics() const
|
|
{
|
|
for (auto i = _fontElements.rbegin(); i != _fontElements.rend(); ++i)
|
|
{
|
|
if (i->italics)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool getUnderline() const
|
|
{
|
|
for (auto i = _fontElements.rbegin(); i != _fontElements.rend(); ++i)
|
|
{
|
|
if (i->underline)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool getStrikethrough() const
|
|
{
|
|
for (auto i = _fontElements.rbegin(); i != _fontElements.rend(); ++i)
|
|
{
|
|
if (i->strikethrough)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public:
|
|
explicit MyXMLVisitor(RichText* richText)
|
|
: _richText(richText)
|
|
, _fontElements(20)
|
|
{}
|
|
virtual ~MyXMLVisitor() {}
|
|
|
|
/// Visit an element.
|
|
virtual bool VisitEnter( const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* firstAttribute )
|
|
{
|
|
auto elementName = element.Value();
|
|
|
|
if (strcmp(elementName, "font") == 0)
|
|
{
|
|
// supported attributes:
|
|
// size, color, align, face
|
|
auto size = element.Attribute("size");
|
|
auto color = element.Attribute("color");
|
|
auto face = element.Attribute("face");
|
|
|
|
Attributes attribs;
|
|
if (size)
|
|
attribs.fontSize = atof(size);
|
|
if (color)
|
|
{
|
|
if (strlen(color) == 7)
|
|
{
|
|
int r,g,b;
|
|
sscanf(color, "%*c%2x%2x%2x", &r, &g, &b);
|
|
attribs.setColor(Color3B(r,g,b));
|
|
}
|
|
else
|
|
attribs.setColor(Color3B::WHITE);
|
|
}
|
|
if (face)
|
|
attribs.face = face;
|
|
|
|
_fontElements.push_back(attribs);
|
|
}
|
|
else if (strcmp(elementName, "b") == 0)
|
|
{
|
|
// no supported attributes
|
|
Attributes attribs;
|
|
attribs.bold = 1;
|
|
_fontElements.push_back(attribs);
|
|
}
|
|
else if (strcmp(elementName, "i") == 0)
|
|
{
|
|
// no supported attributes
|
|
Attributes attribs;
|
|
attribs.italics = 1;
|
|
_fontElements.push_back(attribs);
|
|
}
|
|
else if (strcmp(elementName, "del") == 0)
|
|
{
|
|
// no supported attributes
|
|
Attributes attribs;
|
|
attribs.strikethrough = true;
|
|
_fontElements.push_back(attribs);
|
|
}
|
|
else if (strcmp(elementName, "u") == 0)
|
|
{
|
|
// no supported attributes
|
|
Attributes attribs;
|
|
attribs.underline = true;
|
|
_fontElements.push_back(attribs);
|
|
}
|
|
else if (strcmp(elementName, "small") == 0)
|
|
{
|
|
Attributes attribs;
|
|
attribs.fontSize = getFontSize() * 0.8;
|
|
_fontElements.push_back(attribs);
|
|
}
|
|
|
|
else if (strcmp(elementName, "big") == 0)
|
|
{
|
|
Attributes attribs;
|
|
attribs.fontSize = getFontSize() * 1.25;
|
|
_fontElements.push_back(attribs);
|
|
}
|
|
|
|
else if (strcmp(elementName, "img") == 0)
|
|
{
|
|
// supported attributes:
|
|
// src, height, width
|
|
auto src = element.Attribute("src");
|
|
auto height = element.Attribute("height");
|
|
auto width = element.Attribute("width");
|
|
|
|
if (src) {
|
|
auto elementNL = RichElementImage::create(0, getColor(), 255, src);
|
|
|
|
if (height)
|
|
elementNL->setHeight(atoi(height));
|
|
if (width)
|
|
elementNL->setWidth(atoi(width));
|
|
|
|
_richText->pushBackElement(elementNL);
|
|
}
|
|
}
|
|
else if (strcmp(elementName, "a") == 0)
|
|
{
|
|
// supported attributes:
|
|
Attributes attribs;
|
|
auto href = element.Attribute("href");
|
|
attribs.setColor(Color3B::BLUE);
|
|
attribs.underline = true;
|
|
attribs.url = href;
|
|
_fontElements.push_back(attribs);
|
|
}
|
|
else if (strcmp(elementName, "br") == 0)
|
|
{
|
|
auto color = getColor();
|
|
auto elementNL = RichElementNewLine::create(0, color, 255);
|
|
_richText->pushBackElement(elementNL);
|
|
}
|
|
return true;
|
|
}
|
|
/// Visit an element.
|
|
virtual bool VisitExit( const tinyxml2::XMLElement& element )
|
|
{
|
|
auto elementName = element.Value();
|
|
if ((strcmp(elementName, "font") == 0) ||
|
|
(strcmp(elementName, "i") == 0) ||
|
|
(strcmp(elementName, "b") == 0) ||
|
|
(strcmp(elementName, "del") == 0) ||
|
|
(strcmp(elementName, "u") == 0) ||
|
|
(strcmp(elementName, "small") == 0) ||
|
|
(strcmp(elementName, "big") == 0) ||
|
|
(strcmp(elementName, "a") == 0))
|
|
{
|
|
_fontElements.pop_back();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Visit a text node.
|
|
virtual bool Visit(const tinyxml2::XMLText& text)
|
|
{
|
|
auto color = getColor();
|
|
auto face = getFace();
|
|
auto fontSize = getFontSize();
|
|
auto italics = getItalics();
|
|
auto underline = getUnderline();
|
|
auto strikethrough = getStrikethrough();
|
|
auto bold = getBold();
|
|
auto url = getURL();
|
|
|
|
uint32_t flags = 0;
|
|
if (italics)
|
|
flags |= RichElementText::ITALICS_FLAG;
|
|
if (bold)
|
|
flags |= RichElementText::BOLD_FLAG;
|
|
if (underline)
|
|
flags |= RichElementText::UNDERLINE_FLAG;
|
|
if (strikethrough)
|
|
flags |= RichElementText::STRIKETHROUGH_FLAG;
|
|
if (url.size() > 0)
|
|
flags |= RichElementText::URL_FLAG;
|
|
|
|
auto element = RichElementText::create(0, color, 255, text.Value(), face, fontSize, flags, url);
|
|
_richText->pushBackElement(element);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
bool RichText::initWithXML(const std::string& origxml)
|
|
{
|
|
if (Widget::init())
|
|
{
|
|
tinyxml2::XMLDocument document;
|
|
|
|
// solves to issues:
|
|
// - creates defaults values
|
|
// - makes sure that the xml well formed and starts with an element
|
|
auto xml = "<font face=\"Verdana\" size=\"12\" color=\"#ffffff\">" + origxml + "</font>";
|
|
|
|
auto error = document.Parse(xml.c_str(), xml.length());
|
|
if (error == tinyxml2::XML_SUCCESS)
|
|
{
|
|
MyXMLVisitor visitor(this);
|
|
document.Accept(&visitor);
|
|
return true;
|
|
}
|
|
CCLOG("cocos2d: UI::RichText: Error parsing xml: %s, %s", document.GetErrorStr1(), document.GetErrorStr2());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void RichText::initRenderer()
|
|
{
|
|
}
|
|
|
|
void RichText::insertElement(RichElement *element, int index)
|
|
{
|
|
_richElements.insert(index, element);
|
|
_formatTextDirty = true;
|
|
}
|
|
|
|
void RichText::pushBackElement(RichElement *element)
|
|
{
|
|
_richElements.pushBack(element);
|
|
_formatTextDirty = true;
|
|
}
|
|
|
|
void RichText::removeElement(int index)
|
|
{
|
|
_richElements.erase(index);
|
|
_formatTextDirty = true;
|
|
}
|
|
|
|
void RichText::removeElement(RichElement *element)
|
|
{
|
|
_richElements.eraseObject(element);
|
|
_formatTextDirty = true;
|
|
}
|
|
|
|
RichText::WrapMode RichText::getWrapMode() const
|
|
{
|
|
return _wrapMode;
|
|
}
|
|
|
|
void RichText::setWrapMode(RichText::WrapMode wrapMode)
|
|
{
|
|
if (_wrapMode != wrapMode)
|
|
{
|
|
_wrapMode = wrapMode;
|
|
_formatTextDirty = true;
|
|
}
|
|
}
|
|
|
|
void RichText::formatText()
|
|
{
|
|
if (_formatTextDirty)
|
|
{
|
|
this->removeAllProtectedChildren();
|
|
_elementRenders.clear();
|
|
if (_ignoreSize)
|
|
{
|
|
addNewLine();
|
|
for (ssize_t i=0; i<_richElements.size(); i++)
|
|
{
|
|
RichElement* element = _richElements.at(i);
|
|
Node* elementRenderer = nullptr;
|
|
switch (element->_type)
|
|
{
|
|
case RichElement::Type::TEXT:
|
|
{
|
|
RichElementText* elmtText = static_cast<RichElementText*>(element);
|
|
Label* label;
|
|
if (FileUtils::getInstance()->isFileExist(elmtText->_fontName))
|
|
{
|
|
label = Label::createWithTTF(elmtText->_text, elmtText->_fontName, elmtText->_fontSize);
|
|
}
|
|
else
|
|
{
|
|
label = Label::createWithSystemFont(elmtText->_text, elmtText->_fontName, elmtText->_fontSize);
|
|
}
|
|
if (elmtText->_flags & RichElementText::ITALICS_FLAG)
|
|
label->enableItalics();
|
|
if (elmtText->_flags & RichElementText::BOLD_FLAG)
|
|
label->enableBold();
|
|
if (elmtText->_flags & RichElementText::UNDERLINE_FLAG)
|
|
label->enableUnderline();
|
|
if (elmtText->_flags & RichElementText::STRIKETHROUGH_FLAG)
|
|
label->enableStrikethrough();
|
|
if (elmtText->_flags & RichElementText::URL_FLAG)
|
|
label->addComponent(ListenerComponent::create(label, elmtText->_url));
|
|
elementRenderer = label;
|
|
break;
|
|
}
|
|
case RichElement::Type::IMAGE:
|
|
{
|
|
RichElementImage* elmtImage = static_cast<RichElementImage*>(element);
|
|
elementRenderer = Sprite::create(elmtImage->_filePath);
|
|
if (elementRenderer && (elmtImage->_height != -1 || elmtImage->_width != -1))
|
|
{
|
|
auto currentSize = elementRenderer->getContentSize();
|
|
if (elmtImage->_width != -1)
|
|
elementRenderer->setScaleX(elmtImage->_width / currentSize.width);
|
|
if (elmtImage->_height != -1)
|
|
elementRenderer->setScaleY(elmtImage->_height / currentSize.height);
|
|
elementRenderer->setContentSize(Size(currentSize.width * elementRenderer->getScaleX(),
|
|
currentSize.height * elementRenderer->getScaleY()));
|
|
}
|
|
break;
|
|
}
|
|
case RichElement::Type::CUSTOM:
|
|
{
|
|
RichElementCustomNode* elmtCustom = static_cast<RichElementCustomNode*>(element);
|
|
elementRenderer = elmtCustom->_customNode;
|
|
break;
|
|
}
|
|
case RichElement::Type::NEWLINE:
|
|
{
|
|
addNewLine();
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (elementRenderer)
|
|
{
|
|
elementRenderer->setColor(element->_color);
|
|
elementRenderer->setOpacity(element->_opacity);
|
|
pushToContainer(elementRenderer);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
addNewLine();
|
|
for (ssize_t i=0; i<_richElements.size(); i++)
|
|
{
|
|
RichElement* element = static_cast<RichElement*>(_richElements.at(i));
|
|
switch (element->_type)
|
|
{
|
|
case RichElement::Type::TEXT:
|
|
{
|
|
RichElementText* elmtText = static_cast<RichElementText*>(element);
|
|
handleTextRenderer(elmtText->_text, elmtText->_fontName, elmtText->_fontSize, elmtText->_color, elmtText->_opacity, elmtText->_flags, elmtText->_url);
|
|
break;
|
|
}
|
|
case RichElement::Type::IMAGE:
|
|
{
|
|
RichElementImage* elmtImage = static_cast<RichElementImage*>(element);
|
|
handleImageRenderer(elmtImage->_filePath, elmtImage->_color, elmtImage->_opacity, elmtImage->_width, elmtImage->_height);
|
|
break;
|
|
}
|
|
case RichElement::Type::CUSTOM:
|
|
{
|
|
RichElementCustomNode* elmtCustom = static_cast<RichElementCustomNode*>(element);
|
|
handleCustomRenderer(elmtCustom->_customNode);
|
|
break;
|
|
}
|
|
case RichElement::Type::NEWLINE:
|
|
{
|
|
addNewLine();
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
formarRenderers();
|
|
_formatTextDirty = false;
|
|
}
|
|
}
|
|
|
|
static int getPrevWord(const std::string& text, int idx)
|
|
{
|
|
// start from idx-1
|
|
for (int i=idx-1; i>=0; --i)
|
|
{
|
|
if (!std::isalnum(text[i], std::locale()))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static bool isWrappable(const std::string& text)
|
|
{
|
|
for (int i=0; i<text.length(); ++i)
|
|
{
|
|
if (!std::isalnum(text[i], std::locale()))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int RichText::findSplitPositionForWord(cocos2d::Label* label, const std::string& text)
|
|
{
|
|
auto originalLeftSpaceWidth = _leftSpaceWidth + label->getContentSize().width;
|
|
|
|
bool startingNewLine = (_customSize.width == originalLeftSpaceWidth);
|
|
if (!isWrappable(text))
|
|
{
|
|
if (startingNewLine)
|
|
return (int) text.length();
|
|
return 0;
|
|
}
|
|
|
|
for(int idx = (int)text.size()-1; idx >=0; )
|
|
{
|
|
int newidx = getPrevWord(text, idx);
|
|
if (newidx >=0)
|
|
{
|
|
idx = newidx;
|
|
auto leftStr = Helper::getSubStringOfUTF8String(text, 0, idx);
|
|
label->setString(leftStr);
|
|
if (label->getContentSize().width <= originalLeftSpaceWidth)
|
|
return idx;
|
|
}
|
|
else
|
|
{
|
|
if (startingNewLine)
|
|
return idx;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// no spaces... return the original label + size
|
|
label->setString(text);
|
|
return (int)text.size();
|
|
}
|
|
|
|
|
|
int RichText::findSplitPositionForChar(cocos2d::Label* label, const std::string& text)
|
|
{
|
|
float textRendererWidth = label->getContentSize().width;
|
|
|
|
float overstepPercent = (-_leftSpaceWidth) / textRendererWidth;
|
|
std::string curText = text;
|
|
size_t stringLength = StringUtils::getCharacterCountInUTF8String(text);
|
|
|
|
// rough estimate
|
|
int leftLength = stringLength * (1.0f - overstepPercent);
|
|
|
|
// The adjustment of the new line position
|
|
auto originalLeftSpaceWidth = _leftSpaceWidth + textRendererWidth;
|
|
auto leftStr = Helper::getSubStringOfUTF8String(curText, 0, leftLength);
|
|
label->setString(leftStr);
|
|
auto leftWidth = label->getContentSize().width;
|
|
if (originalLeftSpaceWidth < leftWidth) {
|
|
// Have protruding
|
|
for (;;) {
|
|
leftLength--;
|
|
leftStr = Helper::getSubStringOfUTF8String(curText, 0, leftLength);
|
|
label->setString(leftStr);
|
|
leftWidth = label->getContentSize().width;
|
|
if (leftWidth <= originalLeftSpaceWidth) {
|
|
break;
|
|
}
|
|
else if (leftLength <= 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (leftWidth < originalLeftSpaceWidth) {
|
|
// A wide margin
|
|
for (;;) {
|
|
leftLength++;
|
|
leftStr = Helper::getSubStringOfUTF8String(curText, 0, leftLength);
|
|
label->setString(leftStr);
|
|
leftWidth = label->getContentSize().width;
|
|
if (originalLeftSpaceWidth < leftWidth) {
|
|
leftLength--;
|
|
break;
|
|
}
|
|
else if (stringLength <= leftLength) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (leftLength < 0)
|
|
leftLength = (int)text.size()-1;
|
|
return leftLength;
|
|
}
|
|
|
|
void RichText::handleTextRenderer(const std::string& text, const std::string& fontName, float fontSize, const Color3B &color, GLubyte opacity, uint32_t flags, const std::string& url)
|
|
{
|
|
auto fileExist = FileUtils::getInstance()->isFileExist(fontName);
|
|
Label* textRenderer = nullptr;
|
|
if (fileExist)
|
|
{
|
|
textRenderer = Label::createWithTTF(text, fontName, fontSize);
|
|
}
|
|
else
|
|
{
|
|
textRenderer = Label::createWithSystemFont(text, fontName, fontSize);
|
|
}
|
|
if (flags & RichElementText::ITALICS_FLAG)
|
|
textRenderer->enableItalics();
|
|
if (flags & RichElementText::BOLD_FLAG)
|
|
textRenderer->enableBold();
|
|
if (flags & RichElementText::UNDERLINE_FLAG)
|
|
textRenderer->enableUnderline();
|
|
if (flags & RichElementText::STRIKETHROUGH_FLAG)
|
|
textRenderer->enableStrikethrough();
|
|
if (flags & RichElementText::URL_FLAG)
|
|
textRenderer->addComponent(ListenerComponent::create(textRenderer, url));
|
|
|
|
float textRendererWidth = textRenderer->getContentSize().width;
|
|
_leftSpaceWidth -= textRendererWidth;
|
|
if (_leftSpaceWidth < 0.0f)
|
|
{
|
|
int leftLength = 0;
|
|
if (_wrapMode == WRAP_PER_WORD)
|
|
leftLength = findSplitPositionForWord(textRenderer, text);
|
|
else
|
|
leftLength = findSplitPositionForChar(textRenderer, text);
|
|
|
|
//The minimum cut length is 1, otherwise will cause the infinite loop.
|
|
// if (0 == leftLength) leftLength = 1;
|
|
std::string leftWords = Helper::getSubStringOfUTF8String(text, 0, leftLength);
|
|
int rightStart = leftLength;
|
|
if (std::isspace(text[rightStart], std::locale()))
|
|
rightStart++;
|
|
std::string cutWords = Helper::getSubStringOfUTF8String(text, rightStart, text.length() - leftLength);
|
|
if (leftLength > 0)
|
|
{
|
|
Label* leftRenderer = nullptr;
|
|
if (fileExist)
|
|
{
|
|
leftRenderer = Label::createWithTTF(Helper::getSubStringOfUTF8String(leftWords, 0, leftLength), fontName, fontSize);
|
|
}
|
|
else
|
|
{
|
|
leftRenderer = Label::createWithSystemFont(Helper::getSubStringOfUTF8String(leftWords, 0, leftLength), fontName, fontSize);
|
|
}
|
|
if (leftRenderer)
|
|
{
|
|
leftRenderer->setColor(color);
|
|
leftRenderer->setOpacity(opacity);
|
|
pushToContainer(leftRenderer);
|
|
|
|
if (flags & RichElementText::ITALICS_FLAG)
|
|
leftRenderer->enableItalics();
|
|
if (flags & RichElementText::BOLD_FLAG)
|
|
leftRenderer->enableBold();
|
|
if (flags & RichElementText::UNDERLINE_FLAG)
|
|
leftRenderer->enableUnderline();
|
|
if (flags & RichElementText::STRIKETHROUGH_FLAG)
|
|
leftRenderer->enableStrikethrough();
|
|
if (flags & RichElementText::URL_FLAG)
|
|
leftRenderer->addComponent(ListenerComponent::create(leftRenderer, url));
|
|
|
|
}
|
|
}
|
|
|
|
addNewLine();
|
|
handleTextRenderer(cutWords, fontName, fontSize, color, opacity, flags, url);
|
|
}
|
|
else
|
|
{
|
|
textRenderer->setColor(color);
|
|
textRenderer->setOpacity(opacity);
|
|
pushToContainer(textRenderer);
|
|
}
|
|
}
|
|
|
|
void RichText::handleImageRenderer(const std::string& filePath, const Color3B &color, GLubyte opacity, int width, int height)
|
|
{
|
|
Sprite* imageRenderer = Sprite::create(filePath);
|
|
if (imageRenderer)
|
|
{
|
|
auto currentSize = imageRenderer->getContentSize();
|
|
if (width != -1)
|
|
imageRenderer->setScaleX(width / currentSize.width);
|
|
if (height != -1)
|
|
imageRenderer->setScaleY(height / currentSize.height);
|
|
imageRenderer->setContentSize(Size(currentSize.width * imageRenderer->getScaleX(),
|
|
currentSize.height * imageRenderer->getScaleY()));
|
|
|
|
handleCustomRenderer(imageRenderer);
|
|
}
|
|
}
|
|
|
|
void RichText::handleCustomRenderer(cocos2d::Node *renderer)
|
|
{
|
|
Size imgSize = renderer->getContentSize();
|
|
_leftSpaceWidth -= imgSize.width;
|
|
if (_leftSpaceWidth < 0.0f)
|
|
{
|
|
addNewLine();
|
|
pushToContainer(renderer);
|
|
_leftSpaceWidth -= imgSize.width;
|
|
}
|
|
else
|
|
{
|
|
pushToContainer(renderer);
|
|
}
|
|
}
|
|
|
|
void RichText::addNewLine()
|
|
{
|
|
_leftSpaceWidth = _customSize.width;
|
|
_elementRenders.push_back(new Vector<Node*>());
|
|
}
|
|
|
|
void RichText::formarRenderers()
|
|
{
|
|
if (_ignoreSize)
|
|
{
|
|
float newContentSizeWidth = 0.0f;
|
|
float nextPosY = 0.0f;
|
|
for (auto& element: _elementRenders)
|
|
{
|
|
Vector<Node*>* row = element;
|
|
float nextPosX = 0.0f;
|
|
float maxY = 0.0f;
|
|
for (ssize_t j=0; j<row->size(); j++)
|
|
{
|
|
Node* l = row->at(j);
|
|
l->setAnchorPoint(Vec2::ZERO);
|
|
l->setPosition(nextPosX, nextPosY);
|
|
this->addProtectedChild(l, 1);
|
|
Size iSize = l->getContentSize();
|
|
newContentSizeWidth += iSize.width;
|
|
nextPosX += iSize.width;
|
|
maxY = MAX(maxY, iSize.height);
|
|
}
|
|
nextPosY -= maxY;
|
|
}
|
|
this->setContentSize(Size(newContentSizeWidth, -nextPosY));
|
|
}
|
|
else
|
|
{
|
|
float newContentSizeHeight = 0.0f;
|
|
float *maxHeights = new (std::nothrow) float[_elementRenders.size()];
|
|
|
|
for (size_t i=0; i<_elementRenders.size(); i++)
|
|
{
|
|
Vector<Node*>* row = (_elementRenders[i]);
|
|
float maxHeight = 0.0f;
|
|
for (ssize_t j=0; j<row->size(); j++)
|
|
{
|
|
Node* l = row->at(j);
|
|
maxHeight = MAX(l->getContentSize().height, maxHeight);
|
|
}
|
|
maxHeights[i] = maxHeight;
|
|
newContentSizeHeight += maxHeights[i];
|
|
}
|
|
|
|
float nextPosY = _customSize.height;
|
|
for (size_t i=0; i<_elementRenders.size(); i++)
|
|
{
|
|
Vector<Node*>* row = (_elementRenders[i]);
|
|
float nextPosX = 0.0f;
|
|
nextPosY -= (maxHeights[i] + _verticalSpace);
|
|
|
|
for (ssize_t j=0; j<row->size(); j++)
|
|
{
|
|
Node* l = row->at(j);
|
|
l->setAnchorPoint(Vec2::ZERO);
|
|
l->setPosition(nextPosX, nextPosY);
|
|
this->addProtectedChild(l, 1);
|
|
nextPosX += l->getContentSize().width;
|
|
}
|
|
}
|
|
delete [] maxHeights;
|
|
}
|
|
|
|
size_t length = _elementRenders.size();
|
|
for (size_t i = 0; i<length; i++)
|
|
{
|
|
Vector<Node*>* l = _elementRenders[i];
|
|
l->clear();
|
|
delete l;
|
|
}
|
|
_elementRenders.clear();
|
|
|
|
if (_ignoreSize)
|
|
{
|
|
Size s = getVirtualRendererSize();
|
|
this->setContentSize(s);
|
|
}
|
|
else
|
|
{
|
|
this->setContentSize(_customSize);
|
|
}
|
|
updateContentSizeWithTextureSize(_contentSize);
|
|
}
|
|
|
|
void RichText::adaptRenderers()
|
|
{
|
|
this->formatText();
|
|
}
|
|
|
|
void RichText::pushToContainer(cocos2d::Node *renderer)
|
|
{
|
|
if (_elementRenders.size() <= 0)
|
|
{
|
|
return;
|
|
}
|
|
_elementRenders[_elementRenders.size()-1]->pushBack(renderer);
|
|
}
|
|
|
|
void RichText::setVerticalSpace(float space)
|
|
{
|
|
_verticalSpace = space;
|
|
}
|
|
|
|
void RichText::ignoreContentAdaptWithSize(bool ignore)
|
|
{
|
|
if (_ignoreSize != ignore)
|
|
{
|
|
_formatTextDirty = true;
|
|
Widget::ignoreContentAdaptWithSize(ignore);
|
|
}
|
|
}
|
|
|
|
std::string RichText::getDescription() const
|
|
{
|
|
return "RichText";
|
|
}
|
|
|
|
}
|
|
|
|
NS_CC_END
|