Client Upload Part 2

RD Core / Commit #5

Commit Client Upload Part 2

Alex Alex committed 19/10/2026 00:06 main Patch
1,244 files +271,215 -0
A RD_Client/modules/corelib/outputmessage.lua +69-0 View file
@@ -0,0 +1,69 @@
1+function OutputMessage:addData(data)
2+ if type(data) == 'boolean' then
3+ self:addU8(NetworkMessageTypes.Boolean)
4+ self:addU8(booleantonumber(data))
5+ elseif type(data) == 'number' then
6+ if math.isu8(data) then
7+ self:addU8(NetworkMessageTypes.U8)
8+ self:addU8(data)
9+ elseif math.isu16(data) then
10+ self:addU8(NetworkMessageTypes.U16)
11+ self:addU16(data)
12+ elseif math.isu32(data) then
13+ self:addU8(NetworkMessageTypes.U32)
14+ self:addU32(data)
15+ elseif math.isu64(data) then
16+ self:addU8(NetworkMessageTypes.U64)
17+ self:addU64(data)
18+ else -- negative or non integer numbers
19+ self:addU8(NetworkMessageTypes.NumberString)
20+ self:addString(tostring(data))
21+ end
22+ elseif type(data) == 'string' then
23+ self:addU8(NetworkMessageTypes.String)
24+ self:addString(data)
25+ elseif type(data) == 'table' then
26+ self:addU8(NetworkMessageTypes.Table)
27+ self:addTable(data)
28+ else
29+ perror('Invalid data type ' .. type(data))
30+ end
31+end
32+
33+function OutputMessage:addTable(data)
34+ local size = 0
35+
36+ -- reserve for size (should be addData, find a way to use it further)
37+ local sizePos = self:getWritePos()
38+ self:addU16(size)
39+ local sizeSize = self:getWritePos() - sizePos
40+
41+ -- add values
42+ for key,value in pairs(data) do
43+ self:addData(key)
44+ self:addData(value)
45+ size = size + 1
46+ end
47+
48+ -- write size
49+ local currentPos = self:getWritePos()
50+ self:setWritePos(sizePos)
51+ self:addU16(size)
52+
53+ -- fix msg size and go back to end
54+ self:setMessageSize(self:getMessageSize() - sizeSize)
55+ self:setWritePos(currentPos)
56+end
57+
58+function OutputMessage:addColor(color)
59+ self:addU8(color.r)
60+ self:addU8(color.g)
61+ self:addU8(color.b)
62+ self:addU8(color.a)
63+end
64+
65+function OutputMessage:addPosition(position)
66+ self:addU16(position.x)
67+ self:addU16(position.y)
68+ self:addU8(position.z)
69+end
A RD_Client/modules/corelib/settings.lua +3-0 View file
@@ -0,0 +1,3 @@
1+g_settings = makesingleton(g_configs.getSettings())
2+
3+-- Reserved for future functionality
A RD_Client/modules/corelib/string.lua +59-0 View file
@@ -0,0 +1,59 @@
1+-- @docclass string
2+
3+function string:split(delim)
4+ local start = 1
5+ local results = {}
6+ while true do
7+ local pos = string.find(self, delim, start, true)
8+ if not pos then
9+ break
10+ end
11+ table.insert(results, string.sub(self, start, pos-1))
12+ start = pos + string.len(delim)
13+ end
14+ table.insert(results, string.sub(self, start))
15+ table.removevalue(results, '')
16+ return results
17+end
18+
19+function string:starts(start)
20+ return string.sub(self, 1, #start) == start
21+end
22+
23+function string:ends(test)
24+ return test =='' or string.sub(self,-string.len(test)) == test
25+end
26+
27+function string:trim()
28+ return string.match(self, '^%s*(.*%S)') or ''
29+end
30+
31+function string:explode(sep, limit)
32+ if type(sep) ~= 'string' or tostring(self):len() == 0 or sep:len() == 0 then
33+ return {}
34+ end
35+
36+ local i, pos, tmp, t = 0, 1, "", {}
37+ for s, e in function() return string.find(self, sep, pos) end do
38+ tmp = self:sub(pos, s - 1):trim()
39+ table.insert(t, tmp)
40+ pos = e + 1
41+
42+ i = i + 1
43+ if limit ~= nil and i == limit then
44+ break
45+ end
46+ end
47+
48+ tmp = self:sub(pos):trim()
49+ table.insert(t, tmp)
50+ return t
51+end
52+
53+function string:contains(str, checkCase, start, plain)
54+ if(not checkCase) then
55+ self = self:lower()
56+ str = str:lower()
57+ end
58+ return string.find(self, str, start and start or 1, plain == nil and true or false)
59+end
A RD_Client/modules/corelib/struct.lua +173-0 View file
@@ -0,0 +1,173 @@
1+Struct = {}
2+
3+function Struct.pack(format, ...)
4+ local stream = {}
5+ local vars = {...}
6+ local endianness = true
7+
8+ for i = 1, format:len() do
9+ local opt = format:sub(i, i)
10+
11+ if opt == '>' then
12+ endianness = false
13+ elseif opt:find('[bBhHiIlL]') then
14+ local n = opt:find('[hH]') and 2 or opt:find('[iI]') and 4 or opt:find('[lL]') and 8 or 1
15+ local val = tonumber(table.remove(vars, 1))
16+
17+ if val < 0 then
18+ val = val + 2 ^ (n * 8 - 1)
19+ end
20+
21+ local bytes = {}
22+ for j = 1, n do
23+ table.insert(bytes, string.char(val % (2 ^ 8)))
24+ val = math.floor(val / (2 ^ 8))
25+ end
26+
27+ if not endianness then
28+ table.insert(stream, string.reverse(table.concat(bytes)))
29+ else
30+ table.insert(stream, table.concat(bytes))
31+ end
32+ elseif opt:find('[fd]') then
33+ local val = tonumber(table.remove(vars, 1))
34+ local sign = 0
35+
36+ if val < 0 then
37+ sign = 1
38+ val = -val
39+ end
40+
41+ local mantissa, exponent = math.frexp(val)
42+ if val == 0 then
43+ mantissa = 0
44+ exponent = 0
45+ else
46+ mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, (opt == 'd') and 53 or 24)
47+ exponent = exponent + ((opt == 'd') and 1022 or 126)
48+ end
49+
50+ local bytes = {}
51+ if opt == 'd' then
52+ val = mantissa
53+ for i = 1, 6 do
54+ table.insert(bytes, string.char(math.floor(val) % (2 ^ 8)))
55+ val = math.floor(val / (2 ^ 8))
56+ end
57+ else
58+ table.insert(bytes, string.char(math.floor(mantissa) % (2 ^ 8)))
59+ val = math.floor(mantissa / (2 ^ 8))
60+ table.insert(bytes, string.char(math.floor(val) % (2 ^ 8)))
61+ val = math.floor(val / (2 ^ 8))
62+ end
63+
64+ table.insert(bytes, string.char(math.floor(exponent * ((opt == 'd') and 16 or 128) + val) % (2 ^ 8)))
65+ val = math.floor((exponent * ((opt == 'd') and 16 or 128) + val) / (2 ^ 8))
66+ table.insert(bytes, string.char(math.floor(sign * 128 + val) % (2 ^ 8)))
67+ val = math.floor((sign * 128 + val) / (2 ^ 8))
68+
69+ if not endianness then
70+ table.insert(stream, string.reverse(table.concat(bytes)))
71+ else
72+ table.insert(stream, table.concat(bytes))
73+ end
74+ elseif opt == 's' then
75+ table.insert(stream, tostring(table.remove(vars, 1)))
76+ table.insert(stream, string.char(0))
77+ elseif opt == 'c' then
78+ local n = format:sub(i + 1):match('%d+')
79+ local length = tonumber(n)
80+
81+ if length > 0 then
82+ local str = tostring(table.remove(vars, 1))
83+ if length - str:len() > 0 then
84+ str = str .. string.rep(' ', length - str:len())
85+ end
86+ table.insert(stream, str:sub(1, length))
87+ end
88+ i = i + n:len()
89+ end
90+ end
91+
92+ return table.concat(stream)
93+end
94+
95+function Struct.unpack(format, stream)
96+ local vars = {}
97+ local iterator = 1
98+ local endianness = true
99+
100+ for i = 1, format:len() do
101+ local opt = format:sub(i, i)
102+
103+ if opt == '>' then
104+ endianness = false
105+ elseif opt:find('[bBhHiIlL]') then
106+ local n = opt:find('[hH]') and 2 or opt:find('[iI]') and 4 or opt:find('[lL]') and 8 or 1
107+ local signed = opt:lower() == opt
108+
109+ local val = 0
110+ for j = 1, n do
111+ local byte = string.byte(stream:sub(iterator, iterator))
112+ if endianness then
113+ val = val + byte * (2 ^ ((j - 1) * 8))
114+ else
115+ val = val + byte * (2 ^ ((n - j) * 8))
116+ end
117+ iterator = iterator + 1
118+ end
119+
120+ if signed then
121+ val = val - 2 ^ (n * 8 - 1)
122+ end
123+
124+ table.insert(vars, val)
125+ elseif opt:find('[fd]') then
126+ local n = (opt == 'd') and 8 or 4
127+ local x = stream:sub(iterator, iterator + n - 1)
128+ iterator = iterator + n
129+
130+ if not endianness then
131+ x = string.reverse(x)
132+ end
133+
134+ local sign = 1
135+ local mantissa = string.byte(x, (opt == 'd') and 7 or 3) % ((opt == 'd') and 16 or 128)
136+ for i = n - 2, 1, -1 do
137+ mantissa = mantissa * (2 ^ 8) + string.byte(x, i)
138+ end
139+
140+ if string.byte(x, n) > 127 then
141+ sign = -1
142+ end
143+
144+ local exponent = (string.byte(x, n) % 128) * ((opt == 'd') and 16 or 2) + math.floor(string.byte(x, n - 1) / ((opt == 'd') and 16 or 128))
145+ if exponent == 0 then
146+ table.insert(vars, 0.0)
147+ else
148+ mantissa = (math.ldexp(mantissa, (opt == 'd') and -52 or -23) + 1) * sign
149+ table.insert(vars, math.ldexp(mantissa, exponent - ((opt == 'd') and 1023 or 127)))
150+ end
151+ elseif opt == 's' then
152+ local bytes = {}
153+ for j = iterator, stream:len() do
154+ if stream:sub(j, j) == string.char(0) then
155+ break
156+ end
157+
158+ table.insert(bytes, stream:sub(j, j))
159+ end
160+
161+ local str = table.concat(bytes)
162+ iterator = iterator + str:len() + 1
163+ table.insert(vars, str)
164+ elseif opt == 'c' then
165+ local n = format:sub(i + 1):match('%d+')
166+ table.insert(vars, stream:sub(iterator, iterator + tonumber(n)))
167+ iterator = iterator + tonumber(n)
168+ i = i + n:len()
169+ end
170+ end
171+
172+ return unpack(vars)
173+end
A RD_Client/modules/corelib/table.lua +288-0 View file
@@ -0,0 +1,288 @@
1+-- @docclass table
2+
3+function table.dump(t, depth)
4+ if not depth then depth = 0 end
5+ for k,v in pairs(t) do
6+ str = (' '):rep(depth * 2) .. k .. ': '
7+ if type(v) ~= "table" then
8+ print(str .. tostring(v))
9+ else
10+ print(str)
11+ table.dump(v, depth+1)
12+ end
13+ end
14+end
15+
16+function table.clear(t)
17+ for k,v in pairs(t) do
18+ t[k] = nil
19+ end
20+end
21+
22+function table.copy(t)
23+ local res = {}
24+ for k,v in pairs(t) do
25+ res[k] = v
26+ end
27+ return res
28+end
29+
30+function table.recursivecopy(t)
31+ local res = {}
32+ for k,v in pairs(t) do
33+ if type(v) == "table" then
34+ res[k] = table.recursivecopy(v)
35+ else
36+ res[k] = v
37+ end
38+ end
39+ return res
40+end
41+
42+function table.selectivecopy(t, keys)
43+ local res = { }
44+ for i,v in ipairs(keys) do
45+ res[v] = t[v]
46+ end
47+ return res
48+end
49+
50+function table.merge(t, src)
51+ for k,v in pairs(src) do
52+ t[k] = v
53+ end
54+end
55+
56+function table.find(t, value, lowercase)
57+ for k,v in pairs(t) do
58+ if lowercase and type(value) == 'string' and type(v) == 'string' then
59+ if v:lower() == value:lower() then return k end
60+ end
61+ if v == value then return k end
62+ end
63+end
64+
65+function table.findbykey(t, key, lowercase)
66+ for k,v in pairs(t) do
67+ if lowercase and type(key) == 'string' and type(k) == 'string' then
68+ if k:lower() == key:lower() then return v end
69+ end
70+ if k == key then return v end
71+ end
72+end
73+
74+function table.contains(t, value, lowercase)
75+ return table.find(t, value, lowercase) ~= nil
76+end
77+
78+function table.findkey(t, key)
79+ if t and type(t) == 'table' then
80+ for k,v in pairs(t) do
81+ if k == key then return k end
82+ end
83+ end
84+end
85+
86+function table.haskey(t, key)
87+ return table.findkey(t, key) ~= nil
88+end
89+
90+function table.removevalue(t, value)
91+ for k,v in pairs(t) do
92+ if v == value then
93+ table.remove(t, k)
94+ return true
95+ end
96+ end
97+ return false
98+end
99+
100+function table.popvalue(value)
101+ local index = nil
102+ for k,v in pairs(t) do
103+ if v == value or not value then
104+ index = k
105+ end
106+ end
107+ if index then
108+ table.remove(t, index)
109+ return true
110+ end
111+ return false
112+end
113+
114+function table.compare(t, other)
115+ if #t ~= #other then return false end
116+ for k,v in pairs(t) do
117+ if v ~= other[k] then return false end
118+ end
119+ return true
120+end
121+
122+function table.empty(t)
123+ if t and type(t) == 'table' then
124+ return next(t) == nil
125+ end
126+ return true
127+end
128+
129+function table.permute(t, n, count)
130+ n = n or #t
131+ for i=1,count or n do
132+ local j = math.random(i, n)
133+ t[i], t[j] = t[j], t[i]
134+ end
135+ return t
136+end
137+
138+function table.findbyfield(t, fieldname, fieldvalue)
139+ for _i,subt in pairs(t) do
140+ if subt[fieldname] == fieldvalue then
141+ return subt
142+ end
143+ end
144+ return nil
145+end
146+
147+function table.size(t)
148+ local size = 0
149+ for i, n in pairs(t) do
150+ size = size + 1
151+ end
152+
153+ return size
154+end
155+
156+function table.tostring(t)
157+ local maxn = #t
158+ local str = ""
159+ for k,v in pairs(t) do
160+ v = tostring(v)
161+ if k == maxn and k ~= 1 then
162+ str = str .. " and " .. v
163+ elseif maxn > 1 and k ~= 1 then
164+ str = str .. ", " .. v
165+ else
166+ str = str .. " " .. v
167+ end
168+ end
169+ return str
170+end
171+
172+function table.collect(t, func)
173+ local res = {}
174+ for k,v in pairs(t) do
175+ local a,b = func(k,v)
176+ if a and b then
177+ res[a] = b
178+ elseif a ~= nil then
179+ table.insert(res,a)
180+ end
181+ end
182+ return res
183+end
184+
185+function table.equals(t, comp)
186+ if type(t) == "table" and type(comp) == "table" then
187+ for k,v in pairs(t) do
188+ if v ~= comp[k] then return false end
189+ end
190+ end
191+ return true
192+end
193+
194+function table.equal(t1,t2,ignore_mt)
195+ local ty1 = type(t1)
196+ local ty2 = type(t2)
197+ if ty1 ~= ty2 then return false end
198+ -- non-table types can be directly compared
199+ if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end
200+ -- as well as tables which have the metamethod __eq
201+ local mt = getmetatable(t1)
202+ if not ignore_mt and mt and mt.__eq then return t1 == t2 end
203+ for k1,v1 in pairs(t1) do
204+ local v2 = t2[k1]
205+ if v2 == nil or not table.equal(v1,v2) then return false end
206+ end
207+ for k2,v2 in pairs(t2) do
208+ local v1 = t1[k2]
209+ if v1 == nil or not table.equal(v1,v2) then return false end
210+ end
211+ return true
212+end
213+
214+function table.isList(t)
215+ local size = #t
216+ return table.size(t) == size and size > 0
217+end
218+
219+function table.isStringList(t)
220+ if not table.isList(t) then return false end
221+ for k,v in ipairs(t) do
222+ if type(v) ~= 'string' then
223+ return false
224+ end
225+ end
226+ return true
227+end
228+
229+function table.isStringPairList(t)
230+ if not table.isList(t) then return false end
231+ for k,v in ipairs(t) do
232+ if type(v) ~= 'table' or #v ~= 2 or type(v[1]) ~= 'string' or type(v[2]) ~= 'string' then
233+ return false
234+ end
235+ end
236+ return true
237+end
238+
239+function table.encodeStringPairList(t)
240+ local ret = ""
241+ for k,v in ipairs(t) do
242+ if v[2]:find("\n") then
243+ ret = ret .. v[1] .. ":[[\n" .. v[2] .. "\n]]\n"
244+ else
245+ ret = ret .. v[1] .. ":" .. v[2] .. "\n"
246+ end
247+ end
248+ return ret
249+end
250+
251+function table.decodeStringPairList(l)
252+ local ret = {}
253+ local r = regexMatch(l, "(?:^|\\n)([^:^\n]{1,20}):?(.*)(?:$|\\n)")
254+ local multiline = ""
255+ local multilineKey = ""
256+ local multilineActive = false
257+ for k,v in ipairs(r) do
258+ if multilineActive then
259+ local endPos = v[1]:find("%]%]")
260+ if endPos then
261+ if endPos > 1 then
262+ table.insert(ret, {multilineKey, multiline .. "\n" .. v[1]:sub(1, endPos - 1)})
263+ else
264+ table.insert(ret, {multilineKey, multiline})
265+ end
266+ multilineActive = false
267+ multiline = ""
268+ multilineKey = ""
269+ else
270+ if multiline:len() == 0 then
271+ multiline = v[1]
272+ else
273+ multiline = multiline .. "\n" .. v[1]
274+ end
275+ end
276+ else
277+ local bracketPos = v[3]:find("%[%[")
278+ if bracketPos == 1 then -- multiline begin
279+ multiline = v[3]:sub(bracketPos + 2)
280+ multilineActive = true
281+ multilineKey = v[2]
282+ elseif v[2]:len() > 0 and v[3]:len() > 0 then
283+ table.insert(ret, {v[2], v[3]})
284+ end
285+ end
286+ end
287+ return ret
288+end
A RD_Client/modules/corelib/ui/effects.lua +69-0 View file
@@ -0,0 +1,69 @@
1+-- @docclass
2+g_effects = {}
3+
4+function g_effects.fadeIn(widget, time, elapsed)
5+ if not elapsed then elapsed = 0 end
6+ if not time then time = 300 end
7+ widget:setOpacity(math.min(elapsed/time, 1))
8+ removeEvent(widget.fadeEvent)
9+ if elapsed < time then
10+ removeEvent(widget.fadeEvent)
11+ widget.fadeEvent = scheduleEvent(function()
12+ g_effects.fadeIn(widget, time, elapsed + 30)
13+ end, 30)
14+ else
15+ widget.fadeEvent = nil
16+ end
17+end
18+
19+function g_effects.fadeOut(widget, time, elapsed)
20+ if not elapsed then elapsed = 0 end
21+ if not time then time = 300 end
22+ elapsed = math.max((1 - widget:getOpacity()) * time, elapsed)
23+ removeEvent(widget.fadeEvent)
24+ widget:setOpacity(math.max((time - elapsed)/time, 0))
25+ if elapsed < time then
26+ widget.fadeEvent = scheduleEvent(function()
27+ g_effects.fadeOut(widget, time, elapsed + 30)
28+ end, 30)
29+ else
30+ widget.fadeEvent = nil
31+ end
32+end
33+
34+function g_effects.cancelFade(widget)
35+ if widget then
36+ removeEvent(widget.fadeEvent)
37+ widget.fadeEvent = nil
38+ end
39+end
40+
41+function g_effects.startBlink(widget, duration, interval, clickCancel)
42+ duration = duration or 0 -- until stop is called
43+ interval = interval or 500
44+ clickCancel = clickCancel or true
45+
46+ removeEvent(widget.blinkEvent)
47+ removeEvent(widget.blinkStopEvent)
48+
49+ widget.blinkEvent = cycleEvent(function()
50+ widget:setOn(not widget:isOn())
51+ end, interval)
52+
53+ if duration > 0 then
54+ widget.blinkStopEvent = scheduleEvent(function()
55+ g_effects.stopBlink(widget)
56+ end, duration)
57+ end
58+
59+ connect(widget, { onClick = g_effects.stopBlink })
60+end
61+
62+function g_effects.stopBlink(widget)
63+ disconnect(widget, { onClick = g_effects.stopBlink })
64+ removeEvent(widget.blinkEvent)
65+ removeEvent(widget.blinkStopEvent)
66+ widget.blinkEvent = nil
67+ widget.blinkStopEvent = nil
68+ widget:setOn(false)
69+end
A RD_Client/modules/corelib/ui/uibutton.lua +12-0 View file
@@ -0,0 +1,12 @@
1+-- @docclass
2+UIButton = extends(UIWidget, "UIButton")
3+
4+function UIButton.create()
5+ local button = UIButton.internalCreate()
6+ button:setFocusable(false)
7+ return button
8+end
9+
10+function UIButton:onMouseRelease(pos, button)
11+ return self:isPressed()
12+end
A RD_Client/modules/corelib/ui/uicheckbox.lua +13-0 View file
@@ -0,0 +1,13 @@
1+-- @docclass
2+UICheckBox = extends(UIWidget, "UICheckBox")
3+
4+function UICheckBox.create()
5+ local checkbox = UICheckBox.internalCreate()
6+ checkbox:setFocusable(false)
7+ checkbox:setTextAlign(AlignLeft)
8+ return checkbox
9+end
10+
11+function UICheckBox:onClick()
12+ self:setChecked(not self:isChecked())
13+end
A RD_Client/modules/corelib/ui/uicombobox.lua +184-0 View file
@@ -0,0 +1,184 @@
1+-- @docclass
2+UIComboBox = extends(UIWidget, "UIComboBox")
3+
4+function UIComboBox.create()
5+ local combobox = UIComboBox.internalCreate()
6+ combobox:setFocusable(false)
7+ combobox.options = {}
8+ combobox.currentIndex = -1
9+ combobox.mouseScroll = true
10+ combobox.menuScroll = false
11+ combobox.menuHeight = 100
12+ combobox.menuScrollStep = 0
13+ return combobox
14+end
15+
16+function UIComboBox:clearOptions()
17+ self.options = {}
18+ self.currentIndex = -1
19+ self:clearText()
20+end
21+
22+function UIComboBox:clear()
23+ return self:clearOptions()
24+end
25+
26+function UIComboBox:getOptionsCount()
27+ return #self.options
28+end
29+
30+function UIComboBox:isOption(text)
31+ if not self.options then return false end
32+ for i,v in ipairs(self.options) do
33+ if v.text == text then
34+ return true
35+ end
36+ end
37+ return false
38+end
39+
40+function UIComboBox:setOption(text, dontSignal)
41+ self:setCurrentOption(text, dontSignal)
42+end
43+
44+function UIComboBox:setCurrentOption(text, dontSignal)
45+ if not self.options then return end
46+ for i,v in ipairs(self.options) do
47+ if v.text == text and self.currentIndex ~= i then
48+ self.currentIndex = i
49+ self:setText(text)
50+ if not dontSignal then
51+ signalcall(self.onOptionChange, self, text, v.data)
52+ end
53+ return
54+ end
55+ end
56+end
57+
58+function UIComboBox:updateCurrentOption(newText)
59+ self.options[self.currentIndex].text = newText
60+ self:setText(newText)
61+end
62+
63+function UIComboBox:setCurrentOptionByData(data, dontSignal)
64+ if not self.options then return end
65+ for i,v in ipairs(self.options) do
66+ if v.data == data and self.currentIndex ~= i then
67+ self.currentIndex = i
68+ self:setText(v.text)
69+ if not dontSignal then
70+ signalcall(self.onOptionChange, self, v.text, v.data)
71+ end
72+ return
73+ end
74+ end
75+end
76+
77+function UIComboBox:setCurrentIndex(index, dontSignal)
78+ if index >= 1 and index <= #self.options then
79+ local v = self.options[index]
80+ self.currentIndex = index
81+ self:setText(v.text)
82+ if not dontSignal then
83+ signalcall(self.onOptionChange, self, v.text, v.data)
84+ end
85+ end
86+end
87+
88+function UIComboBox:getCurrentOption()
89+ if table.haskey(self.options, self.currentIndex) then
90+ return self.options[self.currentIndex]
91+ end
92+end
93+
94+function UIComboBox:addOption(text, data)
95+ table.insert(self.options, { text = text, data = data })
96+ local index = #self.options
97+ if index == 1 then self:setCurrentOption(text) end
98+ return index
99+end
100+
101+function UIComboBox:removeOption(text)
102+ for i,v in ipairs(self.options) do
103+ if v.text == text then
104+ table.remove(self.options, i)
105+ if self.currentIndex == i then
106+ self:setCurrentIndex(1)
107+ elseif self.currentIndex > i then
108+ self.currentIndex = self.currentIndex - 1
109+ end
110+ return
111+ end
112+ end
113+end
114+
115+function UIComboBox:onMousePress(mousePos, mouseButton)
116+ local menu
117+ if self.menuScroll then
118+ menu = g_ui.createWidget(self:getStyleName() .. 'PopupScrollMenu')
119+ menu:setHeight(self.menuHeight)
120+ if self.menuScrollStep > 0 then
121+ menu:setScrollbarStep(self.menuScrollStep)
122+ end
123+ else
124+ menu = g_ui.createWidget(self:getStyleName() .. 'PopupMenu')
125+ end
126+ menu:setId(self:getId() .. 'PopupMenu')
127+ for i,v in ipairs(self.options) do
128+ menu:addOption(v.text, function() self:setCurrentOption(v.text) end)
129+ end
130+ menu:setWidth(self:getWidth())
131+ menu:display({ x = self:getX(), y = self:getY() + self:getHeight() })
132+ connect(menu, { onDestroy = function() self:setOn(false) end })
133+ self:setOn(true)
134+ return true
135+end
136+
137+function UIComboBox:onMouseWheel(mousePos, direction)
138+ if not self.mouseScroll or self.disableScroll then
139+ return false
140+ end
141+ if direction == MouseWheelUp and self.currentIndex > 1 then
142+ self:setCurrentIndex(self.currentIndex - 1)
143+ elseif direction == MouseWheelDown and self.currentIndex < #self.options then
144+ self:setCurrentIndex(self.currentIndex + 1)
145+ end
146+ return true
147+end
148+
149+function UIComboBox:onStyleApply(styleName, styleNode)
150+ if styleNode.options then
151+ for k,option in pairs(styleNode.options) do
152+ self:addOption(option)
153+ end
154+ end
155+
156+ if styleNode.data then
157+ for k,data in pairs(styleNode.data) do
158+ local option = self.options[k]
159+ if option then
160+ option.data = data
161+ end
162+ end
163+ end
164+
165+ for name,value in pairs(styleNode) do
166+ if name == 'mouse-scroll' then
167+ self.mouseScroll = value
168+ elseif name == 'menu-scroll' then
169+ self.menuScroll = value
170+ elseif name == 'menu-height' then
171+ self.menuHeight = value
172+ elseif name == 'menu-scroll-step' then
173+ self.menuScrollStep = value
174+ end
175+ end
176+end
177+
178+function UIComboBox:setMouseScroll(scroll)
179+ self.mouseScroll = scroll
180+end
181+
182+function UIComboBox:canMouseScroll()
183+ return self.mouseScroll
184+end
A RD_Client/modules/corelib/ui/uiimageview.lua +99-0 View file
@@ -0,0 +1,99 @@
1+-- @docclass
2+UIImageView = extends(UIWidget, "UIImageView")
3+
4+function UIImageView.create()
5+ local imageView = UIImageView.internalCreate()
6+ imageView.zoom = 1
7+ imageView.minZoom = math.pow(10, -2)
8+ imageView.maxZoom = math.pow(10, 2)
9+ imageView:setClipping(true)
10+ return imageView
11+end
12+
13+function UIImageView:getDefaultZoom()
14+ local width = self:getWidth()
15+ local height = self:getHeight()
16+ local textureWidth = self:getImageTextureWidth()
17+ local textureHeight = self:getImageTextureHeight()
18+ local zoomX = width / textureWidth
19+ local zoomY = height / textureHeight
20+ return math.min(zoomX, zoomY)
21+end
22+
23+function UIImageView:getImagePosition(x, y)
24+ x = x or self:getWidth() / 2
25+ y = y or self:getHeight() / 2
26+ local offsetX = self:getImageOffsetX()
27+ local offsetY = self:getImageOffsetY()
28+ local posX = (x - offsetX) / self.zoom
29+ local posY = (y - offsetY) / self.zoom
30+ return posX, posY
31+end
32+
33+function UIImageView:setImage(image)
34+ self:setImageSource(image)
35+ local zoom = self:getDefaultZoom()
36+ self:setZoom(zoom)
37+ self:center()
38+end
39+
40+function UIImageView:setZoom(zoom, x, y)
41+ zoom = math.max(math.min(zoom, self.maxZoom), self.minZoom)
42+ local posX, posY = self:getImagePosition(x, y)
43+ local textureWidth = self:getImageTextureWidth()
44+ local textureHeight = self:getImageTextureHeight()
45+ local imageWidth = textureWidth * zoom
46+ local imageHeight = textureHeight * zoom
47+ self:setImageWidth(imageWidth)
48+ self:setImageHeight(imageHeight)
49+ self.zoom = zoom
50+ self:move(posX, posY, x, y)
51+end
52+
53+function UIImageView:zoomIn(x, y)
54+ local zoom = self.zoom * 1.1
55+ self:setZoom(zoom, x, y)
56+end
57+
58+function UIImageView:zoomOut(x, y)
59+ local zoom = self.zoom / 1.1
60+ self:setZoom(zoom, x, y)
61+end
62+
63+function UIImageView:center()
64+ self:move(self:getImageTextureWidth() / 2, self:getImageTextureHeight() / 2)
65+end
66+
67+function UIImageView:move(x, y, centerX, centerY)
68+ x = math.max(math.min(x, self:getImageTextureWidth()), 0)
69+ y = math.max(math.min(y, self:getImageTextureHeight()), 0)
70+ local centerX = centerX or self:getWidth() / 2
71+ local centerY = centerY or self:getHeight() / 2
72+ local offsetX = centerX - x * self.zoom
73+ local offsetY = centerY - y * self.zoom
74+ self:setImageOffset({x=offsetX, y=offsetY})
75+end
76+
77+function UIImageView:onDragEnter(pos)
78+ return true
79+end
80+
81+function UIImageView:onDragMove(pos, moved)
82+ local posX, posY = self:getImagePosition()
83+ self:move(posX - moved.x / self.zoom, posY - moved.y / self.zoom)
84+ return true
85+end
86+
87+function UIImageView:onDragLeave(widget, pos)
88+ return true
89+end
90+
91+function UIImageView:onMouseWheel(mousePos, direction)
92+ local x = mousePos.x - self:getX()
93+ local y = mousePos.y - self:getY()
94+ if direction == MouseWheelUp then
95+ self:zoomIn(x, y)
96+ elseif direction == MouseWheelDown then
97+ self:zoomOut(x, y)
98+ end
99+end
A RD_Client/modules/corelib/ui/uiinputbox.lua +114-0 View file
@@ -0,0 +1,114 @@
1+if not UIWindow then dofile 'uiwindow' end
2+
3+-- @docclass
4+UIInputBox = extends(UIWindow, "UIInputBox")
5+
6+function UIInputBox.create(title, okCallback, cancelCallback)
7+ local inputBox = UIInputBox.internalCreate()
8+
9+ inputBox:setText(title)
10+ inputBox.inputs = {}
11+ inputBox.onEnter = function()
12+ local results = {}
13+ for _,func in pairs(inputBox.inputs) do
14+ table.insert(results, func())
15+ end
16+ okCallback(unpack(results))
17+ inputBox:destroy()
18+ end
19+ inputBox.onEscape = function()
20+ if cancelCallback then
21+ cancelCallback()
22+ end
23+ inputBox:destroy()
24+ end
25+
26+ return inputBox
27+end
28+
29+function UIInputBox:addLabel(text)
30+ local label = g_ui.createWidget('InputBoxLabel', self)
31+ label:setText(text)
32+ return label
33+end
34+
35+function UIInputBox:addLineEdit(labelText, defaultText, maxLength)
36+ if labelText then self:addLabel(labelText) end
37+ local lineEdit = g_ui.createWidget('InputBoxLineEdit', self)
38+ if defaultText then lineEdit:setText(defaultText) end
39+ if maxLength then lineEdit:setMaxLength(maxLength) end
40+ table.insert(self.inputs, function() return lineEdit:getText() end)
41+ return lineEdit
42+end
43+
44+function UIInputBox:addTextEdit(labelText, defaultText, maxLength, visibleLines)
45+ if labelText then self:addLabel(labelText) end
46+ local textEdit = g_ui.createWidget('InputBoxTextEdit', self)
47+ if defaultText then textEdit:setText(defaultText) end
48+ if maxLength then textEdit:setMaxLength(maxLength) end
49+ visibleLines = visibleLines or 1
50+ textEdit:setHeight(textEdit:getHeight() * visibleLines)
51+ table.insert(self.inputs, function() return textEdit:getText() end)
52+ return textEdit
53+end
54+
55+function UIInputBox:addCheckBox(text, checked)
56+ local checkBox = g_ui.createWidget('InputBoxCheckBox', self)
57+ checkBox:setText(text)
58+ checkBox:setChecked(checked)
59+ table.insert(self.inputs, function() return checkBox:isChecked() end)
60+ return checkBox
61+end
62+
63+function UIInputBox:addComboBox(labelText, ...)
64+ if labelText then self:addLabel(labelText) end
65+ local comboBox = g_ui.createWidget('InputBoxComboBox', self)
66+ local options = {...}
67+ for i=1,#options do
68+ comboBox:addOption(options[i])
69+ end
70+ table.insert(self.inputs, function() return comboBox:getCurrentOption() end)
71+ return comboBox
72+end
73+
74+function UIInputBox:addSpinBox(labelText, minimum, maximum, value, step)
75+ if labelText then self:addLabel(labelText) end
76+ local spinBox = g_ui.createWidget('InputBoxSpinBox', self)
77+ spinBox:setMinimum(minimum)
78+ spinBox:setMaximum(maximum)
79+ spinBox:setValue(value)
80+ spinBox:setStep(step)
81+ table.insert(self.inputs, function() return spinBox:getValue() end)
82+ return spinBox
83+end
84+
85+function UIInputBox:display(okButtonText, cancelButtonText)
86+ okButtonText = okButtonText or tr('Ok')
87+ cancelButtonText = cancelButtonText or tr('Cancel')
88+
89+ local buttonsWidget = g_ui.createWidget('InputBoxButtonsPanel', self)
90+ local okButton = g_ui.createWidget('InputBoxButton', buttonsWidget)
91+ okButton:setText(okButtonText)
92+ okButton.onClick = self.onEnter
93+
94+ local cancelButton = g_ui.createWidget('InputBoxButton', buttonsWidget)
95+ cancelButton:setText(cancelButtonText)
96+ cancelButton.onClick = self.onEscape
97+
98+ buttonsWidget:setHeight(okButton:getHeight())
99+
100+ rootWidget:addChild(self)
101+ self:setStyle('InputBoxWindow')
102+end
103+
104+function displayTextInputBox(title, label, okCallback, cancelCallback)
105+ local inputBox = UIInputBox.create(title, okCallback, cancelCallback)
106+ inputBox:addLineEdit(label)
107+ inputBox:display()
108+end
109+
110+function displayNumberInputBox(title, label, okCallback, cancelCallback, min, max, value, step)
111+ local inputBox = UIInputBox.create(title, okCallback, cancelCallback)
112+ inputBox:addSpinBox(label, min, max, value, step)
113+ inputBox:display()
114+end
A RD_Client/modules/corelib/ui/uilabel.lua +10-0 View file
@@ -0,0 +1,10 @@
1+-- @docclass
2+UILabel = extends(UIWidget, "UILabel")
3+
4+function UILabel.create()
5+ local label = UILabel.internalCreate()
6+ label:setPhantom(true)
7+ label:setFocusable(false)
8+ label:setTextAlign(AlignLeft)
9+ return label
10+end
A RD_Client/modules/corelib/ui/uimessagebox.lua +96-0 View file
@@ -0,0 +1,96 @@
1+if not UIWindow then dofile 'uiwindow' end
2+
3+-- @docclass
4+UIMessageBox = extends(UIWindow, "UIMessageBox")
5+
6+-- messagebox cannot be created from otui files
7+UIMessageBox.create = nil
8+
9+function UIMessageBox.display(title, message, buttons, onEnterCallback, onEscapeCallback)
10+ local messageBox = UIMessageBox.internalCreate()
11+ rootWidget:addChild(messageBox)
12+
13+ messageBox:setStyle('MainWindow')
14+ messageBox:setText(title)
15+
16+ local messageLabel = g_ui.createWidget('MessageBoxLabel', messageBox)
17+ messageLabel:setText(message)
18+
19+ local buttonsWidth = 0
20+ local buttonsHeight = 0
21+
22+ local anchor = AnchorRight
23+ if buttons.anchor then anchor = buttons.anchor end
24+
25+ local buttonHolder = g_ui.createWidget('MessageBoxButtonHolder', messageBox)
26+ buttonHolder:addAnchor(anchor, 'parent', anchor)
27+
28+ for i=1,#buttons do
29+ local button = messageBox:addButton(buttons[i].text, buttons[i].callback)
30+ if i == 1 then
31+ button:setMarginLeft(0)
32+ button:addAnchor(AnchorBottom, 'parent', AnchorBottom)
33+ button:addAnchor(AnchorLeft, 'parent', AnchorLeft)
34+ buttonsHeight = button:getHeight()
35+ else
36+ button:addAnchor(AnchorBottom, 'prev', AnchorBottom)
37+ button:addAnchor(AnchorLeft, 'prev', AnchorRight)
38+ end
39+ buttonsWidth = buttonsWidth + button:getWidth() + button:getMarginLeft()
40+ end
41+
42+ buttonHolder:setWidth(buttonsWidth)
43+ buttonHolder:setHeight(buttonsHeight)
44+
45+ if onEnterCallback then connect(messageBox, { onEnter = onEnterCallback }) end
46+ if onEscapeCallback then connect(messageBox, { onEscape = onEscapeCallback }) end
47+
48+ messageBox:setWidth(math.max(messageLabel:getWidth(), messageBox:getTextSize().width, buttonHolder:getWidth()) + messageBox:getPaddingLeft() + messageBox:getPaddingRight())
49+ messageBox:setHeight(messageLabel:getHeight() + messageBox:getPaddingTop() + messageBox:getPaddingBottom() + buttonHolder:getHeight() + buttonHolder:getMarginTop())
50+ return messageBox
51+end
52+
53+function displayInfoBox(title, message)
54+ local messageBox
55+ local defaultCallback = function() messageBox:ok() end
56+ messageBox = UIMessageBox.display(title, message, {{text='Ok', callback=defaultCallback}}, defaultCallback, defaultCallback)
57+ return messageBox
58+end
59+
60+function displayErrorBox(title, message)
61+ local messageBox
62+ local defaultCallback = function() messageBox:ok() end
63+ messageBox = UIMessageBox.display(title, message, {{text='Ok', callback=defaultCallback}}, defaultCallback, defaultCallback)
64+ return messageBox
65+end
66+
67+function displayCancelBox(title, message)
68+ local messageBox
69+ local defaultCallback = function() messageBox:cancel() end
70+ messageBox = UIMessageBox.display(title, message, {{text='Cancel', callback=defaultCallback}}, defaultCallback, defaultCallback)
71+ return messageBox
72+end
73+
74+function displayGeneralBox(title, message, buttons, onEnterCallback, onEscapeCallback)
75+ return UIMessageBox.display(title, message, buttons, onEnterCallback, onEscapeCallback)
76+end
77+
78+function UIMessageBox:addButton(text, callback)
79+ local buttonHolder = self:getChildById('buttonHolder')
80+ local button = g_ui.createWidget('MessageBoxButton', buttonHolder)
81+ button:setText(text)
82+ connect(button, { onClick = callback })
83+ return button
84+end
85+
86+function UIMessageBox:ok()
87+ signalcall(self.onOk, self)
88+ self.onOk = nil
89+ self:destroy()
90+end
91+
92+function UIMessageBox:cancel()
93+ signalcall(self.onCancel, self)
94+ self.onCancel = nil
95+ self:destroy()
96+end
A RD_Client/modules/corelib/ui/uiminiwindow.lua +444-0 View file
@@ -0,0 +1,444 @@
1+-- @docclass
2+UIMiniWindow = extends(UIWindow, "UIMiniWindow")
3+
4+function UIMiniWindow.create()
5+ local miniwindow = UIMiniWindow.internalCreate()
6+ miniwindow.UIMiniWindowContainer = true
7+ return miniwindow
8+end
9+
10+function UIMiniWindow:open(dontSave)
11+ self:setVisible(true)
12+
13+ if not dontSave then
14+ self:setSettings({closed = false})
15+ end
16+
17+ signalcall(self.onOpen, self)
18+end
19+
20+function UIMiniWindow:close(dontSave)
21+ if not self:isExplicitlyVisible() then return end
22+ if self.forceOpen then return end
23+ self:setVisible(false)
24+
25+ if not dontSave then
26+ self:setSettings({closed = true})
27+ end
28+
29+ signalcall(self.onClose, self)
30+end
31+
32+function UIMiniWindow:minimize(dontSave)
33+ self:setOn(true)
34+ self:getChildById('contentsPanel'):hide()
35+ self:getChildById('miniwindowScrollBar'):hide()
36+ self:getChildById('bottomResizeBorder'):hide()
37+ if self.minimizeButton then
38+ self.minimizeButton:setOn(true)
39+ end
40+ self.maximizedHeight = self:getHeight()
41+ self:setHeight(self.minimizedHeight)
42+
43+ if not dontSave then
44+ self:setSettings({minimized = true})
45+ end
46+
47+ signalcall(self.onMinimize, self)
48+end
49+
50+function UIMiniWindow:maximize(dontSave)
51+ self:setOn(false)
52+ self:getChildById('contentsPanel'):show()
53+ self:getChildById('miniwindowScrollBar'):show()
54+ self:getChildById('bottomResizeBorder'):show()
55+ if self.minimizeButton then
56+ self.minimizeButton:setOn(false)
57+ end
58+ self:setHeight(self:getSettings('height') or self.maximizedHeight)
59+
60+ if not dontSave then
61+ self:setSettings({minimized = false})
62+ end
63+
64+ local parent = self:getParent()
65+ if parent and parent:getClassName() == 'UIMiniWindowContainer' then
66+ parent:fitAll(self)
67+ end
68+
69+ signalcall(self.onMaximize, self)
70+end
71+
72+function UIMiniWindow:lock(dontSave)
73+ local lockButton = self:getChildById('lockButton')
74+ if lockButton then
75+ lockButton:setOn(true)
76+ end
77+ self:setDraggable(false)
78+ if not dontsave then
79+ self:setSettings({locked = true})
80+ end
81+
82+ signalcall(self.onLockChange, self)
83+end
84+
85+function UIMiniWindow:unlock(dontSave)
86+ local lockButton = self:getChildById('lockButton')
87+ if lockButton then
88+ lockButton:setOn(false)
89+ end
90+ self:setDraggable(true)
91+ if not dontsave then
92+ self:setSettings({locked = false})
93+ end
94+ signalcall(self.onLockChange, self)
95+end
96+
97+function UIMiniWindow:setup()
98+ self:getChildById('closeButton').onClick =
99+ function()
100+ self:close()
101+ end
102+ if self.forceOpen then
103+ if self.closeButton then
104+ self.closeButton:hide()
105+ end
106+ end
107+
108+ if(self.minimizeButton) then
109+ self.minimizeButton.onClick =
110+ function()
111+ if self:isOn() then
112+ self:maximize()
113+ else
114+ self:minimize()
115+ end
116+ end
117+ end
118+
119+ local lockButton = self:getChildById('lockButton')
120+ if lockButton then
121+ lockButton.onClick =
122+ function ()
123+ if self:isDraggable() then
124+ self:lock()
125+ else
126+ self:unlock()
127+ end
128+ end
129+ end
130+
131+ self:getChildById('miniwindowTopBar').onDoubleClick =
132+ function()
133+ if self:isOn() then
134+ self:maximize()
135+ else
136+ self:minimize()
137+ end
138+ end
139+
140+ local oldParent = self:getParent()
141+
142+ local settings = g_settings.getNode('MiniWindows')
143+ if settings then
144+ local selfSettings = settings[self:getId()]
145+ if selfSettings then
146+ if selfSettings.parentId then
147+ local parent = rootWidget:recursiveGetChildById(selfSettings.parentId)
148+ if parent then
149+ if parent:getClassName() == 'UIMiniWindowContainer' and selfSettings.index and parent:isOn() then
150+ self.miniIndex = selfSettings.index
151+ parent:scheduleInsert(self, selfSettings.index)
152+ elseif selfSettings.position then
153+ self:setParent(parent, true)
154+ self:setPosition(topoint(selfSettings.position))
155+ end
156+ end
157+ end
158+
159+ if selfSettings.minimized then
160+ self:minimize(true)
161+ else
162+ if selfSettings.height and self:isResizeable() then
163+ self:setHeight(selfSettings.height)
164+ elseif selfSettings.height and not self:isResizeable() then
165+ self:eraseSettings({height = true})
166+ end
167+ end
168+
169+ if selfSettings.closed and not self.forceOpen then
170+ self:close(true)
171+ end
172+
173+ if selfSettings.locked then
174+ self:lock(true)
175+ end
176+ else
177+ if not self.forceOpen and self.autoOpen ~= nil and (self.autoOpen == 0 or self.autoOpen == false) then
178+ self:close(true)
179+ end
180+ end
181+ end
182+
183+ local newParent = self:getParent()
184+
185+ self.miniLoaded = true
186+
187+ if self.save then
188+ if oldParent and oldParent:getClassName() == 'UIMiniWindowContainer' then
189+ addEvent(function() oldParent:order() end)
190+ end
191+ if newParent and newParent:getClassName() == 'UIMiniWindowContainer' and newParent ~= oldParent then
192+ addEvent(function() newParent:order() end)
193+ end
194+ end
195+
196+ self:fitOnParent()
197+end
198+
199+function UIMiniWindow:onVisibilityChange(visible)
200+ self:fitOnParent()
201+end
202+
203+function UIMiniWindow:onDragEnter(mousePos)
204+ local parent = self:getParent()
205+ if not parent then return false end
206+
207+ if parent:getClassName() == 'UIMiniWindowContainer' then
208+ local containerParent = parent:getParent():getParent()
209+ parent:removeChild(self)
210+ containerParent:addChild(self)
211+ parent:saveChildren()
212+ end
213+
214+ local oldPos = self:getPosition()
215+ self.movingReference = { x = mousePos.x - oldPos.x, y = mousePos.y - oldPos.y }
216+ self:setPosition(oldPos)
217+ self.free = true
218+ return true
219+end
220+
221+function UIMiniWindow:onDragLeave(droppedWidget, mousePos)
222+ if self.movedWidget then
223+ self.setMovedChildMargin(self.movedOldMargin or 0)
224+ self.movedWidget = nil
225+ self.setMovedChildMargin = nil
226+ self.movedOldMargin = nil
227+ self.movedIndex = nil
228+ end
229+
230+ UIWindow:onDragLeave(self, droppedWidget, mousePos)
231+ self:saveParent(self:getParent())
232+end
233+
234+function UIMiniWindow:onDragMove(mousePos, mouseMoved)
235+ local oldMousePosY = mousePos.y - mouseMoved.y
236+ local children = rootWidget:recursiveGetChildrenByMarginPos(mousePos)
237+ local overAnyWidget = false
238+ for i=1,#children do
239+ local child = children[i]
240+ if child:getParent():getClassName() == 'UIMiniWindowContainer' then
241+ overAnyWidget = true
242+
243+ local childCenterY = child:getY() + child:getHeight() / 2
244+ if child == self.movedWidget and mousePos.y < childCenterY and oldMousePosY < childCenterY then
245+ break
246+ end
247+
248+ if self.movedWidget then
249+ self.setMovedChildMargin(self.movedOldMargin or 0)
250+ self.setMovedChildMargin = nil
251+ end
252+
253+ if mousePos.y < childCenterY then
254+ self.movedOldMargin = child:getMarginTop()
255+ self.setMovedChildMargin = function(v) child:setMarginTop(v) end
256+ self.movedIndex = 0
257+ else
258+ self.movedOldMargin = child:getMarginBottom()
259+ self.setMovedChildMargin = function(v) child:setMarginBottom(v) end
260+ self.movedIndex = 1
261+ end
262+
263+ self.movedWidget = child
264+ self.setMovedChildMargin(self:getHeight())
265+ break
266+ end
267+ end
268+
269+ if not overAnyWidget and self.movedWidget then
270+ self.setMovedChildMargin(self.movedOldMargin or 0)
271+ self.movedWidget = nil
272+ end
273+
274+ return UIWindow.onDragMove(self, mousePos, mouseMoved)
275+end
276+
277+function UIMiniWindow:onMousePress()
278+ local parent = self:getParent()
279+ if not parent then return false end
280+ if parent:getClassName() ~= 'UIMiniWindowContainer' then
281+ self:raise()
282+ return true
283+ end
284+end
285+
286+function UIMiniWindow:onFocusChange(focused)
287+ if not focused then return end
288+ local parent = self:getParent()
289+ if parent and parent:getClassName() ~= 'UIMiniWindowContainer' then
290+ self:raise()
291+ end
292+end
293+
294+function UIMiniWindow:onHeightChange(height)
295+ if not self:isOn() then
296+ self:setSettings({height = height})
297+ end
298+ self:fitOnParent()
299+end
300+
301+function UIMiniWindow:getSettings(name)
302+ if not self.save then return nil end
303+ local settings = g_settings.getNode('MiniWindows')
304+ if settings then
305+ local selfSettings = settings[self:getId()]
306+ if selfSettings then
307+ return selfSettings[name]
308+ end
309+ end
310+ return nil
311+end
312+
313+function UIMiniWindow:setSettings(data)
314+ if not self.save then return end
315+
316+ local settings = g_settings.getNode('MiniWindows')
317+ if not settings then
318+ settings = {}
319+ end
320+
321+ local id = self:getId()
322+ if not settings[id] then
323+ settings[id] = {}
324+ end
325+
326+ for key,value in pairs(data) do
327+ settings[id][key] = value
328+ end
329+
330+ g_settings.setNode('MiniWindows', settings)
331+end
332+
333+function UIMiniWindow:eraseSettings(data)
334+ if not self.save then return end
335+
336+ local settings = g_settings.getNode('MiniWindows')
337+ if not settings then
338+ settings = {}
339+ end
340+
341+ local id = self:getId()
342+ if not settings[id] then
343+ settings[id] = {}
344+ end
345+
346+ for key,value in pairs(data) do
347+ settings[id][key] = nil
348+ end
349+
350+ g_settings.setNode('MiniWindows', settings)
351+end
352+
353+function UIMiniWindow:saveParent(parent)
354+ local parent = self:getParent()
355+ if parent then
356+ if parent:getClassName() == 'UIMiniWindowContainer' then
357+ parent:saveChildren()
358+ else
359+ self:saveParentPosition(parent:getId(), self:getPosition())
360+ end
361+ end
362+end
363+
364+function UIMiniWindow:saveParentPosition(parentId, position)
365+ local selfSettings = {}
366+ selfSettings.parentId = parentId
367+ selfSettings.position = pointtostring(position)
368+ self:setSettings(selfSettings)
369+end
370+
371+function UIMiniWindow:saveParentIndex(parentId, index)
372+ local selfSettings = {}
373+ selfSettings.parentId = parentId
374+ selfSettings.index = index
375+ self:setSettings(selfSettings)
376+ self.miniIndex = index
377+end
378+
379+function UIMiniWindow:disableResize()
380+ self:getChildById('bottomResizeBorder'):disable()
381+end
382+
383+function UIMiniWindow:enableResize()
384+ self:getChildById('bottomResizeBorder'):enable()
385+end
386+
387+function UIMiniWindow:fitOnParent()
388+ local parent = self:getParent()
389+ if self:isVisible() and parent and parent:getClassName() == 'UIMiniWindowContainer' then
390+ parent:fitAll(self)
391+ end
392+end
393+
394+function UIMiniWindow:setParent(parent, dontsave)
395+ UIWidget.setParent(self, parent)
396+ if not dontsave then
397+ self:saveParent(parent)
398+ end
399+ self:fitOnParent()
400+end
401+
402+function UIMiniWindow:setHeight(height)
403+ UIWidget.setHeight(self, height)
404+ signalcall(self.onHeightChange, self, height)
405+end
406+
407+function UIMiniWindow:setContentHeight(height)
408+ local contentsPanel = self:getChildById('contentsPanel')
409+ local minHeight = contentsPanel:getMarginTop() + contentsPanel:getMarginBottom() + contentsPanel:getPaddingTop() + contentsPanel:getPaddingBottom()
410+
411+ local resizeBorder = self:getChildById('bottomResizeBorder')
412+ resizeBorder:setParentSize(minHeight + height)
413+end
414+
415+function UIMiniWindow:setContentMinimumHeight(height)
416+ local contentsPanel = self:getChildById('contentsPanel')
417+ local minHeight = contentsPanel:getMarginTop() + contentsPanel:getMarginBottom() + contentsPanel:getPaddingTop() + contentsPanel:getPaddingBottom()
418+
419+ local resizeBorder = self:getChildById('bottomResizeBorder')
420+ resizeBorder:setMinimum(minHeight + height)
421+end
422+
423+function UIMiniWindow:setContentMaximumHeight(height)
424+ local contentsPanel = self:getChildById('contentsPanel')
425+ local minHeight = contentsPanel:getMarginTop() + contentsPanel:getMarginBottom() + contentsPanel:getPaddingTop() + contentsPanel:getPaddingBottom()
426+
427+ local resizeBorder = self:getChildById('bottomResizeBorder')
428+ resizeBorder:setMaximum(minHeight + height)
429+end
430+
431+function UIMiniWindow:getMinimumHeight()
432+ local resizeBorder = self:getChildById('bottomResizeBorder')
433+ return resizeBorder:getMinimum()
434+end
435+
436+function UIMiniWindow:getMaximumHeight()
437+ local resizeBorder = self:getChildById('bottomResizeBorder')
438+ return resizeBorder:getMaximum()
439+end
440+
441+function UIMiniWindow:isResizeable()
442+ local resizeBorder = self:getChildById('bottomResizeBorder')
443+ return resizeBorder:isExplicitlyVisible() and resizeBorder:isEnabled()
444+end
Top