├── 06C0BE26-17B5-4753-9729-D909E2099FB2.png ├── README.md └── SourceCode ├── RichTextEx.lua ├── UIRichText.cpp └── UIRichText.h /06C0BE26-17B5-4753-9729-D909E2099FB2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArcherPeng/RichTextEXWithUnderLineAndOutLine/c8923e6e302d3bfcacf62edf95c0c07806207fd9/06C0BE26-17B5-4753-9729-D909E2099FB2.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RichTextEX支持下划线和描边的版本(用于Cocos2d-x) 2 | ## 这个是干什么的 3 | 将如下文字内容 4 | `"<#F37C2A><30>【世】<#3AB5B3>寒江孤叶<#F8F4D7>:HelloWorld"` 5 | 生成如图所示样式的RichText(**支持图片以及闪烁、旋转和其他自定义的效果、控件**) 6 | 7 | 示例图片 8 | ## 关于它 9 | 这个是LUA版本的,CPP版本的没写,欢迎移植CPP和JS版本 10 | LUA文件是用一个别人写的文件修改的(添加一些功能,修复几个BUG……话说之前跑都跑不起来啊亲……什么鬼) 11 | 另外抱歉,找不到他的Github链接了…… 12 | ***·****TTF字体支持描边,系统字体是不支持的* 13 | ## 使用说明 14 | RichTextEx使用起来非常简单,只要将RichTextEx.lua复制到你的项目目录中,并require它就可以了 15 | 比如这样: 16 | 17 | APUtils = APUtils or {} 18 | APUtils.RichTextEx = APUtils.RichTextEx or require("APUtils/gui/RichTextEx.lua") 19 | 20 | 使用RichText来创建一个富文本是非常简单的: 21 | 22 | local txt = RichTextEx:create() -- 或 RichTextEx:create(26, cc.c3b(10, 10, 10)) 23 | txt:setText("<#EFB65C><24>您的元宝和银券不足请<#FF0000><35>充值<#EFB65C><24>,或领取抽取元宝奖励!") 24 | -- 多行模式要同时设置 ignoreContentAdaptWithSize(false) 和 contentSize 25 | txt:setMultiLineMode(true) -- 这行其实就是 ignoreContentAdaptWithSize(false) 26 | txt:setContentSize(200, 400) 27 | someNode:addChild(txt) 28 | 29 | ***如果字符串是由用户输入的话,建议调用`RichTextEx.htmlEncode("")`将用户输入内容编码一下,以避免用户输入关键字符导致无法预知的错误*** 30 | **在生成字符串之前会自动调用RichTextEx.htmlDecode,如果你自定义了用于显示文字内容的控件,请记得调用它,以对字符串进行解码** 31 | ###RichTextEx的基本选项 32 | 33 | <#F00> = <#FF0000> = 文字颜色 34 | <32> = 字体大小 35 | = 文字字体 支持TTF 36 | = 图片(filename 可以是已经在 SpriteFrameCache 里的 key,或磁盘文件) 37 | = 指定大小的图片 38 | <+2> <-2> <*2> = 当前字体大小 +-*/ 39 | = 颜色、字体和字体大小恢复默认 40 | \n \t = 换行 和 tab,可能暂时实现得不是很好 最好不要用 如果需要换行你可以创建多个RichText然后依次放好 41 | = 设置1像素描边,只支持TTF字体 42 | = 是否开启下划线 43 | ###RichTextEx的示例选项 (在 RichTextEx.defaultCb 中提供) 44 | = (动画)闪烁那些文字 45 | = (动画)旋转那些文字 46 | = (动画)缩放那些文字 47 | (但如果你做了 setText(t, callback) 除非你在 callback 主动调用 defaultCb,否则以上选项会被忽略) 48 | 49 | ###你可以对功能进行扩展 50 | ` 例如从网络下载图片` 51 | 52 | 同时支持自定义特殊语法,加入 callback 回调就可,如 53 | 54 | txt:setText("XXXXX xxx", function(text, sender) -- 第二个参数 sender 可选 55 | -- 对每一个自定义的 <***> 都会调用此 callback 56 | -- text 就等于 *** (不含<>) 57 | -- 简单的返回一个 Node 的子实例就可,如 58 | -- 如果接收第二个参数 sender,就可获取当前文字大小、颜色: sender._fontSize、sender._textColor 59 | 60 | if string.sub(text, 1, 4) == "aaaa" then 61 | return ccui.Text:create("aaa111" .. string.sub(text, 6)), "", 32) 62 | --这里如果为了代码的健壮性最好加入self:htmlDecode 63 | --return ccui.Text:create(self:htmlDecode("aaa111" .. string.sub(text, 6))), "", 32) 64 | elseif text == "bbbb" then 65 | -- 用当前文字大小和颜色 66 | local lbl = ccui.Text:create("bbb111", "", sender._fontSize) 67 | lbl:setTextColor(sender._textColor) 68 | return lbl 69 | elseif string.sub(text, 1, 4) == "CCCC" then 70 | local img = ccui.ImageView:create(....) 71 | img:setScale(...) 72 | img:runAction(...) 73 | return img 74 | end 75 | end) 76 | 77 | ## 你还要做什么 78 | ***为了支持描边和下划线还要修改一下Cocos的源码*** 79 | UIRichText.h和UIRichText.cpp放到项目源码目录 替换原来的 80 | 路径:frameworks/cocos2d-x/cocos/ui 81 | (修改内容主要三个方面:加入下划线设置,加入描边设置,RichText可以自动更改高度了) 82 | 另外还需要修改toLua文件 83 | frameworks/cocos2d-x/cocos/scripting/lua-bindings/auto/lua_cocos2dx_ui_auto.cpp 84 | 18878行左右能看到两个函数 85 | int lua_cocos2dx_ui_RichElementText_init(lua_State* tolua_S) 86 | 和 87 | int lua_cocos2dx_ui_RichElementText_create(lua_State* tolua_S) 88 | 将这两个函数的实现替换为如下形式: 89 | 90 | int lua_cocos2dx_ui_RichElementText_init(lua_State* tolua_S) 91 | { 92 | int argc = 0; 93 | cocos2d::ui::RichElementText* cobj = nullptr; 94 | bool ok = true; 95 | 96 | #if COCOS2D_DEBUG >= 1 97 | tolua_Error tolua_err; 98 | #endif 99 | 100 | 101 | #if COCOS2D_DEBUG >= 1 102 | if (!tolua_isusertype(tolua_S,1,"ccui.RichElementText",0,&tolua_err)) goto tolua_lerror; 103 | #endif 104 | 105 | cobj = (cocos2d::ui::RichElementText*)tolua_tousertype(tolua_S,1,0); 106 | 107 | #if COCOS2D_DEBUG >= 1 108 | if (!cobj) 109 | { 110 | tolua_error(tolua_S,"invalid 'cobj' in function 'lua_cocos2dx_ui_RichElementText_init'", nullptr); 111 | return 0; 112 | } 113 | #endif 114 | 115 | argc = lua_gettop(tolua_S)-1; 116 | if (argc == 8) 117 | { 118 | int arg0; 119 | cocos2d::Color3B arg1; 120 | uint16_t arg2; 121 | std::string arg3; 122 | std::string arg4; 123 | double arg5; 124 | int arg6; 125 | bool arg7; 126 | 127 | ok &= luaval_to_int32(tolua_S, 2,(int *)&arg0, "ccui.RichElementText:init"); 128 | 129 | ok &= luaval_to_color3b(tolua_S, 3, &arg1, "ccui.RichElementText:init"); 130 | 131 | ok &= luaval_to_uint16(tolua_S, 4,&arg2, "ccui.RichElementText:init"); 132 | 133 | ok &= luaval_to_std_string(tolua_S, 5,&arg3, "ccui.RichElementText:init"); 134 | 135 | ok &= luaval_to_std_string(tolua_S, 6,&arg4, "ccui.RichElementText:init"); 136 | 137 | ok &= luaval_to_number(tolua_S, 7,&arg5, "ccui.RichElementText:init"); 138 | 139 | ok &= luaval_to_int32(tolua_S, 8,&arg6, "ccui.RichElementText:init"); 140 | 141 | ok &= luaval_to_boolean(tolua_S, 9,&arg7, "ccui.RichElementText:init"); 142 | if(!ok) 143 | { 144 | tolua_error(tolua_S,"invalid arguments in function 'lua_cocos2dx_ui_RichElementText_init'", nullptr); 145 | return 0; 146 | } 147 | bool ret = cobj->init(arg0, arg1, arg2, arg3, arg4, arg5,arg6,arg7); 148 | tolua_pushboolean(tolua_S,(bool)ret); 149 | return 1; 150 | } 151 | luaL_error(tolua_S, "%s has wrong number of arguments: %d, was expecting %d \n", "ccui.RichElementText:init",argc, 6); 152 | return 0; 153 | 154 | #if COCOS2D_DEBUG >= 1 155 | tolua_lerror: 156 | tolua_error(tolua_S,"#ferror in function 'lua_cocos2dx_ui_RichElementText_init'.",&tolua_err); 157 | #endif 158 | 159 | return 0; 160 | } 161 | int lua_cocos2dx_ui_RichElementText_create(lua_State* tolua_S) 162 | { 163 | int argc = 0; 164 | bool ok = true; 165 | 166 | #if COCOS2D_DEBUG >= 1 167 | tolua_Error tolua_err; 168 | #endif 169 | 170 | #if COCOS2D_DEBUG >= 1 171 | if (!tolua_isusertable(tolua_S,1,"ccui.RichElementText",0,&tolua_err)) goto tolua_lerror; 172 | #endif 173 | 174 | argc = lua_gettop(tolua_S) - 1; 175 | 176 | if (argc == 6) 177 | { 178 | int arg0; 179 | cocos2d::Color3B arg1; 180 | uint16_t arg2; 181 | std::string arg3; 182 | std::string arg4; 183 | double arg5; 184 | ok &= luaval_to_int32(tolua_S, 2,(int *)&arg0, "ccui.RichElementText:create"); 185 | ok &= luaval_to_color3b(tolua_S, 3, &arg1, "ccui.RichElementText:create"); 186 | ok &= luaval_to_uint16(tolua_S, 4,&arg2, "ccui.RichElementText:create"); 187 | ok &= luaval_to_std_string(tolua_S, 5,&arg3, "ccui.RichElementText:create"); 188 | ok &= luaval_to_std_string(tolua_S, 6,&arg4, "ccui.RichElementText:create"); 189 | ok &= luaval_to_number(tolua_S, 7,&arg5, "ccui.RichElementText:create"); 190 | if(!ok) 191 | { 192 | tolua_error(tolua_S,"invalid arguments in function 'lua_cocos2dx_ui_RichElementText_create'", nullptr); 193 | return 0; 194 | } 195 | cocos2d::ui::RichElementText* ret = cocos2d::ui::RichElementText::create(arg0, arg1, arg2, arg3, arg4, arg5); 196 | object_to_luaval(tolua_S, "ccui.RichElementText",(cocos2d::ui::RichElementText*)ret); 197 | return 1; 198 | } 199 | if (argc == 7) 200 | { 201 | int arg0; 202 | cocos2d::Color3B arg1; 203 | uint16_t arg2; 204 | std::string arg3; 205 | std::string arg4; 206 | double arg5; 207 | int arg6; 208 | ok &= luaval_to_int32(tolua_S, 2,(int *)&arg0, "ccui.RichElementText:create"); 209 | ok &= luaval_to_color3b(tolua_S, 3, &arg1, "ccui.RichElementText:create"); 210 | ok &= luaval_to_uint16(tolua_S, 4,&arg2, "ccui.RichElementText:create"); 211 | ok &= luaval_to_std_string(tolua_S, 5,&arg3, "ccui.RichElementText:create"); 212 | ok &= luaval_to_std_string(tolua_S, 6,&arg4, "ccui.RichElementText:create"); 213 | ok &= luaval_to_number(tolua_S, 7,&arg5, "ccui.RichElementText:create"); 214 | ok &= luaval_to_int32(tolua_S, 8,&arg6, "ccui.RichElementText:create"); 215 | if(!ok) 216 | { 217 | tolua_error(tolua_S,"invalid arguments in function 'lua_cocos2dx_ui_RichElementText_create'", nullptr); 218 | return 0; 219 | } 220 | cocos2d::ui::RichElementText* ret = cocos2d::ui::RichElementText::create(arg0, arg1, arg2, arg3, arg4, arg5, arg6); 221 | object_to_luaval(tolua_S, "ccui.RichElementText",(cocos2d::ui::RichElementText*)ret); 222 | return 1; 223 | } 224 | if (argc == 8) 225 | { 226 | int arg0; 227 | cocos2d::Color3B arg1; 228 | uint16_t arg2; 229 | std::string arg3; 230 | std::string arg4; 231 | double arg5; 232 | int arg6; 233 | bool arg7; 234 | ok &= luaval_to_int32(tolua_S, 2,(int *)&arg0, "ccui.RichElementText:create"); 235 | ok &= luaval_to_color3b(tolua_S, 3, &arg1, "ccui.RichElementText:create"); 236 | ok &= luaval_to_uint16(tolua_S, 4,&arg2, "ccui.RichElementText:create"); 237 | ok &= luaval_to_std_string(tolua_S, 5,&arg3, "ccui.RichElementText:create"); 238 | ok &= luaval_to_std_string(tolua_S, 6,&arg4, "ccui.RichElementText:create"); 239 | ok &= luaval_to_number(tolua_S, 7,&arg5, "ccui.RichElementText:create"); 240 | ok &= luaval_to_int32(tolua_S, 8,&arg6, "ccui.RichElementText:create"); 241 | ok &= luaval_to_boolean(tolua_S, 9,&arg7, "ccui.RichElementText:create"); 242 | if(!ok) 243 | { 244 | tolua_error(tolua_S,"invalid arguments in function 'lua_cocos2dx_ui_RichElementText_create'", nullptr); 245 | return 0; 246 | } 247 | cocos2d::ui::RichElementText* ret = cocos2d::ui::RichElementText::create(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); 248 | object_to_luaval(tolua_S, "ccui.RichElementText",(cocos2d::ui::RichElementText*)ret); 249 | return 1; 250 | } 251 | luaL_error(tolua_S, "%s has wrong number of arguments: %d, was expecting %d\n ", "ccui.RichElementText:create",argc, 6); 252 | return 0; 253 | #if COCOS2D_DEBUG >= 1 254 | tolua_lerror: 255 | tolua_error(tolua_S,"#ferror in function 'lua_cocos2dx_ui_RichElementText_create'.",&tolua_err); 256 | #endif 257 | return 0; 258 | } 259 | 重新编译一下项目,然后就可以在项目里用了 260 | ##下个版本要更新的内容 261 | 1.继续修改Cocos2d-x的RichText的源码,使其更好的支持tab和换行 262 | 2.加入可点击的文字,以及点击后变色 263 | 3.为系统字体加入描边(判断为系统字体时,描边采用阴影替代) 264 | 下划线实现的非常拙略,如果你有更好的方法一定要告诉我。 265 | ***欢迎交流QQ:446569365*** 266 | -------------------------------------------------------------------------------- /SourceCode/RichTextEx.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | ------------------------------------------------- 3 | RichTextEx.lua 4 | Created by liangX on 15-04-10. 5 | Fixed by ArcherPeng on 15-07-02. 6 | ------------------------------------------------- 7 | 一个简单的富文本 Label,用法 8 | 9 | local txt = RichTextEx:create() -- 或 RichTextEx:create(26, cc.c3b(10, 10, 10)) 10 | txt:setText("<#333>你\t好<#800>\n\t<世界><33bad_fmt<#555><64>Big<#077><18>SMALL<") 11 | -- 多行模式要同时设置 ignoreContentAdaptWithSize(false) 和 contentSize 12 | txt:setMultiLineMode(true) -- 这行其实就是 ignoreContentAdaptWithSize(false) 13 | txt:setContentSize(200, 400) 14 | addChild(txt) 15 | 16 | 如果字符串是由用户输入的话,建议调用RichTextEx.htmlEncode("")将用户输入内容编码一下,以避免用户输入关键字符导致无法预知的错误 17 | 在生成字符串之前会自动调用RichTextEx.htmlDecode,如果你自定义了字符串创建,请记得调用这个,以解码 18 | 19 | 基本选项是 20 | <#F00> = <#FF0000> = 文字颜色 21 | <32> = 字体大小 22 | = 文字字体 支持TTF 23 | = 图片(filename 可以是已经在 SpriteFrameCache 里的 key,或磁盘文件) 24 | = 指定大小的图片 25 | <+2> <-2> <*2> = 当前字体大小 +-*/ 26 | = 颜色、字体和字体大小恢复默认 27 | \n \t = 换行 和 tab,可能暂时实现得不是很好 28 | 29 | --下边功能需要更换ArcherPeng修改过的RichText! 30 | = 设置1像素描边,只支持TTF字体 31 | = 是否开启下划线 32 | 33 | 示例选项是 (在 RichTextEx.defaultCb 中提供) 34 | = (动画)闪烁那些文字 35 | = (动画)旋转那些文字 36 | = (动画)缩放那些文字 37 | (但如果你做了 setText(t, callback) 除非你在 callback 主动调用 defaultCb,否则以上选项会被忽略) 38 | 39 | TODO 或自己自行可扩展 40 | 从网络下载图片 41 | ... 42 | 43 | 同时支持自定义特殊语法,加入 callback 回调就可,如 44 | 45 | txt:setText("XXXXX xxx", function(text, sender) -- 第二个参数 sender 可选 46 | -- 对每一个自定义的 <***> 都会调用此 callback 47 | -- text 就等于 *** (不含<>) 48 | -- 简单的返回一个 Node 的子实例就可,如 49 | -- 如果接收第二个参数 sender,就可获取当前文字大小、颜色: sender._fontSize、sender._textColor 50 | 51 | if string.sub(text, 1, 4) == "aaaa" then 52 | return ccui.Text:create("aaa111" .. string.sub(text, 6)), "", 32) 53 | --这里如果为了代码的健壮性最好加入self:htmlDecode 54 | --return ccui.Text:create(self:htmlDecode("aaa111" .. string.sub(text, 6))), "", 32) 55 | elseif text == "bbbb" then 56 | -- 用当前文字大小和颜色 57 | local lbl = ccui.Text:create("bbb111", "", sender._fontSize) 58 | lbl:setTextColor(sender._textColor) 59 | return lbl 60 | elseif string.sub(text, 1, 4) == "CCCC" then 61 | local img = ccui.ImageView:create(....) 62 | img:setScale(...) 63 | img:runAction(...) 64 | return img 65 | end 66 | end) 67 | 68 | ArcherPeng FixedLog: 69 | 1.修复Create时候tolua的报错 70 | 2.加入自定义字体的功能 71 | --]] 72 | 73 | --///////////////////////////////////////////////////////////////////////////// 74 | 75 | local _M = class("RichTextEx", function(...) 76 | return ccui.RichText:create(...) 77 | 78 | end) 79 | 80 | 81 | --///////////////////////////////////////////////////////////////////////////// 82 | local str_sub = string.sub 83 | local str_rep = string.rep 84 | local str_byte = string.byte 85 | local str_gsub = string.gsub 86 | local str_find = string.find 87 | 88 | local str_trim = function(input) 89 | input = str_gsub(input, "^[ \t\n\r]+", "") 90 | return str_gsub(input, "[ \t\n\r]+$", "") 91 | end 92 | 93 | local C_AND = str_byte("&") 94 | local P_BEG = str_byte("<") 95 | local P_END = str_byte(">") 96 | local SHARP = str_byte("#") 97 | local ULINE = str_byte("_") 98 | local C_LN = str_byte("\n") 99 | local C_TAB = str_byte("\t") 100 | local C_RST = str_byte("!") 101 | local C_INC = str_byte("+") 102 | local C_DEC = str_byte("-") 103 | local C_MUL = str_byte("*") 104 | local C_DIV = str_byte("/") 105 | 106 | local function c3b_to_c4b(c3b) 107 | return { r = c3b.r, g = c3b.g, b = c3b.b, a = 255 } 108 | end 109 | 110 | -------------------------------------------------------------------------------- 111 | -- #RRGGBB/#RGB to c3b 112 | local function c3b_parse(s) 113 | local r, g, b = 0, 0, 0 114 | if #s == 4 then 115 | r, g, b = tonumber(str_rep(str_sub(s, 2, 2), 2), 16), 116 | tonumber(str_rep(str_sub(s, 3, 3), 2), 16), 117 | tonumber(str_rep(str_sub(s, 4, 4), 2), 16) 118 | elseif #s == 7 then 119 | r, g, b = tonumber(str_sub(s, 2, 3), 16), 120 | tonumber(str_sub(s, 4, 5), 16), 121 | tonumber(str_sub(s, 6, 7), 16) 122 | end 123 | return cc.c3b(r, g, b) 124 | end 125 | 126 | -------------------------------------------------------------------------------- 127 | -- local _FIX = { 128 | -- ["<"] = "<", 129 | -- [">"] = ">", 130 | -- } 131 | -- local function str_fix(s) 132 | -- for k, v in pairs(_FIX) do 133 | -- s = str_gsub(s, k, v) 134 | -- end 135 | -- return s 136 | -- end 137 | 138 | --///////////////////////////////////////////////////////////////////////////// 139 | function _M:ctor(fontSize, textColor) 140 | self._text = "" 141 | self._fontSizeDef = fontSize or 26 142 | self._textColorDef = textColor or cc.c3b(11, 11, 11) 143 | self._fontSize = self._fontSizeDef 144 | self._textColor = self._textColorDef 145 | self._elements = {} 146 | self._textFont = "" 147 | self._outLine = 0 148 | self._underLine = false 149 | 150 | end 151 | 152 | --///////////////////////////////////////////////////////////////////////////// 153 | -- 多行模式,要设置 ignoreContentAdaptWithSize(false) 和设置 setContentSize() 154 | function _M:setMultiLineMode(b) 155 | self:ignoreContentAdaptWithSize(not b) 156 | return self 157 | end 158 | 159 | --///////////////////////////////////////////////////////////////////////////// 160 | function _M.defaultCb(text, sender) 161 | local BLINK = "blink " 162 | local ROTATE = "rotate " 163 | local SCALE = "scale " 164 | 165 | if str_sub(text, 1, #BLINK) == BLINK then 166 | local lbl = ccui.Text:create(self:htmlDecode(str_sub(text, #BLINK + 1)), "", sender._fontSize) 167 | lbl:setTextColor(c3b_to_c4b(sender._textColor)) 168 | lbl:runAction(cc.RepeatForever:create(cc.Blink:create(10, 10))) 169 | return lbl 170 | elseif str_sub(text, 1, #ROTATE) == ROTATE then 171 | local lbl = ccui.Text:create(self:htmlDecode(str_sub(text, #ROTATE + 1)), "", sender._fontSize) 172 | lbl:setTextColor(c3b_to_c4b(sender._textColor)) 173 | lbl:runAction(cc.RepeatForever:create(cc.RotateBy:create(0.1, 5))) 174 | return lbl 175 | elseif str_sub(text, 1, #SCALE) == SCALE then 176 | local lbl = ccui.Text:create(self:htmlDecode(str_sub(text, #SCALE + 1)), "", sender._fontSize) 177 | lbl:setTextColor(c3b_to_c4b(sender._textColor)) 178 | lbl:runAction(cc.RepeatForever:create(cc.Sequence:create(cc.ScaleTo:create(1.0, 0.1), cc.ScaleTo:create(1.0, 1.0)))) 179 | return lbl 180 | end 181 | 182 | return nil 183 | end 184 | 185 | --///////////////////////////////////////////////////////////////////////////// 186 | -- TODO: 对 http:// 开头的路径进行动态网络下载 187 | function _M.defaultImgCb(text) 188 | local w, h = 0, 0 189 | if str_byte(text, 1) == ULINE then 190 | local p1 = str_find(text, "*") 191 | local p2 = str_find(text, " ") 192 | 193 | if p1 and p2 and p2 > p1 then 194 | w = tonumber(str_sub(text, 2, p1 - 1)) 195 | h = tonumber(str_sub(text, p1 + 1, p2)) 196 | end 197 | 198 | if p2 then 199 | text = str_trim(str_sub(text, p2 + 1)) 200 | end 201 | end 202 | 203 | local spf, img = cc.SpriteFrameCache:getInstance():getSpriteFrame(text), nil 204 | if spf then 205 | -- img = cc.Sprite:createWithSpriteFrame(spf) 206 | img = ccui.ImageView:create(text, ccui.TextureResType.plistType) 207 | elseif cc.FileUtils:getInstance():isFileExist(text) then 208 | -- img = cc.Sprite:create(text) 209 | img = ccui.ImageView:create(text, ccui.TextureResType.localType) 210 | end 211 | 212 | if img and w and h and w > 0 and h > 0 then 213 | img:ignoreContentAdaptWithSize(false) -- cc.Sprite can't do this, so we use ccui.ImageView 214 | img:setContentSize(cc.size(w, h)) 215 | end 216 | 217 | return img 218 | end 219 | 220 | --///////////////////////////////////////////////////////////////////////////// 221 | function _M:addCustomNode(node) 222 | if node then 223 | local anc = node:getAnchorPoint() 224 | if anc.x ~= 0.0 or anc.y ~= 0.0 then 225 | local tmp = node 226 | local siz = node:getContentSize() 227 | node = cc.Node:create() 228 | node:setContentSize(siz) 229 | node:addChild(tmp) 230 | tmp:setPosition(cc.p(siz.width * anc.x, siz.height * anc.y)) 231 | end 232 | local obj = ccui.RichElementCustomNode:create(0, cc.c3b(255,255,255), 255, node) 233 | self:pushBackElement(obj) 234 | self._elements[#self._elements + 1] = obj 235 | end 236 | end 237 | 238 | --///////////////////////////////////////////////////////////////////////////// 239 | -- 可以在 callback 里添加各种自定义语法控制 240 | function _M:setText(text, callback) 241 | assert(text) 242 | 243 | self._text = text 244 | self._callback = callback or self.defaultCb 245 | 246 | self._fontSize = self._fontSizeDef 247 | self._textColor = self._textColorDef 248 | 249 | -- clear 250 | for _, lbl in pairs(self._elements) do 251 | self:removeElement(lbl) 252 | end 253 | self._elements = {} 254 | 255 | local p, i, b, c = 1, 1, false 256 | local str, len, chr, obj = "", #text 257 | 258 | while i <= len do 259 | c = str_byte(text, i) 260 | if c == P_BEG then -- < 261 | if (not b) and (i > p) then 262 | str = str_sub(text, p, i - 1) 263 | obj = ccui.RichElementText:create(0, self._textColor, 255, self:htmlDecode(str), self._textFont, self._fontSize,self._outLine,self._underLine) 264 | self:pushBackElement(obj) 265 | self._elements[#self._elements + 1] = obj 266 | end 267 | 268 | b = true; p = i + 1; i = p 269 | 270 | while i < len do 271 | if str_byte(text, i) == P_END then -- > 272 | b = false 273 | if i > p then 274 | str = str_trim(str_sub(text, p, i - 1)) 275 | chr = str_byte(str, 1) 276 | if chr == SHARP and (#str == 4 or #str == 7) and tonumber(str_sub(str, 2), 16) then -- textColor 277 | self._textColor = c3b_parse(str) 278 | elseif chr == C_RST and #str == 1 then -- reset 279 | self._textColor = self._textColorDef 280 | self._fontSize = self._fontSizeDef 281 | self._textFont = "" 282 | self._outLine = 0 283 | self._underLine = false 284 | elseif (chr == C_INC or chr == C_DEC or chr == C_MUL or chr == C_DIV) 285 | and tonumber(str_sub(str, 2)) then 286 | local v = tonumber(str_sub(str, 2)) or 0 287 | if chr == C_INC then 288 | self._fontSize = self._fontSize + v 289 | elseif chr == C_DEC then 290 | self._fontSize = self._fontSize - v 291 | elseif chr == C_MUL then 292 | self._fontSize = self._fontSize * v 293 | elseif v ~= 0 then 294 | self._fontSize = self._fontSize / v 295 | end 296 | elseif tonumber(str) then -- fontSize 297 | self._fontSize = tonumber(str) 298 | elseif str_sub(str, 1, 5) == "font " or str_sub(str, 1, 5) == "font_" then 299 | self._textFont = str_trim(str_sub(str, 5, i - 1)) 300 | elseif str_sub(str, 1, 8) == "outLine " or str_sub(str, 1, 8) == "outLine_" then 301 | local strTemp = str_trim(str_sub(str, 8, i - 1)) 302 | self._outLine = tonumber(strTemp) 303 | elseif str_sub(str, 1, 10) == "underLine " or str_sub(str, 1, 10) == "underLine_" then 304 | local strTemp = str_trim(str_sub(str, 10, i - 1)) 305 | if strTemp == "true" then 306 | self._underLine = true 307 | else 308 | self._underLine = false 309 | end 310 | 311 | elseif str_sub(str, 1, 4) == "img " or str_sub(str, 1, 4) == "img_" then 312 | self:addCustomNode(self.defaultImgCb(str_trim(str_sub(str, 4, i - 1)))) 313 | elseif self._callback then 314 | self:addCustomNode(self._callback(str, self)) 315 | end 316 | end 317 | 318 | break 319 | end 320 | i = i + 1 321 | end 322 | 323 | p = i + 1 324 | elseif c == C_LN or c == C_TAB then 325 | if (not b) and (i > p) then 326 | str = str_sub(text, p, i - 1) 327 | obj = ccui.RichElementText:create(0, self._textColor, 255, self:htmlDecode(str), self._textFont, self._fontSize,self._outLine,self._underLine) 328 | self:pushBackElement(obj) 329 | self._elements[#self._elements + 1] = obj 330 | end 331 | 332 | obj = cc.Node:create() 333 | if c == C_LN then 334 | obj:setContentSize(cc.size(self:getContentSize().width, 1)) 335 | else 336 | obj:setContentSize(cc.size(self._fontSize * 2, 1)) 337 | end 338 | self:addCustomNode(obj) 339 | 340 | 341 | p = i + 1 342 | end 343 | 344 | i = i + 1 345 | end 346 | 347 | if (not b) and (p <= len) then 348 | str = str_sub(text, p) 349 | obj = ccui.RichElementText:create(0, self._textColor, 255, self:htmlDecode(str), self._textFont, self._fontSize,self._outLine,self._underLine) 350 | self:pushBackElement(obj) 351 | self._elements[#self._elements + 1] = obj 352 | end 353 | 354 | return self 355 | end 356 | function _M:setDefaultFont(font) 357 | self._textFont = font 358 | end 359 | 360 | 361 | --[[-- 362 | 363 | 将特殊字符转为 HTML 转义符 364 | 365 | ~~~ lua 366 | 367 | print(RichTextEx.htmlEncode("")) 368 | -- 输出 <ABC> 369 | 370 | ~~~ 371 | 372 | @param string input 输入字符串 373 | 374 | @return string 转换结果 375 | 376 | 377 | 本来想直接把触控的function里边的算法扳过来,发现不对,也不知道哪个二货写的算法。也许是我看的哪个function版本太低了 378 | 对于<> 编码成 < > 这个很正常,然后他又把>的&给编码成& 379 | 期望的编码结果 "<ABC>" 最终让他给编成了 "&lt;ABC&gt;" 解析时候就出错了。。。。 380 | ]] 381 | 382 | function _M.htmlEncode(self,input) 383 | if not input then input = self end 384 | input = string.gsub(input,"&", "&") 385 | input = string.gsub(input,"\"", """) 386 | input = string.gsub(input,"'", "'") 387 | input = string.gsub(input,"<", "<") 388 | input = string.gsub(input,">", ">") 389 | return input 390 | end 391 | 392 | --[[-- 393 | 394 | 将 HTML 转义符还原为特殊字符,功能与 string.htmlEncode() 正好相反 395 | 396 | ~~~ lua 397 | 398 | print(RichTextEx.htmlDecode("<ABC>")) 399 | -- 输出 400 | 401 | ~~~ 402 | 403 | @param string input 输入字符串 404 | 405 | @return string 转换结果 406 | 407 | ]] 408 | function _M.htmlDecode(self,input) 409 | if not input then input = self end 410 | input = string.gsub(input,">",">") 411 | input = string.gsub(input,"<","<") 412 | input = string.gsub(input,"'","'") 413 | input = string.gsub(input,""","\"") 414 | input = string.gsub(input,"&","&") 415 | return input 416 | end 417 | function _M:create(...) 418 | local richTextEx = _M.new(...) 419 | return richTextEx 420 | end 421 | 422 | --///////////////////////////////////////////////////////////////////////////// 423 | 424 | return _M 425 | 426 | -------------------------------------------------------------------------------- /SourceCode/UIRichText.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | Copyright (c) 2013 cocos2d-x.org 3 | 4 | http://www.cocos2d-x.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | ****************************************************************************/ 24 | 25 | #include "UIRichText.h" 26 | #include "platform/CCFileUtils.h" 27 | #include "2d/CCLabel.h" 28 | #include "2d/CCSprite.h" 29 | #include "base/ccUTF8.h" 30 | #include "ui/UIHelper.h" 31 | 32 | NS_CC_BEGIN 33 | 34 | namespace ui { 35 | 36 | 37 | bool RichElement::init(int tag, const Color3B &color, GLubyte opacity) 38 | { 39 | _tag = tag; 40 | _color = color; 41 | _opacity = opacity; 42 | return true; 43 | } 44 | 45 | 46 | RichElementText* RichElementText::create(int tag, const Color3B &color, GLubyte opacity, const std::string& text, const std::string& fontName, float fontSize,int outLine,bool underLine) 47 | { 48 | RichElementText* element = new (std::nothrow) RichElementText(); 49 | if (element && element->init(tag, color, opacity, text, fontName, fontSize,outLine,underLine)) 50 | { 51 | element->autorelease(); 52 | return element; 53 | } 54 | CC_SAFE_DELETE(element); 55 | return nullptr; 56 | } 57 | 58 | bool RichElementText::init(int tag, const Color3B &color, GLubyte opacity, const std::string& text, const std::string& fontName, float fontSize,int outLine,bool underLine) 59 | { 60 | if (RichElement::init(tag, color, opacity)) 61 | { 62 | _text = text; 63 | _fontName = fontName; 64 | _fontSize = fontSize; 65 | _outLine = outLine; 66 | _underLine = underLine; 67 | return true; 68 | } 69 | return false; 70 | } 71 | 72 | RichElementImage* RichElementImage::create(int tag, const Color3B &color, GLubyte opacity, const std::string& filePath) 73 | { 74 | RichElementImage* element = new (std::nothrow) RichElementImage(); 75 | if (element && element->init(tag, color, opacity, filePath)) 76 | { 77 | element->autorelease(); 78 | return element; 79 | } 80 | CC_SAFE_DELETE(element); 81 | return nullptr; 82 | } 83 | 84 | bool RichElementImage::init(int tag, const Color3B &color, GLubyte opacity, const std::string& filePath) 85 | { 86 | if (RichElement::init(tag, color, opacity)) 87 | { 88 | _filePath = filePath; 89 | return true; 90 | } 91 | return false; 92 | } 93 | 94 | RichElementCustomNode* RichElementCustomNode::create(int tag, const Color3B &color, GLubyte opacity, cocos2d::Node *customNode) 95 | { 96 | RichElementCustomNode* element = new (std::nothrow) RichElementCustomNode(); 97 | if (element && element->init(tag, color, opacity, customNode)) 98 | { 99 | element->autorelease(); 100 | return element; 101 | } 102 | CC_SAFE_DELETE(element); 103 | return nullptr; 104 | } 105 | 106 | bool RichElementCustomNode::init(int tag, const Color3B &color, GLubyte opacity, cocos2d::Node *customNode) 107 | { 108 | if (RichElement::init(tag, color, opacity)) 109 | { 110 | _customNode = customNode; 111 | _customNode->retain(); 112 | return true; 113 | } 114 | return false; 115 | } 116 | 117 | RichText::RichText(): 118 | _formatTextDirty(true), 119 | _leftSpaceWidth(0.0f), 120 | _verticalSpace(0.0f), 121 | _elementRenderersContainer(nullptr) 122 | { 123 | 124 | } 125 | 126 | RichText::~RichText() 127 | { 128 | _richElements.clear(); 129 | } 130 | 131 | RichText* RichText::create() 132 | { 133 | RichText* widget = new (std::nothrow) RichText(); 134 | if (widget && widget->init()) 135 | { 136 | widget->autorelease(); 137 | return widget; 138 | } 139 | CC_SAFE_DELETE(widget); 140 | return nullptr; 141 | } 142 | 143 | bool RichText::init() 144 | { 145 | if (Widget::init()) 146 | { 147 | return true; 148 | } 149 | return false; 150 | } 151 | 152 | void RichText::initRenderer() 153 | { 154 | _elementRenderersContainer = Node::create(); 155 | _elementRenderersContainer->setAnchorPoint(Vec2(0.5f, 0.5f)); 156 | addProtectedChild(_elementRenderersContainer, 0, -1); 157 | } 158 | 159 | void RichText::insertElement(RichElement *element, int index) 160 | { 161 | _richElements.insert(index, element); 162 | _formatTextDirty = true; 163 | } 164 | 165 | void RichText::pushBackElement(RichElement *element) 166 | { 167 | _richElements.pushBack(element); 168 | _formatTextDirty = true; 169 | } 170 | 171 | void RichText::removeElement(int index) 172 | { 173 | _richElements.erase(index); 174 | _formatTextDirty = true; 175 | } 176 | 177 | void RichText::removeElement(RichElement *element) 178 | { 179 | _richElements.eraseObject(element); 180 | _formatTextDirty = true; 181 | } 182 | 183 | void RichText::formatText() 184 | { 185 | if (_formatTextDirty) 186 | { 187 | _elementRenderersContainer->removeAllChildren(); 188 | _elementRenders.clear(); 189 | if (_ignoreSize) 190 | { 191 | addNewLine(); 192 | for (ssize_t i=0; i<_richElements.size(); i++) 193 | { 194 | RichElement* element = _richElements.at(i); 195 | Node* elementRenderer = nullptr; 196 | switch (element->_type) 197 | { 198 | case RichElement::Type::TEXT: 199 | { 200 | RichElementText* elmtText = static_cast(element); 201 | if (FileUtils::getInstance()->isFileExist(elmtText->_fontName)) 202 | { 203 | elementRenderer = Label::create(); 204 | TTFConfig config; 205 | config.fontFilePath =elmtText->_fontName; 206 | config.fontSize = elmtText->_fontSize; 207 | config.outlineSize = elmtText->_outLine; 208 | CCLOG("config.outlineSize %d",config.outlineSize); 209 | auto labelElement = dynamic_cast(elementRenderer); 210 | labelElement->setTTFConfig(config); 211 | labelElement->setString(elmtText->_text); 212 | if(elmtText->_underLine) 213 | { 214 | auto sp = Sprite::createWithTexture(nullptr, Rect(0, 0, labelElement->getBoundingBox().size.width, elmtText->_fontSize/20)); 215 | 216 | sp->setColor(elmtText->_color); 217 | sp->setAnchorPoint(Vec2::ZERO); 218 | sp->setPosition(Vec2::ZERO); 219 | labelElement->setUserData((void*)sp); 220 | } 221 | } 222 | else 223 | { 224 | elementRenderer = Label::createWithSystemFont(elmtText->_text.c_str(), elmtText->_fontName, elmtText->_fontSize); 225 | if(elmtText->_underLine) 226 | { 227 | auto labelElement = dynamic_cast(elementRenderer); 228 | auto sp = Sprite::createWithTexture(nullptr, Rect(0, 0, labelElement->getBoundingBox().size.width, elmtText->_fontSize/20)); 229 | 230 | sp->setColor(elmtText->_color); 231 | sp->setAnchorPoint(Vec2::ZERO); 232 | sp->setPosition(Vec2::ZERO); 233 | labelElement->setUserData((void*)sp); 234 | } 235 | } 236 | break; 237 | } 238 | case RichElement::Type::IMAGE: 239 | { 240 | RichElementImage* elmtImage = static_cast(element); 241 | elementRenderer = Sprite::create(elmtImage->_filePath.c_str()); 242 | break; 243 | } 244 | case RichElement::Type::CUSTOM: 245 | { 246 | RichElementCustomNode* elmtCustom = static_cast(element); 247 | elementRenderer = elmtCustom->_customNode; 248 | break; 249 | } 250 | default: 251 | break; 252 | } 253 | elementRenderer->setColor(element->_color); 254 | elementRenderer->setOpacity(element->_opacity); 255 | pushToContainer(elementRenderer); 256 | } 257 | } 258 | else 259 | { 260 | addNewLine(); 261 | for (ssize_t i=0; i<_richElements.size(); i++) 262 | { 263 | 264 | RichElement* element = static_cast(_richElements.at(i)); 265 | switch (element->_type) 266 | { 267 | case RichElement::Type::TEXT: 268 | { 269 | RichElementText* elmtText = static_cast(element); 270 | handleTextRenderer(elmtText->_text.c_str(), elmtText->_fontName.c_str(), elmtText->_fontSize, elmtText->_color, elmtText->_opacity,elmtText->_outLine,elmtText->_underLine); 271 | break; 272 | } 273 | case RichElement::Type::IMAGE: 274 | { 275 | RichElementImage* elmtImage = static_cast(element); 276 | handleImageRenderer(elmtImage->_filePath.c_str(), elmtImage->_color, elmtImage->_opacity); 277 | break; 278 | } 279 | case RichElement::Type::CUSTOM: 280 | { 281 | RichElementCustomNode* elmtCustom = static_cast(element); 282 | handleCustomRenderer(elmtCustom->_customNode); 283 | break; 284 | } 285 | default: 286 | break; 287 | } 288 | } 289 | } 290 | formarRenderers(); 291 | _formatTextDirty = false; 292 | } 293 | } 294 | 295 | void RichText::handleTextRenderer(const std::string& text, const std::string& fontName, float fontSize, const Color3B &color, GLubyte opacity,int ouline,bool underline) 296 | { 297 | auto fileExist = FileUtils::getInstance()->isFileExist(fontName); 298 | Label* textRenderer = nullptr; 299 | if (fileExist) 300 | { 301 | // textRenderer = Label::createWithTTF(text, fontName, fontSize); 302 | textRenderer = Label::create(); 303 | TTFConfig config; 304 | config.fontFilePath =fontName; 305 | config.fontSize = fontSize; 306 | config.outlineSize = ouline; 307 | auto labelElement = dynamic_cast(textRenderer); 308 | labelElement->setTTFConfig(config); 309 | labelElement->setString(text); 310 | if(underline) 311 | { 312 | auto sp = Sprite::createWithTexture(nullptr, Rect(0, 0, labelElement->getBoundingBox().size.width, fontSize/20)); 313 | 314 | sp->setColor(color); 315 | sp->setAnchorPoint(Vec2::ZERO); 316 | sp->setPosition(Vec2::ZERO); 317 | labelElement->setUserData((void*)sp); 318 | 319 | } 320 | 321 | } 322 | else 323 | { 324 | textRenderer = Label::createWithSystemFont(text, fontName, fontSize); 325 | if(underline) 326 | { 327 | auto labelElement = dynamic_cast(textRenderer); 328 | auto sp = Sprite::createWithTexture(nullptr, Rect(0, 0, labelElement->getBoundingBox().size.width, fontSize/20)); 329 | 330 | sp->setColor(color); 331 | sp->setAnchorPoint(Vec2::ZERO); 332 | sp->setPosition(Vec2::ZERO); 333 | labelElement->setUserData((void*)sp); 334 | } 335 | } 336 | float textRendererWidth = textRenderer->getContentSize().width; 337 | _leftSpaceWidth -= textRendererWidth; 338 | if (_leftSpaceWidth < 0.0f) 339 | { 340 | float overstepPercent = (-_leftSpaceWidth) / textRendererWidth; 341 | std::string curText = text; 342 | size_t stringLength = StringUtils::getCharacterCountInUTF8String(text); 343 | int leftLength = stringLength * (1.0f - overstepPercent); 344 | std::string leftWords = Helper::getSubStringOfUTF8String(curText,0,leftLength); 345 | std::string cutWords = Helper::getSubStringOfUTF8String(curText, leftLength, stringLength - leftLength); 346 | if (leftLength > 0) 347 | { 348 | Label* leftRenderer = nullptr; 349 | if (fileExist) 350 | { 351 | // leftRenderer = Label::createWithTTF(Helper::getSubStringOfUTF8String(leftWords, 0, leftLength), fontName, fontSize); 352 | leftRenderer = Label::create(); 353 | TTFConfig config; 354 | config.fontFilePath =fontName; 355 | config.fontSize = fontSize; 356 | config.outlineSize = ouline; 357 | auto labelElement = dynamic_cast(leftRenderer); 358 | labelElement->setTTFConfig(config); 359 | labelElement->setString(Helper::getSubStringOfUTF8String(leftWords, 0, leftLength)); 360 | if(underline) 361 | { 362 | auto sp = Sprite::createWithTexture(nullptr, Rect(0, 0, labelElement->getBoundingBox().size.width, fontSize/20)); 363 | 364 | sp->setColor(color); 365 | sp->setAnchorPoint(Vec2::ZERO); 366 | sp->setPosition(Vec2::ZERO); 367 | labelElement->setUserData((void*)sp); 368 | 369 | } 370 | } 371 | else 372 | { 373 | leftRenderer = Label::createWithSystemFont(Helper::getSubStringOfUTF8String(leftWords, 0, leftLength), fontName, fontSize); 374 | if(underline) 375 | { 376 | auto labelElement = dynamic_cast(textRenderer); 377 | auto sp = Sprite::createWithTexture(nullptr, Rect(0, 0, labelElement->getBoundingBox().size.width, fontSize/20)); 378 | 379 | sp->setColor(color); 380 | sp->setAnchorPoint(Vec2::ZERO); 381 | sp->setPosition(Vec2::ZERO); 382 | labelElement->setUserData((void*)sp); 383 | } 384 | } 385 | if (leftRenderer) 386 | { 387 | leftRenderer->setColor(color); 388 | leftRenderer->setOpacity(opacity); 389 | pushToContainer(leftRenderer); 390 | } 391 | } 392 | 393 | addNewLine(); 394 | handleTextRenderer(cutWords.c_str(), fontName, fontSize, color, opacity,ouline,underline); 395 | } 396 | else 397 | { 398 | textRenderer->setColor(color); 399 | textRenderer->setOpacity(opacity); 400 | pushToContainer(textRenderer); 401 | } 402 | } 403 | 404 | void RichText::handleImageRenderer(const std::string& fileParh, const Color3B &color, GLubyte opacity) 405 | { 406 | Sprite* imageRenderer = Sprite::create(fileParh); 407 | handleCustomRenderer(imageRenderer); 408 | } 409 | 410 | void RichText::handleCustomRenderer(cocos2d::Node *renderer) 411 | { 412 | Size imgSize = renderer->getContentSize(); 413 | _leftSpaceWidth -= imgSize.width; 414 | if (_leftSpaceWidth < 0.0f) 415 | { 416 | addNewLine(); 417 | pushToContainer(renderer); 418 | _leftSpaceWidth -= imgSize.width; 419 | } 420 | else 421 | { 422 | pushToContainer(renderer); 423 | } 424 | } 425 | 426 | void RichText::addNewLine() 427 | { 428 | _leftSpaceWidth = _customSize.width; 429 | _elementRenders.push_back(new Vector()); 430 | } 431 | 432 | void RichText::formarRenderers() 433 | { 434 | // float setHeight = 0.0f; 435 | auto pos = this->getPosition(); 436 | if (_ignoreSize) 437 | { 438 | float newContentSizeWidth = 0.0f; 439 | float newContentSizeHeight = 0.0f; 440 | 441 | Vector* row = (_elementRenders[0]); 442 | float nextPosX = 0.0f; 443 | for (ssize_t j=0; jsize(); j++) 444 | { 445 | Node* l = row->at(j); 446 | l->setAnchorPoint(Vec2::ZERO); 447 | l->setPosition(nextPosX, 0.0f); 448 | _elementRenderersContainer->addChild(l, 1); 449 | Size iSize = l->getContentSize(); 450 | newContentSizeWidth += iSize.width; 451 | newContentSizeHeight = MAX(newContentSizeHeight, iSize.height); 452 | nextPosX += iSize.width; 453 | } 454 | _elementRenderersContainer->setContentSize(Size(newContentSizeWidth, newContentSizeHeight)); 455 | } 456 | else 457 | { 458 | float newContentSizeHeight = 0.0f; 459 | float *maxHeights = new float[_elementRenders.size()]; 460 | 461 | for (size_t i=0; i<_elementRenders.size(); i++) 462 | { 463 | Vector* row = (_elementRenders[i]); 464 | float maxHeight = 0.0f; 465 | for (ssize_t j=0; jsize(); j++) 466 | { 467 | Node* l = row->at(j); 468 | maxHeight = MAX(l->getContentSize().height, maxHeight); 469 | } 470 | maxHeights[i] = maxHeight; 471 | newContentSizeHeight += maxHeights[i]; 472 | } 473 | /* 474 | *如果 用户设置 txt:setContentSize(460, 0),height设置为0,视为自动适应大小的RichText 475 | *亦可单独弄一个方法用于设置是”否自动设置RichText的高度“,但是,我觉得ContentSize的hight设置为0,就自动设置高度是蛮合理的。与Label的setDimensions用法相似 476 | */ 477 | if(_customSize.height == 0) 478 | { 479 | _customSize.height =newContentSizeHeight; 480 | } 481 | float nextPosY = _customSize.height; 482 | for (size_t i=0; i<_elementRenders.size(); i++) 483 | { 484 | Vector* row = (_elementRenders[i]); 485 | float nextPosX = 0.0f; 486 | nextPosY -= (maxHeights[i] + _verticalSpace); 487 | 488 | for (ssize_t j=0; jsize(); j++) 489 | { 490 | Node* l = row->at(j); 491 | l->setAnchorPoint(Vec2::ZERO); 492 | l->setPosition(nextPosX, nextPosY); 493 | _elementRenderersContainer->addChild(l, 1); 494 | if (l->getUserData()) 495 | { 496 | Sprite* sp = (Sprite*)(l->getUserData()); 497 | sp->setPosition(Vec2(nextPosX,nextPosY)); 498 | _elementRenderersContainer->addChild(sp, 1); 499 | } 500 | nextPosX += l->getContentSize().width; 501 | } 502 | } 503 | _elementRenderersContainer->setContentSize(_customSize);//这里改成使用_customSize 504 | delete [] maxHeights; 505 | } 506 | 507 | size_t length = _elementRenders.size(); 508 | for (size_t i = 0; i* l = _elementRenders[i]; 511 | l->clear(); 512 | delete l; 513 | } 514 | _elementRenders.clear(); 515 | 516 | if (_ignoreSize) 517 | { 518 | Size s = getVirtualRendererSize(); 519 | this->setContentSize(s); 520 | } 521 | else 522 | { 523 | this->setContentSize(_customSize); 524 | } 525 | updateContentSizeWithTextureSize(_contentSize); 526 | 527 | _elementRenderersContainer->setPosition(_contentSize.width / 2.0f, _contentSize.height / 2.0f); 528 | } 529 | 530 | void RichText::adaptRenderers() 531 | { 532 | this->formatText(); 533 | } 534 | 535 | void RichText::pushToContainer(cocos2d::Node *renderer) 536 | { 537 | if (_elementRenders.size() <= 0) 538 | { 539 | return; 540 | } 541 | _elementRenders[_elementRenders.size()-1]->pushBack(renderer); 542 | } 543 | 544 | void RichText::setVerticalSpace(float space) 545 | { 546 | _verticalSpace = space; 547 | } 548 | 549 | void RichText::setAnchorPoint(const Vec2 &pt) 550 | { 551 | Widget::setAnchorPoint(pt); 552 | _elementRenderersContainer->setAnchorPoint(pt); 553 | } 554 | 555 | Size RichText::getVirtualRendererSize() const 556 | { 557 | return _elementRenderersContainer->getContentSize(); 558 | } 559 | 560 | void RichText::ignoreContentAdaptWithSize(bool ignore) 561 | { 562 | if (_ignoreSize != ignore) 563 | { 564 | _formatTextDirty = true; 565 | Widget::ignoreContentAdaptWithSize(ignore); 566 | } 567 | } 568 | 569 | std::string RichText::getDescription() const 570 | { 571 | return "RichText"; 572 | } 573 | 574 | } 575 | 576 | NS_CC_END 577 | -------------------------------------------------------------------------------- /SourceCode/UIRichText.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | Copyright (c) 2013 cocos2d-x.org 3 | 4 | http://www.cocos2d-x.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | ****************************************************************************/ 24 | 25 | #ifndef __UIRICHTEXT_H__ 26 | #define __UIRICHTEXT_H__ 27 | 28 | #include "ui/UIWidget.h" 29 | #include "ui/GUIExport.h" 30 | 31 | NS_CC_BEGIN 32 | /** 33 | * @addtogroup ui 34 | * @{ 35 | */ 36 | 37 | namespace ui { 38 | 39 | /** 40 | *@brief Rich text element base class. 41 | * It defines the basic common properties for all rich text element. 42 | */ 43 | class CC_GUI_DLL RichElement : public Ref 44 | { 45 | public: 46 | /** 47 | *@brief Rich element type. 48 | */ 49 | enum class Type 50 | { 51 | TEXT, 52 | IMAGE, 53 | CUSTOM 54 | }; 55 | 56 | /** 57 | * @brief Default constructor. 58 | */ 59 | RichElement(){}; 60 | 61 | /** 62 | * @brief Default destructor. 63 | */ 64 | virtual ~RichElement(){}; 65 | 66 | 67 | /** 68 | * @brief Initialize a rich element with different arguments. 69 | * 70 | * @param tag A integer tag value. 71 | * @param color A color in @see `Color3B`. 72 | * @param opacity A opacity value in `GLubyte`. 73 | * @return True if initialize success, false otherwise. 74 | */ 75 | bool init(int tag, const Color3B& color, GLubyte opacity); 76 | protected: 77 | Type _type; 78 | int _tag; 79 | Color3B _color; 80 | GLubyte _opacity; 81 | friend class RichText; 82 | }; 83 | 84 | /** 85 | *@brief Rich element for displaying text. 86 | */ 87 | class CC_GUI_DLL RichElementText : public RichElement 88 | { 89 | public: 90 | 91 | /** 92 | *@brief Default constructor. 93 | */ 94 | RichElementText(){_type = Type::TEXT;}; 95 | 96 | 97 | /** 98 | *@brief Default destructor. 99 | */ 100 | virtual ~RichElementText(){}; 101 | 102 | /** 103 | * @brief Initialize a RichElementText with various arguments. 104 | * 105 | * @param tag A integer tag value. 106 | * @param color A color in Color3B. 107 | * @param opacity A opacity in GLubyte. 108 | * @param text Content string. 109 | * @param fontName Content font name. 110 | * @param fontSize Content font size. 111 | * @return True if initialize scucess, false otherwise. 112 | */ 113 | bool init(int tag, const Color3B& color, GLubyte opacity, const std::string& text, const std::string& fontName, float fontSize,int outLine,bool underLine); 114 | 115 | 116 | /** 117 | * @brief Create a RichElementText with various arguments. 118 | * 119 | * @param tag A integer tag value. 120 | * @param color A color in Color3B. 121 | * @param opacity A opacity in GLubyte. 122 | * @param text Content string. 123 | * @param fontName Content font name. 124 | * @param fontSize Content font size. 125 | * Add By ArcherPeng 126 | * @param outLine 阴影. 127 | * @param underLine 下划线. 128 | * @return RichElementText instance. 129 | */ 130 | static RichElementText* create(int tag, const Color3B& color, GLubyte opacity, const std::string& text, const std::string& fontName, float fontSize,int outLine = 0,bool underLine = false); 131 | protected: 132 | std::string _text; 133 | std::string _fontName; 134 | float _fontSize; 135 | int _outLine; 136 | bool _underLine; 137 | friend class RichText; 138 | 139 | }; 140 | 141 | /** 142 | *@brief Rich element for displaying images. 143 | */ 144 | class CC_GUI_DLL RichElementImage : public RichElement 145 | { 146 | public: 147 | 148 | /** 149 | * @brief Default constructor. 150 | * 151 | */ 152 | RichElementImage(){_type = Type::IMAGE;}; 153 | 154 | 155 | /** 156 | * @brief Default destructor. 157 | */ 158 | virtual ~RichElementImage(){}; 159 | 160 | 161 | /** 162 | * @brief Initialize a RichElementImage with various arguments. 163 | * 164 | * @param tag A integer tag value. 165 | * @param color A color in Color3B. 166 | * @param opacity A opacity in GLubyte. 167 | * @param filePath A image file name. 168 | * @return True if initialize success, false otherwise. 169 | */ 170 | bool init(int tag, const Color3B& color, GLubyte opacity, const std::string& filePath); 171 | 172 | 173 | /** 174 | * @brief Create a RichElementImage with various arguments. 175 | * 176 | * @param tag A integer tag value. 177 | * @param color A color in Color3B. 178 | * @param opacity A opacity in GLubyte. 179 | * @param filePath A image file name. 180 | * @return A RichElementImage instance. 181 | */ 182 | static RichElementImage* create(int tag, const Color3B& color, GLubyte opacity, const std::string& filePath); 183 | protected: 184 | std::string _filePath; 185 | Rect _textureRect; 186 | int _textureType; 187 | friend class RichText; 188 | }; 189 | 190 | /** 191 | *@brief Rich element for displaying custom node type. 192 | */ 193 | class CC_GUI_DLL RichElementCustomNode : public RichElement 194 | { 195 | public: 196 | 197 | /** 198 | * @brief Default constructor. 199 | */ 200 | RichElementCustomNode(){_type = Type::CUSTOM;}; 201 | 202 | 203 | /** 204 | * @brief Default destructor. 205 | */ 206 | virtual ~RichElementCustomNode(){CC_SAFE_RELEASE(_customNode);}; 207 | 208 | 209 | /** 210 | * @brief Initialize a RichElementCustomNode with various arguments. 211 | * 212 | * @param tag A integer tag value. 213 | * @param color A color in Color3B. 214 | * @param opacity A opacity in GLubyte. 215 | * @param customNode A custom node pointer. 216 | * @return True if initialize success, false otherwise. 217 | */ 218 | bool init(int tag, const Color3B& color, GLubyte opacity, Node* customNode); 219 | 220 | /** 221 | * @brief Create a RichElementCustomNode with various arguments. 222 | * 223 | * @param tag A integer tag value. 224 | * @param color A color in Color3B. 225 | * @param opacity A opacity in GLubyte. 226 | * @param customNode A custom node pointer. 227 | * @return A RichElementCustomNode instance. 228 | */ 229 | static RichElementCustomNode* create(int tag, const Color3B& color, GLubyte opacity, Node* customNode); 230 | protected: 231 | Node* _customNode; 232 | friend class RichText; 233 | }; 234 | 235 | /** 236 | *@brief A container for displaying various RichElements. 237 | * We could use it to display texts with images easily. 238 | */ 239 | class CC_GUI_DLL RichText : public Widget 240 | { 241 | public: 242 | 243 | /** 244 | * @brief Default constructor. 245 | */ 246 | RichText(); 247 | 248 | /** 249 | * @brief Default destructor. 250 | */ 251 | virtual ~RichText(); 252 | 253 | /** 254 | * @brief Create a empty RichText. 255 | * 256 | * @return RichText instance. 257 | */ 258 | static RichText* create(); 259 | 260 | /** 261 | * @brief Insert a RichElement at a given index. 262 | * 263 | * @param element A RichElement type. 264 | * @param index A given index. 265 | */ 266 | void insertElement(RichElement* element, int index); 267 | 268 | /** 269 | * @brief Add a RichElement at the end of RichText. 270 | * 271 | * @param element A RichElement instance. 272 | */ 273 | void pushBackElement(RichElement* element); 274 | 275 | /** 276 | * @brief Remove a RichElement at a given index. 277 | * 278 | * @param index A integer index value. 279 | */ 280 | void removeElement(int index); 281 | 282 | /** 283 | * @brief Remove specific RichElement. 284 | * 285 | * @param element A RichElement type. 286 | */ 287 | void removeElement(RichElement* element); 288 | 289 | /** 290 | * @brief Set vertical space between each RichElement. 291 | * 292 | * @param space Point in float. 293 | */ 294 | void setVerticalSpace(float space); 295 | 296 | /** 297 | * @brief Rearrange all RichElement in the RichText. 298 | * It's usually called internally. 299 | */ 300 | void formatText(); 301 | 302 | //override functions. 303 | virtual void setAnchorPoint(const Vec2 &pt) override; 304 | virtual Size getVirtualRendererSize() const override; 305 | virtual void ignoreContentAdaptWithSize(bool ignore) override; 306 | virtual std::string getDescription() const override; 307 | 308 | CC_CONSTRUCTOR_ACCESS: 309 | virtual bool init() override; 310 | 311 | protected: 312 | virtual void adaptRenderers() override; 313 | 314 | virtual void initRenderer() override; 315 | void pushToContainer(Node* renderer); 316 | void handleTextRenderer(const std::string& text, const std::string& fontName, float fontSize, const Color3B& color, GLubyte opacity,int ouline,bool underline); 317 | void handleImageRenderer(const std::string& fileParh, const Color3B& color, GLubyte opacity); 318 | void handleCustomRenderer(Node* renderer); 319 | void formarRenderers(); 320 | void addNewLine(); 321 | protected: 322 | bool _formatTextDirty; 323 | Vector _richElements; 324 | std::vector*> _elementRenders; 325 | float _leftSpaceWidth; 326 | float _verticalSpace; 327 | Node* _elementRenderersContainer; 328 | }; 329 | 330 | } 331 | 332 | // end of ui group 333 | /// @} 334 | NS_CC_END 335 | 336 | #endif /* defined(__UIRichText__) */ 337 | --------------------------------------------------------------------------------