├── .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 |
--------------------------------------------------------------------------------