├── .gitignore ├── res └── creator │ ├── img │ └── animal_panda.png │ ├── 29bd4115-e734-40e7-bbd5-484530644fe5 │ └── TarantellaMF.ttf │ └── main.json ├── README.md ├── src ├── app │ ├── utils │ │ ├── htmlparser │ │ │ ├── voidelements.lua │ │ │ └── ElementNode.lua │ │ ├── bezier.lua │ │ ├── LabelTTFEx.lua │ │ ├── PushCenter.lua │ │ ├── RichTextEx.lua │ │ ├── spline.lua │ │ ├── htmlparser.lua │ │ ├── Updater.lua │ │ ├── TableView.lua │ │ ├── delaunay.lua │ │ └── TableViewPro.lua │ ├── MyApp.lua │ └── scenes │ │ └── MainScene.lua ├── main.lua └── config.lua └── tools └── GenResMD5.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /res/creator/img/animal_panda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/u0u0/Lua-utils/HEAD/res/creator/img/animal_panda.png -------------------------------------------------------------------------------- /res/creator/29bd4115-e734-40e7-bbd5-484530644fe5/TarantellaMF.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/u0u0/Lua-utils/HEAD/res/creator/29bd4115-e734-40e7-bbd5-484530644fe5/TarantellaMF.ttf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lua-utils 2 | 3 | Quick-Cocos2dx-Community Lua utils 4 | 5 | 3.7以后社区版进行了精简,纯Lua的框架将放在这个仓库中独立维护。 6 | 7 | ## 广告 8 | 9 | [《Cocos2d-x游戏开发 手把手教你Lua语言的编程方法》](http://www.cocos2d-lua.org/book/index.md)是基于社区3.7.x引擎编写的入门开发指南,也是一本参考手册。在发布社区版3.7以来,引擎进行了大刀阔斧的裁剪和改进。尤其是UI部分的变化,让老开发和新收都无从下手,本书对ccui框架进行了全方位的介绍,并结合CocosStudio进行说明。 -------------------------------------------------------------------------------- /src/app/utils/htmlparser/voidelements.lua: -------------------------------------------------------------------------------- 1 | -- vim: ft=lua ts=2 2 | return { 3 | area = true, 4 | base = true, 5 | br = true, 6 | col = true, 7 | command = true, 8 | embed = true, 9 | hr = true, 10 | img = true, 11 | input = true, 12 | keygen = true, 13 | link = true, 14 | meta = true, 15 | param = true, 16 | source = true, 17 | track = true, 18 | wbr = true 19 | } 20 | -------------------------------------------------------------------------------- /src/main.lua: -------------------------------------------------------------------------------- 1 | 2 | function __G__TRACKBACK__(errorMessage) 3 | print("----------------------------------------") 4 | print("LUA ERROR: " .. tostring(errorMessage) .. "\n") 5 | print(debug.traceback("", 2)) 6 | print("----------------------------------------") 7 | end 8 | 9 | package.path = package.path .. ";src/?.lua;src/framework/protobuf/?.lua" 10 | require("app.MyApp").new():run() 11 | -------------------------------------------------------------------------------- /src/config.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 0 - disable debug info, 1 - less debug info, 2 - verbose debug info 3 | DEBUG = 1 4 | 5 | -- display FPS stats on screen 6 | DEBUG_FPS = true 7 | 8 | -- dump memory info every 10 seconds 9 | DEBUG_MEM = false 10 | 11 | -- design resolution 12 | CONFIG_SCREEN_WIDTH = 960 13 | CONFIG_SCREEN_HEIGHT = 640 14 | 15 | -- auto scale mode 16 | CONFIG_SCREEN_AUTOSCALE = "FIXED_HEIGHT" 17 | -------------------------------------------------------------------------------- /src/app/MyApp.lua: -------------------------------------------------------------------------------- 1 | 2 | require("config") 3 | require("cocos.init") 4 | require("framework.init") 5 | 6 | local AppBase = require("framework.AppBase") 7 | local MyApp = class("MyApp", AppBase) 8 | 9 | function MyApp:ctor() 10 | MyApp.super.ctor(self) 11 | end 12 | 13 | function MyApp:run() 14 | cc.FileUtils:getInstance():addSearchPath("res/") 15 | self:enterScene("MainScene") 16 | end 17 | 18 | return MyApp 19 | -------------------------------------------------------------------------------- /src/app/utils/bezier.lua: -------------------------------------------------------------------------------- 1 | local tableInsert = table.insert 2 | local mathPow = math.pow 3 | local ipairs = ipairs 4 | 5 | local function YangHuiR3(line) 6 | local s = 1 7 | local nums = {1} 8 | for j = 1, line - 1 do 9 | s = (line - j) * s / j 10 | tableInsert(nums, s) 11 | end 12 | return nums 13 | end 14 | 15 | local function point(x, y) 16 | return {x = x, y = y} 17 | end 18 | 19 | --[[ 20 | @desc: 根据控制点返回贝塞尔曲线 21 | author:Bogey 22 | time:2019-06-21 11:57:36 23 | --@points: 控制点 24 | --@segments: 采样,越大越平滑 25 | @return: 26 | ]] 27 | local function bezier(points, segments) 28 | local pointNum = #points 29 | local nums = YangHuiR3(pointNum) 30 | local results = {points[1]} 31 | for i = 1, segments do 32 | local t = i / segments 33 | local x = 0 34 | local y = 0 35 | for k,v in ipairs(points) do 36 | x = x + nums[k] * mathPow(1 - t, pointNum - k) * mathPow(t, k - 1) * v.x 37 | y = y + nums[k] * mathPow(1 - t, pointNum - k) * mathPow(t, k - 1) * v.y 38 | end 39 | tableInsert(results, point(x, y)) 40 | end 41 | return results 42 | end 43 | 44 | return bezier -------------------------------------------------------------------------------- /tools/GenResMD5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | NAME 5 | GenResMD5 -- 6 | 7 | 8 | SYNOPSIS 9 | GenResMD5 [-h] 10 | """ 11 | 12 | import os 13 | import hashlib 14 | import json 15 | import re 16 | 17 | ignoreDir = ["Default"] 18 | output = "version.json" 19 | scriptRoot = os.path.split(os.path.realpath(__file__))[0] 20 | info = {} 21 | info["EngineVersion"] = "1.0.0" 22 | info["GameVersion"] = "1.0.0" 23 | info["packages"] = ["game", "gameA"] # first package name is fixed for cpp 24 | info["assets"] = {} 25 | 26 | def joinDir(root, *dirs): 27 | for item in dirs: 28 | root = os.path.join(root, item) 29 | return root 30 | 31 | scanRoot = joinDir(scriptRoot, "res") 32 | 33 | def getMD5(root): 34 | files = os.listdir(root) 35 | for f in files: 36 | itemPath = joinDir(root, f) 37 | if os.path.isdir(itemPath): 38 | if (f[0] == '.' or (f in ignoreDir)): 39 | pass 40 | else: 41 | getMD5(itemPath) 42 | elif os.path.isfile(itemPath): 43 | if f[0] != '.' and f != output: 44 | fp = open(itemPath, 'rb') 45 | m5 = hashlib.md5() 46 | m5.update(fp.read()) 47 | fp.close() 48 | name = itemPath[(len(scanRoot) + 1):] 49 | if os.sep == '\\': 50 | name = re.sub('\\\\', '/', name) 51 | # key is path, value[0] = md5, value[2] = size 52 | info["assets"][name] = [m5.hexdigest(), os.path.getsize(itemPath)] 53 | 54 | getMD5(scanRoot) 55 | jsonStr = json.dumps(info) 56 | fp = open(joinDir(scanRoot, output), "wb") 57 | fp.write(jsonStr) 58 | fp.close() 59 | -------------------------------------------------------------------------------- /src/app/utils/LabelTTFEx.lua: -------------------------------------------------------------------------------- 1 | local LabelTTFEx = class("LabelTTFEx", function() 2 | return cc.Node:create() -- for underline draw 3 | end) 4 | 5 | function LabelTTFEx:ctor(text, font, size, color) 6 | local font = font or display.DEFAULT_TTF_FONT 7 | local size = size or display.DEFAULT_TTF_FONT_SIZE 8 | local color = color or display.COLOR_WHITE 9 | self.color = color 10 | 11 | local label 12 | if cc.FileUtils:getInstance():isFileExist(font) then 13 | label = cc.Label:createWithTTF(text, font, size) 14 | label:setColor(color) 15 | else 16 | label = cc.Label:createWithSystemFont(text, font, size) 17 | label:setTextColor(color) 18 | end 19 | 20 | local size = label:getContentSize() 21 | label:pos(size.width / 2, size.height / 2):addTo(self) 22 | self.label = label 23 | 24 | self:setContentSize(size) -- for Richtext 25 | self:setAnchorPoint(cc.p(0.5, 0.5)) -- same as label 26 | end 27 | 28 | function LabelTTFEx:enableUnderLine() 29 | if self.line then return end 30 | 31 | local size = self:getContentSize() 32 | local borderWidth = size.height / 18 33 | local line = display.newLine( 34 | {{0, borderWidth / 2}, {size.width, borderWidth / 2}}, 35 | { 36 | borderColor = cc.c4f(self.color.r / 255, self.color.g / 255, self.color.b / 255, 1.0), 37 | borderWidth = borderWidth 38 | } 39 | ):addTo(self) 40 | self.line = line 41 | return self 42 | end 43 | 44 | function LabelTTFEx:enableItalics() 45 | self.label:setRotationSkewX(12) 46 | -- fix the contentSize 47 | local size = self:getContentSize() 48 | size.width = size.width * 1.02 49 | self:setContentSize(size) 50 | return self 51 | end 52 | 53 | -- ONLY worked for External font 54 | function LabelTTFEx:enableBold() 55 | self.label:enableShadow(cc.c4b(self.color.r, self.color.g, self.color.b, 255), cc.size(0.9, 0), 0) 56 | return self 57 | end 58 | 59 | return LabelTTFEx 60 | -------------------------------------------------------------------------------- /src/app/utils/PushCenter.lua: -------------------------------------------------------------------------------- 1 | local center = {} 2 | center._listeners = {} 3 | 4 | -- eventName:string, func:function, tag:anything 5 | function center.addListener(eventName, func, tag) 6 | assert(tag, "Tag must not be nil") 7 | local listeners = center._listeners 8 | if not listeners[eventName] then 9 | listeners[eventName] = {} 10 | end 11 | 12 | local eventListeners = listeners[eventName] 13 | for i = 1, #eventListeners do 14 | if tag == eventListeners[i][2] then 15 | -- avoid repeate add listener for a tag 16 | return 17 | end 18 | end 19 | table.insert(eventListeners, {func, tag}) 20 | end 21 | 22 | function center.removeListener(func) 23 | local listeners = center._listeners 24 | for eventName, eventListeners in pairs(listeners) do 25 | for i = 1, #eventListeners do 26 | if eventListeners[i][1] == func then 27 | -- remove listener 28 | table.remove(eventListeners, i) 29 | -- clear table 30 | if 0 == #listeners[eventName] then 31 | listeners[eventName] = nil 32 | end 33 | return 34 | end 35 | end 36 | end 37 | end 38 | 39 | function center.removeListenerByNameAndTag(eventName, tag) 40 | assert(tag, "Tag must not be nil") 41 | local listeners = center._listeners 42 | local eventListeners = listeners[eventName] 43 | if not eventListeners then return end 44 | 45 | for i = #eventListeners, 1, -1 do 46 | if eventListeners[i][2] == tag then 47 | -- remove listener 48 | table.remove(eventListeners, i) 49 | break 50 | end 51 | end 52 | -- clear table 53 | if 0 == #eventListeners then 54 | listeners[eventName] = nil 55 | end 56 | end 57 | 58 | function center.removeListenersByTag(tag) 59 | assert(tag, "Tag must not be nil") 60 | local listeners = center._listeners 61 | for eventName, eventListeners in pairs(listeners) do 62 | center.removeListenerByNameAndTag(eventName, tag) 63 | end 64 | end 65 | 66 | function center.removeAllListeners() 67 | center._listeners = {} 68 | end 69 | 70 | function center.pushEvent(eventName, ...) 71 | local listeners = center._listeners 72 | local eventListeners = listeners[eventName] 73 | if not eventListeners then 74 | return 75 | end 76 | 77 | -- keep register order to send message 78 | local tmp = {} 79 | for index, listeners in ipairs(eventListeners) do 80 | -- copy table to avoid listener remove in deal func 81 | tmp[index] = listeners 82 | end 83 | for _, listeners in ipairs(tmp) do 84 | listeners[1](...) 85 | end 86 | end 87 | 88 | return center 89 | -------------------------------------------------------------------------------- /src/app/utils/RichTextEx.lua: -------------------------------------------------------------------------------- 1 | local htmlparser = import(".htmlparser") 2 | 3 | local RichTextEx = class("RichTextEx", function() 4 | return ccui.RichText:create() 5 | end) 6 | 7 | local string = string 8 | local ipairs = ipairs 9 | local tonumber = tonumber 10 | 11 | -- wapper this with project 12 | local defaultFont = "0_UIPic/gamefont.TTF" 13 | local defaultFontSize = 24 14 | local defaultFontColor = cc.c3b(255, 255, 255) 15 | 16 | -- #RRGGBB/#RGB to c3b 17 | local function c3b_parse(s) 18 | local r, g, b = 0, 0, 0 19 | if #s == 4 then 20 | r = tonumber(string.rep(string.sub(s, 2, 2), 2), 16) 21 | g = tonumber(string.rep(string.sub(s, 3, 3), 2), 16) 22 | b = tonumber(string.rep(string.sub(s, 4, 4), 2), 16) 23 | elseif #s == 7 then 24 | r = tonumber(string.sub(s, 2, 3), 16) 25 | g = tonumber(string.sub(s, 4, 5), 16) 26 | b = tonumber(string.sub(s, 6, 7), 16) 27 | end 28 | return cc.c3b(r, g, b) 29 | end 30 | 31 | --[[ 32 | 用法: 33 | local RichTextEx = require("app.utils.RichTextEx") 34 | local clickDeal = function(id, content) 35 | print(id, content) 36 | end 37 | 38 | local rt = RichTextEx.new([==[ 39 | Hello World!
40 | ]==], clickDeal) 41 | :addTo(self) 42 | :center() 43 | rt:formatText() 44 | dump(rt:getContentSize()) 45 | ]]-- 46 | 47 | -- do not support nesting 48 | function RichTextEx:ctor(str, callback) 49 | local root = htmlparser.parse(str) 50 | self._callback = callback 51 | self:render(root.nodes) 52 | end 53 | 54 | function RichTextEx:render(nodes) 55 | local addTouch = function(target, id, content) 56 | target:addNodeEventListener(cc.NODE_TOUCH_EVENT, function(event) 57 | if event.name == "began" then 58 | return true 59 | end 60 | if event.name == "ended" then 61 | if self._callback then 62 | self._callback(id, content) 63 | end 64 | end 65 | end) 66 | target:setTouchEnabled(true) 67 | end 68 | 69 | local tag = { 70 | t = function(e) -- text 71 | local font = e.attributes.f or defaultFont 72 | local size = defaultFontSize 73 | if e.attributes.s then 74 | size = tonumber(e.attributes.s) 75 | end 76 | local color = defaultFontColor 77 | if e.attributes.c then 78 | color = c3b_parse(e.attributes.c) 79 | end 80 | 81 | local label 82 | if cc.FileUtils:getInstance():isFileExist(font) then 83 | label = cc.Label:createWithTTF(e:getcontent(), font, size) 84 | label:setColor(color) 85 | else 86 | label = cc.Label:createWithSystemFont(e:getcontent(), font, size) 87 | label:setTextColor(color) 88 | end 89 | 90 | if e.attributes.id then 91 | addTouch(label, e.attributes.id, e:getcontent()) 92 | label:enableUnderline() 93 | end 94 | return ccui.RichElementCustomNode:create(0, display.COLOR_WHITE, 255, label) 95 | end, 96 | i = function(e) -- image 97 | local isSpriteFrame = 0 98 | local src = e.attributes.s 99 | if string.byte(src, 1) == 35 then -- # spriteframe 100 | src = string.sub(src, 2) 101 | isSpriteFrame = 1 102 | end 103 | local image = ccui.ImageView:create(src, isSpriteFrame) 104 | local size = image:getContentSize() 105 | if e.attributes.w then 106 | size.width = tonumber(e.attributes.w) 107 | end 108 | if e.attributes.h then 109 | size.height = tonumber(e.attributes.h) 110 | end 111 | image:ignoreContentAdaptWithSize(false) 112 | image:setContentSize(size) 113 | if e.attributes.id then -- set underline for clicked text 114 | addTouch(image, e.attributes.id, src) 115 | end 116 | return ccui.RichElementCustomNode:create(0, display.COLOR_WHITE, 255, image) 117 | end, 118 | br = function(e) -- break 119 | return ccui.RichElementNewLine:create(0, display.COLOR_WHITE, 255) 120 | end, 121 | } 122 | 123 | for _, e in ipairs(nodes) do 124 | local element = tag[e.name](e) 125 | self:pushBackElement(element) 126 | end 127 | end 128 | 129 | return RichTextEx 130 | -------------------------------------------------------------------------------- /src/app/scenes/MainScene.lua: -------------------------------------------------------------------------------- 1 | local creator = require("app.utils.creator") 2 | require("app.utils.TableViewPro") 3 | 4 | local MainScene = class("MainScene", function() 5 | return display.newScene("MainScene") 6 | end) 7 | 8 | function MainScene:ctor() 9 | -- self:TableViewTest() 10 | -- self:TableViewProTest() 11 | self:CurveDrawTest() 12 | end 13 | 14 | function MainScene:TableViewTest() 15 | -- create listview, or get listview from csb 16 | local lv = ccui.ListView:create() 17 | lv:setBounceEnabled(true) 18 | lv:setContentSize(cc.size(300, 200)) 19 | lv:center():addTo(self) 20 | lv:setBackGroundColorType(1) 21 | lv:setBackGroundColor(cc.c3b(0, 100, 0)) 22 | lv:setAnchorPoint(cc.p(0.5, 0.5)) 23 | lv:setItemsMargin(4) 24 | -- convert to tablevlew 25 | local TableView = require("app.utils.TableView") 26 | local sizeSource = function(self, index) 27 | return cc.size(300, 15) 28 | end 29 | local loadSoruce = function(self, index) 30 | return ccui.Text:create(index, "Airal", 18) 31 | end 32 | local unloadSoruce = function(self, index) 33 | print("do texture unload here:", index) 34 | end 35 | TableView.attachTo(lv, sizeSource, loadSoruce, unloadSoruce) 36 | lv:initDefaultItems(300) 37 | lv:jumpTo(300) 38 | lv:addScrollViewEventListener(function(ref, type) 39 | print("event:", type) 40 | end) 41 | end 42 | 43 | function MainScene:TableViewProTest() 44 | local amount = 1000 45 | 46 | local tab = cc.TableView.new(cc.size(100,400)) 47 | local function size(tableview, index) 48 | return 100, 100 49 | end 50 | 51 | local function number(tableview) 52 | return amount 53 | end 54 | 55 | local function loadCell(tableview, index) 56 | print("loadCell:", index) 57 | local cell = tableview:dequeueCell() 58 | if not cell then 59 | cell = cc.TableViewCell.new() 60 | local text = ccui.Text:create(index, "", 50):addTo(cell, 1, 666):align(display.LEFT_BOTTOM, 0, 0) 61 | text:setTextColor(cc.c3b(255,255,math.random(1, 255))) 62 | end 63 | cell:getChildByTag(666):setString(index) 64 | 65 | return cell 66 | end 67 | 68 | local function unloadCell(tableview, index) 69 | print("unloadCell:", index) 70 | end 71 | 72 | tab:setDirection(cc.TableViewDirection.vertical) 73 | tab:setFillOrder(cc.TableViewFillOrder.topToBottom) 74 | tab:registerFunc(cc.TableViewFuncType.cellSize, size) 75 | tab:registerFunc(cc.TableViewFuncType.cellNum, number) 76 | tab:registerFunc(cc.TableViewFuncType.cellLoad, loadCell) 77 | tab:registerFunc(cc.TableViewFuncType.cellUnload, unloadCell) 78 | tab:addTo(self):align(display.CENTER, display.cx, display.cy) 79 | tab:reloadData() 80 | 81 | local text = ccui.Text:create("resize tableview", "", 40):addTo(self):pos(display.cx, display.cy + 250) 82 | text:addNodeEventListener(cc.NODE_TOUCH_EVENT, function(event) 83 | if event.name == "ended" then 84 | amount = 18 85 | tab:reloadDataInPos() 86 | end 87 | return true 88 | end) 89 | text:setTouchEnabled(true) 90 | end 91 | 92 | function MainScene:CurveDrawTest() 93 | local bezier = require("app.utils.bezier") 94 | local spline = require("app.utils.spline") 95 | 96 | local targetPoints = {cc.p(0,0), cc.p(100, 100), cc.p(200, 50), cc.p(300, 200)} 97 | local points = spline(targetPoints) 98 | local drawnode = cc.DrawNode:create() 99 | for i = 1, #points - 1 do 100 | drawnode:drawLine(points[i], points[i + 1], cc.c4f(1,1,1,1)) 101 | end 102 | for i,v in ipairs(targetPoints) do 103 | drawnode:drawDot(v, 4, cc.c4f(1,0,0,1)) 104 | end 105 | drawnode:addTo(self):pos(200, 200) 106 | 107 | local drawnode2 = cc.DrawNode:create() 108 | local points = bezier(targetPoints, 100) 109 | for i = 1, #points - 1 do 110 | drawnode2:drawLine(points[i], points[i + 1], cc.c4f(0,1,0,1)) 111 | end 112 | for i,v in ipairs(targetPoints) do 113 | drawnode2:drawDot(v, 4, cc.c4f(1,0,0,1)) 114 | end 115 | drawnode2:addTo(self):pos(200, 400) 116 | end 117 | 118 | function MainScene:onEnter() 119 | end 120 | 121 | function MainScene:onExit() 122 | end 123 | 124 | return MainScene 125 | -------------------------------------------------------------------------------- /src/app/utils/spline.lua: -------------------------------------------------------------------------------- 1 | local tableInsert = table.insert 2 | local tablesort = table.sort 3 | local ipairs = ipairs 4 | local pairs = pairs 5 | local tonumber = tonumber 6 | 7 | local function point(x, y) 8 | return {x = x, y = y} 9 | end 10 | 11 | local function valuesOfKey(hashtable, key) 12 | local values = {} 13 | for k,v in pairs(hashtable) do 14 | if v[key] then 15 | values[k] = v[key] 16 | end 17 | end 18 | return values 19 | end 20 | 21 | --[[ 22 | @desc: 根据点返回三次样条曲线 23 | author:Bogey 24 | time:2019-06-21 14:38:56 25 | --@points: 26 | @return: 27 | ]] 28 | local function spline(points) 29 | assert(#points >= 2, "A minimum of two points") 30 | tablesort(points, function (a, b) 31 | return a.x < b.x 32 | end) 33 | 34 | local xs = valuesOfKey(points, "x") 35 | local ys = valuesOfKey(points, "y") 36 | local ks = {} 37 | for i,v in ipairs(xs) do 38 | ks[i] = 0 39 | end 40 | 41 | local function zeroMat(r, c) 42 | local A = {} 43 | for i = 1, r do 44 | A[i] = {} 45 | for j = 1, c do 46 | A[i][j] = 0 47 | end 48 | end 49 | return A 50 | end 51 | 52 | local function swapRows(m, k, l) 53 | local p = m[k] 54 | m[k] = m[l] 55 | m[l] = p 56 | end 57 | 58 | local function maxMin(tb) 59 | local max, min 60 | for k,v in pairs(tb) do 61 | local value = tonumber(v) or 0 62 | if not max or max < value then 63 | max = value 64 | end 65 | if not min or min > value then 66 | min = value 67 | end 68 | end 69 | return max, min 70 | end 71 | 72 | local function solve(A, ks) 73 | local m = #A 74 | for k = 1, m do 75 | local i_max = 0 76 | local vali 77 | for i = k, m do 78 | if not vali or A[i][k] > vali then 79 | i_max = i 80 | vali = A[i][k] 81 | end 82 | end 83 | swapRows(A, k, i_max) 84 | for i = k + 2, m do 85 | for j = k + 2, m + 1 do 86 | A[i][j] = A[i][j] - A[k][j] * (A[i][k] / A[k][k]) 87 | end 88 | A[i][k] = 0 89 | end 90 | end 91 | for i = m, 1, -1 do 92 | local v = A[i][m + 1] / A[i][i] 93 | ks[i] = v 94 | for j = i, 1, -1 do 95 | A[j][m + 1] = A[j][m + 1] - A[j][i] * v 96 | A[j][i] = 0 97 | end 98 | end 99 | return ks 100 | end 101 | 102 | local function getNaturalKs(ks) 103 | local n = #xs 104 | local A = zeroMat(n, n + 1) 105 | 106 | for i = 2, n - 1 do 107 | A[i][i - 1] = 1 / (xs[i] - xs[i - 1]) 108 | A[i][i] = 2 * (1 / (xs[i] - xs[i - 1]) + 1 / (xs[i + 1] - xs[i])) 109 | A[i][i + 1] = 1 / (xs[i + 1] - xs[i]) 110 | A[i][n + 1] = 3 * ((ys[i] - ys[i - 1]) / ((xs[i] - xs[i - 1]) * (xs[i] - xs[i - 1])) + (ys[i + 1] - ys[i]) / ((xs[i + 1] - xs[i]) * (xs[i + 1] - xs[i]))) 111 | end 112 | A[1][1] = 2 / (xs[2] - xs[1]) 113 | A[1][2] = 1 / (xs[2] - xs[1]) 114 | A[1][n + 1] = (3 * (ys[2] - ys[1])) / ((xs[2] - xs[1]) * (xs[2] - xs[1])) 115 | A[n][n - 1] = 1 / (xs[n] - xs[n - 1]) 116 | A[n][n] = 2 / (xs[n] - xs[n - 1]) 117 | A[n][n + 1] = (3 * (ys[n] - ys[n - 1])) / ((xs[n] - xs[n - 1]) * (xs[n] - xs[n - 1])) 118 | 119 | return solve(A, ks) 120 | end 121 | 122 | ks = getNaturalKs(ks) 123 | 124 | local function at(x) 125 | local i = 2 126 | while xs[i] < x do 127 | i = i + 1 128 | end 129 | local t = (x - xs[i - 1]) / (xs[i] - xs[i - 1]) 130 | local a = ks[i - 1] * (xs[i] - xs[i - 1]) - (ys[i] - ys[i - 1]) 131 | local b = -ks[i] * (xs[i] - xs[i - 1]) + (ys[i] - ys[i - 1]) 132 | local q = (1 - t) * ys[i - 1] + t * ys[i] + t * (1 - t) * (a * (1 - t) + b * t) 133 | return q 134 | end 135 | 136 | local max, min = maxMin(xs) 137 | local points = {} 138 | for i = min, max do 139 | tableInsert(points, point(i, at(i))) 140 | end 141 | return points 142 | end 143 | 144 | return spline -------------------------------------------------------------------------------- /src/app/utils/htmlparser.lua: -------------------------------------------------------------------------------- 1 | -- vim: ft=lua ts=2 sw=2 2 | 3 | local esc = function(s) return string.gsub(s, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%" .. "%1") end 4 | local str = tostring 5 | local char = string.char 6 | local err = function(s) io.stderr:write(s) end 7 | local out = function(s) io.stdout:write(s) end 8 | 9 | local ElementNode = import(".htmlparser.ElementNode") 10 | local voidelements = import(".htmlparser.voidelements") 11 | 12 | local HtmlParser = {} 13 | 14 | local tpr = { 15 | -- Here we're replacing confusing sequences 16 | -- (things looking like tags, but appearing where tags can't) 17 | -- with definitelly invalid utf sequence, and later we'll replace them back 18 | ["<"] = char(208,209,208,209), 19 | [">"] = char(209,208,209,208), 20 | } 21 | 22 | local function parse(text,limit) 23 | local text=str(text) 24 | 25 | local limit = limit or htmlparser_looplimit or 1000 26 | 27 | local tpl = false 28 | 29 | local function g(id,...) 30 | local arg={...} 31 | arg[id]=tpr[arg[id]] 32 | tpl=true 33 | return table.concat(arg) 34 | end 35 | 36 | text = text 37 | :gsub( 38 | "(<)".. 39 | "([^>]-)".. 40 | "(<)", 41 | function(...)return g(3,...)end 42 | ):gsub( 43 | "("..tpr["<"]..")".. 44 | "([^%w%s])".. 45 | "([^%2]-)".. 46 | "(%2)".. 47 | "(>)".. 48 | "([^>]-)".. 49 | "(>)", 50 | function(...)return g(5,...)end 51 | ):gsub( 52 | [=[(['"])]=].. 53 | [=[([^'">%s]-)]=].. 54 | "(>)".. 55 | [=[([^'">%s]-)]=].. 56 | [=[(['"])]=], 57 | function(...)return g(3,...)end 58 | ) 59 | 60 | local index = 0 61 | local root = ElementNode:new(index, str(text)) 62 | 63 | local node, descend, tpos, opentags = root, true, 1, {} 64 | while true do 65 | if index == limit then 66 | err("[HTMLParser] [ERR] Main loop reached loop limit ("..limit.."). Please, consider increasing it or check the code for errors") 67 | break 68 | end 69 | 70 | local openstart, name 71 | openstart, tpos, name = root._text:find( 72 | "<" .. -- an uncaptured starting "<" 73 | "([%w-]+)" .. -- name = the first word, directly following the "<" 74 | "[^>]*>", -- include, but not capture everything up to the next ">" 75 | tpos) 76 | 77 | if not name then break end 78 | 79 | index = index + 1 80 | 81 | local tag = ElementNode:new(index, str(name), node, descend, openstart, tpos) 82 | node = tag 83 | 84 | local tagloop 85 | local tagst, apos = tag:gettext(), 1 86 | while true do 87 | if tagloop == limit then 88 | err("[HTMLParser] [ERR] tag parsing loop reached loop limit ("..limit.."). Please, consider increasing it or check the code for errors") 89 | break 90 | end 91 | 92 | local start, k, eq, quote, v 93 | start, apos, k, eq, quote = tagst:find( 94 | "%s+" .. -- some uncaptured space 95 | "([^%s=/>]+)" .. -- k = an unspaced string up to an optional "=" or the "/" or ">" 96 | "(=?)" .. -- eq = the optional; "=", else "" 97 | "(['\"]?)", -- quote = an optional "'" or '"' following the "=", or "" 98 | apos) 99 | 100 | if not k or k == "/>" or k == ">" then break end 101 | 102 | if eq == "=" then 103 | pattern = "=([^%s>]*)" 104 | if quote ~= "" then 105 | pattern = quote .. "([^" .. quote .. "]*)" .. quote 106 | end 107 | start, apos, v = tagst:find(pattern, apos) 108 | end 109 | 110 | v=v or "" 111 | 112 | if tpl then 113 | for rk,rv in pairs(tpr) do 114 | v = v:gsub(rv,rk) 115 | end 116 | end 117 | 118 | tag:addattribute(k, v) 119 | tagloop = (tagloop or 0) + 1 120 | end 121 | 122 | if voidelements[tag.name:lower()] then 123 | descend = false 124 | tag:close() 125 | else 126 | opentags[tag.name] = opentags[tag.name] or {} 127 | table.insert(opentags[tag.name], tag) 128 | end 129 | 130 | local closeend = tpos 131 | local closingloop 132 | while true do 133 | if closingloop == limit then 134 | err("[HTMLParser] [ERR] tag closing loop reached loop limit ("..limit.."). Please, consider increasing it or check the code for errors") 135 | break 136 | end 137 | 138 | local closestart, closing, closename 139 | closestart, closeend, closing, closename = root._text:find("[^<]*<(/?)([%w-]+)", closeend) 140 | 141 | if not closing or closing == "" then break end 142 | 143 | tag = table.remove(opentags[closename] or {}) or tag -- kludges for the cases of closing void or non-opened tags 144 | closestart = root._text:find("<", closestart) 145 | tag:close(closestart, closeend + 1) 146 | node = tag.parent 147 | descend = true 148 | closingloop = (closingloop or 0) + 1 149 | end 150 | end 151 | 152 | if tpl then 153 | for k,v in pairs(tpr) do 154 | root._text = root._text:gsub(v,k) 155 | end 156 | end 157 | 158 | return root 159 | end 160 | HtmlParser.parse = parse 161 | 162 | return HtmlParser 163 | 164 | -------------------------------------------------------------------------------- /res/creator/main.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.3", 3 | "root": { 4 | "object": { 5 | "node": { 6 | "contentSize": { 7 | "w": 0, 8 | "h": 0 9 | }, 10 | "enabled": true, 11 | "anchorPoint": { 12 | "x": 0, 13 | "y": 0 14 | }, 15 | "cascadeOpacityEnabled": true, 16 | "color": { 17 | "r": 255, 18 | "g": 255, 19 | "b": 255 20 | }, 21 | "globalZOrder": 0, 22 | "localZOrder": 0, 23 | "opacity": 255, 24 | "opacityModifyRGB": false, 25 | "tag": -1, 26 | "groupIndex": 0, 27 | "colliders": [] 28 | } 29 | }, 30 | "object_type": "Scene", 31 | "children": [ 32 | { 33 | "object": { 34 | "node": { 35 | "contentSize": { 36 | "w": 43, 37 | "h": 34 38 | }, 39 | "enabled": true, 40 | "name": "sprite", 41 | "anchorPoint": { 42 | "x": 0.5, 43 | "y": 0.5 44 | }, 45 | "cascadeOpacityEnabled": true, 46 | "color": { 47 | "r": 255, 48 | "g": 255, 49 | "b": 255 50 | }, 51 | "globalZOrder": 0, 52 | "localZOrder": 0, 53 | "opacity": 255, 54 | "opacityModifyRGB": false, 55 | "position": { 56 | "x": 136, 57 | "y": 186 58 | }, 59 | "rotationSkewX": 0, 60 | "rotationSkewY": 0, 61 | "scaleX": 1, 62 | "scaleY": 1, 63 | "skewX": 0, 64 | "skewY": 0, 65 | "tag": -1, 66 | "groupIndex": 0, 67 | "colliders": [] 68 | }, 69 | "spriteFrameName": "img/animal_panda.png", 70 | "spriteType": "Simple", 71 | "srcBlend": 770, 72 | "dstBlend": 771, 73 | "trimEnabled": true, 74 | "sizeMode": "Trimmed" 75 | }, 76 | "object_type": "Sprite", 77 | "children": [] 78 | }, 79 | { 80 | "object": { 81 | "node": { 82 | "contentSize": { 83 | "w": 82.28, 84 | "h": 20 85 | }, 86 | "enabled": true, 87 | "name": "label", 88 | "anchorPoint": { 89 | "x": 0.5, 90 | "y": 0.5 91 | }, 92 | "cascadeOpacityEnabled": true, 93 | "color": { 94 | "r": 255, 95 | "g": 255, 96 | "b": 255 97 | }, 98 | "globalZOrder": 0, 99 | "localZOrder": 0, 100 | "opacity": 255, 101 | "opacityModifyRGB": false, 102 | "position": { 103 | "x": 240, 104 | "y": 251 105 | }, 106 | "rotationSkewX": 0, 107 | "rotationSkewY": 0, 108 | "scaleX": 1, 109 | "scaleY": 1, 110 | "skewX": 0, 111 | "skewY": 0, 112 | "tag": -1, 113 | "groupIndex": 0, 114 | "colliders": [] 115 | }, 116 | "fontSize": 20, 117 | "labelText": "SysLabel", 118 | "horizontalAlignment": "Center", 119 | "verticalAlignment": "Center", 120 | "overflowType": "None", 121 | "enableWrap": true, 122 | "fontType": "System", 123 | "fontName": "arial" 124 | }, 125 | "object_type": "Label", 126 | "children": [] 127 | }, 128 | { 129 | "object": { 130 | "node": { 131 | "contentSize": { 132 | "w": 184.5, 133 | "h": 30 134 | }, 135 | "enabled": true, 136 | "name": "label", 137 | "anchorPoint": { 138 | "x": 0.5, 139 | "y": 0.5 140 | }, 141 | "cascadeOpacityEnabled": true, 142 | "color": { 143 | "r": 213, 144 | "g": 60, 145 | "b": 170 146 | }, 147 | "globalZOrder": 0, 148 | "localZOrder": 0, 149 | "opacity": 255, 150 | "opacityModifyRGB": false, 151 | "position": { 152 | "x": 179, 153 | "y": 97 154 | }, 155 | "rotationSkewX": 0, 156 | "rotationSkewY": 0, 157 | "scaleX": 1, 158 | "scaleY": 1, 159 | "skewX": 0, 160 | "skewY": 0, 161 | "tag": -1, 162 | "groupIndex": 0, 163 | "colliders": [] 164 | }, 165 | "fontSize": 30, 166 | "labelText": "FreeTypeLabel", 167 | "horizontalAlignment": "Left", 168 | "verticalAlignment": "Bottom", 169 | "overflowType": "None", 170 | "enableWrap": true, 171 | "fontName": "creator/29bd4115-e734-40e7-bbd5-484530644fe5/TarantellaMF.ttf", 172 | "fontType": "TTF", 173 | "lineHeight": 0 174 | }, 175 | "object_type": "Label", 176 | "children": [] 177 | } 178 | ] 179 | }, 180 | "spriteFrames": [ 181 | { 182 | "name": "img/animal_panda.png", 183 | "texturePath": "creator/img/animal_panda.png", 184 | "rect": { 185 | "x": 3, 186 | "y": 9, 187 | "w": 43, 188 | "h": 34 189 | }, 190 | "offset": { 191 | "x": 0.5, 192 | "y": -2 193 | }, 194 | "rotated": false, 195 | "originalSize": { 196 | "w": 48, 197 | "h": 48 198 | }, 199 | "centerRect": { 200 | "x": 0, 201 | "y": 0, 202 | "w": 43, 203 | "h": 34 204 | } 205 | } 206 | ], 207 | "collisionMatrix": [ 208 | { 209 | "value": [ 210 | true 211 | ] 212 | } 213 | ] 214 | } -------------------------------------------------------------------------------- /src/app/utils/htmlparser/ElementNode.lua: -------------------------------------------------------------------------------- 1 | -- vim: ft=lua ts=2 2 | local Set = {} 3 | Set.mt = {__index = Set} 4 | function Set:new(values) 5 | local instance = {} 6 | local isSet if getmetatable(values) == Set.mt then isSet = true end 7 | if type(values) == "table" then 8 | if not isSet and #values > 0 then 9 | for _,v in ipairs(values) do 10 | instance[v] = true 11 | end 12 | else 13 | for k in pairs(values) do 14 | instance[k] = true 15 | end 16 | end 17 | elseif values ~= nil then 18 | instance = {[values] = true} 19 | end 20 | return setmetatable(instance, Set.mt) 21 | end 22 | 23 | function Set:add(e) 24 | if e ~= nil then self[e] = true end 25 | return self 26 | end 27 | 28 | function Set:remove(e) 29 | if e ~= nil then self[e] = nil end 30 | return self 31 | end 32 | 33 | function Set:tolist() 34 | local res = {} 35 | for k in pairs(self) do 36 | table.insert(res, k) 37 | end 38 | return res 39 | end 40 | 41 | Set.mt.__add = function (a, b) 42 | local res, a, b = Set:new(), Set:new(a), Set:new(b) 43 | for k in pairs(a) do res[k] = true end 44 | for k in pairs(b) do res[k] = true end 45 | return res 46 | end 47 | 48 | -- Subtraction 49 | Set.mt.__sub = function (a, b) 50 | local res, a, b = Set:new(), Set:new(a), Set:new(b) 51 | for k in pairs(a) do res[k] = true end 52 | for k in pairs(b) do res[k] = nil end 53 | return res 54 | end 55 | 56 | -- Intersection 57 | Set.mt.__mul = function (a, b) 58 | local res, a, b = Set:new(), Set:new(a), Set:new(b) 59 | for k in pairs(a) do 60 | res[k] = b[k] 61 | end 62 | return res 63 | end 64 | 65 | -- String representation 66 | Set.mt.__tostring = function (set) 67 | local s = "{" 68 | local sep = "" 69 | for k in pairs(set) do 70 | s = s .. sep .. tostring(k) 71 | sep = ", " 72 | end 73 | return s .. "}" 74 | end 75 | 76 | 77 | local ElementNode = {} 78 | ElementNode.mt = {__index = ElementNode} 79 | function ElementNode:new(index, nameortext, node, descend, openstart, openend) 80 | local instance = { 81 | index = index, 82 | name = nameortext, 83 | level = 0, 84 | parent = nil, 85 | root = nil, 86 | nodes = {}, 87 | _openstart = openstart, _openend = openend, 88 | _closestart = openstart, _closeend = openend, 89 | attributes = {}, 90 | id = nil, 91 | classes = {}, 92 | deepernodes = Set:new(), 93 | deeperelements = {}, deeperattributes = {}, deeperids = {}, deeperclasses = {} 94 | } 95 | if not node then 96 | instance.name = "root" 97 | instance.root = instance 98 | instance._text = nameortext 99 | local length = string.len(nameortext) 100 | instance._openstart, instance._openend = 1, length 101 | instance._closestart, instance._closeend = 1, length 102 | elseif descend then 103 | instance.root = node.root 104 | instance.parent = node 105 | instance.level = node.level + 1 106 | table.insert(node.nodes, instance) 107 | else 108 | instance.root = node.root 109 | instance.parent = node.parent 110 | instance.level = node.level 111 | table.insert(node.parent.nodes, instance) 112 | end 113 | return setmetatable(instance, ElementNode.mt) 114 | end 115 | 116 | function ElementNode:gettext() 117 | return string.sub(self.root._text, self._openstart, self._closeend) 118 | end 119 | 120 | function ElementNode:settext(c) 121 | self.root._text=c 122 | end 123 | 124 | function ElementNode:textonly() 125 | return (self:gettext():gsub("<[^>]*>","")) 126 | end 127 | 128 | function ElementNode:getcontent() 129 | return string.sub(self.root._text, self._openend + 1, self._closestart - 1) 130 | end 131 | 132 | function ElementNode:addattribute(k, v) 133 | self.attributes[k] = v 134 | if string.lower(k) == "id" then 135 | self.id = v 136 | -- class attribute contains "space-separated tokens", each of which we'd like quick access to 137 | elseif string.lower(k) == "class" then 138 | for class in string.gmatch(v, "%S+") do 139 | table.insert(self.classes, class) 140 | end 141 | end 142 | end 143 | 144 | local function insert(table, name, node) 145 | table[name] = table[name] or Set:new() 146 | table[name]:add(node) 147 | end 148 | 149 | function ElementNode:close(closestart, closeend) 150 | if closestart and closeend then 151 | self._closestart, self._closeend = closestart, closeend 152 | end 153 | -- inform hihger level nodes about this element's existence in their branches 154 | local node = self 155 | while true do 156 | node = node.parent 157 | if not node then break end 158 | node.deepernodes:add(self) 159 | insert(node.deeperelements, self.name, self) 160 | for k in pairs(self.attributes) do 161 | insert(node.deeperattributes, k, self) 162 | end 163 | if self.id then 164 | insert(node.deeperids, self.id, self) 165 | end 166 | for _,v in ipairs(self.classes) do 167 | insert(node.deeperclasses, v, self) 168 | end 169 | end 170 | end 171 | 172 | local function escape(s) 173 | -- escape all ^, $, (, ), %, ., [, ], *, +, - , and ? with a % prefix 174 | return string.gsub(s, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%" .. "%1") 175 | end 176 | 177 | local function select(self, s) 178 | if not s or type(s) ~= "string" or s == "" then return Set:new() end 179 | local sets = {[""] = self.deeperelements, ["["] = self.deeperattributes, 180 | ["#"] = self.deeperids, ["."] = self.deeperclasses} 181 | local function match(t, w) 182 | local m, e, v 183 | if t == "[" then w, m, e, v = string.match(w, 184 | "([^=|%*~%$!%^]+)" .. -- w = 1 or more characters up to a possible "=", "|", "*", "~", "$", "!", or "^" 185 | "([|%*~%$!%^]?)" .. -- m = an optional "|", "*", "~", "$", "!", or "^", preceding the optional "=" 186 | "(=?)" .. -- e = the optional "=" 187 | "(.*)" -- v = anything following the "=", or else "" 188 | ) 189 | end 190 | local matched = Set:new(sets[t][w]) 191 | -- attribute value selectors 192 | if e == "=" then 193 | if #v < 2 then v = "'" .. v .. "'" end -- values should be quoted 194 | v = string.sub(v, 2, #v - 1) -- strip quotes 195 | if m == "!" then matched = Set:new(self.deepernodes) end -- include those without that attribute 196 | for node in pairs(matched) do 197 | local a = node.attributes[w] 198 | -- equals 199 | if m == "" and a ~= v then matched:remove(node) 200 | -- not equals 201 | elseif m == "!" and a == v then matched:remove(node) 202 | -- prefix 203 | elseif m =="|" and string.match(a, "^[^-]*") ~= v then matched:remove(node) 204 | -- contains 205 | elseif m =="*" and string.match(a, escape(v)) ~= v then matched:remove(node) 206 | -- word 207 | elseif m =="~" then matched:remove(node) 208 | for word in string.gmatch(a, "%S+") do 209 | if word == v then matched:add(node) break end 210 | end 211 | -- starts with 212 | elseif m =="^" and string.match(a, "^" .. escape(v)) ~= v then matched:remove(node) 213 | -- ends with 214 | elseif m =="$" and string.match(a, escape(v) .. "$") ~= v then matched:remove(node) 215 | end 216 | end -- for node 217 | end -- if v 218 | return matched 219 | end 220 | 221 | local subjects, resultset, childrenonly = Set:new({self}) 222 | for part in string.gmatch(s, "%S+") do 223 | repeat 224 | if part == ">" then childrenonly = true --[[goto nextpart]] break end 225 | resultset = Set:new() 226 | for subject in pairs(subjects) do 227 | local star = subject.deepernodes 228 | if childrenonly then star = Set:new(subject.nodes) end 229 | resultset = resultset + star 230 | end 231 | childrenonly = false 232 | if part == "*" then --[[goto nextpart]] break end 233 | local excludes, filter = Set:new() 234 | local start, pos = 0, 0 235 | while true do 236 | local switch, stype, name, eq, quote 237 | start, pos, switch, stype, name, eq, quote = string.find(part, 238 | "(%(?%)?)" .. -- switch = a possible ( or ) switching the filter on or off 239 | "([:%[#.]?)" .. -- stype = a possible :, [, #, or . 240 | "([%w-_\\]+)" .. -- name = 1 or more alfanumeric chars (+ hyphen, reverse slash and uderscore) 241 | "([|%*~%$!%^]?=?)" .. -- eq = a possible |=, *=, ~=, $=, !=, ^=, or = 242 | "(['\"]?)", -- quote = a ' or " delimiting a possible attribute value 243 | pos + 1 244 | ) 245 | if not name then break end 246 | repeat 247 | if ":" == stype then 248 | filter = name 249 | --[[goto nextname]] break 250 | end 251 | if ")" == switch then 252 | filter = nil 253 | end 254 | if "[" == stype and "" ~= quote then 255 | local value 256 | start, pos, value = string.find(part, "(%b" .. quote .. quote .. ")]", pos) 257 | name = name .. eq .. value 258 | end 259 | local matched = match(stype, name) 260 | if filter == "not" then 261 | excludes = excludes + matched 262 | else 263 | resultset = resultset * matched 264 | end 265 | --::nextname:: 266 | break 267 | until true 268 | end 269 | resultset = resultset - excludes 270 | subjects = Set:new(resultset) 271 | --::nextpart:: 272 | break 273 | until true 274 | end 275 | resultset = resultset:tolist() 276 | table.sort(resultset, function (a, b) return a.index < b.index end) 277 | return resultset 278 | end 279 | 280 | function ElementNode:select(s) return select(self, s) end 281 | ElementNode.mt.__call = select 282 | 283 | return ElementNode 284 | -------------------------------------------------------------------------------- /src/app/utils/Updater.lua: -------------------------------------------------------------------------------- 1 | local Updater = {} 2 | 3 | local scheduler = require("framework.scheduler") 4 | local FileUtils = cc.FileUtils:getInstance() 5 | local maxHTTPRequest = 5 6 | local configFileName = "version.json" 7 | local extName = "URes" 8 | local extTmpName = "UTmp" 9 | local writablePath = string.gsub(FileUtils:getWritablePath(), "\\", "/") 10 | local extPath = writablePath .. extName .. "/" 11 | local extTmp = writablePath .. extTmpName .. "/" 12 | local cpu = "32" 13 | if jit.arch:sub(-2) == "64" then 14 | cpu = "64" 15 | end 16 | 17 | -- ********** internal function ******** 18 | local function copyFile(src, dest) 19 | local buf = FileUtils:getDataFromFile(src) 20 | if buf then 21 | local pInfo = io.pathinfo(dest) 22 | if false == FileUtils:isDirectoryExist(pInfo.dirname) then 23 | FileUtils:createDirectory(pInfo.dirname) -- Create path recursively 24 | end 25 | FileUtils:writeStringToFile(buf, dest) 26 | end 27 | end 28 | 29 | local function saveFile(path, buf) 30 | local pInfo = io.pathinfo(path) 31 | if false == FileUtils:isDirectoryExist(pInfo.dirname) then 32 | FileUtils:createDirectory(pInfo.dirname) -- Create path recursively 33 | end 34 | FileUtils:writeStringToFile(buf, path) 35 | end 36 | 37 | -- compare the string version, return true if need doUpdate 38 | local function checkVersion(my, server) 39 | my = string.split(my, ".") 40 | server = string.split(server, ".") 41 | for i = 1, 3 do 42 | mVer = tonumber(my[i]) 43 | mSver = tonumber(server[i]) 44 | if mVer < mSver then 45 | return true 46 | elseif mVer > mSver then 47 | return false 48 | end 49 | end 50 | return false 51 | end 52 | 53 | local function isNeedDownload(name, md5) 54 | -- check game packages is need for this device 55 | if string.sub(name, #name - 3) == ".zip" then 56 | if string.sub(name, #name - 5, #name - 4) ~= cpu then 57 | return false 58 | end 59 | end 60 | -- check if downloaded 61 | local path = extTmp .. name 62 | if FileUtils:isFileExist(path) and crypto.md5file(path) == md5 then 63 | return false 64 | end 65 | return true 66 | end 67 | 68 | -- check if the remain file is OK 69 | local function isRemainOK(name, md5) 70 | local path = FileUtils:fullPathForFilename(name) 71 | if string.find(path, extPath, 1, true) == 1 then -- ext files 72 | if FileUtils:isFileExist(path) and crypto.md5file(path) == md5 then 73 | copyFile(path, extTmp .. name) -- copy extPath => extTmp 74 | return true 75 | end 76 | return false 77 | end 78 | -- apk files always OK 79 | return true 80 | end 81 | 82 | local function getDiff(my, server) 83 | local change = {} 84 | local size = 0 85 | 86 | local serverAsserts = server.assets 87 | for k, v in pairs(my.assets) do 88 | local value = serverAsserts[k] 89 | if value then 90 | -- get changing files 91 | if value[1] ~= v[1] then 92 | if isNeedDownload(k, value[1]) then 93 | table.insert(change, {url = k, total = value[2]}) 94 | size = size + value[2] 95 | end 96 | else 97 | if not isRemainOK(k, value[1]) then 98 | table.insert(change, {url = k, total = value[2]}) 99 | size = size + value[2] 100 | end 101 | end 102 | value.checked = true 103 | end 104 | end 105 | 106 | for k, value in pairs(serverAsserts) do 107 | if value.checked then -- clean tmp value 108 | value.checked = nil 109 | else -- get new adding files 110 | if isNeedDownload(k, value[1]) then 111 | size = size + value[2] 112 | table.insert(change, {url = k, total = value[2]}) 113 | end 114 | end 115 | end 116 | 117 | return { 118 | change = change, 119 | size = size, 120 | packages = server.packages, 121 | } 122 | end 123 | 124 | local function doUpdate(url, callback, info) 125 | -- info UI to update the size 126 | callback(2, info.size, 0) 127 | 128 | -- http download dealing 129 | local curHttp = 0 130 | local totalGetSize = 0 131 | local totalErrorCount = 0 132 | 133 | local notifySize = function(diff) 134 | totalGetSize = totalGetSize + diff 135 | callback(2, info.size, totalGetSize) 136 | end 137 | 138 | local notifyError = function(downInfo) 139 | curHttp = curHttp - 1 140 | totalErrorCount = totalErrorCount + 1 141 | notifySize(-downInfo.getSize) -- cancel getting size 142 | downInfo.isReqed = nil 143 | downInfo.getSize = nil 144 | end 145 | 146 | local newRequest = function(index) 147 | local downInfo = info.change[index] 148 | local downUrl = url .. "/" .. downInfo.url 149 | local request = network.createHTTPDownload(function(event) 150 | local request = event.request 151 | if event.name == "completed" then 152 | local code = request:getResponseStatusCode() 153 | if code ~= 200 and code ~= 206 then 154 | notifyError(downInfo) 155 | return 156 | end 157 | 158 | -- info size 159 | curHttp = curHttp - 1 160 | local diff = request:getResponseDataLength() - downInfo.getSize 161 | notifySize(diff) 162 | -- mark downloaded 163 | info.change[index] = nil 164 | if totalErrorCount > 0 then -- optimize for remove error count 165 | totalErrorCount = totalErrorCount - 1 166 | end 167 | elseif event.name == "progress" then 168 | local diff = event.dltotal - downInfo.getSize 169 | notifySize(diff) 170 | downInfo.getSize = event.dltotal 171 | else 172 | notifyError(downInfo) 173 | end 174 | end, downUrl, extTmp .. downInfo.url) 175 | 176 | -- add downloading mark 177 | downInfo.isReqed = true -- is downloading 178 | downInfo.getSize = 0 -- downloaded size 179 | curHttp = curHttp + 1 180 | request:start() 181 | end 182 | 183 | Updater._scheduler = scheduler.scheduleUpdateGlobal(function() 184 | -- check exit 185 | if 0 == table.nums(info.change) then 186 | scheduler.unscheduleGlobal(Updater._scheduler) 187 | -- remove extPath, then rename extTmp -> extPath 188 | FileUtils:removeDirectory(extPath) 189 | FileUtils:renameFile(writablePath, extTmpName, extName) 190 | FileUtils:purgeCachedEntries() -- clear filename search cache 191 | -- reload game.zip, purgeCachedData 192 | for _, zip in ipairs(info.packages) do 193 | cc.LuaLoadChunksFromZIP(zip .. cpu .. ".zip") 194 | end 195 | cc.Director:getInstance():purgeCachedData() 196 | -- notify to start play scene 197 | __UpdaterInited = nil 198 | callback(1) 199 | return 200 | end 201 | -- no downloading, and reach the maxHTTPRequest 202 | if 0 == curHttp and totalErrorCount >= maxHTTPRequest then 203 | scheduler.unscheduleGlobal(Updater._scheduler) 204 | callback(5, -1) 205 | return 206 | end 207 | -- a request per frame event 208 | if curHttp < maxHTTPRequest and table.nums(info.change) > curHttp then 209 | for index, downInfo in pairs(info.change) do 210 | if true ~= downInfo.isReqed then 211 | newRequest(index) 212 | break 213 | end 214 | end 215 | end 216 | end) 217 | end 218 | 219 | local function checkUpdate(url, callback) 220 | -- we had set SearchPath, so we will get the right file 221 | local data = FileUtils:getDataFromFile(configFileName) 222 | assert(data, "Error: fail to get data from config.json") 223 | data = json.decode(data) 224 | assert(data, "Error: fail to parser config.json") 225 | 226 | -- get version.json 227 | local request = network.createHTTPRequest(function(event) 228 | local request = event.request 229 | if event.name == "completed" then 230 | local code = request:getResponseStatusCode() 231 | if code ~= 200 then 232 | callback(4, code) 233 | return 234 | end 235 | 236 | -- do the real things 237 | local response = request:getResponseString() 238 | response = json.decode(response) 239 | if checkVersion(data.EngineVersion, response.EngineVersion) then 240 | callback(6) 241 | elseif checkVersion(data.GameVersion, response.GameVersion) then 242 | -- save config 243 | saveFile(extTmp .. configFileName, request:getResponseData()) 244 | local info = getDiff(data, response) 245 | if network.isLocalWiFiAvailable() then 246 | doUpdate(url, callback, info) 247 | else -- need UI pop confirm to continue 248 | callback(7, info.size, function() 249 | doUpdate(url, callback, info) 250 | end) 251 | end 252 | else 253 | print("== no need update") 254 | __UpdaterInited = nil 255 | callback(1) 256 | end 257 | elseif event.name == "progress" then 258 | -- print("progress" .. event.dltotal) 259 | else 260 | callback(5, request:getErrorCode()) 261 | end 262 | end, url .. "/" .. configFileName, "GET") 263 | request:setTimeout(30) 264 | request:start() 265 | end 266 | 267 | local function getHeadUrl(headUrl, callback) 268 | if not network.isInternetConnectionAvailable() then 269 | callback(3) 270 | return 271 | end 272 | 273 | local request = network.createHTTPRequest(function(event) 274 | local request = event.request 275 | if event.name == "completed" then 276 | local code = request:getResponseStatusCode() 277 | if code ~= 200 then 278 | callback(4, code) 279 | return 280 | end 281 | 282 | -- get head version url, start get version json 283 | local url = request:getResponseString() 284 | url = string.gsub(url, "[\n\r]", "") -- remove newline 285 | checkUpdate(url, callback) 286 | elseif event.name == "progress" then 287 | -- print("progress" .. event.dltotal) 288 | else 289 | callback(5, request:getErrorCode()) 290 | end 291 | end, headUrl, "GET") 292 | request:setTimeout(15) 293 | request:start() 294 | end 295 | 296 | --[[ apk's "res/game32.zip" had been loaded by cpp code. 297 | check and load the right package's, then restart the LoadingScene 298 | callback(code, param1, param2) 299 | 1 success 300 | 2 update(param1:total, param2:cur) 301 | 3 Network connect fail 302 | 4 HTTP Server error(param1:httpCode) 303 | 5 HTTP request error(param1:requestCode) 304 | 6 EngineVersion old, need apk or ipa update 305 | 7 Need update, (param1:total, param2:func), wait UI check WIFI. 306 | --]] 307 | function Updater.init(sceneName, headUrl, callback) 308 | if __UpdaterInited then 309 | -- extends loaded, start the network checking now 310 | getHeadUrl(headUrl, callback) 311 | return 312 | end 313 | __UpdaterInited = true 314 | 315 | -- get config in apk 316 | local sandbox = FileUtils:getDataFromFile("res/" .. configFileName) 317 | sandbox = json.decode(sandbox) 318 | -- add extPath before apk path 319 | FileUtils:setSearchPaths{extPath, "res/"} 320 | -- get config in URes or apk 321 | local data = FileUtils:getDataFromFile(configFileName) 322 | data = json.decode(data) 323 | if checkVersion(data.EngineVersion, sandbox.EngineVersion) 324 | or checkVersion(data.GameVersion, sandbox.GameVersion) then 325 | -- apk has update, so remove old URes. 326 | FileUtils:removeDirectory(extPath) 327 | FileUtils:purgeCachedEntries() 328 | data = sandbox -- use apk data to init 329 | end 330 | 331 | -- let the first frame display, and avoid to replaceScene in the scene ctor(BUG) 332 | scheduler.performWithDelayGlobal(function() 333 | -- load chunks 334 | for _, zip in ipairs(data.packages) do 335 | cc.LuaLoadChunksFromZIP(zip .. cpu .. ".zip") 336 | end 337 | print("== restarting", sceneName) 338 | cc.Director:getInstance():replaceScene(require(sceneName).new()) 339 | end, 0) 340 | end 341 | 342 | return Updater 343 | -------------------------------------------------------------------------------- /src/app/utils/TableView.lua: -------------------------------------------------------------------------------- 1 | local TableView = {} 2 | 3 | --[[ In TableView, index is Lua index, from 1 to N 4 | ]]-- 5 | 6 | local ListView = ccui.ListView 7 | 8 | --[[ internal method 9 | self: listview 10 | item: to be check 11 | ]]-- 12 | local function checkInView(self, item) 13 | -- item convert relative to listview 14 | local posA = item:convertToWorldSpace(cc.p(0, 0)) 15 | posA = self:convertToNodeSpace(posA) 16 | posA.x = posA.x + self._checkOffsetX 17 | posA.y = posA.y + self._checkOffsetY 18 | local sizeA = item:getContentSize() 19 | -- AABB 20 | local centerXdelta = (sizeA.width + self._checkWidth) / 2 21 | local centerYdelta = (sizeA.height + self._checkHeight) / 2 22 | if math.abs((posA.x + sizeA.width / 2) - (self._checkWidth / 2)) <= centerXdelta and 23 | math.abs((posA.y + sizeA.height / 2) - (self._checkHeight / 2)) <= centerYdelta then 24 | return true 25 | end 26 | return false 27 | end 28 | 29 | -- internal method 30 | local function scrolling(self) 31 | if self._headIndex == nil then -- out view, try recreate while bouncing 32 | for i = self._tailIndex, 1, -1 do 33 | local item = ListView.getItem(self, i - 1) 34 | if not checkInView(self, item) then 35 | break 36 | end 37 | if #item:getChildren() > 0 then break end 38 | item:addChild(self:_loadSource(i)) 39 | self._headIndex = i 40 | end 41 | return 42 | end 43 | if self._tailIndex == nil then -- out view, try recreate while bouncing 44 | local items = ListView.getItems(self) 45 | for i = self._headIndex, #items do 46 | local item = items[i] 47 | if not checkInView(self, item) then 48 | break 49 | end 50 | if #item:getChildren() > 0 then break end 51 | item:addChild(self:_loadSource(i)) 52 | self._tailIndex = i 53 | end 54 | return 55 | end 56 | 57 | if nil == self._innerP then return end 58 | 59 | local direction = self:getDirection() 60 | local isForward = false 61 | if 1 == direction then -- VERTICAL 62 | local py = self:getInnerContainer():getPositionY() 63 | if self._innerP < py then -- 加载下方数据 64 | isForward = true 65 | end 66 | self._innerP = py 67 | else 68 | local px = self:getInnerContainer():getPositionX() 69 | if self._innerP > px then -- 加载右方数据 70 | isForward = true 71 | end 72 | self._innerP = px 73 | end 74 | 75 | local item 76 | if isForward then 77 | item = ListView.getItem(self, self._tailIndex - 1) 78 | if checkInView(self, item) then -- tail in view 79 | repeat -- add tail 80 | item = ListView.getItem(self, self._tailIndex) 81 | if nil == item then break end 82 | if not checkInView(self, item) then 83 | break 84 | end 85 | self._tailIndex = self._tailIndex + 1 86 | item:addChild(self:_loadSource(self._tailIndex)) 87 | until false 88 | repeat -- remove head 89 | item = ListView.getItem(self, self._headIndex - 1) 90 | if checkInView(self, item) then 91 | break 92 | end 93 | item:removeAllChildren() 94 | self:_unloadSource(self._headIndex) 95 | self._headIndex = self._headIndex + 1 96 | until false 97 | else -- tail out of view, jump scrolling 98 | -- remove all 99 | for i = self._headIndex, self._tailIndex do 100 | item = ListView.getItem(self, i - 1) 101 | item:removeAllChildren() 102 | self:_unloadSource(i) 103 | self._headIndex = nil 104 | end 105 | -- find new head and tail 106 | repeat 107 | item = ListView.getItem(self, self._tailIndex) 108 | if nil == item then break end 109 | if checkInView(self, item) then 110 | self._tailIndex = self._tailIndex + 1 111 | item:addChild(self:_loadSource(self._tailIndex)) 112 | if nil == self._headIndex then 113 | self._headIndex = self._tailIndex 114 | end 115 | else 116 | if self._headIndex then 117 | break 118 | else 119 | self._tailIndex = self._tailIndex + 1 120 | end 121 | end 122 | until false 123 | end 124 | else -- not isForward 125 | item = ListView.getItem(self, self._headIndex - 1) 126 | if checkInView(self, item) then -- head in view 127 | repeat -- add head 128 | item = ListView.getItem(self, self._headIndex - 2) 129 | if nil == item then break end 130 | if not checkInView(self, item) then 131 | break 132 | end 133 | self._headIndex = self._headIndex - 1 134 | item:addChild(self:_loadSource(self._headIndex)) 135 | until false 136 | repeat -- remove tail 137 | item = ListView.getItem(self, self._tailIndex - 1) 138 | if checkInView(self, item) then 139 | break 140 | end 141 | item:removeAllChildren() 142 | self:_unloadSource(self._tailIndex) 143 | self._tailIndex = self._tailIndex - 1 144 | until false 145 | else -- head out of view, jump scrolling 146 | -- remove all 147 | for i = self._headIndex, self._tailIndex do 148 | item = ListView.getItem(self, i - 1) 149 | item:removeAllChildren() 150 | self:_unloadSource(i) 151 | self._tailIndex = nil 152 | end 153 | -- find new head and tail 154 | repeat 155 | item = ListView.getItem(self, self._headIndex - 2) 156 | if nil == item then break end 157 | if checkInView(self, item) then 158 | self._headIndex = self._headIndex - 1 159 | item:addChild(self:_loadSource(self._headIndex)) 160 | if nil == self._tailIndex then 161 | self._tailIndex = self._headIndex 162 | end 163 | else 164 | if self._tailIndex then 165 | break 166 | else 167 | self._headIndex = self._headIndex - 1 168 | end 169 | end 170 | until false 171 | end 172 | end 173 | end 174 | 175 | --[[ external method, must be called at least once. 176 | self: listview 177 | index: make sure item of index is in viewRect, auto load and unload items. 178 | ]]-- 179 | local function jumpTo(self, index) 180 | self:stopAllActions() 181 | self:performWithDelay(function() 182 | local direction = self:getDirection() 183 | local size = self:getContentSize() 184 | 185 | local checkTab = {} 186 | local items = ListView.getItems(self) 187 | for i = self._headIndex, self._tailIndex do 188 | checkTab[i] = false 189 | end 190 | -- adjust scroll view 191 | local item = items[index] 192 | assert(index >= 1 and index <= #items, "Wrong index range") 193 | 194 | if 1 == direction then -- VERTICAL 195 | local destY = size.height - item:getPositionY() - item:getContentSize().height 196 | destY = math.min(0, destY) 197 | self:getInnerContainer():setPositionY(destY) 198 | self._innerP = destY 199 | else 200 | local destX = size.width - item:getPositionX() - item:getContentSize().width 201 | destX = math.min(0, destX) 202 | self:getInnerContainer():setPositionX(destX) 203 | self._innerP = destX 204 | end 205 | -- find items in viewRect 206 | for i = index, 1, -1 do 207 | if not checkInView(self, items[i]) then 208 | break 209 | end 210 | self._headIndex = i 211 | end 212 | for i = index, #items do 213 | if not checkInView(self, items[i]) then 214 | break 215 | end 216 | self._tailIndex = i 217 | end 218 | -- add new 219 | for i = self._headIndex, self._tailIndex do 220 | if nil == checkTab[i] then 221 | items[i]:addChild(self:_loadSource(i)) 222 | end 223 | checkTab[i] = true 224 | end 225 | -- remove out of view 226 | for i, k in pairs(checkTab) do 227 | if false == k then 228 | items[i]:removeAllChildren() 229 | self:_unloadSource(i) 230 | end 231 | end 232 | end, 0) 233 | end 234 | 235 | local function createDefaultWidget() 236 | local layer = ccui.Layout:create() 237 | -- layer:setBackGroundColorType(ccui.LayoutBackGroundColorType.solid) 238 | -- layer:setBackGroundColor(cc.c3b(0, 0, 255)) 239 | return layer 240 | end 241 | 242 | -- init default items, _sizeSource() will be use in this stage 243 | local function initDefaultItems(self, total) 244 | local items = ListView.getItems(self) 245 | local oldTotal = #items 246 | -- remove old items and reset cursor 247 | for i = self._headIndex, self._tailIndex do 248 | items[i]:removeAllChildren() 249 | self:_unloadSource(i) 250 | end 251 | self._headIndex = 0 252 | self._tailIndex = -1 253 | self._innerP = nil 254 | -- clean defaut widgets 255 | ListView.removeAllItems(self) 256 | -- size may change, so need to add defaut widgets again 257 | for i = 1, total do 258 | local widget = createDefaultWidget() 259 | widget:setContentSize(self:_sizeSource(i)) 260 | ListView.pushBackCustomItem(self, widget) 261 | end 262 | end 263 | 264 | local function insertRow(self, index) 265 | local widget = createDefaultWidget() 266 | widget:setContentSize(self:_sizeSource(index)) 267 | ListView.insertCustomItem(self, widget, index - 1) 268 | 269 | if index > self._tailIndex then 270 | return -- no need to change cursor 271 | end 272 | 273 | if index <= self._headIndex then 274 | index = self._headIndex 275 | end 276 | local item = ListView.getItem(self, index - 1) 277 | item:addChild(self:_loadSource(index)) 278 | 279 | self._tailIndex = self._tailIndex + 1 280 | end 281 | 282 | local function deleteRow(self, index) 283 | ListView.removeItem(self, index - 1) 284 | 285 | if index > self._tailIndex then 286 | return -- no need to change cursor 287 | end 288 | 289 | if index < self._headIndex then 290 | self._headIndex = self._headIndex - 1 291 | else 292 | self:_unloadSource(index) 293 | end 294 | 295 | local item = ListView.getItem(self, self._tailIndex - 1) 296 | if item then 297 | item:addChild(self:_loadSource(self._tailIndex)) 298 | else 299 | self._tailIndex = self._tailIndex - 1 300 | end 301 | end 302 | 303 | --[[ convert a ccui.ListView -> TableView 304 | listview, is a instance of ccui.ListView 305 | sizeSource = function(self, index) 306 | return cc.size(100, 50) 307 | end 308 | loadSource = function(self, index) 309 | return display.newNode() -- which size is equal to sizeSource(index) 310 | end 311 | unloadSource = function(self, index) 312 | print("You can unload texture of index here") 313 | end 314 | 315 | note: listview:addScrollViewEventListener() MUST call after TableView.attachTo() 316 | ]]-- 317 | function TableView.attachTo(listview, sizeSource, loadSource, unloadSource) 318 | -- new internal data 319 | listview._sizeSource = sizeSource 320 | listview._loadSource = function(self, index) 321 | local node = loadSource(self, index) 322 | node:ignoreAnchorPointForPosition(false) 323 | node:setAnchorPoint(cc.p(0, 0)) 324 | node:pos(0, 0) 325 | return node 326 | end 327 | listview._unloadSource = unloadSource 328 | 329 | -- multiple calls protection 330 | if listview._headIndex then 331 | return 332 | end 333 | 334 | -- check size double of realSize, improve bounce User Experience 335 | local size = listview:getContentSize() 336 | listview._checkOffsetX = size.width / 2 337 | listview._checkOffsetY = size.height / 2 338 | listview._checkWidth = size.width * 2 339 | listview._checkHeight = size.height * 2 340 | listview._headIndex = 0 -- init to defaut cursor 341 | listview._tailIndex = -1 -- init to defaut cursor 342 | -- hide ccui.ListView 's item methods 343 | local function protectInfo () 344 | assert(nil, "The ListView method is protected by TableView") 345 | end 346 | listview.pushBackDefaultItem = protectInfo 347 | listview.insertDefaultItem = protectInfo 348 | listview.pushBackCustomItem = protectInfo 349 | listview.insertCustomItem = protectInfo 350 | listview.removeLastItem = protectInfo 351 | listview.removeItem = protectInfo 352 | listview.removeAllItems = protectInfo 353 | listview.getItem = protectInfo 354 | listview.getItems = protectInfo 355 | listview.getIndex = protectInfo 356 | -- new external mothods 357 | listview.jumpTo = jumpTo 358 | listview.initDefaultItems = initDefaultItems 359 | listview.insertRow = insertRow 360 | listview.deleteRow = deleteRow 361 | 362 | -- init event 363 | listview:addScrollViewEventListener(function(self, type) 364 | if type >= 4 then -- SCROLLING & BOUNCE_XXX 365 | -- avoid crash while remove touch node in scrolling event 366 | self:performWithDelay(function() 367 | scrolling(self) 368 | end, 0) 369 | end 370 | if self._scrollCB then 371 | self:_scrollCB(type) 372 | end 373 | end) 374 | listview.addScrollViewEventListener = function(self, cb) 375 | self._scrollCB = cb 376 | end 377 | end 378 | 379 | return TableView 380 | -------------------------------------------------------------------------------- /src/app/utils/delaunay.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | --------------- 3 | -- ## Delaunay, Lua module for convex polygon triangulation 4 | -- @author Roland Yonaba 5 | -- @copyright 2013-2016 6 | -- @license MIT 7 | -- @script delaunay 8 | 9 | -- ================ 10 | -- Private helpers 11 | -- ================ 12 | 13 | local setmetatable = setmetatable 14 | local tostring = tostring 15 | local assert = assert 16 | local unpack = unpack or table.unpack 17 | local remove = table.remove 18 | local sqrt = math.sqrt 19 | local max = math.max 20 | 21 | -- Internal class constructor 22 | local class = function(...) 23 | local klass = {} 24 | klass.__index = klass 25 | klass.__call = function(_, ...) 26 | return klass:new(...) 27 | end 28 | function klass:new(...) 29 | local instance = setmetatable({}, klass) 30 | klass.__init(instance, ...) 31 | return instance 32 | end 33 | return setmetatable(klass, {__call = klass.__call}) 34 | end 35 | 36 | -- Triangle semi-perimeter by Heron's formula 37 | local function quatCross(a, b, c) 38 | local p = (a + b + c) * (a + b - c) * (a - b + c) * (-a + b + c) 39 | return sqrt(p) 40 | end 41 | 42 | -- Cross product (p1-p2, p2-p3) 43 | local function crossProduct(p1, p2, p3) 44 | local x1, x2 = p2.x - p1.x, p3.x - p2.x 45 | local y1, y2 = p2.y - p1.y, p3.y - p2.y 46 | return x1 * y2 - y1 * x2 47 | end 48 | 49 | -- Checks if angle (p1-p2-p3) is flat 50 | local function isFlatAngle(p1, p2, p3) 51 | return (crossProduct(p1, p2, p3) == 0) 52 | end 53 | 54 | -- ================ 55 | -- Module classes 56 | -- ================ 57 | 58 | --- `Edge` class 59 | -- @type Edge 60 | local Edge = class() 61 | Edge.__eq = function(a, b) 62 | return (a.p1 == b.p1 and a.p2 == b.p2) 63 | end 64 | Edge.__tostring = function(e) 65 | return (("Edge :\n %s\n %s"):format(tostring(e.p1), tostring(e.p2))) 66 | end 67 | 68 | --- Creates a new `Edge` 69 | -- @name Edge:new 70 | -- @param p1 a `Point` 71 | -- @param p2 a `Point` 72 | -- @return a new `Edge` 73 | -- @usage 74 | -- local Delaunay = require 'Delaunay' 75 | -- local Edge = Delaunay.Edge 76 | -- local Point = Delaunay.Point 77 | -- local e = Edge:new(Point(1,1), Point(2,5)) 78 | -- local e = Edge(Point(1,1), Point(2,5)) -- Alias to Edge.new 79 | -- print(e) -- print the edge members p1 and p2 80 | -- 81 | function Edge:__init(p1, p2) 82 | self.p1, self.p2 = p1, p2 83 | end 84 | 85 | --- Test if `otherEdge` is similar to self. It does not take into account the direction. 86 | -- @param otherEdge an `Edge` 87 | -- @return `true` or `false` 88 | -- @usage 89 | -- local e1 = Edge(Point(1,1), Point(2,5)) 90 | -- local e2 = Edge(Point(2,5), Point(1,1)) 91 | -- print(e1:same(e2)) --> true 92 | -- print(e1 == e2)) --> false, == operator considers the direction 93 | -- 94 | function Edge:same(otherEdge) 95 | return ((self.p1 == otherEdge.p1) and (self.p2 == otherEdge.p2)) or ((self.p1 == otherEdge.p2) and (self.p2 == otherEdge.p1)) 96 | end 97 | 98 | --- Returns the length. 99 | -- @return the length of self 100 | -- @usage 101 | -- local e = Edge(Point(), Point(10,0)) 102 | -- print(e:length()) --> 10 103 | -- 104 | function Edge:length() 105 | return self.p1:dist(self.p2) 106 | end 107 | 108 | --- Returns the midpoint coordinates. 109 | -- @return the x-coordinate of self midpoint 110 | -- @return the y-coordinate of self midpoint 111 | -- @usage 112 | -- local e = Edge(Point(), Point(10,0)) 113 | -- print(e:getMidPoint()) --> 5, 0 114 | -- 115 | function Edge:getMidPoint() 116 | local x = self.p1.x + (self.p2.x - self.p1.x) / 2 117 | local y = self.p1.y + (self.p2.y - self.p1.y) / 2 118 | return x, y 119 | end 120 | 121 | --- Point class 122 | -- @type Point 123 | local Point = class() 124 | Point.__eq = function(a, b) 125 | return (a.x == b.x and a.y == b.y) 126 | end 127 | Point.__tostring = function(p) 128 | return ("Point (%s) x: %.2f y: %.2f"):format(p.id, p.x, p.y) 129 | end 130 | 131 | --- Creates a new `Point` 132 | -- @name Point:new 133 | -- @param x the x-coordinate 134 | -- @param y the y-coordinate 135 | -- @return a new `Point` 136 | -- @usage 137 | -- local Delaunay = require 'Delaunay' 138 | -- local Point = Delaunay.Point 139 | -- local p = Point:new(1,1) 140 | -- local p = Point(1,1) -- Alias to Point.new 141 | -- print(p) -- print the point members x and y 142 | -- 143 | function Point:__init(x, y) 144 | self.x, self.y, self.id = x or 0, y or 0, "?" 145 | end 146 | 147 | --- Returns the square distance to another `Point`. 148 | -- @param p a `Point` 149 | -- @return the square distance from self to `p`. 150 | -- @usage 151 | -- local p1, p2 = Point(), Point(1,1) 152 | -- print(p1:dist2(p2)) --> 2 153 | -- 154 | function Point:dist2(p) 155 | local dx, dy = (self.x - p.x), (self.y - p.y) 156 | return dx * dx + dy * dy 157 | end 158 | 159 | --- Returns the distance to another `Point`. 160 | -- @param p a `Point` 161 | -- @return the distance from self to `p`. 162 | -- @usage 163 | -- local p1, p2 = Point(), Point(1,1) 164 | -- print(p1:dist2(p2)) --> 1.4142135623731 165 | -- 166 | function Point:dist(p) 167 | return sqrt(self:dist2(p)) 168 | end 169 | 170 | --- Checks if self lies into the bounds of a circle 171 | -- @param cx the x-coordinate of the circle center 172 | -- @param cy the y-coordinate of the circle center 173 | -- @param r the radius of the circle 174 | -- @return `true` or `false` 175 | -- @usage 176 | -- local p = Point() 177 | -- print(p:isInCircle(0,0,1)) --> true 178 | -- 179 | function Point:isInCircle(cx, cy, r) 180 | local dx = (cx - self.x) 181 | local dy = (cy - self.y) 182 | return ((dx * dx + dy * dy) <= (r * r)) 183 | end 184 | 185 | --- `Triangle` class 186 | -- @type Triangle 187 | 188 | local Triangle = class() 189 | Triangle.__tostring = function(t) 190 | return (("Triangle: \n %s\n %s\n %s"):format(tostring(t.p1), tostring(t.p2), tostring(t.p3))) 191 | end 192 | 193 | --- Creates a new `Triangle` 194 | -- @name Triangle:new 195 | -- @param p1 a `Point` 196 | -- @param p2 a `Point` 197 | -- @param p3 a `Point` 198 | -- @return a new `Triangle` 199 | -- @usage 200 | -- local Delaunay = require 'Delaunay' 201 | -- local Triangle = Delaunay.Triangle 202 | -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1) 203 | -- local t = Triangle:new(p1, p2, p3) 204 | -- local t = Triangle(p1, p2, p3) -- Alias to Triangle.new 205 | -- print(t) -- print the triangle members p1, p2 and p3 206 | -- 207 | function Triangle:__init(p1, p2, p3) 208 | assert(not isFlatAngle(p1, p2, p3), ("angle (p1, p2, p3) is flat:\n %s\n %s\n %s"):format(tostring(p1), tostring(p2), tostring(p3))) 209 | self.p1, self.p2, self.p3 = p1, p2, p3 210 | self.e1, self.e2, self.e3 = Edge(p1, p2), Edge(p2, p3), Edge(p3, p1) 211 | end 212 | 213 | --- Checks if the triangle is defined clockwise (sequence p1-p2-p3) 214 | -- @return `true` or `false` 215 | -- @usage 216 | -- local p1, p2, p3 = Point(), Point(1,1), Point(2,0) 217 | -- local t = Triangle(p1, p2, p3) 218 | -- print(t:isCW()) --> true 219 | -- 220 | function Triangle:isCW() 221 | return (crossProduct(self.p1, self.p2, self.p3) < 0) 222 | end 223 | 224 | --- Checks if the triangle is defined counter-clockwise (sequence p1-p2-p3) 225 | -- @return `true` or `false` 226 | -- @usage 227 | -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1) 228 | -- local t = Triangle(p1, p2, p3) 229 | -- print(t:isCCW()) --> true 230 | -- 231 | function Triangle:isCCW() 232 | return (crossProduct(self.p1, self.p2, self.p3) > 0) 233 | end 234 | 235 | --- Returns the length of the edges 236 | -- @return the length of the edge p1-p2 237 | -- @return the length of the edge p2-p3 238 | -- @return the length of the edge p3-p1 239 | -- @usage 240 | -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1) 241 | -- local t = Triangle(p1, p2, p3) 242 | -- print(t:getSidesLength()) --> 2 1.4142135623731 1.4142135623731 243 | -- 244 | function Triangle:getSidesLength() 245 | return self.e1:length(), self.e2:length(), self.e3:length() 246 | end 247 | 248 | --- Returns the coordinates of the center 249 | -- @return the x-coordinate of the center 250 | -- @return the y-coordinate of the center 251 | -- @usage 252 | -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1) 253 | -- local t = Triangle(p1, p2, p3) 254 | -- print(t:getCenter()) --> 1 0.33333333333333 255 | -- 256 | function Triangle:getCenter() 257 | local x = (self.p1.x + self.p2.x + self.p3.x) / 3 258 | local y = (self.p1.y + self.p2.y + self.p3.y) / 3 259 | return x, y 260 | end 261 | 262 | --- Returns the coordinates of the circumcircle center and its radius 263 | -- @return the x-coordinate of the circumcircle center 264 | -- @return the y-coordinate of the circumcircle center 265 | -- @return the radius of the circumcircle 266 | -- @usage 267 | -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1) 268 | -- local t = Triangle(p1, p2, p3) 269 | -- print(t:getCircumCircle()) --> 1 0 1 270 | -- 271 | function Triangle:getCircumCircle() 272 | local x, y = self:getCircumCenter() 273 | local r = self:getCircumRadius() 274 | return x, y, r 275 | end 276 | 277 | --- Returns the coordinates of the circumcircle center 278 | -- @return the x-coordinate of the circumcircle center 279 | -- @return the y-coordinate of the circumcircle center 280 | -- @usage 281 | -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1) 282 | -- local t = Triangle(p1, p2, p3) 283 | -- print(t:getCircumCenter()) --> 1 0 284 | -- 285 | function Triangle:getCircumCenter() 286 | local p1, p2, p3 = self.p1, self.p2, self.p3 287 | local D = (p1.x * (p2.y - p3.y) + p2.x * (p3.y - p1.y) + p3.x * (p1.y - p2.y)) * 2 288 | local x = ((p1.x * p1.x + p1.y * p1.y) * (p2.y - p3.y) + (p2.x * p2.x + p2.y * p2.y) * (p3.y - p1.y) + (p3.x * p3.x + p3.y * p3.y) * (p1.y - p2.y)) 289 | local y = ((p1.x * p1.x + p1.y * p1.y) * (p3.x - p2.x) + (p2.x * p2.x + p2.y * p2.y) * (p1.x - p3.x) + (p3.x * p3.x + p3.y * p3.y) * (p2.x - p1.x)) 290 | return (x / D), (y / D) 291 | end 292 | 293 | --- Returns the radius of the circumcircle 294 | -- @return the radius of the circumcircle 295 | -- @usage 296 | -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1) 297 | -- local t = Triangle(p1, p2, p3) 298 | -- print(t:getCircumRadius()) --> 1 299 | -- 300 | function Triangle:getCircumRadius() 301 | local a, b, c = self:getSidesLength() 302 | return ((a * b * c) / quatCross(a, b, c)) 303 | end 304 | 305 | --- Returns the area 306 | -- @return the area 307 | -- @usage 308 | -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1) 309 | -- local t = Triangle(p1, p2, p3) 310 | -- print(t:getArea()) --> 1 311 | -- 312 | function Triangle:getArea() 313 | local a, b, c = self:getSidesLength() 314 | return (quatCross(a, b, c) / 4) 315 | end 316 | 317 | --- Checks if a given point lies into the triangle circumcircle 318 | -- @param p a `Point` 319 | -- @return `true` or `false` 320 | -- @usage 321 | -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1) 322 | -- local t = Triangle(p1, p2, p3) 323 | -- print(t:inCircumCircle(Point(1,-1))) --> true 324 | -- 325 | function Triangle:inCircumCircle(p) 326 | return p:isInCircle(self:getCircumCircle()) 327 | end 328 | 329 | --- Delaunay module 330 | -- @section public 331 | 332 | --- Delaunay module 333 | -- @table Delaunay 334 | -- @field Point reference to the `Point` class 335 | -- @field Edge reference to the `Edge` class 336 | -- @field Triangle reference to the `Triangle` class 337 | -- @field convexMultiplier multiplier heuristic for bounding triangle calculation. When small (~1) produces convex-hull, when large, produces concave hulls. Defaults to 1000. 338 | -- @field _VERSION the version of the current module 339 | local Delaunay = { 340 | Point = Point, 341 | Edge = Edge, 342 | Triangle = Triangle, 343 | convexMultiplier = 1e3, 344 | _VERSION = "0.1" 345 | } 346 | 347 | --- Triangulates a set of given vertices 348 | -- @param ... a `vargarg` list of objects of type `Point` 349 | -- @return a set of objects of type `Triangle` 350 | -- @usage 351 | -- local Delaunay = require 'Delaunay' 352 | -- local Point = Delaunay.Point 353 | -- local p1, p2, p3, p4 = Point(), Point(2,0), Point(1,1), Point(1,-1) 354 | -- local triangles = Delaunay.triangulate(p1, p2, p3, p4) 355 | -- for i = 1, #triangles do 356 | -- print(triangles[i]) 357 | -- end 358 | -- 359 | function Delaunay.triangulate(...) 360 | local vertices = {...} 361 | local nvertices = #vertices 362 | assert(nvertices > 2, "Cannot triangulate, needs more than 3 vertices") 363 | if nvertices == 3 then 364 | return {Triangle(unpack(vertices))} 365 | end 366 | 367 | local trmax = nvertices * 4 368 | 369 | local minX, minY = vertices[1].x, vertices[1].y 370 | local maxX, maxY = minX, minY 371 | 372 | for i = 1, #vertices do 373 | local vertex = vertices[i] 374 | vertex.id = i 375 | if vertex.x < minX then 376 | minX = vertex.x 377 | end 378 | if vertex.y < minY then 379 | minY = vertex.y 380 | end 381 | if vertex.x > maxX then 382 | maxX = vertex.x 383 | end 384 | if vertex.y > maxY then 385 | maxY = vertex.y 386 | end 387 | end 388 | 389 | local convex_mult = Delaunay.convexMultiplier 390 | local dx, dy = (maxX - minX) * convex_mult, (maxY - minY) * convex_mult 391 | local deltaMax = max(dx, dy) 392 | local midx, midy = (minX + maxX) * 0.5, (minY + maxY) * 0.5 393 | 394 | local p1 = Point(midx - 2 * deltaMax, midy - deltaMax) 395 | local p2 = Point(midx, midy + 2 * deltaMax) 396 | local p3 = Point(midx + 2 * deltaMax, midy - deltaMax) 397 | p1.id, p2.id, p3.id = nvertices + 1, nvertices + 2, nvertices + 3 398 | vertices[p1.id] = p1 399 | vertices[p2.id] = p2 400 | vertices[p3.id] = p3 401 | 402 | local triangles = {} 403 | triangles[#triangles + 1] = Triangle(vertices[nvertices + 1], vertices[nvertices + 2], vertices[nvertices + 3]) 404 | 405 | for i = 1, nvertices do 406 | local edges = {} 407 | local ntriangles = #triangles 408 | 409 | for j = #triangles, 1, -1 do 410 | local curTriangle = triangles[j] 411 | if curTriangle:inCircumCircle(vertices[i]) then 412 | edges[#edges + 1] = curTriangle.e1 413 | edges[#edges + 1] = curTriangle.e2 414 | edges[#edges + 1] = curTriangle.e3 415 | remove(triangles, j) 416 | end 417 | end 418 | 419 | for j = #edges - 1, 1, -1 do 420 | for k = #edges, j + 1, -1 do 421 | if edges[j] and edges[k] and edges[j]:same(edges[k]) then 422 | remove(edges, j) 423 | remove(edges, k - 1) 424 | end 425 | end 426 | end 427 | 428 | for j = 1, #edges do 429 | local n = #triangles 430 | assert(n <= trmax, "Generated more than needed triangles") 431 | triangles[n + 1] = Triangle(edges[j].p1, edges[j].p2, vertices[i]) 432 | end 433 | end 434 | 435 | for i = #triangles, 1, -1 do 436 | local triangle = triangles[i] 437 | if (triangle.p1.id > nvertices or triangle.p2.id > nvertices or triangle.p3.id > nvertices) then 438 | remove(triangles, i) 439 | end 440 | end 441 | 442 | for _ = 1, 3 do 443 | remove(vertices) 444 | end 445 | 446 | return triangles 447 | end 448 | 449 | return Delaunay 450 | -------------------------------------------------------------------------------- /src/app/utils/TableViewPro.lua: -------------------------------------------------------------------------------- 1 | local TableViewCell = class("TableViewCell", function() 2 | return cc.Node:create() 3 | end) 4 | 5 | function TableViewCell:ctor() 6 | self.index = 0 7 | end 8 | 9 | function TableViewCell:setIndex(index) 10 | self.index = index 11 | end 12 | 13 | function TableViewCell:getIndex(index) 14 | return self.index 15 | end 16 | 17 | function TableViewCell:reset() 18 | self.index = 0 19 | end 20 | 21 | 22 | 23 | local TableView = class("TableView", function() 24 | return ccui.ScrollView:create() 25 | end) 26 | 27 | local TableViewDirection = { 28 | none = 0, 29 | vertical = 1, 30 | horizontal = 2 31 | } 32 | 33 | local TableViewFuncType = { 34 | cellSize = "_cellSizeAtIndex", --获取cell size,返回width, height 35 | cellNum = "_numberOfCells", --获取cell数量 36 | cellLoad = "_loadCellAtIndex", --加载cell,必须返回一个cell 37 | cellUnload = "_unloadCellAtIndex", --卸载一个cell时触发 38 | viewScroll = "_scrollViewScroll" --tableview滚动时触发 39 | } 40 | 41 | local TableViewFillOrder = { 42 | topToBottom = 1, 43 | bottomToTop = 2, 44 | leftToRight = 3, 45 | rightToLeft = 4 46 | } 47 | 48 | function TableView:ctor(size) 49 | self._cellsPos = {} --记录每个cell的位置 50 | 51 | self._cellsUsed = {} --记录正在使用的cell 52 | self._cellsIndex = {} --记录正在使用的cell的index 53 | self._cellsFreed = {} --记录当前未使用的cell 54 | 55 | self.direction = TableViewDirection.none 56 | self.fillOrder = TableViewFillOrder.topToBottom 57 | 58 | self:addEventListener(function (self, type) 59 | if type >= 4 then 60 | self:_scrollViewDidScroll() 61 | self:_scrollViewScroll() 62 | end 63 | end) 64 | self:setBounceEnabled(true) 65 | 66 | self:setContentSize(size or cc.size(0, 0)) 67 | self:_updateCellsPosition() 68 | self:_updateContentSize() 69 | end 70 | 71 | --[[ 72 | @desc: 注册TableViewFuncType定义的方法 73 | author:Bogey 74 | time:2019-08-14 09:48:58 75 | --@type: 76 | --@func: 77 | @return: 78 | ]] 79 | function TableView:registerFunc(type, func) 80 | assert(self[type], "Invalid func type") 81 | self[type] = func 82 | end 83 | 84 | --[[ 85 | @desc: 获取index位置上的cell,可能为空 86 | author:Bogey 87 | time:2019-08-14 09:49:28 88 | --@index: 89 | @return: 90 | ]] 91 | function TableView:cellAtIndex(index) 92 | if self._cellsIndex[index] then 93 | for k,v in pairs(self._cellsUsed) do 94 | if v:getIndex() == index then 95 | return v, k 96 | end 97 | end 98 | end 99 | end 100 | 101 | --[[ 102 | @desc: 重新加载 103 | author:Bogey 104 | time:2019-08-13 18:32:31 105 | @return: 106 | ]] 107 | function TableView:reloadData() 108 | self:_correctFillOrder() 109 | self.direction = TableViewDirection.none -- 在_updateContentSize中会设置正确的方向,使内部容器回到初始位置 110 | 111 | local cell = table.remove(self._cellsUsed) 112 | while cell do 113 | self:_unloadCellAtIndex(cell:getIndex()) 114 | cell:setVisible(false) 115 | table.insert(self._cellsFreed, cell) 116 | cell:reset() 117 | cell = table.remove(self._cellsUsed) 118 | end 119 | self._cellsUsed = {} 120 | self._cellsIndex = {} 121 | 122 | self:_updateCellsPosition() 123 | self:_updateContentSize() 124 | 125 | if self:_numberOfCells() > 0 then 126 | self:_scrollViewDidScroll() 127 | end 128 | end 129 | 130 | --[[ 131 | @desc: 在尽量不改变位置的情况下重新加载 132 | author:Bogey 133 | time:2019-08-13 18:32:45 134 | @return: 135 | ]] 136 | function TableView:reloadDataInPos() 137 | self:_correctFillOrder() 138 | local baseSize = self:getContentSize() 139 | local x, y = self:getInnerContainer():getPosition() 140 | local beforeSize = self:getInnerContainerSize() 141 | 142 | local cell = table.remove(self._cellsUsed) 143 | while cell do 144 | self:_unloadCellAtIndex(cell:getIndex()) 145 | cell:setVisible(false) 146 | table.insert(self._cellsFreed, cell) 147 | cell:reset() 148 | cell = table.remove(self._cellsUsed) 149 | end 150 | self._cellsUsed = {} 151 | self._cellsIndex = {} 152 | 153 | self:_updateCellsPosition() 154 | self:_updateContentSize() 155 | 156 | local afterSize = self:getInnerContainerSize() 157 | if self.fillOrder == TableViewFillOrder.topToBottom then 158 | y = math.max(math.min(0, beforeSize.height - afterSize.height + y), baseSize.height - afterSize.height) 159 | elseif self.fillOrder == TableViewFillOrder.bottomToTop then 160 | y = math.max(math.min(0, y), baseSize.height - afterSize.height) 161 | end 162 | if self.fillOrder == TableViewFillOrder.rightToLeft then 163 | x = math.max(math.min(0, beforeSize.width - afterSize.width + x), baseSize.width - afterSize.width) 164 | elseif self.fillOrder == TableViewFillOrder.leftToRight then 165 | x = math.max(math.min(0, x), baseSize.width - afterSize.width) 166 | end 167 | self:getInnerContainer():setPosition(cc.p(x, y)) 168 | 169 | if self:_numberOfCells() > 0 then 170 | self:_scrollViewDidScroll() 171 | end 172 | end 173 | 174 | --[[ 175 | @desc: 返回一个闲置的cell,可能为空 176 | author:Bogey 177 | time:2019-08-13 18:33:26 178 | @return: 179 | ]] 180 | function TableView:dequeueCell() 181 | return table.remove(self._cellsFreed) 182 | end 183 | 184 | --[[ 185 | @desc: 设置填充方向 186 | author:Bogey 187 | time:2019-08-13 18:33:55 188 | --@order: 189 | @return: 190 | ]] 191 | function TableView:setFillOrder(order) 192 | if self.fillOrder ~= order then 193 | self.fillOrder = order 194 | if #self._cellsUsed > 0 then 195 | self:reloadData() 196 | end 197 | end 198 | end 199 | 200 | --[[ 201 | @desc: 根据index更新cell 202 | author:Bogey 203 | time:2019-08-13 18:34:37 204 | --@index: 205 | @return: 206 | ]] 207 | function TableView:updateCellAtIndex(index) 208 | if index <= 0 then 209 | return 210 | end 211 | local cellsCount = self:_numberOfCells() 212 | if cellsCount == 0 or index > cellsCount then 213 | return 214 | end 215 | local cell, newIndex = self:cellAtIndex(index) 216 | if cell then 217 | self:_moveCellOutOfSight(cell, newIndex) 218 | end 219 | cell = self:_loadCellAtIndex(index) 220 | self:_setIndexForCell(index, cell) 221 | self:_addCellIfNecessary(cell) 222 | end 223 | 224 | --[[ 225 | @desc: 在index位置插入 226 | author:Bogey 227 | time:2019-08-13 18:35:04 228 | --@index: 229 | @return: 230 | ]] 231 | function TableView:insertCellAtIndex(index) 232 | if index <= 0 then 233 | return 234 | end 235 | local cellsCount = self:_numberOfCells() 236 | if cellsCount == 0 or index > cellsCount then 237 | return 238 | end 239 | local cell, newIndex = self:cellAtIndex(index) 240 | if cell then 241 | for i = newIndex, #self._cellsUsed do 242 | cell = self._cellsUsed[i] 243 | self:_setIndexForCell(cell:getIndex() + 1, cell) 244 | self._cellsIndex[cell:getIndex()] = true 245 | end 246 | end 247 | 248 | cell = self:_loadCellAtIndex(index) 249 | self:_setIndexForCell(index, cell) 250 | self:_addCellIfNecessary(cell) 251 | 252 | self:_updateCellsPosition() 253 | self:_updateContentSize() 254 | end 255 | 256 | --[[ 257 | @desc: 移除index位置的cell 258 | author:Bogey 259 | time:2019-08-13 18:35:22 260 | --@index: 261 | @return: 262 | ]] 263 | function TableView:removeCellAtIndex(index) 264 | if index <= 0 then 265 | return 266 | end 267 | local cellsCount = self:_numberOfCells() 268 | if cellsCount == 0 or index > cellsCount then 269 | return 270 | end 271 | local cell, newIndex = self:cellAtIndex(index) 272 | if cell then 273 | self:_moveCellOutOfSight(cell, newIndex) 274 | self:_updateCellsPosition() 275 | local cellSize = #self._cellsUsed 276 | for i = cellSize, newIndex, -1 do 277 | cell = self._cellsUsed[i] 278 | if i == cellSize then 279 | self._cellsIndex[cell:getIndex()] = nil 280 | end 281 | self:_setIndexForCell(cell:getIndex() - 1, cell) 282 | self._cellsIndex[cell:getIndex()] = true 283 | end 284 | end 285 | end 286 | -------------------------------------------------- 287 | 288 | function TableView:_correctFillOrder() 289 | local dir = self:getDirection() 290 | self.direction = dir 291 | self.fillOrder = ((self.fillOrder - 1) % 2 + 1) + 2 * (self.direction - 1) 292 | end 293 | 294 | function TableView:_scrollViewDidScroll() 295 | local cellsCount = self:_numberOfCells() 296 | if cellsCount <= 0 then 297 | return 298 | end 299 | 300 | local baseSize = self:getContentSize() 301 | if self._isUsedCellsDirty then 302 | self._isUsedCellsDirty = false 303 | table.sort(self._cellsUsed, function(a, b) 304 | return a:getIndex() < b:getIndex() 305 | end) 306 | end 307 | 308 | local startIdx, endIdx, idx, maxIdx = 0, 0, 0, 0 309 | local offset = cc.p(self:getInnerContainer():getPosition()) 310 | offset = cc.p(-offset.x, -offset.y) 311 | maxIdx = math.max(cellsCount, 1) 312 | if self.fillOrder == TableViewFillOrder.topToBottom then 313 | offset.y = offset.y + baseSize.height 314 | elseif self.fillOrder == TableViewFillOrder.rightToLeft then 315 | offset.x = offset.x + baseSize.width 316 | end 317 | startIdx = self:_indexFromOffset(clone(offset)) or maxIdx 318 | 319 | if self.fillOrder == TableViewFillOrder.topToBottom then 320 | offset.y = offset.y - baseSize.height 321 | elseif self.fillOrder == TableViewFillOrder.bottomToTop then 322 | offset.y = offset.y + baseSize.height 323 | end 324 | if self.fillOrder == TableViewFillOrder.leftToRight then 325 | offset.x = offset.x + baseSize.width 326 | elseif self.fillOrder == TableViewFillOrder.rightToLeft then 327 | offset.x = offset.x - baseSize.width 328 | end 329 | endIdx = self:_indexFromOffset(clone(offset)) or maxIdx 330 | 331 | if #self._cellsUsed > 0 then --移除顶部节点 332 | local cell = self._cellsUsed[1] 333 | idx = cell:getIndex() 334 | while idx < startIdx do 335 | self:_moveCellOutOfSight(cell, 1) 336 | if #self._cellsUsed > 0 then 337 | cell = self._cellsUsed[1] 338 | idx = cell:getIndex() 339 | else 340 | break 341 | end 342 | end 343 | end 344 | 345 | if #self._cellsUsed > 0 then --移除底部节点 346 | local cell = self._cellsUsed[#self._cellsUsed] 347 | idx = cell:getIndex() 348 | while idx <= maxIdx and idx > endIdx do 349 | self:_moveCellOutOfSight(cell, #self._cellsUsed) 350 | if #self._cellsUsed > 0 then 351 | cell = self._cellsUsed[#self._cellsUsed] 352 | idx = cell:getIndex() 353 | else 354 | break 355 | end 356 | end 357 | end 358 | 359 | for i = startIdx, endIdx do --更新节点 360 | if not self._cellsIndex[i] then 361 | self:updateCellAtIndex(i) 362 | end 363 | end 364 | end 365 | 366 | function TableView:_setIndexForCell(index, cell) 367 | cell:setAnchorPoint(cc.p(0, 0)) 368 | cell:setPosition(self:_offsetFromIndex(index)) 369 | cell:setIndex(index) 370 | end 371 | 372 | function TableView:_moveCellOutOfSight(cell, index) 373 | table.insert(self._cellsFreed, table.remove(self._cellsUsed, index)) 374 | self._cellsIndex[cell:getIndex()] = nil 375 | self._isUsedCellsDirty = true 376 | self:_unloadCellAtIndex(cell:getIndex()) 377 | 378 | cell:reset() 379 | cell:setVisible(false) 380 | end 381 | 382 | function TableView:_addCellIfNecessary(cell) 383 | cell:setVisible(true) 384 | if cell:getParent() ~= self:getInnerContainer() then 385 | self:addChild(cell) 386 | end 387 | table.insert(self._cellsUsed, cell) 388 | self._cellsIndex[cell:getIndex()] = true 389 | self._isUsedCellsDirty = true 390 | end 391 | 392 | function TableView:_indexFromOffset(offset) 393 | local size = self:getInnerContainerSize() 394 | if self.fillOrder == TableViewFillOrder.topToBottom then 395 | offset.y = size.height - offset.y 396 | elseif self.fillOrder == TableViewFillOrder.rightToLeft then 397 | offset.x = size.width - offset.x 398 | end 399 | local search 400 | if self.direction == TableViewDirection.horizontal then 401 | search = offset.x 402 | else 403 | search = offset.y 404 | end 405 | 406 | local low = 1 407 | local high = self:_numberOfCells() 408 | while high >= low do 409 | local index = math.floor(low + (high - low) / 2) 410 | local cellSatrt = self._cellsPos[index] 411 | local cellEnd = self._cellsPos[index + 1] 412 | if search >= cellSatrt and search <= cellEnd then 413 | return index 414 | elseif search < cellSatrt then 415 | high = index - 1 416 | else 417 | low = index + 1 418 | end 419 | end 420 | if low <= 1 then 421 | return 1 422 | end 423 | end 424 | 425 | function TableView:_offsetFromIndex(index) 426 | local offset 427 | if self.direction == TableViewDirection.horizontal then 428 | offset = cc.p(self._cellsPos[index], 0) 429 | else 430 | offset = cc.p(0, self._cellsPos[index]) 431 | end 432 | local cellSize = cc.size(self:_cellSizeAtIndex(index)) 433 | if self.fillOrder == TableViewFillOrder.topToBottom then 434 | offset.y = self:getInnerContainerSize().height - offset.y - cellSize.height 435 | elseif self.fillOrder == TableViewFillOrder.rightToLeft then 436 | offset.x = self:getInnerContainerSize().width - offset.x - cellSize.width 437 | end 438 | return offset 439 | end 440 | 441 | function TableView:_updateContentSize() 442 | local baseSize = self:getContentSize() 443 | local size = self:getInnerContainerSize() 444 | local cellsCount = self:_numberOfCells() 445 | local dir = self:getDirection() 446 | 447 | if cellsCount > 0 then 448 | local maxPos = self._cellsPos[#self._cellsPos] 449 | if dir == TableViewDirection.horizontal then 450 | size.width = math.max(baseSize.width, maxPos) 451 | else 452 | size.height = math.max(baseSize.height, maxPos) 453 | end 454 | end 455 | self:getInnerContainer():setContentSize(size) 456 | self:_setInnerContainerInitPos() 457 | end 458 | 459 | function TableView:_setInnerContainerInitPos() 460 | local dir = self:getDirection() 461 | if self.direction ~= dir then 462 | if dir == TableViewDirection.horizontal then 463 | if self.fillOrder == TableViewFillOrder.leftToRight then 464 | self:getInnerContainer():setPosition(cc.p(0, 0)) 465 | elseif self.fillOrder == TableViewFillOrder.rightToLeft then 466 | self:getInnerContainer():setPosition(cc.p(self:_getMinContainerOffset().x, 0)) 467 | end 468 | else 469 | if self.fillOrder == TableViewFillOrder.topToBottom then 470 | self:getInnerContainer():setPosition(cc.p(0, self:_getMinContainerOffset().y)) 471 | elseif self.fillOrder == TableViewFillOrder.bottomToTop then 472 | self:getInnerContainer():setPosition(cc.p(0, 0)) 473 | end 474 | end 475 | self.direction = dir 476 | end 477 | end 478 | 479 | function TableView:_updateCellsPosition() 480 | local cellsCount = self:_numberOfCells() 481 | self._cellsPos = {} 482 | 483 | if cellsCount > 0 then 484 | local curPos = 0 485 | local cellSize 486 | local dir = self:getDirection() 487 | for i = 1, cellsCount do 488 | table.insert(self._cellsPos, curPos) 489 | cellSize = cc.size(self:_cellSizeAtIndex(i)) 490 | if dir == TableViewDirection.horizontal then 491 | curPos = curPos + cellSize.width 492 | else 493 | curPos = curPos + cellSize.height 494 | end 495 | end 496 | table.insert(self._cellsPos, curPos) --多添加一个可以用来获取最后一个cell的右侧或者底部 497 | end 498 | end 499 | 500 | function TableView:_getMinContainerOffset() 501 | local con = self:getInnerContainer() 502 | local ap = con:getAnchorPoint() 503 | local conSize = con:getContentSize() 504 | local baseSize = self:getContentSize() 505 | return cc.p(baseSize.width - (1 - ap.x) * conSize.width, baseSize.height - (1 - ap.y) * conSize.height) 506 | end 507 | 508 | -------------------------------------------------- 509 | function TableView:_cellSizeAtIndex(index) 510 | return 0, 0 511 | end 512 | 513 | function TableView:_numberOfCells() 514 | return 0 515 | end 516 | 517 | function TableView:_loadCellAtIndex(index) 518 | local cell = self:dequeueCell() 519 | if not cell then 520 | return TableViewCell.new() 521 | end 522 | return cell 523 | end 524 | 525 | function TableView:_unloadCellAtIndex(index) 526 | end 527 | 528 | function TableView:_scrollViewScroll() 529 | end 530 | 531 | -------------------------------------------------- 532 | function TableView.attachTo(scrollView) 533 | assert(tolua.type(scrollView) == "ccui.ScrollView") 534 | scrollView._addCellIfNecessary = TableView._addCellIfNecessary 535 | scrollView._correctFillOrder = TableView._correctFillOrder 536 | scrollView._getMinContainerOffset = TableView._getMinContainerOffset 537 | scrollView._indexFromOffset = TableView._indexFromOffset 538 | scrollView._moveCellOutOfSight = TableView._moveCellOutOfSight 539 | scrollView._offsetFromIndex = TableView._offsetFromIndex 540 | scrollView._scrollViewDidScroll = TableView._scrollViewDidScroll 541 | scrollView._setIndexForCell = TableView._setIndexForCell 542 | scrollView._setInnerContainerInitPos = TableView._setInnerContainerInitPos 543 | scrollView._updateCellsPosition = TableView._updateCellsPosition 544 | scrollView._updateContentSize = TableView._updateContentSize 545 | 546 | scrollView._cellSizeAtIndex = TableView._cellSizeAtIndex 547 | scrollView._numberOfCells = TableView._numberOfCells 548 | scrollView._loadCellAtIndex = TableView._loadCellAtIndex 549 | scrollView._unloadCellAtIndex = TableView._unloadCellAtIndex 550 | scrollView._scrollViewScroll = TableView._scrollViewScroll 551 | 552 | scrollView.ctor = TableView.ctor 553 | scrollView.cellAtIndex = TableView.cellAtIndex 554 | scrollView.dequeueCell = TableView.dequeueCell 555 | scrollView.registerFunc = TableView.registerFunc 556 | scrollView.reloadData = TableView.reloadData 557 | scrollView.reloadDataInPos = TableView.reloadDataInPos 558 | scrollView.setFillOrder = TableView.setFillOrder 559 | scrollView.updateCellAtIndex = TableView.updateCellAtIndex 560 | 561 | scrollView:ctor(scrollView:getContentSize()) 562 | return scrollView 563 | end 564 | 565 | cc.TableView = TableView 566 | cc.TableViewCell = TableViewCell 567 | cc.TableViewFillOrder = TableViewFillOrder 568 | cc.TableViewDirection = TableViewDirection 569 | cc.TableViewFuncType = TableViewFuncType 570 | --------------------------------------------------------------------------------