--[[ Copyright (c) 2013 Andrei Maximov 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. ]]-- --[[ Display library provides useful methods for texture caching, scaling, and positioning Sprite instances It also provides the following classes: - Container : Public (Sprite) - Image : Public (Container) - Shape : Public (Container) - TTFont : Public (Gideros TTFont) - Text : Public (Container) - MLText : Public (Container) ]]-- ------------------------------ ---- DEPENDENCY FUNCTIONS ---- ------------------------------ -- Replace value with another if it exists, otherwise resort to default local function setIfExists(value, default) if value ~= nil then return value else return default end end -- Clip a single RGB value local function clipColorValue(colorValue) if colorValue > 255 then colorValue = 255 elseif colorValue < 0 then colorValue = 0 end return colorValue end -- Convert rgb values to hex local function rgbToHex(r, g, b) local sum = r * 65536 + g * 256 + b return sum end ------------------------ ---- IMPLEMENTATION ---- ------------------------ _G["Display"] = {} -- Make tables that hold fonts and textures local textures = {} -- Table that holds the methods/keys that are prohibited during "dual - inheritance" function copying local prohibitedData = { ["__new"] = true, ["__index"] = true, ["new"] = true, ["init"] = true, } ------------------------ -- PRIVATE METHODS ------------------------ -- Adds origianl Gideros methods of a class to a Container with that class being a child of the container local function mixClasses(newClass, giderosClass) for fieldName, fieldData in pairs(giderosClass) do if not prohibitedData[fieldName] then newClass[fieldName] = function(...) local self = arg[1] local newArgs = { self.__inner } for i = 2, #arg, 1 do newArgs[i] = arg[i] end self.__inner[fieldName](unpack(newArgs)) end end end end -- Scales x-coordinate a certain distance for nearest edge local function pullXFromEdge(x) local newX if Display.X_STRETCHED == true then if x < application:getContentWidth() * 0.5 then newX = x - application:getLogicalTranslateX()/application:getLogicalScaleX() else local dx = x - application:getContentWidth() newX = application:getContentWidth() + application:getLogicalTranslateX()/application:getLogicalScaleX() + dx dx = nil end else newX = x end return newX end -- Scales y-coordinate a certain distance for nearest edge local function pullYFromEdge(y) local newY if Display.Y_STRETCHED == true then if y < application:getContentHeight() * 0.5 then newY = y - application:getLogicalTranslateY()/application:getLogicalScaleY() else local dy = y - application:getContentHeight() newY = application:getContentHeight() + application:getLogicalTranslateY()/application:getLogicalScaleY() + dy dy = nil end else newY = y end return newY end -- Private function to redraw a shape local function ShapeRedraw(self) -- Save anchor points and rotation local rotation = self:getRotation() local anchorX, anchorY = self:getAnchorPoint() -- Set rotation to 0 self:setRotation(0) -- Iterate through history local history = self.__history self:clear() if self.__fillStyle then self.__shape:setFillStyle(unpack(self.__fillStyle)) end if self.__lineStyle then self.__shape:setLineStyle(unpack(self.__lineStyle)) end for i = 1, #history, 1 do history[i].method(unpack(history[i].args)) end -- Reset original rotation and anchor point self:setAnchorPoint(anchorX, anchorY) self:setRotation(rotation) end -- Set logical and actual ratios and widths local function checkRatio() if application:getOrientation() == application.LANDSCAPE_RIGHT or application:getOrientation() == application.LANDSCAPE_LEFT then Display.ACTUAL_RATIO = application:getDeviceWidth()/application:getDeviceHeight() Display.FULL_WIDTH = application:getDeviceHeight()/application:getLogicalScaleY() Display.FULL_HEIGHT = application:getDeviceWidth()/application:getLogicalScaleX() else Display.ACTUAL_RATIO = application:getDeviceHeight()/application:getDeviceWidth() Display.FULL_WIDTH = application:getDeviceWidth()/application:getLogicalScaleX() Display.FULL_HEIGHT = application:getDeviceWidth()/application:getLogicalScaleY() end Display.LOGICAL_RATIO = application:getContentHeight()/application:getContentWidth() end checkRatio() -- Check for "stretched" axis local function checkStretch() if Display.ACTUAL_RATIO > Display.LOGICAL_RATIO then Display.Y_STRETCHED = true Display.X_STRETCHED = false elseif Display.LOGICAL_RATIO > Display.ACTUAL_RATIO then Display.Y_STRETCHED = false Display.X_STRETCHED = true else Display.Y_STRETCHED = false Display.X_STRETCHED = false end end -- Check stretch at initlialization checkStretch() -- Set the shorcut values for common screen coordinates local function checkScreen() Display.WIDTH = application:getContentWidth()+1 Display.HEIGHT = application:getContentHeight()+1 Display.LEFT = pullXFromEdge(0)-1 Display.RIGHT = pullXFromEdge(Display.WIDTH)+1 Display.TOP = pullYFromEdge(0)-1 Display.BOTTOM = pullYFromEdge(Display.HEIGHT)+1 Display.HALF_WIDTH = Display.WIDTH/2 Display.HALF_HEIGHT = Display.HEIGHT/2 if Display.X_STRETCHED then Display.WIDTH = Display.RIGHT-Display.LEFT end if Display.Y_STRETCHED then Display.HEIGHT = Display.BOTTOM-Display.TOP end end checkScreen() ------------------------ -- PUBLIC METHODS ------------------------ function Application:clearStage() while stage:getNumChildren() > 0 do stage:removeChildAt(1) end Timer.stopAll() end Display.cacheTexture = function(filePath, doAA) if not textures[filePath] then textures[filePath] = {} end doAA = setIfExists(doAA, true) if doAA == true then if not textures[filePath].aliased then textures[filePath].aliased = Texture.new(filePath, doAA) end else if not textures[filePath].notaliased then textures[filePath].notaliased = Texture.new(filePath, doAA) end end end Display.uncacheTexture = function(filePath, doAA) doAA = setIfExists(doAA, true) if textures[filePath] then if doAA == true then if textures[filePath].aliased then textures[filePath].aliased = nil else print("WARNING: Aliased version of '"..filePath.."' does not exist.") end else if textures[filePath].notaliased then textures[filePath].notaliased = nil else print("WARNING: Non-aliased version of '"..filePath.."' does not exist.") end end -- Now check if we have unloaded the aliased and notaliased version of the texture if not textures[filePath].aliased and not textures[filePath].notaliased then textures[filePath] = nil end end end Display.remove = function(object) if object then object:removeSelf() end end ------------------------ -- CLASSES ------------------------ -- Container class, Essentially an enhaced Sprite _G["Container"] = Core.class(Sprite) function Container:init() -- Properties self.__anchorX = 0 self.__anchorY = 0 self.__defaultAnchorX = 0 self.__defaultAnchorY = 0 self.__doBlockTouches = false self.__eventHolder = {} end function Container:setAnchorPoint(x,y) local bx, by, width, height = self:getBounds(self) local targetbx = -x * width local targetby = -y * height local dx = targetbx - bx local dy = targetby - by self.__anchorX = x self.__anchorY = y for cnt = 1, self:getNumChildren() do local child = self:getChildAt(cnt) child:setX(child:getX() + dx) child:setY(child:getY() + dy) end end function Container:getAnchorPoint() return self:getAnchorX(), self:getAnchorY() end function Container:setDefaultAnchorPoint(x, y) self.__defaultAnchorX = x self.__defaultAnchorY = y end function Container:getDefaultAnchorPoint() return self.__defaultAnchorX, self.__defaultAnchorY end function Container:setAnchorX(x) local bx, by, width, height = self:getBounds(self) local targetbx = -x * width local dx = targetbx - bx self.__anchorX = x for cnt = 1, self:getNumChildren() do local child = self:getChildAt(cnt) child:setX(child:getX() + dx) end end function Container:getAnchorX() return self.__anchorX end function Container:setAnchorY(y) local bx, by, width, height = self:getBounds(self) local targetby = -y * height local dy = targetby - by self.__anchorY = y for cnt = 1, self:getNumChildren() do local child = self:getChildAt(cnt) child:setY(child:getY() + dy) end end function Container:getAnchorY() return self.__anchorY end function Container:setGlobalX(x) local parent = self:getParent() local gx = self:getGlobalX() local dxTotal = x - gx local rad if parent ~= stage then rad = math.rad(parent:getGlobalRotation()) else rad = math.rad(parent:getRotation()) end local dx = dxTotal * math.cos(rad) if parent then if parent ~= stage then dx = dx/parent:getGlobalScaleX() else dx = dx/parent:getScaleX() end end local dy = dxTotal * -math.sin(rad) if parent ~= stage then if parent ~= stage then dy = dy/parent:getGlobalScaleY() else dy = dy/parent:getScaleY() end end self:deltaX(dx) self:deltaY(dy) end function Container:getGlobalX() return self:localToGlobal(0, 0) end function Container:setGlobalY(y) local parent = self:getParent() local gy = self:getGlobalY() local dyTotal = y - gy local rad if parent ~= stage then rad = math.rad(parent:getGlobalRotation()) else rad = math.rad(parent:getRotation()) end local dx = dyTotal * math.sin(rad) if parent then if parent ~= stage then dx = dx/parent:getGlobalScaleX() else dx = dx/parent:getScaleX() end end local dy = dyTotal * math.cos(rad) if parent ~= stage then if parent ~= stage then dy = dy/parent:getGlobalScaleY() else dy = dy/parent:getScaleY() end end self:deltaX(dx) self:deltaY(dy) end function Container:getGlobalY() local _,gy = self:localToGlobal(0, 0) return gy end function Container:setGlobalPosition(x, y) self:setGlobalX(x) self:setGlobalY(y) end function Container:getGlobalPosition() return self:localToGlobal(0, 0) end function Container:deltaX(deltaX) self:setX(self:getX() + deltaX) end function Container:deltaY(deltaY) self:setY(self:getY() + deltaY) end function Container:setGlobalRotation(theAngle) local gRot = self:getGlobalRotation() local dTheta = theAngle - gRot self:setRotation(self:getRotation()+dTheta) end function Container:getGlobalRotation() local parent = self:getParent() local gRot if parent then if parent ~= stage then gRot = self:getRotation()+parent:getGlobalRotation() else gRot = self:getRotation() end else gRot = 0 end parent = nil return gRot end function Container:setGlobalScale(scaleX, scaleY) self:setGlobalScaleX(scaleX) self:setGlobalScaleY(scaleY) end function Container:getGlobalScale() return self:getGlobalScaleX(), self:getGlobalScaleY() end function Container:setGlobalScaleX(scaleX) local curGlobalScaleX = self:getGlobalScaleX() self:setScaleX(self:getScaleX() * scaleX/curGlobalScaleX) end function Container:getGlobalScaleX() local parent = self:getParent() if parent ~= stage then return self:getScaleX() * parent:getGlobalScaleX() else return self:getScaleX() end end function Container:setGlobalScaleY(scaleY) local curGlobalScaleY = self:getGlobalScaleY() self:setScaleY(self:getScaleY() * scaleY/curGlobalScaleY) end function Container:getGlobalScaleY() local parent = self:getParent() if parent ~= stage then return self:getScaleY() * parent:getGlobalScaleY() else return self:getScaleY() end end function Container:isOnScreen() local screenX, screenY = self:getGlobalPosition() if screenX < Display.LEFT or screenX > Display.RIGHT or screenY < Display.TOP or screenY > Display.BOTTOM then return false else return true end end function Container:toBack() local parent = self:getParent() if parent then parent:removeChild(self) parent:addChildAt(self, 1) end end function Container:toFront() local parent = self:getParent() if parent then parent:removeChild(self) parent:addChild(self) end end function Container:deltaZ(increment) local parent = self:getParent() if parent then increment = increment or 0 local newIndex = parent:getChildIndex(self) + increment if newIndex < 1 then newIndex = 1 end parent:addChildAt(self, newIndex) end end function Container:getZ() local parent = self:getParent() if parent then return parent:getChildIndex(self) end end function Container:hitTestPointX(x) return self:hitTestPoint(x, self:getGlobalY()) end function Container:setBlockTouches(doBlock, inBoundsOnly) doBlock = doBlock or false if doBlock ~= self.__doBlockTouches then self.__doBlockTouches = doBlock end end function Container:addEventListener(theEvent, theFunction, ...) if self.__eventHolder[theEvent] == nil then self.__eventHolder[theEvent] = {} end if self.__eventHolder[theEvent][theFunction] == nil then self.__eventHolder[theEvent][theFunction] = {} end local eventFunctionTable = self.__eventHolder[theEvent][theFunction] local argTable = {} for i = 1, arg["n"], 1 do argTable[i] = arg[i] end eventFunctionTable[#eventFunctionTable + 1] = argTable -- Now make a new table to unpack to the native addEventListener function local nativeTable = { self, theEvent, theFunction } for i = 1, #argTable, 1 do nativeTable[#nativeTable + 1] = argTable[i] end Sprite.addEventListener(unpack(nativeTable)) end function Container:removeEventListener(theEvent, theFunction, ...) if self.__eventHolder[theEvent] ~= nil and self.__eventHolder[theEvent][theFunction] ~= nil then local eventFunctionTable = self.__eventHolder[theEvent][theFunction] local argTable = {} for i = 1, arg["n"], 1 do argTable[i] = arg[i] end -- Go through all listeners of this event and function to find the first one with the right arguments and remove it for n = 1, #eventFunctionTable, 1 do local isRight = true for m = 1, #argTable, 1 do if argTable[m] ~= eventFunctionTable[n][m] then isRight = false end end table.remove(eventFunctionTable, n) -- Now make a new table to unpack to the native addEventListener function local nativeTable = { self, theEvent, theFunction } for i = 1, #argTable, 1 do nativeTable[#nativeTable + 1] = argTable[i] end Sprite.removeEventListener(unpack(nativeTable)) break; end end end function Container:removeAllEventListeners() for theEvent, functionsTable in pairs(self.__eventHolder) do for theFunction, listeners in pairs(functionsTable) do for b = 1, #listeners, 1 do local argsTable = listeners[b] local nativeTable = { self, theEvent, theFunction } for t = 1, #argsTable, 1 do nativeTable[#nativeTable + 1] = argsTable[t] end Sprite.removeEventListener(unpack(nativeTable)) end end end end function Container:removeChildren() while self:getNumChildren() > 0 do local child = self:getChildAt(self:getNumChildren()) local status, err = pcall(child.removeSelf, child) if not status then child:getParent():removeChild(child) end end end function Container:removeSelf() -- Remove all children from Container self:removeChildren() -- Finally remove the entire sprite from its parent(if it has a parent) local parent = self:getParent() if parent then parent:removeChild(self) end -- Remove all event listeners self:removeAllEventListeners() end _G["Image"] = Core.class(Container) --If arg1 is image path -> arg2 = aliasing, arg3 = parent; If arg1 is texture -> arg2 = parent function Image:init(theTexture, theParent) -- Check if theTexture is a string field path or an actual texture if type(theTexture) == "string" then self.__bitmap = Bitmap.new(Texture.new(theTexture,true)) if theParent and type(theParent) == "table" then theParent:addChild(self) end elseif type(theTexture) == "table" then self.__bitmap = Bitmap.new(theTexture) if theParent and type(theParent) == "table" then theParent:addChild(self) end end self:addChild(self.__bitmap) end function Image:resize(width,height) local scaleX,scaleY if width then scaleX = width/self:getWidth() end if height then scaleY = height/self:getHeight() end if scaleX and not scaleY then scaleY = scaleX end if scaleY and not scaleX then scaleX = scaleY end if scaleX and scaleY then self:setScale(scaleX,scaleY) end end -- Extended 'Shape' class local GiderosShape = Shape _G["Shape"] = Core.class(Container) -- Properties Shape.EVEN_ODD = GiderosShape.EVEN_ODD Shape.NONE = GiderosShape.NONE Shape.NON_ZERO = GiderosShape.NON_ZERO Shape.SOLID = GiderosShape.SOLID Shape.TEXTURE = GiderosShape.TEXTURE function Shape:init() self.__history = {} self.__fillRed = 0 self.__fillGreen = 0 self.__fillBlue = 0 self.__shape = GiderosShape.new() self:setFillColor(self.__fillRed, self.__fillGreen, self.__fillBlue) self:addChild(self.__shape) end function Shape:beginPath(winding) self.__history[#self.__history + 1] = {method = self.beginPath, args = {self, winding}} if winding then self.__shape:beginPath(winding) else self.__shape:beginPath() end end function Shape:clear() self.__history = {} self.__shape:clear() end function Shape:closePath() self.__history[#self.__history + 1] = {method = self.closePath, args = {self}} self.__shape:closePath() end function Shape:endPath() self.__history[#self.__history + 1] = {method = self.endPath, args = {self}} self.__shape:endPath() end function Shape:lineTo(x, y) self.__history[#self.__history + 1] = {method = self.lineTo, args = {self, x, y}} self.__shape:lineTo(x, y) end function Shape:moveTo(x, y) self.__history[#self.__history + 1] = {method = self.moveTo, args = {self, x, y}} self.__shape:moveTo(x, y) end function Shape:setFillColor(r, g, b, a) r = clipColorValue(r) g = clipColorValue(g) b = clipColorValue(b) if self.__fillRed ~= r or self.__fillGreen == g or self.__fillBlue == b then self.__fillRed = r self.__fillGreen = g self.__fillBlue = b local hex = rgbToHex(self.__fillRed, self.__fillGreen, self.__fillBlue) if a == nil then self.__fillStyle = {Shape.SOLID, hex} else self.__fillStyle = {Shape.SOLID, hex, a} end self:setFillStyle(unpack(self.__fillStyle)) end end function Shape:getFillColor() return self.__fillRed, self.__fillGreen, self.__fillBlue end function Shape:setFillStyle(...) self.__fillStyle = arg ShapeRedraw(self) end function Shape:setLineColor(r, g, b, a) r = clipColorValue(r) g = clipColorValue(g) b = clipColorValue(b) if self.__lineRed ~= r or self.__lineGreen == g or self.__lineBlue == b then self.__lineRed = r self.__lineGreen = g self.__lineBlue = b local hex = rgbToHex(self.__lineRed, self.__lineGreen, self.__lineBlue) self.__lineWidth = setIfExists(self.__lineWidth, 10) if a == nil then self.__lineStyle = {self.__lineWidth, hex} else self.__lineStyle = {self.__lineWidth, hex, a} end self:setLineStyle(unpack(self.__lineStyle)) end end function Shape:getLineColor() return self.__lineRed, self.__lineGreen, self.__lineBlue end function Shape:setLineWidth(width) if self.__lineWidth ~= width then self.__lineWidth = width if not self.__lineStyle then self.__lineRed = 0 self.__lineGreen = 0 self.__lineBlue = 0 local hex = rgbToHex(self.__lineRed, self.__lineGreen, self.__lineBlue) self.__lineStyle = {10, hex} else self.__lineStyle[1] = width end if width == nil then self.__lineStyle = nil ShapeRedraw(self) else self:setLineStyle(unpack(self.__lineStyle)) end end end function Shape:getLineWidth() return self.__lineWidth end function Shape:setLineStyle(...) self.__lineStyle = arg ShapeRedraw(self) end -- Cache the original TTFont class to a local variable local GiderosTTFont = TTFont -- Improved TTFont class _G["TTFont"] = Core.class(GiderosTTFont) function TTFont:init(...) self.__fontname = arg[1] self.__size = arg[2] self.__filtered = false if arg[3] then if type(arg[3]) == "string" then self.__cache = arg[3] elseif type(arg[3] == "bool") then self.__filtered = arg[3] end end if arg[4] then if type(arg[4]) == "boolean" then self.__filtered = arg[4] end end end function TTFont:getSize() return self.__size end function TTFont:getFontname() return self.__fontname end function TTFont:getCache() return self.__cache end function TTFont:getFiltered() return self.__filtered end -- Vector text class _G["Text"] = Core.class(Container) function Text:init(theFont, theText, parent) self.__textfield = TextField.new(theFont, theText) self.__font = theFont -- By default the text is black self.__red = 0 self.__green = 0 self.__blue = 0 self:setTextColor(self.__red, self.__green, self.__blue) -- Add the TextField to self Container self:addChild(self.__textfield) -- Add the Text to a parent if one is passed in if parent then self.__textfield:setPosition(0, self.__textfield:getHeight()) parent:addChild(self) end end function Text:setFont(theFont) if self.__font ~= theFont then -- Save the old text object data to recreate the necessary parts local oldObj = { text = self.__textfield:getText(), anchorX = self:getAnchorX(), anchorY = self:getAnchorY(), letterSpacing = self:getLetterSpacing(), } self:removeChild(self.__textfield) -- Make a new textfield with a new font self.__font = theFont self.__textfield = TextField.new(self.__font, oldObj.text) self.__textfield:setLetterSpacing(oldObj.letterSpacing) self:addChild(self.__textfield) -- Reset anchor point self:setAnchorPoint(oldObj.anchorX, oldObj.anchorY) -- Finally bring back the old color self.__textfield:setTextColor(rgbToHex(self.__red, self.__green, self.__blue)) end end function Text:getFontSize() return self.__font:getSize() end function Text:setText(text) self.__textfield:setText(text) -- Resize the textfield just in case if self.__maxWidth and self:getWidth() > self.__maxWidth then self:setScale(self.__maxWidth/self:getWidth()) end end function Text:getText() return self.__textfield:getText() end function Text:setTextColor(r, g, b) self.__red = clipColorValue(r) self.__green = clipColorValue(g) self.__blue = clipColorValue(b) self.__textfield:setTextColor(rgbToHex(self.__red, self.__green, self.__blue)) end function Text:getTextColor() return self.__red, self.__green, self.__blue end function Text:setLetterSpacing(spacing) return self.__textfield:setLetterSpacing(spacing) end function Text:getLetterSpacing() return self.__textfield:getLetterSpacing() end function Text:setMaxWidth(theWidth) self.__maxWidth = theWidth -- Resize the textfield just in case if self.__maxWidth and self:getWidth() > self.__maxWidth then self:setScale(self.__maxWidth/self:getWidth()) end end -- MultiLine text class _G["MLText"] = Core.class(Container) function MLText:init(font, text, parent) --Argument check if not font then font = nil end if not width then width = Display.FULL_WIDTH end if not align then align = 0 end --Internal settings self.__red = 0 self.__green = 0 self.__blue = 0 self.__text = text self.__align = align self.__width = width self.__font = font self:setText(text) if parent then parent:addChild(self) end end function MLText:setFontSize(theSize) if self.__font:getSize() ~= theSize then -- Save the old text object data to recreate the necessary parts local oldFont = { fontname = self.__font:getFontname(), cache = self.__font:getCache(), filtered = self.__font:getFiltered(), } self.__font = TTFont.new(oldFont.fontname, theSize, oldFont.cache, oldFont.filtered) self:setText(self.__text) end end function MLText:getFontSize() return self.__font:getSize() end local function takeUniform(str, count) str = str:gsub("\13\10","\10") local _,length = str:gsub("[^\128-\193]","") local freq = count/length local sum = 0.5 local concat = {} for chr in str:gfind("([%z\1-\127\194-\244][\128-\191]*)") do sum = sum+freq if sum > 1 then concat[#concat+1] = chr sum = sum-1 end end return table.concat(concat), #concat end local function splitToWords(str) local words = {} local init = 1 while true do local s, e = str:find(" [^ ]", init) words[#words+1] = str:sub(init,s) if s == nil then break end init = e end return words end local function splitToParagraphs(str) local paragraphs = {} local init = 1 while true do local s = str:find("\10",init) paragraphs[#paragraphs+1] = str:sub(init,s and (s-1)) if s == nil then break end init = s + 1 end return paragraphs end local function splitToLines(str, font, width) local substr, subcount = takeUniform(str,250) local tf = Text.new(font,substr) local letterChunks = math.floor(subcount*width/tf:getWidth()) local lines = {} local paragraphs = splitToParagraphs(str) for i=1,#paragraphs do local line = "" local len = 0 local words = splitToWords(paragraphs[i]) for j=1,#words do local word = words[j] local _,wordlen = word:gsub("[^\128-\193]","") if len+wordlen >= letterChunks then lines[#lines+1] = line line = word len = wordlen else line = line .. word len = len+wordlen end end lines[#lines+1] = line end return lines end function MLText:setText(text) self.__text = text while(self:getNumChildren() > 0) do self:removeChildAt(self:getNumChildren()) end local height = 0 for i,line in ipairs(splitToLines(text,self.__font,self.__width)) do local line = Text.new(self.__font, line) line:setTextColor(self.__red, self.__green, self.__blue) self:addChild(line) line:setY(height) line:setAnchorX(self.__align) height = height+18 end end function MLText:getText() return self.__text end function MLText:setTextColor(r, g, b) self.__red = clipColorValue(r) self.__green = clipColorValue(g) self.__blue = clipColorValue(b) for i = 1, self:getNumChildren() do local txt = self:getChildAt(i) txt:setTextColor(self.__red, self.__green, self.__blue) end end function MLText:getTextColor() return self.__red, self.__green, self.__blue end function MLText:setAlignment(align) self.__align = align self:setText(self.__text) end function MLText:getAlignment() return self.__align end function MLText:setWidth(width) self.__width = width self:setText(self.__text) end function MLText:getWidth() return self.__width end function MLText:setFont(font) self.__font = font self:setText(self.__text) end function MLText:getFont() return self.__font end