mirror of
https://github.com/ErikasKontenis/SabrehavenServer.git
synced 2025-10-13 22:34:53 +02:00
Resolve "Merge the best from 7.40 branch"
This commit is contained in:
176
SabrehavenOTClient/modules/corelib/base64.lua
Normal file
176
SabrehavenOTClient/modules/corelib/base64.lua
Normal file
@@ -0,0 +1,176 @@
|
||||
--[[
|
||||
|
||||
base64 -- v1.5.1 public domain Lua base64 encoder/decoder
|
||||
no warranty implied; use at your own risk
|
||||
|
||||
Needs bit32.extract function. If not present it's implemented using BitOp
|
||||
or Lua 5.3 native bit operators. For Lua 5.1 fallbacks to pure Lua
|
||||
implementation inspired by Rici Lake's post:
|
||||
http://ricilake.blogspot.co.uk/2007/10/iterating-bits-in-lua.html
|
||||
|
||||
author: Ilya Kolbin (iskolbin@gmail.com)
|
||||
url: github.com/iskolbin/lbase64
|
||||
|
||||
COMPATIBILITY
|
||||
|
||||
Lua 5.1, 5.2, 5.3, LuaJIT
|
||||
|
||||
LICENSE
|
||||
|
||||
See end of file for license information.
|
||||
|
||||
--]]
|
||||
|
||||
|
||||
base64 = {}
|
||||
|
||||
local extract = _G.bit32 and _G.bit32.extract
|
||||
if not extract then
|
||||
if _G.bit then
|
||||
local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band
|
||||
extract = function( v, from, width )
|
||||
return band( shr( v, from ), shl( 1, width ) - 1 )
|
||||
end
|
||||
elseif _G._VERSION >= "Lua 5.3" then
|
||||
extract = load[[return function( v, from, width )
|
||||
return ( v >> from ) & ((1 << width) - 1)
|
||||
end]]()
|
||||
else
|
||||
extract = function( v, from, width )
|
||||
local w = 0
|
||||
local flag = 2^from
|
||||
for i = 0, width-1 do
|
||||
local flag2 = flag + flag
|
||||
if v % flag2 >= flag then
|
||||
w = w + 2^i
|
||||
end
|
||||
flag = flag2
|
||||
end
|
||||
return w
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function base64.makeencoder( s62, s63, spad )
|
||||
local encoder = {}
|
||||
for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J',
|
||||
'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y',
|
||||
'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n',
|
||||
'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2',
|
||||
'3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} do
|
||||
encoder[b64code] = char:byte()
|
||||
end
|
||||
return encoder
|
||||
end
|
||||
|
||||
function base64.makedecoder( s62, s63, spad )
|
||||
local decoder = {}
|
||||
for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) do
|
||||
decoder[charcode] = b64code
|
||||
end
|
||||
return decoder
|
||||
end
|
||||
|
||||
local DEFAULT_ENCODER = base64.makeencoder()
|
||||
local DEFAULT_DECODER = base64.makedecoder()
|
||||
|
||||
local char, concat = string.char, table.concat
|
||||
|
||||
function base64.encode( str, encoder, usecaching )
|
||||
encoder = encoder or DEFAULT_ENCODER
|
||||
local t, k, n = {}, 1, #str
|
||||
local lastn = n % 3
|
||||
local cache = {}
|
||||
for i = 1, n-lastn, 3 do
|
||||
local a, b, c = str:byte( i, i+2 )
|
||||
local v = a*0x10000 + b*0x100 + c
|
||||
local s
|
||||
if usecaching then
|
||||
s = cache[v]
|
||||
if not s then
|
||||
s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])
|
||||
cache[v] = s
|
||||
end
|
||||
else
|
||||
s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])
|
||||
end
|
||||
t[k] = s
|
||||
k = k + 1
|
||||
end
|
||||
if lastn == 2 then
|
||||
local a, b = str:byte( n-1, n )
|
||||
local v = a*0x10000 + b*0x100
|
||||
t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64])
|
||||
elseif lastn == 1 then
|
||||
local v = str:byte( n )*0x10000
|
||||
t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64])
|
||||
end
|
||||
return concat( t )
|
||||
end
|
||||
|
||||
function base64.decode( b64, decoder, usecaching )
|
||||
decoder = decoder or DEFAULT_DECODER
|
||||
local pattern = '[^%w%+%/%=]'
|
||||
if decoder then
|
||||
local s62, s63
|
||||
for charcode, b64code in pairs( decoder ) do
|
||||
if b64code == 62 then s62 = charcode
|
||||
elseif b64code == 63 then s63 = charcode
|
||||
end
|
||||
end
|
||||
pattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) )
|
||||
end
|
||||
b64 = b64:gsub( pattern, '' )
|
||||
local cache = usecaching and {}
|
||||
local t, k = {}, 1
|
||||
local n = #b64
|
||||
local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0
|
||||
for i = 1, padding > 0 and n-4 or n, 4 do
|
||||
local a, b, c, d = b64:byte( i, i+3 )
|
||||
local s
|
||||
if usecaching then
|
||||
local v0 = a*0x1000000 + b*0x10000 + c*0x100 + d
|
||||
s = cache[v0]
|
||||
if not s then
|
||||
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
|
||||
s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8))
|
||||
cache[v0] = s
|
||||
end
|
||||
else
|
||||
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
|
||||
s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8))
|
||||
end
|
||||
t[k] = s
|
||||
k = k + 1
|
||||
end
|
||||
if padding == 1 then
|
||||
local a, b, c = b64:byte( n-3, n-1 )
|
||||
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40
|
||||
t[k] = char( extract(v,16,8), extract(v,8,8))
|
||||
elseif padding == 2 then
|
||||
local a, b = b64:byte( n-3, n-2 )
|
||||
local v = decoder[a]*0x40000 + decoder[b]*0x1000
|
||||
t[k] = char( extract(v,16,8))
|
||||
end
|
||||
return concat( t )
|
||||
end
|
||||
|
||||
--[[
|
||||
Copyright (c) 2018 Ilya Kolbin
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
--]]
|
17
SabrehavenOTClient/modules/corelib/bitwise.lua
Normal file
17
SabrehavenOTClient/modules/corelib/bitwise.lua
Normal file
@@ -0,0 +1,17 @@
|
||||
Bit = {}
|
||||
|
||||
function Bit.bit(p)
|
||||
return 2 ^ p
|
||||
end
|
||||
|
||||
function Bit.hasBit(x, p)
|
||||
return x % (p + p) >= p
|
||||
end
|
||||
|
||||
function Bit.setbit(x, p)
|
||||
return Bit.hasBit(x, p) and x or x + p
|
||||
end
|
||||
|
||||
function Bit.clearbit(x, p)
|
||||
return Bit.hasBit(x, p) and x - p or x
|
||||
end
|
73
SabrehavenOTClient/modules/corelib/config.lua
Normal file
73
SabrehavenOTClient/modules/corelib/config.lua
Normal file
@@ -0,0 +1,73 @@
|
||||
-- @docclass
|
||||
|
||||
local function convertSettingValue(value)
|
||||
if type(value) == 'table' then
|
||||
if value.x and value.width then
|
||||
return recttostring(value)
|
||||
elseif value.x then
|
||||
return pointtostring(value)
|
||||
elseif value.width then
|
||||
return sizetostring(value)
|
||||
elseif value.r then
|
||||
return colortostring(value)
|
||||
else
|
||||
return value
|
||||
end
|
||||
elseif value == nil then
|
||||
return ''
|
||||
else
|
||||
return tostring(value)
|
||||
end
|
||||
end
|
||||
|
||||
function Config:set(key, value)
|
||||
self:setValue(key, convertSettingValue(value))
|
||||
end
|
||||
|
||||
function Config:setDefault(key, value)
|
||||
if self:exists(key) then return false end
|
||||
self:set(key, value)
|
||||
return true
|
||||
end
|
||||
|
||||
function Config:get(key, default)
|
||||
if not self:exists(key) and default ~= nil then
|
||||
self:set(key, default)
|
||||
end
|
||||
return self:getValue(key)
|
||||
end
|
||||
|
||||
function Config:getString(key, default)
|
||||
return self:get(key, default)
|
||||
end
|
||||
|
||||
function Config:getInteger(key, default)
|
||||
local v = tonumber(self:get(key, default)) or 0
|
||||
return v
|
||||
end
|
||||
|
||||
function Config:getNumber(key, default)
|
||||
local v = tonumber(self:get(key, default)) or 0
|
||||
return v
|
||||
end
|
||||
|
||||
function Config:getBoolean(key, default)
|
||||
return toboolean(self:get(key, default))
|
||||
end
|
||||
|
||||
function Config:getPoint(key, default)
|
||||
return topoint(self:get(key, default))
|
||||
end
|
||||
|
||||
function Config:getRect(key, default)
|
||||
return torect(self:get(key, default))
|
||||
end
|
||||
|
||||
function Config:getSize(key, default)
|
||||
return tosize(self:get(key, default))
|
||||
end
|
||||
|
||||
function Config:getColor(key, default)
|
||||
return tocolor(self:get(key, default))
|
||||
end
|
||||
|
325
SabrehavenOTClient/modules/corelib/const.lua
Normal file
325
SabrehavenOTClient/modules/corelib/const.lua
Normal file
@@ -0,0 +1,325 @@
|
||||
-- @docconsts @{
|
||||
|
||||
AnchorNone = 0
|
||||
AnchorTop = 1
|
||||
AnchorBottom = 2
|
||||
AnchorLeft = 3
|
||||
AnchorRight = 4
|
||||
AnchorVerticalCenter = 5
|
||||
AnchorHorizontalCenter = 6
|
||||
|
||||
LogDebug = 0
|
||||
LogInfo = 1
|
||||
LogWarning = 2
|
||||
LogError = 3
|
||||
LogFatal = 4
|
||||
|
||||
MouseFocusReason = 0
|
||||
KeyboardFocusReason = 1
|
||||
ActiveFocusReason = 2
|
||||
OtherFocusReason = 3
|
||||
|
||||
AutoFocusNone = 0
|
||||
AutoFocusFirst = 1
|
||||
AutoFocusLast = 2
|
||||
|
||||
KeyboardNoModifier = 0
|
||||
KeyboardCtrlModifier = 1
|
||||
KeyboardAltModifier = 2
|
||||
KeyboardCtrlAltModifier = 3
|
||||
KeyboardShiftModifier = 4
|
||||
KeyboardCtrlShiftModifier = 5
|
||||
KeyboardAltShiftModifier = 6
|
||||
KeyboardCtrlAltShiftModifier = 7
|
||||
|
||||
MouseNoButton = 0
|
||||
MouseLeftButton = 1
|
||||
MouseRightButton = 2
|
||||
MouseMidButton = 3
|
||||
MouseTouch = 4
|
||||
MouseTouch2 = 5 -- multitouch, 2nd finger
|
||||
MouseTouch3 = 6 -- multitouch, 3th finger
|
||||
|
||||
MouseNoWheel = 0
|
||||
MouseWheelUp = 1
|
||||
MouseWheelDown = 2
|
||||
|
||||
AlignNone = 0
|
||||
AlignLeft = 1
|
||||
AlignRight = 2
|
||||
AlignTop = 4
|
||||
AlignBottom = 8
|
||||
AlignHorizontalCenter = 16
|
||||
AlignVerticalCenter = 32
|
||||
AlignTopLeft = 5
|
||||
AlignTopRight = 6
|
||||
AlignBottomLeft = 9
|
||||
AlignBottomRight = 10
|
||||
AlignLeftCenter = 33
|
||||
AlignRightCenter = 34
|
||||
AlignTopCenter = 20
|
||||
AlignBottomCenter = 24
|
||||
AlignCenter = 48
|
||||
|
||||
KeyUnknown = 0
|
||||
KeyEscape = 1
|
||||
KeyTab = 2
|
||||
KeyBackspace = 3
|
||||
KeyEnter = 5
|
||||
KeyInsert = 6
|
||||
KeyDelete = 7
|
||||
KeyPause = 8
|
||||
KeyPrintScreen = 9
|
||||
KeyHome = 10
|
||||
KeyEnd = 11
|
||||
KeyPageUp = 12
|
||||
KeyPageDown = 13
|
||||
KeyUp = 14
|
||||
KeyDown = 15
|
||||
KeyLeft = 16
|
||||
KeyRight = 17
|
||||
KeyNumLock = 18
|
||||
KeyScrollLock = 19
|
||||
KeyCapsLock = 20
|
||||
KeyCtrl = 21
|
||||
KeyShift = 22
|
||||
KeyAlt = 23
|
||||
KeyMeta = 25
|
||||
KeyMenu = 26
|
||||
KeySpace = 32 -- ' '
|
||||
KeyExclamation = 33 -- !
|
||||
KeyQuote = 34 -- "
|
||||
KeyNumberSign = 35 -- #
|
||||
KeyDollar = 36 -- $
|
||||
KeyPercent = 37 -- %
|
||||
KeyAmpersand = 38 -- &
|
||||
KeyApostrophe = 39 -- '
|
||||
KeyLeftParen = 40 -- (
|
||||
KeyRightParen = 41 -- )
|
||||
KeyAsterisk = 42 -- *
|
||||
KeyPlus = 43 -- +
|
||||
KeyComma = 44 -- ,
|
||||
KeyMinus = 45 -- -
|
||||
KeyPeriod = 46 -- .
|
||||
KeySlash = 47 -- /
|
||||
Key0 = 48 -- 0
|
||||
Key1 = 49 -- 1
|
||||
Key2 = 50 -- 2
|
||||
Key3 = 51 -- 3
|
||||
Key4 = 52 -- 4
|
||||
Key5 = 53 -- 5
|
||||
Key6 = 54 -- 6
|
||||
Key7 = 55 -- 7
|
||||
Key8 = 56 -- 8
|
||||
Key9 = 57 -- 9
|
||||
KeyColon = 58 -- :
|
||||
KeySemicolon = 59 -- ;
|
||||
KeyLess = 60 -- <
|
||||
KeyEqual = 61 -- =
|
||||
KeyGreater = 62 -- >
|
||||
KeyQuestion = 63 -- ?
|
||||
KeyAtSign = 64 -- @
|
||||
KeyA = 65 -- a
|
||||
KeyB = 66 -- b
|
||||
KeyC = 67 -- c
|
||||
KeyD = 68 -- d
|
||||
KeyE = 69 -- e
|
||||
KeyF = 70 -- f
|
||||
KeyG = 71 -- g
|
||||
KeyH = 72 -- h
|
||||
KeyI = 73 -- i
|
||||
KeyJ = 74 -- j
|
||||
KeyK = 75 -- k
|
||||
KeyL = 76 -- l
|
||||
KeyM = 77 -- m
|
||||
KeyN = 78 -- n
|
||||
KeyO = 79 -- o
|
||||
KeyP = 80 -- p
|
||||
KeyQ = 81 -- q
|
||||
KeyR = 82 -- r
|
||||
KeyS = 83 -- s
|
||||
KeyT = 84 -- t
|
||||
KeyU = 85 -- u
|
||||
KeyV = 86 -- v
|
||||
KeyW = 87 -- w
|
||||
KeyX = 88 -- x
|
||||
KeyY = 89 -- y
|
||||
KeyZ = 90 -- z
|
||||
KeyLeftBracket = 91 -- [
|
||||
KeyBackslash = 92 -- '\'
|
||||
KeyRightBracket = 93 -- ]
|
||||
KeyCaret = 94 -- ^
|
||||
KeyUnderscore = 95 -- _
|
||||
KeyGrave = 96 -- `
|
||||
KeyLeftCurly = 123 -- {
|
||||
KeyBar = 124 -- |
|
||||
KeyRightCurly = 125 -- }
|
||||
KeyTilde = 126 -- ~
|
||||
KeyF1 = 128
|
||||
KeyF2 = 129
|
||||
KeyF3 = 130
|
||||
KeyF4 = 131
|
||||
KeyF5 = 132
|
||||
KeyF6 = 134
|
||||
KeyF7 = 135
|
||||
KeyF8 = 136
|
||||
KeyF9 = 137
|
||||
KeyF10 = 138
|
||||
KeyF11 = 139
|
||||
KeyF12 = 140
|
||||
KeyNumpad0 = 141
|
||||
KeyNumpad1 = 142
|
||||
KeyNumpad2 = 143
|
||||
KeyNumpad3 = 144
|
||||
KeyNumpad4 = 145
|
||||
KeyNumpad5 = 146
|
||||
KeyNumpad6 = 147
|
||||
KeyNumpad7 = 148
|
||||
KeyNumpad8 = 149
|
||||
KeyNumpad9 = 150
|
||||
|
||||
FirstKey = KeyUnknown
|
||||
LastKey = KeyNumpad9
|
||||
|
||||
ExtendedActivate = 0
|
||||
ExtendedLocales = 1
|
||||
ExtendedParticles = 2
|
||||
|
||||
-- @}
|
||||
|
||||
KeyCodeDescs = {
|
||||
[KeyUnknown] = 'Unknown',
|
||||
[KeyEscape] = 'Escape',
|
||||
[KeyTab] = 'Tab',
|
||||
[KeyBackspace] = 'Backspace',
|
||||
[KeyEnter] = 'Enter',
|
||||
[KeyInsert] = 'Insert',
|
||||
[KeyDelete] = 'Delete',
|
||||
[KeyPause] = 'Pause',
|
||||
[KeyPrintScreen] = 'PrintScreen',
|
||||
[KeyHome] = 'Home',
|
||||
[KeyEnd] = 'End',
|
||||
[KeyPageUp] = 'PageUp',
|
||||
[KeyPageDown] = 'PageDown',
|
||||
[KeyUp] = 'Up',
|
||||
[KeyDown] = 'Down',
|
||||
[KeyLeft] = 'Left',
|
||||
[KeyRight] = 'Right',
|
||||
[KeyNumLock] = 'NumLock',
|
||||
[KeyScrollLock] = 'ScrollLock',
|
||||
[KeyCapsLock] = 'CapsLock',
|
||||
[KeyCtrl] = 'Ctrl',
|
||||
[KeyShift] = 'Shift',
|
||||
[KeyAlt] = 'Alt',
|
||||
[KeyMeta] = 'Meta',
|
||||
[KeyMenu] = 'Menu',
|
||||
[KeySpace] = 'Space',
|
||||
[KeyExclamation] = '!',
|
||||
[KeyQuote] = '\"',
|
||||
[KeyNumberSign] = '#',
|
||||
[KeyDollar] = '$',
|
||||
[KeyPercent] = '%',
|
||||
[KeyAmpersand] = '&',
|
||||
[KeyApostrophe] = '\'',
|
||||
[KeyLeftParen] = '(',
|
||||
[KeyRightParen] = ')',
|
||||
[KeyAsterisk] = '*',
|
||||
[KeyPlus] = 'Plus',
|
||||
[KeyComma] = ',',
|
||||
[KeyMinus] = '-',
|
||||
[KeyPeriod] = '.',
|
||||
[KeySlash] = '/',
|
||||
[Key0] = '0',
|
||||
[Key1] = '1',
|
||||
[Key2] = '2',
|
||||
[Key3] = '3',
|
||||
[Key4] = '4',
|
||||
[Key5] = '5',
|
||||
[Key6] = '6',
|
||||
[Key7] = '7',
|
||||
[Key8] = '8',
|
||||
[Key9] = '9',
|
||||
[KeyColon] = ':',
|
||||
[KeySemicolon] = ';',
|
||||
[KeyLess] = '<',
|
||||
[KeyEqual] = '=',
|
||||
[KeyGreater] = '>',
|
||||
[KeyQuestion] = '?',
|
||||
[KeyAtSign] = '@',
|
||||
[KeyA] = 'A',
|
||||
[KeyB] = 'B',
|
||||
[KeyC] = 'C',
|
||||
[KeyD] = 'D',
|
||||
[KeyE] = 'E',
|
||||
[KeyF] = 'F',
|
||||
[KeyG] = 'G',
|
||||
[KeyH] = 'H',
|
||||
[KeyI] = 'I',
|
||||
[KeyJ] = 'J',
|
||||
[KeyK] = 'K',
|
||||
[KeyL] = 'L',
|
||||
[KeyM] = 'M',
|
||||
[KeyN] = 'N',
|
||||
[KeyO] = 'O',
|
||||
[KeyP] = 'P',
|
||||
[KeyQ] = 'Q',
|
||||
[KeyR] = 'R',
|
||||
[KeyS] = 'S',
|
||||
[KeyT] = 'T',
|
||||
[KeyU] = 'U',
|
||||
[KeyV] = 'V',
|
||||
[KeyW] = 'W',
|
||||
[KeyX] = 'X',
|
||||
[KeyY] = 'Y',
|
||||
[KeyZ] = 'Z',
|
||||
[KeyLeftBracket] = '[',
|
||||
[KeyBackslash] = '\\',
|
||||
[KeyRightBracket] = ']',
|
||||
[KeyCaret] = '^',
|
||||
[KeyUnderscore] = '_',
|
||||
[KeyGrave] = '`',
|
||||
[KeyLeftCurly] = '{',
|
||||
[KeyBar] = '|',
|
||||
[KeyRightCurly] = '}',
|
||||
[KeyTilde] = '~',
|
||||
[KeyF1] = 'F1',
|
||||
[KeyF2] = 'F2',
|
||||
[KeyF3] = 'F3',
|
||||
[KeyF4] = 'F4',
|
||||
[KeyF5] = 'F5',
|
||||
[KeyF6] = 'F6',
|
||||
[KeyF7] = 'F7',
|
||||
[KeyF8] = 'F8',
|
||||
[KeyF9] = 'F9',
|
||||
[KeyF10] = 'F10',
|
||||
[KeyF11] = 'F11',
|
||||
[KeyF12] = 'F12',
|
||||
[KeyNumpad0] = 'Numpad0',
|
||||
[KeyNumpad1] = 'Numpad1',
|
||||
[KeyNumpad2] = 'Numpad2',
|
||||
[KeyNumpad3] = 'Numpad3',
|
||||
[KeyNumpad4] = 'Numpad4',
|
||||
[KeyNumpad5] = 'Numpad5',
|
||||
[KeyNumpad6] = 'Numpad6',
|
||||
[KeyNumpad7] = 'Numpad7',
|
||||
[KeyNumpad8] = 'Numpad8',
|
||||
[KeyNumpad9] = 'Numpad9',
|
||||
}
|
||||
|
||||
NetworkMessageTypes = {
|
||||
Boolean = 1,
|
||||
U8 = 2,
|
||||
U16 = 3,
|
||||
U32 = 4,
|
||||
U64 = 5,
|
||||
NumberString = 6,
|
||||
String = 7,
|
||||
Table = 8,
|
||||
}
|
||||
|
||||
SoundChannels = {
|
||||
Music = 1,
|
||||
Ambient = 2,
|
||||
Effect = 3,
|
||||
Bot = 4
|
||||
}
|
34
SabrehavenOTClient/modules/corelib/corelib.otmod
Normal file
34
SabrehavenOTClient/modules/corelib/corelib.otmod
Normal file
@@ -0,0 +1,34 @@
|
||||
Module
|
||||
name: corelib
|
||||
description: Contains core lua classes, functions and constants used by other modules
|
||||
author: OTClient team
|
||||
website: https://github.com/edubart/otclient
|
||||
reloadable: false
|
||||
|
||||
@onLoad: |
|
||||
dofile 'math'
|
||||
dofile 'string'
|
||||
dofile 'table'
|
||||
dofile 'bitwise'
|
||||
dofile 'struct'
|
||||
|
||||
dofile 'const'
|
||||
dofile 'util'
|
||||
dofile 'globals'
|
||||
dofile 'config'
|
||||
dofile 'settings'
|
||||
dofile 'keyboard'
|
||||
dofile 'mouse'
|
||||
dofile 'net'
|
||||
|
||||
dofiles 'classes'
|
||||
dofiles 'ui'
|
||||
|
||||
dofile 'inputmessage'
|
||||
dofile 'outputmessage'
|
||||
dofile 'orderedtable'
|
||||
|
||||
dofile 'base64'
|
||||
dofile 'json'
|
||||
dofile 'http'
|
||||
dofile 'test'
|
76
SabrehavenOTClient/modules/corelib/globals.lua
Normal file
76
SabrehavenOTClient/modules/corelib/globals.lua
Normal file
@@ -0,0 +1,76 @@
|
||||
-- @docvars @{
|
||||
|
||||
-- root widget
|
||||
rootWidget = g_ui.getRootWidget()
|
||||
modules = package.loaded
|
||||
|
||||
-- G is used as a global table to save variables in memory between reloads
|
||||
G = G or {}
|
||||
|
||||
-- @}
|
||||
|
||||
-- @docfuncs @{
|
||||
|
||||
function scheduleEvent(callback, delay)
|
||||
local desc = "lua"
|
||||
local info = debug.getinfo(2, "Sl")
|
||||
if info then
|
||||
desc = info.short_src .. ":" .. info.currentline
|
||||
end
|
||||
local event = g_dispatcher.scheduleEvent(desc, callback, delay)
|
||||
-- must hold a reference to the callback, otherwise it would be collected
|
||||
event._callback = callback
|
||||
return event
|
||||
end
|
||||
|
||||
function addEvent(callback, front)
|
||||
local desc = "lua"
|
||||
local info = debug.getinfo(2, "Sl")
|
||||
if info then
|
||||
desc = info.short_src .. ":" .. info.currentline
|
||||
end
|
||||
local event = g_dispatcher.addEvent(desc, callback, front)
|
||||
-- must hold a reference to the callback, otherwise it would be collected
|
||||
event._callback = callback
|
||||
return event
|
||||
end
|
||||
|
||||
function cycleEvent(callback, interval)
|
||||
local desc = "lua"
|
||||
local info = debug.getinfo(2, "Sl")
|
||||
if info then
|
||||
desc = info.short_src .. ":" .. info.currentline
|
||||
end
|
||||
local event = g_dispatcher.cycleEvent(desc, callback, interval)
|
||||
-- must hold a reference to the callback, otherwise it would be collected
|
||||
event._callback = callback
|
||||
return event
|
||||
end
|
||||
|
||||
function periodicalEvent(eventFunc, conditionFunc, delay, autoRepeatDelay)
|
||||
delay = delay or 30
|
||||
autoRepeatDelay = autoRepeatDelay or delay
|
||||
|
||||
local func
|
||||
func = function()
|
||||
if conditionFunc and not conditionFunc() then
|
||||
func = nil
|
||||
return
|
||||
end
|
||||
eventFunc()
|
||||
scheduleEvent(func, delay)
|
||||
end
|
||||
|
||||
scheduleEvent(function()
|
||||
func()
|
||||
end, autoRepeatDelay)
|
||||
end
|
||||
|
||||
function removeEvent(event)
|
||||
if event then
|
||||
event:cancel()
|
||||
event._callback = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- @}
|
278
SabrehavenOTClient/modules/corelib/http.lua
Normal file
278
SabrehavenOTClient/modules/corelib/http.lua
Normal file
@@ -0,0 +1,278 @@
|
||||
HTTP = {
|
||||
timeout=5,
|
||||
websocketTimeout=15,
|
||||
agent="Mozilla/5.0",
|
||||
imageId=1000,
|
||||
images={},
|
||||
operations={},
|
||||
}
|
||||
|
||||
function HTTP.get(url, callback)
|
||||
if not g_http or not g_http.get then
|
||||
return error("HTTP.get is not supported")
|
||||
end
|
||||
local operation = g_http.get(url, HTTP.timeout)
|
||||
HTTP.operations[operation] = {type="get", url=url, callback=callback}
|
||||
return operation
|
||||
end
|
||||
|
||||
function HTTP.getJSON(url, callback)
|
||||
if not g_http or not g_http.get then
|
||||
return error("HTTP.getJSON is not supported")
|
||||
end
|
||||
local operation = g_http.get(url, HTTP.timeout)
|
||||
HTTP.operations[operation] = {type="get", json=true, url=url, callback=callback}
|
||||
return operation
|
||||
end
|
||||
|
||||
function HTTP.post(url, data, callback)
|
||||
if not g_http or not g_http.post then
|
||||
return error("HTTP.post is not supported")
|
||||
end
|
||||
if type(data) == "table" then
|
||||
data = json.encode(data)
|
||||
end
|
||||
local operation = g_http.post(url, data, HTTP.timeout)
|
||||
HTTP.operations[operation] = {type="post", url=url, callback=callback}
|
||||
return operation
|
||||
end
|
||||
|
||||
function HTTP.postJSON(url, data, callback)
|
||||
if not g_http or not g_http.post then
|
||||
return error("HTTP.postJSON is not supported")
|
||||
end
|
||||
if type(data) == "table" then
|
||||
data = json.encode(data)
|
||||
end
|
||||
local operation = g_http.post(url, data, HTTP.timeout)
|
||||
HTTP.operations[operation] = {type="post", json=true, url=url, callback=callback}
|
||||
return operation
|
||||
end
|
||||
|
||||
function HTTP.download(url, file, callback, progressCallback)
|
||||
if not g_http or not g_http.download then
|
||||
return error("HTTP.download is not supported")
|
||||
end
|
||||
local operation = g_http.download(url, file, HTTP.timeout)
|
||||
HTTP.operations[operation] = {type="download", url=url, file=file, callback=callback, progressCallback=progressCallback}
|
||||
return operation
|
||||
end
|
||||
|
||||
function HTTP.downloadImage(url, callback)
|
||||
if not g_http or not g_http.download then
|
||||
return error("HTTP.downloadImage is not supported")
|
||||
end
|
||||
if HTTP.images[url] ~= nil then
|
||||
if callback then
|
||||
callback('/downloads/' .. HTTP.images[url], nil)
|
||||
end
|
||||
return
|
||||
end
|
||||
local file = "autoimage_" .. HTTP.imageId .. ".png"
|
||||
HTTP.imageId = HTTP.imageId + 1
|
||||
local operation = g_http.download(url, file, HTTP.timeout)
|
||||
HTTP.operations[operation] = {type="image", url=url, file=file, callback=callback}
|
||||
return operation
|
||||
end
|
||||
|
||||
function HTTP.webSocket(url, callbacks, timeout, jsonWebsocket)
|
||||
if not g_http or not g_http.ws then
|
||||
return error("WebSocket is not supported")
|
||||
end
|
||||
if not timeout or timeout < 1 then
|
||||
timeout = HTTP.websocketTimeout
|
||||
end
|
||||
local operation = g_http.ws(url, timeout)
|
||||
HTTP.operations[operation] = {type="ws", json=jsonWebsocket, url=url, callbacks=callbacks}
|
||||
return {
|
||||
id = operation,
|
||||
url = url,
|
||||
close = function()
|
||||
g_http.wsClose(operation)
|
||||
end,
|
||||
send = function(message)
|
||||
if type(message) == "table" then
|
||||
message = json.encode(message)
|
||||
end
|
||||
g_http.wsSend(operation, message)
|
||||
end
|
||||
}
|
||||
end
|
||||
HTTP.WebSocket = HTTP.webSocket
|
||||
|
||||
function HTTP.webSocketJSON(url, callbacks, timeout)
|
||||
return HTTP.webSocket(url, callbacks, timeout, true)
|
||||
end
|
||||
HTTP.WebSocketJSON = HTTP.webSocketJSON
|
||||
|
||||
function HTTP.cancel(operationId)
|
||||
if not g_http or not g_http.cancel then
|
||||
return
|
||||
end
|
||||
HTTP.operations[operationId] = nil
|
||||
return g_http.cancel(operationId)
|
||||
end
|
||||
|
||||
function HTTP.onGet(operationId, url, err, data)
|
||||
local operation = HTTP.operations[operationId]
|
||||
if operation == nil then
|
||||
return
|
||||
end
|
||||
if err and err:len() == 0 then
|
||||
err = nil
|
||||
end
|
||||
if not err and operation.json then
|
||||
local status, result = pcall(function() return json.decode(data) end)
|
||||
if not status then
|
||||
err = "JSON ERROR: " .. result
|
||||
if data and data:len() > 0 then
|
||||
err = err .. " (" .. data:sub(1, 100) .. ")"
|
||||
end
|
||||
end
|
||||
data = result
|
||||
end
|
||||
if operation.callback then
|
||||
operation.callback(data, err)
|
||||
end
|
||||
end
|
||||
|
||||
function HTTP.onGetProgress(operationId, url, progress)
|
||||
local operation = HTTP.operations[operationId]
|
||||
if operation == nil then
|
||||
return
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function HTTP.onPost(operationId, url, err, data)
|
||||
local operation = HTTP.operations[operationId]
|
||||
if operation == nil then
|
||||
return
|
||||
end
|
||||
if err and err:len() == 0 then
|
||||
err = nil
|
||||
end
|
||||
if not err and operation.json then
|
||||
local status, result = pcall(function() return json.decode(data) end)
|
||||
if not status then
|
||||
err = "JSON ERROR: " .. result
|
||||
if data and data:len() > 0 then
|
||||
err = err .. " (" .. data:sub(1, 100) .. ")"
|
||||
end
|
||||
end
|
||||
data = result
|
||||
end
|
||||
if operation.callback then
|
||||
operation.callback(data, err)
|
||||
end
|
||||
end
|
||||
|
||||
function HTTP.onPostProgress(operationId, url, progress)
|
||||
local operation = HTTP.operations[operationId]
|
||||
if operation == nil then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
function HTTP.onDownload(operationId, url, err, path, checksum)
|
||||
local operation = HTTP.operations[operationId]
|
||||
if operation == nil then
|
||||
return
|
||||
end
|
||||
if err and err:len() == 0 then
|
||||
err = nil
|
||||
end
|
||||
if operation.callback then
|
||||
if operation["type"] == "image" then
|
||||
if not err then
|
||||
HTTP.images[url] = path
|
||||
end
|
||||
operation.callback('/downloads/' .. path, err)
|
||||
else
|
||||
operation.callback(path, checksum, err)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function HTTP.onDownloadProgress(operationId, url, progress, speed)
|
||||
local operation = HTTP.operations[operationId]
|
||||
if operation == nil then
|
||||
return
|
||||
end
|
||||
if operation.progressCallback then
|
||||
operation.progressCallback(progress, speed)
|
||||
end
|
||||
end
|
||||
|
||||
function HTTP.onWsOpen(operationId, message)
|
||||
local operation = HTTP.operations[operationId]
|
||||
if operation == nil then
|
||||
return
|
||||
end
|
||||
if operation.callbacks.onOpen then
|
||||
operation.callbacks.onOpen(message, operationId)
|
||||
end
|
||||
end
|
||||
|
||||
function HTTP.onWsMessage(operationId, message)
|
||||
local operation = HTTP.operations[operationId]
|
||||
if operation == nil then
|
||||
return
|
||||
end
|
||||
if operation.callbacks.onMessage then
|
||||
if operation.json then
|
||||
local status, result = pcall(function() return json.decode(message) end)
|
||||
local err = nil
|
||||
if not status then
|
||||
err = "JSON ERROR: " .. result
|
||||
if message and message:len() > 0 then
|
||||
err = err .. " (" .. message:sub(1, 100) .. ")"
|
||||
end
|
||||
end
|
||||
if err then
|
||||
if operation.callbacks.onError then
|
||||
operation.callbacks.onError(err, operationId)
|
||||
end
|
||||
else
|
||||
operation.callbacks.onMessage(result, operationId)
|
||||
end
|
||||
else
|
||||
operation.callbacks.onMessage(message, operationId)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function HTTP.onWsClose(operationId, message)
|
||||
local operation = HTTP.operations[operationId]
|
||||
if operation == nil then
|
||||
return
|
||||
end
|
||||
if operation.callbacks.onClose then
|
||||
operation.callbacks.onClose(message, operationId)
|
||||
end
|
||||
end
|
||||
|
||||
function HTTP.onWsError(operationId, message)
|
||||
local operation = HTTP.operations[operationId]
|
||||
if operation == nil then
|
||||
return
|
||||
end
|
||||
if operation.callbacks.onError then
|
||||
operation.callbacks.onError(message, operationId)
|
||||
end
|
||||
end
|
||||
|
||||
connect(g_http,
|
||||
{
|
||||
onGet = HTTP.onGet,
|
||||
onGetProgress = HTTP.onGetProgress,
|
||||
onPost = HTTP.onPost,
|
||||
onPostProgress = HTTP.onPostProgress,
|
||||
onDownload = HTTP.onDownload,
|
||||
onDownloadProgress = HTTP.onDownloadProgress,
|
||||
onWsOpen = HTTP.onWsOpen,
|
||||
onWsMessage = HTTP.onWsMessage,
|
||||
onWsClose = HTTP.onWsClose,
|
||||
onWsError = HTTP.onWsError,
|
||||
})
|
||||
g_http.setUserAgent(HTTP.agent)
|
51
SabrehavenOTClient/modules/corelib/inputmessage.lua
Normal file
51
SabrehavenOTClient/modules/corelib/inputmessage.lua
Normal file
@@ -0,0 +1,51 @@
|
||||
function InputMessage:getData()
|
||||
local dataType = self:getU8()
|
||||
if dataType == NetworkMessageTypes.Boolean then
|
||||
return numbertoboolean(self:getU8())
|
||||
elseif dataType == NetworkMessageTypes.U8 then
|
||||
return self:getU8()
|
||||
elseif dataType == NetworkMessageTypes.U16 then
|
||||
return self:getU16()
|
||||
elseif dataType == NetworkMessageTypes.U32 then
|
||||
return self:getU32()
|
||||
elseif dataType == NetworkMessageTypes.U64 then
|
||||
return self:getU64()
|
||||
elseif dataType == NetworkMessageTypes.NumberString then
|
||||
return tonumber(self:getString())
|
||||
elseif dataType == NetworkMessageTypes.String then
|
||||
return self:getString()
|
||||
elseif dataType == NetworkMessageTypes.Table then
|
||||
return self:getTable()
|
||||
else
|
||||
perror('Unknown data type ' .. dataType)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function InputMessage:getTable()
|
||||
local ret = {}
|
||||
local size = self:getU16()
|
||||
for i=1,size do
|
||||
local index = self:getData()
|
||||
local value = self:getData()
|
||||
ret[index] = value
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function InputMessage:getColor()
|
||||
local color = {}
|
||||
color.r = self:getU8()
|
||||
color.g = self:getU8()
|
||||
color.b = self:getU8()
|
||||
color.a = self:getU8()
|
||||
return color
|
||||
end
|
||||
|
||||
function InputMessage:getPosition()
|
||||
local position = {}
|
||||
position.x = self:getU16()
|
||||
position.y = self:getU16()
|
||||
position.z = self:getU8()
|
||||
return position
|
||||
end
|
419
SabrehavenOTClient/modules/corelib/json.lua
Normal file
419
SabrehavenOTClient/modules/corelib/json.lua
Normal file
@@ -0,0 +1,419 @@
|
||||
--
|
||||
-- json.lua
|
||||
--
|
||||
-- Copyright (c) 2019 rxi
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
-- of the Software, and to permit persons to whom the Software is furnished to do
|
||||
-- so, subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all
|
||||
-- copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
-- SOFTWARE.
|
||||
--
|
||||
|
||||
json = { _version = "0.1.1" }
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Encode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local encode
|
||||
|
||||
local escape_char_map = {
|
||||
[ "\\" ] = "\\\\",
|
||||
[ "\"" ] = "\\\"",
|
||||
[ "\b" ] = "\\b",
|
||||
[ "\f" ] = "\\f",
|
||||
[ "\n" ] = "\\n",
|
||||
[ "\r" ] = "\\r",
|
||||
[ "\t" ] = "\\t",
|
||||
}
|
||||
|
||||
local escape_char_map_inv = { [ "\\/" ] = "/" }
|
||||
for k, v in pairs(escape_char_map) do
|
||||
escape_char_map_inv[v] = k
|
||||
end
|
||||
|
||||
|
||||
local function make_indent(state)
|
||||
return string.rep(" ", state.currentIndentLevel * state.indent)
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return escape_char_map[c] or string.format("\\u%04x", c:byte())
|
||||
end
|
||||
|
||||
|
||||
local function encode_nil()
|
||||
return "null"
|
||||
end
|
||||
|
||||
|
||||
local function encode_table(val, state)
|
||||
local res = {}
|
||||
local stack = state.stack
|
||||
local pretty = state.indent > 0
|
||||
|
||||
local close_indent = make_indent(state)
|
||||
local comma = pretty and ",\n" or ","
|
||||
local colon = pretty and ": " or ":"
|
||||
local open_brace = pretty and "{\n" or "{"
|
||||
local close_brace = pretty and ("\n" .. close_indent .. "}") or "}"
|
||||
local open_bracket = pretty and "[\n" or "["
|
||||
local close_bracket = pretty and ("\n" .. close_indent .. "]") or "]"
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if rawget(val, 1) ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if n ~= #val then
|
||||
error("invalid table: sparse array")
|
||||
end
|
||||
-- Encode
|
||||
for _, v in ipairs(val) do
|
||||
state.currentIndentLevel = state.currentIndentLevel + 1
|
||||
table.insert(res, make_indent(state) .. encode(v, state))
|
||||
state.currentIndentLevel = state.currentIndentLevel - 1
|
||||
end
|
||||
stack[val] = nil
|
||||
return open_bracket .. table.concat(res, comma) .. close_bracket
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
state.currentIndentLevel = state.currentIndentLevel + 1
|
||||
table.insert(res, make_indent(state) .. encode(k, state) .. colon .. encode(v, state))
|
||||
state.currentIndentLevel = state.currentIndentLevel - 1
|
||||
end
|
||||
stack[val] = nil
|
||||
return open_brace .. table.concat(res, comma) .. close_brace
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function encode_string(val)
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
|
||||
local function encode_number(val)
|
||||
-- Check for NaN, -inf and inf
|
||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||
error("unexpected number value '" .. tostring(val) .. "'")
|
||||
end
|
||||
return string.format("%.14g", val)
|
||||
end
|
||||
|
||||
|
||||
local type_func_map = {
|
||||
[ "nil" ] = encode_nil,
|
||||
[ "table" ] = encode_table,
|
||||
[ "string" ] = encode_string,
|
||||
[ "number" ] = encode_number,
|
||||
[ "boolean" ] = tostring,
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, state)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, state)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
function json.encode(val, indent)
|
||||
local state = {
|
||||
indent = indent or 0,
|
||||
currentIndentLevel = 0,
|
||||
stack = {}
|
||||
}
|
||||
return encode(val, state)
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Decode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local parse
|
||||
|
||||
local function create_set(...)
|
||||
local res = {}
|
||||
for i = 1, select("#", ...) do
|
||||
res[ select(i, ...) ] = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||
local literals = create_set("true", "false", "null")
|
||||
|
||||
local literal_map = {
|
||||
[ "true" ] = true,
|
||||
[ "false" ] = false,
|
||||
[ "null" ] = nil,
|
||||
}
|
||||
|
||||
|
||||
local function next_char(str, idx, set, negate)
|
||||
for i = idx, #str do
|
||||
if set[str:sub(i, i)] ~= negate then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #str + 1
|
||||
end
|
||||
|
||||
|
||||
local function decode_error(str, idx, msg)
|
||||
local line_count = 1
|
||||
local col_count = 1
|
||||
for i = 1, idx - 1 do
|
||||
col_count = col_count + 1
|
||||
if str:sub(i, i) == "\n" then
|
||||
line_count = line_count + 1
|
||||
col_count = 1
|
||||
end
|
||||
end
|
||||
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
||||
end
|
||||
|
||||
|
||||
local function codepoint_to_utf8(n)
|
||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||
local f = math.floor
|
||||
if n <= 0x7f then
|
||||
return string.char(n)
|
||||
elseif n <= 0x7ff then
|
||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||
elseif n <= 0xffff then
|
||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
elseif n <= 0x10ffff then
|
||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
end
|
||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||
end
|
||||
|
||||
|
||||
local function parse_unicode_escape(s)
|
||||
local n1 = tonumber( s:sub(3, 6), 16 )
|
||||
local n2 = tonumber( s:sub(9, 12), 16 )
|
||||
-- Surrogate pair?
|
||||
if n2 then
|
||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||
else
|
||||
return codepoint_to_utf8(n1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function parse_string(str, i)
|
||||
local has_unicode_escape = false
|
||||
local has_surrogate_escape = false
|
||||
local has_escape = false
|
||||
local last
|
||||
for j = i + 1, #str do
|
||||
local x = str:byte(j)
|
||||
|
||||
if x < 32 then
|
||||
decode_error(str, j, "control character in string")
|
||||
end
|
||||
|
||||
if last == 92 then -- "\\" (escape char)
|
||||
if x == 117 then -- "u" (unicode escape sequence)
|
||||
local hex = str:sub(j + 1, j + 5)
|
||||
if not hex:find("%x%x%x%x") then
|
||||
decode_error(str, j, "invalid unicode escape in string")
|
||||
end
|
||||
if hex:find("^[dD][89aAbB]") then
|
||||
has_surrogate_escape = true
|
||||
else
|
||||
has_unicode_escape = true
|
||||
end
|
||||
else
|
||||
local c = string.char(x)
|
||||
if not escape_chars[c] then
|
||||
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
|
||||
end
|
||||
has_escape = true
|
||||
end
|
||||
last = nil
|
||||
|
||||
elseif x == 34 then -- '"' (end of string)
|
||||
local s = str:sub(i + 1, j - 1)
|
||||
if has_surrogate_escape then
|
||||
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
|
||||
end
|
||||
if has_unicode_escape then
|
||||
s = s:gsub("\\u....", parse_unicode_escape)
|
||||
end
|
||||
if has_escape then
|
||||
s = s:gsub("\\.", escape_char_map_inv)
|
||||
end
|
||||
return s, j + 1
|
||||
|
||||
else
|
||||
last = x
|
||||
end
|
||||
end
|
||||
decode_error(str, i, "expected closing quote for string")
|
||||
end
|
||||
|
||||
|
||||
local function parse_number(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local s = str:sub(i, x - 1)
|
||||
local n = tonumber(s)
|
||||
if not n then
|
||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||
end
|
||||
return n, x
|
||||
end
|
||||
|
||||
|
||||
local function parse_literal(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local word = str:sub(i, x - 1)
|
||||
if not literals[word] then
|
||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||
end
|
||||
return literal_map[word], x
|
||||
end
|
||||
|
||||
|
||||
local function parse_array(str, i)
|
||||
local res = {}
|
||||
local n = 1
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local x
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of array?
|
||||
if str:sub(i, i) == "]" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read token
|
||||
x, i = parse(str, i)
|
||||
res[n] = x
|
||||
n = n + 1
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "]" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local function parse_object(str, i)
|
||||
local res = {}
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local key, val
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of object?
|
||||
if str:sub(i, i) == "}" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read key
|
||||
if str:sub(i, i) ~= '"' then
|
||||
decode_error(str, i, "expected string for key")
|
||||
end
|
||||
key, i = parse(str, i)
|
||||
-- Read ':' delimiter
|
||||
i = next_char(str, i, space_chars, true)
|
||||
if str:sub(i, i) ~= ":" then
|
||||
decode_error(str, i, "expected ':' after key")
|
||||
end
|
||||
i = next_char(str, i + 1, space_chars, true)
|
||||
-- Read value
|
||||
val, i = parse(str, i)
|
||||
-- Set
|
||||
res[key] = val
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "}" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local char_func_map = {
|
||||
[ '"' ] = parse_string,
|
||||
[ "0" ] = parse_number,
|
||||
[ "1" ] = parse_number,
|
||||
[ "2" ] = parse_number,
|
||||
[ "3" ] = parse_number,
|
||||
[ "4" ] = parse_number,
|
||||
[ "5" ] = parse_number,
|
||||
[ "6" ] = parse_number,
|
||||
[ "7" ] = parse_number,
|
||||
[ "8" ] = parse_number,
|
||||
[ "9" ] = parse_number,
|
||||
[ "-" ] = parse_number,
|
||||
[ "t" ] = parse_literal,
|
||||
[ "f" ] = parse_literal,
|
||||
[ "n" ] = parse_literal,
|
||||
[ "[" ] = parse_array,
|
||||
[ "{" ] = parse_object,
|
||||
}
|
||||
|
||||
|
||||
parse = function(str, idx)
|
||||
local chr = str:sub(idx, idx)
|
||||
local f = char_func_map[chr]
|
||||
if f then
|
||||
return f(str, idx)
|
||||
end
|
||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.decode(str)
|
||||
if type(str) ~= "string" then
|
||||
error("expected argument of type string, got " .. type(str))
|
||||
end
|
||||
local res, idx = parse(str, next_char(str, 1, space_chars, true))
|
||||
idx = next_char(str, idx, space_chars, true)
|
||||
if idx <= #str then
|
||||
decode_error(str, idx, "trailing garbage")
|
||||
end
|
||||
return res
|
||||
end
|
251
SabrehavenOTClient/modules/corelib/keyboard.lua
Normal file
251
SabrehavenOTClient/modules/corelib/keyboard.lua
Normal file
@@ -0,0 +1,251 @@
|
||||
-- @docclass
|
||||
g_keyboard = {}
|
||||
|
||||
-- private functions
|
||||
function translateKeyCombo(keyCombo)
|
||||
if not keyCombo or #keyCombo == 0 then return nil end
|
||||
local keyComboDesc = ''
|
||||
for k,v in pairs(keyCombo) do
|
||||
local keyDesc = KeyCodeDescs[v]
|
||||
if keyDesc == nil then return nil end
|
||||
keyComboDesc = keyComboDesc .. '+' .. keyDesc
|
||||
end
|
||||
keyComboDesc = keyComboDesc:sub(2)
|
||||
return keyComboDesc
|
||||
end
|
||||
|
||||
local function getKeyCode(key)
|
||||
for keyCode, keyDesc in pairs(KeyCodeDescs) do
|
||||
if keyDesc:lower() == key:trim():lower() then
|
||||
return keyCode
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function retranslateKeyComboDesc(keyComboDesc)
|
||||
if keyComboDesc == nil then
|
||||
error('Unable to translate key combo \'' .. keyComboDesc .. '\'')
|
||||
end
|
||||
|
||||
if type(keyComboDesc) == 'number' then
|
||||
keyComboDesc = tostring(keyComboDesc)
|
||||
end
|
||||
|
||||
local keyCombo = {}
|
||||
for i,currentKeyDesc in ipairs(keyComboDesc:split('+')) do
|
||||
for keyCode, keyDesc in pairs(KeyCodeDescs) do
|
||||
if keyDesc:lower() == currentKeyDesc:trim():lower() then
|
||||
table.insert(keyCombo, keyCode)
|
||||
end
|
||||
end
|
||||
end
|
||||
return translateKeyCombo(keyCombo)
|
||||
end
|
||||
|
||||
function determineKeyComboDesc(keyCode, keyboardModifiers)
|
||||
local keyCombo = {}
|
||||
if keyCode == KeyCtrl or keyCode == KeyShift or keyCode == KeyAlt then
|
||||
table.insert(keyCombo, keyCode)
|
||||
elseif KeyCodeDescs[keyCode] ~= nil then
|
||||
if keyboardModifiers == KeyboardCtrlModifier then
|
||||
table.insert(keyCombo, KeyCtrl)
|
||||
elseif keyboardModifiers == KeyboardAltModifier then
|
||||
table.insert(keyCombo, KeyAlt)
|
||||
elseif keyboardModifiers == KeyboardCtrlAltModifier then
|
||||
table.insert(keyCombo, KeyCtrl)
|
||||
table.insert(keyCombo, KeyAlt)
|
||||
elseif keyboardModifiers == KeyboardShiftModifier then
|
||||
table.insert(keyCombo, KeyShift)
|
||||
elseif keyboardModifiers == KeyboardCtrlShiftModifier then
|
||||
table.insert(keyCombo, KeyCtrl)
|
||||
table.insert(keyCombo, KeyShift)
|
||||
elseif keyboardModifiers == KeyboardAltShiftModifier then
|
||||
table.insert(keyCombo, KeyAlt)
|
||||
table.insert(keyCombo, KeyShift)
|
||||
elseif keyboardModifiers == KeyboardCtrlAltShiftModifier then
|
||||
table.insert(keyCombo, KeyCtrl)
|
||||
table.insert(keyCombo, KeyAlt)
|
||||
table.insert(keyCombo, KeyShift)
|
||||
end
|
||||
table.insert(keyCombo, keyCode)
|
||||
end
|
||||
return translateKeyCombo(keyCombo)
|
||||
end
|
||||
|
||||
local function onWidgetKeyDown(widget, keyCode, keyboardModifiers)
|
||||
if keyCode == KeyUnknown then return false end
|
||||
local callback = widget.boundAloneKeyDownCombos[determineKeyComboDesc(keyCode, KeyboardNoModifier)]
|
||||
signalcall(callback, widget, keyCode)
|
||||
callback = widget.boundKeyDownCombos[determineKeyComboDesc(keyCode, keyboardModifiers)]
|
||||
return signalcall(callback, widget, keyCode)
|
||||
end
|
||||
|
||||
local function onWidgetKeyUp(widget, keyCode, keyboardModifiers)
|
||||
if keyCode == KeyUnknown then return false end
|
||||
local callback = widget.boundAloneKeyUpCombos[determineKeyComboDesc(keyCode, KeyboardNoModifier)]
|
||||
signalcall(callback, widget, keyCode)
|
||||
callback = widget.boundKeyUpCombos[determineKeyComboDesc(keyCode, keyboardModifiers)]
|
||||
return signalcall(callback, widget, keyCode)
|
||||
end
|
||||
|
||||
local function onWidgetKeyPress(widget, keyCode, keyboardModifiers, autoRepeatTicks)
|
||||
if keyCode == KeyUnknown then return false end
|
||||
local callback = widget.boundKeyPressCombos[determineKeyComboDesc(keyCode, keyboardModifiers)]
|
||||
return signalcall(callback, widget, keyCode, autoRepeatTicks)
|
||||
end
|
||||
|
||||
local function connectKeyDownEvent(widget)
|
||||
if widget.boundKeyDownCombos then return end
|
||||
connect(widget, { onKeyDown = onWidgetKeyDown })
|
||||
widget.boundKeyDownCombos = {}
|
||||
widget.boundAloneKeyDownCombos = {}
|
||||
end
|
||||
|
||||
local function connectKeyUpEvent(widget)
|
||||
if widget.boundKeyUpCombos then return end
|
||||
connect(widget, { onKeyUp = onWidgetKeyUp })
|
||||
widget.boundKeyUpCombos = {}
|
||||
widget.boundAloneKeyUpCombos = {}
|
||||
end
|
||||
|
||||
local function connectKeyPressEvent(widget)
|
||||
if widget.boundKeyPressCombos then return end
|
||||
connect(widget, { onKeyPress = onWidgetKeyPress })
|
||||
widget.boundKeyPressCombos = {}
|
||||
end
|
||||
|
||||
-- public functions
|
||||
function g_keyboard.bindKeyDown(keyComboDesc, callback, widget, alone)
|
||||
widget = widget or rootWidget
|
||||
connectKeyDownEvent(widget)
|
||||
local keyComboDesc = retranslateKeyComboDesc(keyComboDesc)
|
||||
if alone then
|
||||
connect(widget.boundAloneKeyDownCombos, keyComboDesc, callback)
|
||||
else
|
||||
connect(widget.boundKeyDownCombos, keyComboDesc, callback)
|
||||
end
|
||||
end
|
||||
|
||||
function g_keyboard.bindKeyUp(keyComboDesc, callback, widget, alone)
|
||||
widget = widget or rootWidget
|
||||
connectKeyUpEvent(widget)
|
||||
local keyComboDesc = retranslateKeyComboDesc(keyComboDesc)
|
||||
if alone then
|
||||
connect(widget.boundAloneKeyUpCombos, keyComboDesc, callback)
|
||||
else
|
||||
connect(widget.boundKeyUpCombos, keyComboDesc, callback)
|
||||
end
|
||||
end
|
||||
|
||||
function g_keyboard.bindKeyPress(keyComboDesc, callback, widget)
|
||||
widget = widget or rootWidget
|
||||
connectKeyPressEvent(widget)
|
||||
local keyComboDesc = retranslateKeyComboDesc(keyComboDesc)
|
||||
connect(widget.boundKeyPressCombos, keyComboDesc, callback)
|
||||
end
|
||||
|
||||
local function getUnbindArgs(arg1, arg2)
|
||||
local callback
|
||||
local widget
|
||||
if type(arg1) == 'function' then callback = arg1
|
||||
elseif type(arg2) == 'function' then callback = arg2 end
|
||||
if type(arg1) == 'userdata' then widget = arg1
|
||||
elseif type(arg2) == 'userdata' then widget = arg2 end
|
||||
widget = widget or rootWidget
|
||||
return callback, widget
|
||||
end
|
||||
|
||||
function g_keyboard.unbindKeyDown(keyComboDesc, arg1, arg2)
|
||||
local callback, widget = getUnbindArgs(arg1, arg2)
|
||||
if widget.boundKeyDownCombos == nil then return end
|
||||
local keyComboDesc = retranslateKeyComboDesc(keyComboDesc)
|
||||
disconnect(widget.boundKeyDownCombos, keyComboDesc, callback)
|
||||
end
|
||||
|
||||
function g_keyboard.unbindKeyUp(keyComboDesc, arg1, arg2)
|
||||
local callback, widget = getUnbindArgs(arg1, arg2)
|
||||
if widget.boundKeyUpCombos == nil then return end
|
||||
local keyComboDesc = retranslateKeyComboDesc(keyComboDesc)
|
||||
disconnect(widget.boundKeyUpCombos, keyComboDesc, callback)
|
||||
end
|
||||
|
||||
function g_keyboard.unbindKeyPress(keyComboDesc, arg1, arg2)
|
||||
local callback, widget = getUnbindArgs(arg1, arg2)
|
||||
if widget.boundKeyPressCombos == nil then return end
|
||||
local keyComboDesc = retranslateKeyComboDesc(keyComboDesc)
|
||||
disconnect(widget.boundKeyPressCombos, keyComboDesc, callback)
|
||||
end
|
||||
|
||||
function g_keyboard.getModifiers()
|
||||
return g_window.getKeyboardModifiers()
|
||||
end
|
||||
|
||||
function g_keyboard.isKeyPressed(key)
|
||||
if type(key) == 'string' then
|
||||
key = getKeyCode(key)
|
||||
end
|
||||
return g_window.isKeyPressed(key)
|
||||
end
|
||||
|
||||
function g_keyboard.areKeysPressed(keyComboDesc)
|
||||
for i,currentKeyDesc in ipairs(keyComboDesc:split('+')) do
|
||||
for keyCode, keyDesc in pairs(KeyCodeDescs) do
|
||||
if keyDesc:lower() == currentKeyDesc:trim():lower() then
|
||||
if keyDesc:lower() == "ctrl" then
|
||||
if not g_keyboard.isCtrlPressed() then
|
||||
return false
|
||||
end
|
||||
elseif keyDesc:lower() == "shift" then
|
||||
if not g_keyboard.isShiftPressed() then
|
||||
return false
|
||||
end
|
||||
elseif keyDesc:lower() == "alt" then
|
||||
if not g_keyboard.isAltPressed() then
|
||||
return false
|
||||
end
|
||||
elseif not g_window.isKeyPressed(keyCode) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function g_keyboard.isKeySetPressed(keys, all)
|
||||
all = all or false
|
||||
local result = {}
|
||||
for k,v in pairs(keys) do
|
||||
if type(v) == 'string' then
|
||||
v = getKeyCode(v)
|
||||
end
|
||||
if g_window.isKeyPressed(v) then
|
||||
if not all then
|
||||
return true
|
||||
end
|
||||
table.insert(result, true)
|
||||
end
|
||||
end
|
||||
return #result == #keys
|
||||
end
|
||||
|
||||
function g_keyboard.isInUse()
|
||||
for i = FirstKey, LastKey do
|
||||
if g_window.isKeyPressed(key) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function g_keyboard.isCtrlPressed()
|
||||
return bit32.band(g_window.getKeyboardModifiers(), KeyboardCtrlModifier) ~= 0
|
||||
end
|
||||
|
||||
function g_keyboard.isAltPressed()
|
||||
return bit32.band(g_window.getKeyboardModifiers(), KeyboardAltModifier) ~= 0
|
||||
end
|
||||
|
||||
function g_keyboard.isShiftPressed()
|
||||
return bit32.band(g_window.getKeyboardModifiers(), KeyboardShiftModifier) ~= 0
|
||||
end
|
35
SabrehavenOTClient/modules/corelib/math.lua
Normal file
35
SabrehavenOTClient/modules/corelib/math.lua
Normal file
@@ -0,0 +1,35 @@
|
||||
-- @docclass math
|
||||
|
||||
local U8 = 2^8
|
||||
local U16 = 2^16
|
||||
local U32 = 2^32
|
||||
local U64 = 2^64
|
||||
|
||||
function math.round(num, idp)
|
||||
local mult = 10^(idp or 0)
|
||||
if num >= 0 then
|
||||
return math.floor(num * mult + 0.5) / mult
|
||||
else
|
||||
return math.ceil(num * mult - 0.5) / mult
|
||||
end
|
||||
end
|
||||
|
||||
function math.isu8(num)
|
||||
return math.isinteger(num) and num >= 0 and num < U8
|
||||
end
|
||||
|
||||
function math.isu16(num)
|
||||
return math.isinteger(num) and num >= U8 and num < U16
|
||||
end
|
||||
|
||||
function math.isu32(num)
|
||||
return math.isinteger(num) and num >= U16 and num < U32
|
||||
end
|
||||
|
||||
function math.isu64(num)
|
||||
return math.isinteger(num) and num >= U32 and num < U64
|
||||
end
|
||||
|
||||
function math.isinteger(num)
|
||||
return ((type(num) == 'number') and (num == math.floor(num)))
|
||||
end
|
36
SabrehavenOTClient/modules/corelib/mouse.lua
Normal file
36
SabrehavenOTClient/modules/corelib/mouse.lua
Normal file
@@ -0,0 +1,36 @@
|
||||
-- @docclass
|
||||
function g_mouse.bindAutoPress(widget, callback, delay, button)
|
||||
local button = button or MouseLeftButton
|
||||
connect(widget, { onMousePress = function(widget, mousePos, mouseButton)
|
||||
if mouseButton ~= button then
|
||||
return false
|
||||
end
|
||||
local startTime = g_clock.millis()
|
||||
callback(widget, mousePos, mouseButton, 0)
|
||||
periodicalEvent(function()
|
||||
callback(widget, g_window.getMousePosition(), mouseButton, g_clock.millis() - startTime)
|
||||
end, function()
|
||||
return g_mouse.isPressed(mouseButton)
|
||||
end, 30, delay)
|
||||
return true
|
||||
end })
|
||||
end
|
||||
|
||||
function g_mouse.bindPressMove(widget, callback)
|
||||
connect(widget, { onMouseMove = function(widget, mousePos, mouseMoved)
|
||||
if widget:isPressed() then
|
||||
callback(mousePos, mouseMoved)
|
||||
return true
|
||||
end
|
||||
end })
|
||||
end
|
||||
|
||||
function g_mouse.bindPress(widget, callback, button)
|
||||
connect(widget, { onMousePress = function(widget, mousePos, mouseButton)
|
||||
if not button or button == mouseButton then
|
||||
callback(mousePos, mouseButton)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end })
|
||||
end
|
16
SabrehavenOTClient/modules/corelib/net.lua
Normal file
16
SabrehavenOTClient/modules/corelib/net.lua
Normal file
@@ -0,0 +1,16 @@
|
||||
function translateNetworkError(errcode, connecting, errdesc)
|
||||
local text
|
||||
if errcode == 111 then
|
||||
text = tr('Connection refused, the server might be offline or restarting.\nPlease try again later.')
|
||||
elseif errcode == 110 then
|
||||
text = tr('Connection timed out. Either your network is failing or the server is offline.')
|
||||
elseif errcode == 1 then
|
||||
text = tr('Connection failed, the server address does not exist.')
|
||||
elseif connecting then
|
||||
text = tr('Connection failed.')
|
||||
else
|
||||
text = tr('Your connection has been lost.\nEither your network or the server went down.')
|
||||
end
|
||||
text = text .. ' ' .. tr('(ERROR %d)', errcode)
|
||||
return text
|
||||
end
|
43
SabrehavenOTClient/modules/corelib/orderedtable.lua
Normal file
43
SabrehavenOTClient/modules/corelib/orderedtable.lua
Normal file
@@ -0,0 +1,43 @@
|
||||
function __genOrderedIndex( t )
|
||||
local orderedIndex = {}
|
||||
for key in pairs(t) do
|
||||
table.insert( orderedIndex, key )
|
||||
end
|
||||
table.sort( orderedIndex )
|
||||
return orderedIndex
|
||||
end
|
||||
|
||||
function orderedNext(t, state)
|
||||
-- Equivalent of the next function, but returns the keys in the alphabetic
|
||||
-- order. We use a temporary ordered key table that is stored in the
|
||||
-- table being iterated.
|
||||
|
||||
local key = nil
|
||||
--print("orderedNext: state = "..tostring(state) )
|
||||
if state == nil then
|
||||
-- the first time, generate the index
|
||||
t.__orderedIndex = __genOrderedIndex( t )
|
||||
key = t.__orderedIndex[1]
|
||||
else
|
||||
-- fetch the next value
|
||||
for i = 1,table.getn(t.__orderedIndex) do
|
||||
if t.__orderedIndex[i] == state then
|
||||
key = t.__orderedIndex[i+1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if key then
|
||||
return key, t[key]
|
||||
end
|
||||
|
||||
-- no more value to return, cleanup
|
||||
t.__orderedIndex = nil
|
||||
return
|
||||
end
|
||||
|
||||
function orderedPairs(t)
|
||||
-- Equivalent of the pairs() function on tables. Allows to iterate
|
||||
-- in order
|
||||
return orderedNext, t, nil
|
||||
end
|
69
SabrehavenOTClient/modules/corelib/outputmessage.lua
Normal file
69
SabrehavenOTClient/modules/corelib/outputmessage.lua
Normal file
@@ -0,0 +1,69 @@
|
||||
function OutputMessage:addData(data)
|
||||
if type(data) == 'boolean' then
|
||||
self:addU8(NetworkMessageTypes.Boolean)
|
||||
self:addU8(booleantonumber(data))
|
||||
elseif type(data) == 'number' then
|
||||
if math.isu8(data) then
|
||||
self:addU8(NetworkMessageTypes.U8)
|
||||
self:addU8(data)
|
||||
elseif math.isu16(data) then
|
||||
self:addU8(NetworkMessageTypes.U16)
|
||||
self:addU16(data)
|
||||
elseif math.isu32(data) then
|
||||
self:addU8(NetworkMessageTypes.U32)
|
||||
self:addU32(data)
|
||||
elseif math.isu64(data) then
|
||||
self:addU8(NetworkMessageTypes.U64)
|
||||
self:addU64(data)
|
||||
else -- negative or non integer numbers
|
||||
self:addU8(NetworkMessageTypes.NumberString)
|
||||
self:addString(tostring(data))
|
||||
end
|
||||
elseif type(data) == 'string' then
|
||||
self:addU8(NetworkMessageTypes.String)
|
||||
self:addString(data)
|
||||
elseif type(data) == 'table' then
|
||||
self:addU8(NetworkMessageTypes.Table)
|
||||
self:addTable(data)
|
||||
else
|
||||
perror('Invalid data type ' .. type(data))
|
||||
end
|
||||
end
|
||||
|
||||
function OutputMessage:addTable(data)
|
||||
local size = 0
|
||||
|
||||
-- reserve for size (should be addData, find a way to use it further)
|
||||
local sizePos = self:getWritePos()
|
||||
self:addU16(size)
|
||||
local sizeSize = self:getWritePos() - sizePos
|
||||
|
||||
-- add values
|
||||
for key,value in pairs(data) do
|
||||
self:addData(key)
|
||||
self:addData(value)
|
||||
size = size + 1
|
||||
end
|
||||
|
||||
-- write size
|
||||
local currentPos = self:getWritePos()
|
||||
self:setWritePos(sizePos)
|
||||
self:addU16(size)
|
||||
|
||||
-- fix msg size and go back to end
|
||||
self:setMessageSize(self:getMessageSize() - sizeSize)
|
||||
self:setWritePos(currentPos)
|
||||
end
|
||||
|
||||
function OutputMessage:addColor(color)
|
||||
self:addU8(color.r)
|
||||
self:addU8(color.g)
|
||||
self:addU8(color.b)
|
||||
self:addU8(color.a)
|
||||
end
|
||||
|
||||
function OutputMessage:addPosition(position)
|
||||
self:addU16(position.x)
|
||||
self:addU16(position.y)
|
||||
self:addU8(position.z)
|
||||
end
|
3
SabrehavenOTClient/modules/corelib/settings.lua
Normal file
3
SabrehavenOTClient/modules/corelib/settings.lua
Normal file
@@ -0,0 +1,3 @@
|
||||
g_settings = makesingleton(g_configs.getSettings())
|
||||
|
||||
-- Reserved for future functionality
|
59
SabrehavenOTClient/modules/corelib/string.lua
Normal file
59
SabrehavenOTClient/modules/corelib/string.lua
Normal file
@@ -0,0 +1,59 @@
|
||||
-- @docclass string
|
||||
|
||||
function string:split(delim)
|
||||
local start = 1
|
||||
local results = {}
|
||||
while true do
|
||||
local pos = string.find(self, delim, start, true)
|
||||
if not pos then
|
||||
break
|
||||
end
|
||||
table.insert(results, string.sub(self, start, pos-1))
|
||||
start = pos + string.len(delim)
|
||||
end
|
||||
table.insert(results, string.sub(self, start))
|
||||
table.removevalue(results, '')
|
||||
return results
|
||||
end
|
||||
|
||||
function string:starts(start)
|
||||
return string.sub(self, 1, #start) == start
|
||||
end
|
||||
|
||||
function string:ends(test)
|
||||
return test =='' or string.sub(self,-string.len(test)) == test
|
||||
end
|
||||
|
||||
function string:trim()
|
||||
return string.match(self, '^%s*(.*%S)') or ''
|
||||
end
|
||||
|
||||
function string:explode(sep, limit)
|
||||
if type(sep) ~= 'string' or tostring(self):len() == 0 or sep:len() == 0 then
|
||||
return {}
|
||||
end
|
||||
|
||||
local i, pos, tmp, t = 0, 1, "", {}
|
||||
for s, e in function() return string.find(self, sep, pos) end do
|
||||
tmp = self:sub(pos, s - 1):trim()
|
||||
table.insert(t, tmp)
|
||||
pos = e + 1
|
||||
|
||||
i = i + 1
|
||||
if limit ~= nil and i == limit then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
tmp = self:sub(pos):trim()
|
||||
table.insert(t, tmp)
|
||||
return t
|
||||
end
|
||||
|
||||
function string:contains(str, checkCase, start, plain)
|
||||
if(not checkCase) then
|
||||
self = self:lower()
|
||||
str = str:lower()
|
||||
end
|
||||
return string.find(self, str, start and start or 1, plain == nil and true or false)
|
||||
end
|
173
SabrehavenOTClient/modules/corelib/struct.lua
Normal file
173
SabrehavenOTClient/modules/corelib/struct.lua
Normal file
@@ -0,0 +1,173 @@
|
||||
Struct = {}
|
||||
|
||||
function Struct.pack(format, ...)
|
||||
local stream = {}
|
||||
local vars = {...}
|
||||
local endianness = true
|
||||
|
||||
for i = 1, format:len() do
|
||||
local opt = format:sub(i, i)
|
||||
|
||||
if opt == '>' then
|
||||
endianness = false
|
||||
elseif opt:find('[bBhHiIlL]') then
|
||||
local n = opt:find('[hH]') and 2 or opt:find('[iI]') and 4 or opt:find('[lL]') and 8 or 1
|
||||
local val = tonumber(table.remove(vars, 1))
|
||||
|
||||
if val < 0 then
|
||||
val = val + 2 ^ (n * 8 - 1)
|
||||
end
|
||||
|
||||
local bytes = {}
|
||||
for j = 1, n do
|
||||
table.insert(bytes, string.char(val % (2 ^ 8)))
|
||||
val = math.floor(val / (2 ^ 8))
|
||||
end
|
||||
|
||||
if not endianness then
|
||||
table.insert(stream, string.reverse(table.concat(bytes)))
|
||||
else
|
||||
table.insert(stream, table.concat(bytes))
|
||||
end
|
||||
elseif opt:find('[fd]') then
|
||||
local val = tonumber(table.remove(vars, 1))
|
||||
local sign = 0
|
||||
|
||||
if val < 0 then
|
||||
sign = 1
|
||||
val = -val
|
||||
end
|
||||
|
||||
local mantissa, exponent = math.frexp(val)
|
||||
if val == 0 then
|
||||
mantissa = 0
|
||||
exponent = 0
|
||||
else
|
||||
mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, (opt == 'd') and 53 or 24)
|
||||
exponent = exponent + ((opt == 'd') and 1022 or 126)
|
||||
end
|
||||
|
||||
local bytes = {}
|
||||
if opt == 'd' then
|
||||
val = mantissa
|
||||
for i = 1, 6 do
|
||||
table.insert(bytes, string.char(math.floor(val) % (2 ^ 8)))
|
||||
val = math.floor(val / (2 ^ 8))
|
||||
end
|
||||
else
|
||||
table.insert(bytes, string.char(math.floor(mantissa) % (2 ^ 8)))
|
||||
val = math.floor(mantissa / (2 ^ 8))
|
||||
table.insert(bytes, string.char(math.floor(val) % (2 ^ 8)))
|
||||
val = math.floor(val / (2 ^ 8))
|
||||
end
|
||||
|
||||
table.insert(bytes, string.char(math.floor(exponent * ((opt == 'd') and 16 or 128) + val) % (2 ^ 8)))
|
||||
val = math.floor((exponent * ((opt == 'd') and 16 or 128) + val) / (2 ^ 8))
|
||||
table.insert(bytes, string.char(math.floor(sign * 128 + val) % (2 ^ 8)))
|
||||
val = math.floor((sign * 128 + val) / (2 ^ 8))
|
||||
|
||||
if not endianness then
|
||||
table.insert(stream, string.reverse(table.concat(bytes)))
|
||||
else
|
||||
table.insert(stream, table.concat(bytes))
|
||||
end
|
||||
elseif opt == 's' then
|
||||
table.insert(stream, tostring(table.remove(vars, 1)))
|
||||
table.insert(stream, string.char(0))
|
||||
elseif opt == 'c' then
|
||||
local n = format:sub(i + 1):match('%d+')
|
||||
local length = tonumber(n)
|
||||
|
||||
if length > 0 then
|
||||
local str = tostring(table.remove(vars, 1))
|
||||
if length - str:len() > 0 then
|
||||
str = str .. string.rep(' ', length - str:len())
|
||||
end
|
||||
table.insert(stream, str:sub(1, length))
|
||||
end
|
||||
i = i + n:len()
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(stream)
|
||||
end
|
||||
|
||||
function Struct.unpack(format, stream)
|
||||
local vars = {}
|
||||
local iterator = 1
|
||||
local endianness = true
|
||||
|
||||
for i = 1, format:len() do
|
||||
local opt = format:sub(i, i)
|
||||
|
||||
if opt == '>' then
|
||||
endianness = false
|
||||
elseif opt:find('[bBhHiIlL]') then
|
||||
local n = opt:find('[hH]') and 2 or opt:find('[iI]') and 4 or opt:find('[lL]') and 8 or 1
|
||||
local signed = opt:lower() == opt
|
||||
|
||||
local val = 0
|
||||
for j = 1, n do
|
||||
local byte = string.byte(stream:sub(iterator, iterator))
|
||||
if endianness then
|
||||
val = val + byte * (2 ^ ((j - 1) * 8))
|
||||
else
|
||||
val = val + byte * (2 ^ ((n - j) * 8))
|
||||
end
|
||||
iterator = iterator + 1
|
||||
end
|
||||
|
||||
if signed then
|
||||
val = val - 2 ^ (n * 8 - 1)
|
||||
end
|
||||
|
||||
table.insert(vars, val)
|
||||
elseif opt:find('[fd]') then
|
||||
local n = (opt == 'd') and 8 or 4
|
||||
local x = stream:sub(iterator, iterator + n - 1)
|
||||
iterator = iterator + n
|
||||
|
||||
if not endianness then
|
||||
x = string.reverse(x)
|
||||
end
|
||||
|
||||
local sign = 1
|
||||
local mantissa = string.byte(x, (opt == 'd') and 7 or 3) % ((opt == 'd') and 16 or 128)
|
||||
for i = n - 2, 1, -1 do
|
||||
mantissa = mantissa * (2 ^ 8) + string.byte(x, i)
|
||||
end
|
||||
|
||||
if string.byte(x, n) > 127 then
|
||||
sign = -1
|
||||
end
|
||||
|
||||
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))
|
||||
if exponent == 0 then
|
||||
table.insert(vars, 0.0)
|
||||
else
|
||||
mantissa = (math.ldexp(mantissa, (opt == 'd') and -52 or -23) + 1) * sign
|
||||
table.insert(vars, math.ldexp(mantissa, exponent - ((opt == 'd') and 1023 or 127)))
|
||||
end
|
||||
elseif opt == 's' then
|
||||
local bytes = {}
|
||||
for j = iterator, stream:len() do
|
||||
if stream:sub(j, j) == string.char(0) then
|
||||
break
|
||||
end
|
||||
|
||||
table.insert(bytes, stream:sub(j, j))
|
||||
end
|
||||
|
||||
local str = table.concat(bytes)
|
||||
iterator = iterator + str:len() + 1
|
||||
table.insert(vars, str)
|
||||
elseif opt == 'c' then
|
||||
local n = format:sub(i + 1):match('%d+')
|
||||
table.insert(vars, stream:sub(iterator, iterator + tonumber(n)))
|
||||
iterator = iterator + tonumber(n)
|
||||
i = i + n:len()
|
||||
end
|
||||
end
|
||||
|
||||
return unpack(vars)
|
||||
end
|
287
SabrehavenOTClient/modules/corelib/table.lua
Normal file
287
SabrehavenOTClient/modules/corelib/table.lua
Normal file
@@ -0,0 +1,287 @@
|
||||
-- @docclass table
|
||||
|
||||
function table.dump(t, depth)
|
||||
if not depth then depth = 0 end
|
||||
for k,v in pairs(t) do
|
||||
str = (' '):rep(depth * 2) .. k .. ': '
|
||||
if type(v) ~= "table" then
|
||||
print(str .. tostring(v))
|
||||
else
|
||||
print(str)
|
||||
table.dump(v, depth+1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function table.clear(t)
|
||||
for k,v in pairs(t) do
|
||||
t[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function table.copy(t)
|
||||
local res = {}
|
||||
for k,v in pairs(t) do
|
||||
res[k] = v
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function table.recursivecopy(t)
|
||||
local res = {}
|
||||
for k,v in pairs(t) do
|
||||
if type(v) == "table" then
|
||||
res[k] = table.recursivecopy(v)
|
||||
else
|
||||
res[k] = v
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function table.selectivecopy(t, keys)
|
||||
local res = { }
|
||||
for i,v in ipairs(keys) do
|
||||
res[v] = t[v]
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function table.merge(t, src)
|
||||
for k,v in pairs(src) do
|
||||
t[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
function table.find(t, value, lowercase)
|
||||
for k,v in pairs(t) do
|
||||
if lowercase and type(value) == 'string' and type(v) == 'string' then
|
||||
if v:lower() == value:lower() then return k end
|
||||
end
|
||||
if v == value then return k end
|
||||
end
|
||||
end
|
||||
|
||||
function table.findbykey(t, key, lowercase)
|
||||
for k,v in pairs(t) do
|
||||
if lowercase and type(key) == 'string' and type(k) == 'string' then
|
||||
if k:lower() == key:lower() then return v end
|
||||
end
|
||||
if k == key then return v end
|
||||
end
|
||||
end
|
||||
|
||||
function table.contains(t, value, lowercase)
|
||||
return table.find(t, value, lowercase) ~= nil
|
||||
end
|
||||
|
||||
function table.findkey(t, key)
|
||||
if t and type(t) == 'table' then
|
||||
for k,v in pairs(t) do
|
||||
if k == key then return k end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function table.haskey(t, key)
|
||||
return table.findkey(t, key) ~= nil
|
||||
end
|
||||
|
||||
function table.removevalue(t, value)
|
||||
for k,v in pairs(t) do
|
||||
if v == value then
|
||||
table.remove(t, k)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function table.popvalue(value)
|
||||
local index = nil
|
||||
for k,v in pairs(t) do
|
||||
if v == value or not value then
|
||||
index = k
|
||||
end
|
||||
end
|
||||
if index then
|
||||
table.remove(t, index)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function table.compare(t, other)
|
||||
if #t ~= #other then return false end
|
||||
for k,v in pairs(t) do
|
||||
if v ~= other[k] then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function table.empty(t)
|
||||
if t and type(t) == 'table' then
|
||||
return next(t) == nil
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function table.permute(t, n, count)
|
||||
n = n or #t
|
||||
for i=1,count or n do
|
||||
local j = math.random(i, n)
|
||||
t[i], t[j] = t[j], t[i]
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
function table.findbyfield(t, fieldname, fieldvalue)
|
||||
for _i,subt in pairs(t) do
|
||||
if subt[fieldname] == fieldvalue then
|
||||
return subt
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function table.size(t)
|
||||
local size = 0
|
||||
for i, n in pairs(t) do
|
||||
size = size + 1
|
||||
end
|
||||
|
||||
return size
|
||||
end
|
||||
|
||||
function table.tostring(t)
|
||||
local maxn = #t
|
||||
local str = ""
|
||||
for k,v in pairs(t) do
|
||||
v = tostring(v)
|
||||
if k == maxn and k ~= 1 then
|
||||
str = str .. " and " .. v
|
||||
elseif maxn > 1 and k ~= 1 then
|
||||
str = str .. ", " .. v
|
||||
else
|
||||
str = str .. " " .. v
|
||||
end
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
function table.collect(t, func)
|
||||
local res = {}
|
||||
for k,v in pairs(t) do
|
||||
local a,b = func(k,v)
|
||||
if a and b then
|
||||
res[a] = b
|
||||
elseif a ~= nil then
|
||||
table.insert(res,a)
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function table.equals(t, comp)
|
||||
if type(t) == "table" and type(comp) == "table" then
|
||||
for k,v in pairs(t) do
|
||||
if v ~= comp[k] then return false end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function table.equal(t1,t2,ignore_mt)
|
||||
local ty1 = type(t1)
|
||||
local ty2 = type(t2)
|
||||
if ty1 ~= ty2 then return false end
|
||||
-- non-table types can be directly compared
|
||||
if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end
|
||||
-- as well as tables which have the metamethod __eq
|
||||
local mt = getmetatable(t1)
|
||||
if not ignore_mt and mt and mt.__eq then return t1 == t2 end
|
||||
for k1,v1 in pairs(t1) do
|
||||
local v2 = t2[k1]
|
||||
if v2 == nil or not table.equal(v1,v2) then return false end
|
||||
end
|
||||
for k2,v2 in pairs(t2) do
|
||||
local v1 = t1[k2]
|
||||
if v1 == nil or not table.equal(v1,v2) then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function table.isList(t)
|
||||
local size = #t
|
||||
return table.size(t) == size and size > 0
|
||||
end
|
||||
|
||||
function table.isStringList(t)
|
||||
if not table.isList(t) then return false end
|
||||
for k,v in ipairs(t) do
|
||||
if type(v) ~= 'string' then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function table.isStringPairList(t)
|
||||
if not table.isList(t) then return false end
|
||||
for k,v in ipairs(t) do
|
||||
if type(v) ~= 'table' or #v ~= 2 or type(v[1]) ~= 'string' or type(v[2]) ~= 'string' then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function table.encodeStringPairList(t)
|
||||
local ret = ""
|
||||
for k,v in ipairs(t) do
|
||||
if v[2]:find("\n") then
|
||||
ret = ret .. v[1] .. ":[[\n" .. v[2] .. "\n]]\n"
|
||||
else
|
||||
ret = ret .. v[1] .. ":" .. v[2] .. "\n"
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function table.decodeStringPairList(l)
|
||||
local ret = {}
|
||||
local r = regexMatch(l, "(?:^|\\n)([^:^\n]{1,20}):?(.*)(?:$|\\n)")
|
||||
local multiline = ""
|
||||
local multilineKey = ""
|
||||
local multilineActive = false
|
||||
for k,v in ipairs(r) do
|
||||
if multilineActive then
|
||||
local endPos = v[1]:find("%]%]")
|
||||
if endPos then
|
||||
if endPos > 1 then
|
||||
table.insert(ret, {multilineKey, multiline .. "\n" .. v[1]:sub(1, endPos - 1)})
|
||||
else
|
||||
table.insert(ret, {multilineKey, multiline})
|
||||
end
|
||||
multilineActive = false
|
||||
multiline = ""
|
||||
multilineKey = ""
|
||||
else
|
||||
if multiline:len() == 0 then
|
||||
multiline = v[1]
|
||||
else
|
||||
multiline = multiline .. "\n" .. v[1]
|
||||
end
|
||||
end
|
||||
else
|
||||
local bracketPos = v[3]:find("%[%[")
|
||||
if bracketPos == 1 then -- multiline begin
|
||||
multiline = v[3]:sub(bracketPos + 2)
|
||||
multilineActive = true
|
||||
multilineKey = v[2]
|
||||
elseif v[2]:len() > 0 and v[3]:len() > 0 then
|
||||
table.insert(ret, {v[2], v[3]})
|
||||
end
|
||||
end
|
||||
end
|
||||
return ret
|
62
SabrehavenOTClient/modules/corelib/test.lua
Normal file
62
SabrehavenOTClient/modules/corelib/test.lua
Normal file
@@ -0,0 +1,62 @@
|
||||
Test = {
|
||||
tests = {},
|
||||
activeTest = 0,
|
||||
screenShot = 1
|
||||
}
|
||||
|
||||
Test.Test = function(name, func)
|
||||
local testId = #Test.tests + 1
|
||||
Test.tests[testId] = {
|
||||
name = name,
|
||||
actions = {},
|
||||
delay = 0,
|
||||
start = 0
|
||||
}
|
||||
local test = function(testFunc)
|
||||
table.insert(Test.tests[testId].actions, {type = "test", value = testFunc})
|
||||
end
|
||||
local wait = function(millis)
|
||||
Test.tests[testId].delay = Test.tests[testId].delay + millis
|
||||
table.insert(Test.tests[testId].actions, {type = "wait", value = Test.tests[testId].delay})
|
||||
end
|
||||
local ss = function()
|
||||
table.insert(Test.tests[testId].actions, {type = "screenshot"})
|
||||
end
|
||||
local fail = function(message)
|
||||
g_logger.fatal("Test " .. name .. " failed: " .. message)
|
||||
end
|
||||
func(test, wait, ss, fail)
|
||||
end
|
||||
|
||||
Test.run = function()
|
||||
if Test.activeTest > #Test.tests then
|
||||
g_logger.info("[TEST] Finished tests. Exiting...")
|
||||
return g_app.exit()
|
||||
end
|
||||
local test = Test.tests[Test.activeTest]
|
||||
if not test or #test.actions == 0 then
|
||||
Test.activeTest = Test.activeTest + 1
|
||||
local nextTest = Test.tests[Test.activeTest]
|
||||
if nextTest then
|
||||
nextTest.start = g_clock.millis()
|
||||
g_logger.info("[TEST] Starting test: " .. nextTest.name)
|
||||
end
|
||||
return scheduleEvent(Test.run, 500)
|
||||
end
|
||||
|
||||
local action = test.actions[1]
|
||||
if action.type == "test" then
|
||||
table.remove(test.actions, 1)
|
||||
action.value()
|
||||
elseif action.type == "screenshot" then
|
||||
table.remove(test.actions, 1)
|
||||
g_app.doScreenshot(Test.screenShot .. ".png")
|
||||
Test.screenShot = Test.screenShot + 1
|
||||
elseif action.type == "wait" then
|
||||
if action.value + test.start < g_clock.millis() then
|
||||
table.remove(test.actions, 1)
|
||||
end
|
||||
end
|
||||
|
||||
scheduleEvent(Test.run, 100)
|
||||
end
|
67
SabrehavenOTClient/modules/corelib/ui/effects.lua
Normal file
67
SabrehavenOTClient/modules/corelib/ui/effects.lua
Normal file
@@ -0,0 +1,67 @@
|
||||
-- @docclass
|
||||
g_effects = {}
|
||||
|
||||
function g_effects.fadeIn(widget, time, elapsed)
|
||||
if not elapsed then elapsed = 0 end
|
||||
if not time then time = 300 end
|
||||
widget:setOpacity(math.min(elapsed/time, 1))
|
||||
removeEvent(widget.fadeEvent)
|
||||
if elapsed < time then
|
||||
removeEvent(widget.fadeEvent)
|
||||
widget.fadeEvent = scheduleEvent(function()
|
||||
g_effects.fadeIn(widget, time, elapsed + 30)
|
||||
end, 30)
|
||||
else
|
||||
widget.fadeEvent = nil
|
||||
end
|
||||
end
|
||||
|
||||
function g_effects.fadeOut(widget, time, elapsed)
|
||||
if not elapsed then elapsed = 0 end
|
||||
if not time then time = 300 end
|
||||
elapsed = math.max((1 - widget:getOpacity()) * time, elapsed)
|
||||
removeEvent(widget.fadeEvent)
|
||||
widget:setOpacity(math.max((time - elapsed)/time, 0))
|
||||
if elapsed < time then
|
||||
widget.fadeEvent = scheduleEvent(function()
|
||||
g_effects.fadeOut(widget, time, elapsed + 30)
|
||||
end, 30)
|
||||
else
|
||||
widget.fadeEvent = nil
|
||||
end
|
||||
end
|
||||
|
||||
function g_effects.cancelFade(widget)
|
||||
removeEvent(widget.fadeEvent)
|
||||
widget.fadeEvent = nil
|
||||
end
|
||||
|
||||
function g_effects.startBlink(widget, duration, interval, clickCancel)
|
||||
duration = duration or 0 -- until stop is called
|
||||
interval = interval or 500
|
||||
clickCancel = clickCancel or true
|
||||
|
||||
removeEvent(widget.blinkEvent)
|
||||
removeEvent(widget.blinkStopEvent)
|
||||
|
||||
widget.blinkEvent = cycleEvent(function()
|
||||
widget:setOn(not widget:isOn())
|
||||
end, interval)
|
||||
|
||||
if duration > 0 then
|
||||
widget.blinkStopEvent = scheduleEvent(function()
|
||||
g_effects.stopBlink(widget)
|
||||
end, duration)
|
||||
end
|
||||
|
||||
connect(widget, { onClick = g_effects.stopBlink })
|
||||
end
|
||||
|
||||
function g_effects.stopBlink(widget)
|
||||
disconnect(widget, { onClick = g_effects.stopBlink })
|
||||
removeEvent(widget.blinkEvent)
|
||||
removeEvent(widget.blinkStopEvent)
|
||||
widget.blinkEvent = nil
|
||||
widget.blinkStopEvent = nil
|
||||
widget:setOn(false)
|
||||
end
|
124
SabrehavenOTClient/modules/corelib/ui/tooltip.lua
Normal file
124
SabrehavenOTClient/modules/corelib/ui/tooltip.lua
Normal file
@@ -0,0 +1,124 @@
|
||||
-- @docclass
|
||||
g_tooltip = {}
|
||||
|
||||
-- private variables
|
||||
local toolTipLabel
|
||||
local currentHoveredWidget
|
||||
|
||||
-- private functions
|
||||
local function moveToolTip(first)
|
||||
if not first and (not toolTipLabel:isVisible() or toolTipLabel:getOpacity() < 0.1) then return end
|
||||
|
||||
local pos = g_window.getMousePosition()
|
||||
local windowSize = g_window.getSize()
|
||||
local labelSize = toolTipLabel:getSize()
|
||||
|
||||
pos.x = pos.x + 1
|
||||
pos.y = pos.y + 1
|
||||
|
||||
if windowSize.width - (pos.x + labelSize.width) < 10 then
|
||||
pos.x = pos.x - labelSize.width - 3
|
||||
else
|
||||
pos.x = pos.x + 10
|
||||
end
|
||||
|
||||
if windowSize.height - (pos.y + labelSize.height) < 10 then
|
||||
pos.y = pos.y - labelSize.height - 3
|
||||
else
|
||||
pos.y = pos.y + 10
|
||||
end
|
||||
|
||||
toolTipLabel:setPosition(pos)
|
||||
end
|
||||
|
||||
local function onWidgetHoverChange(widget, hovered)
|
||||
if hovered then
|
||||
if widget.tooltip and not g_mouse.isPressed() then
|
||||
g_tooltip.display(widget.tooltip)
|
||||
currentHoveredWidget = widget
|
||||
end
|
||||
else
|
||||
if widget == currentHoveredWidget then
|
||||
g_tooltip.hide()
|
||||
currentHoveredWidget = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function onWidgetStyleApply(widget, styleName, styleNode)
|
||||
if styleNode.tooltip then
|
||||
widget.tooltip = styleNode.tooltip
|
||||
end
|
||||
end
|
||||
|
||||
-- public functions
|
||||
function g_tooltip.init()
|
||||
connect(UIWidget, { onStyleApply = onWidgetStyleApply,
|
||||
onHoverChange = onWidgetHoverChange})
|
||||
|
||||
addEvent(function()
|
||||
toolTipLabel = g_ui.createWidget('UILabel', rootWidget)
|
||||
toolTipLabel:setId('toolTip')
|
||||
toolTipLabel:setBackgroundColor('#111111cc')
|
||||
toolTipLabel:setTextAlign(AlignCenter)
|
||||
toolTipLabel:hide()
|
||||
end)
|
||||
end
|
||||
|
||||
function g_tooltip.terminate()
|
||||
disconnect(UIWidget, { onStyleApply = onWidgetStyleApply,
|
||||
onHoverChange = onWidgetHoverChange })
|
||||
|
||||
currentHoveredWidget = nil
|
||||
toolTipLabel:destroy()
|
||||
toolTipLabel = nil
|
||||
|
||||
g_tooltip = nil
|
||||
end
|
||||
|
||||
function g_tooltip.display(text)
|
||||
if text == nil or text:len() == 0 then return end
|
||||
if not toolTipLabel then return end
|
||||
|
||||
toolTipLabel:setText(text)
|
||||
toolTipLabel:resizeToText()
|
||||
toolTipLabel:resize(toolTipLabel:getWidth() + 4, toolTipLabel:getHeight() + 4)
|
||||
toolTipLabel:show()
|
||||
toolTipLabel:raise()
|
||||
toolTipLabel:enable()
|
||||
g_effects.fadeIn(toolTipLabel, 100)
|
||||
moveToolTip(true)
|
||||
|
||||
connect(rootWidget, {
|
||||
onMouseMove = moveToolTip,
|
||||
})
|
||||
end
|
||||
|
||||
function g_tooltip.hide()
|
||||
g_effects.fadeOut(toolTipLabel, 100)
|
||||
|
||||
disconnect(rootWidget, {
|
||||
onMouseMove = moveToolTip,
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
-- @docclass UIWidget @{
|
||||
|
||||
-- UIWidget extensions
|
||||
function UIWidget:setTooltip(text)
|
||||
self.tooltip = text
|
||||
end
|
||||
|
||||
function UIWidget:removeTooltip()
|
||||
self.tooltip = nil
|
||||
end
|
||||
|
||||
function UIWidget:getTooltip()
|
||||
return self.tooltip
|
||||
end
|
||||
|
||||
-- @}
|
||||
|
||||
g_tooltip.init()
|
||||
connect(g_app, { onTerminate = g_tooltip.terminate })
|
12
SabrehavenOTClient/modules/corelib/ui/uibutton.lua
Normal file
12
SabrehavenOTClient/modules/corelib/ui/uibutton.lua
Normal file
@@ -0,0 +1,12 @@
|
||||
-- @docclass
|
||||
UIButton = extends(UIWidget, "UIButton")
|
||||
|
||||
function UIButton.create()
|
||||
local button = UIButton.internalCreate()
|
||||
button:setFocusable(false)
|
||||
return button
|
||||
end
|
||||
|
||||
function UIButton:onMouseRelease(pos, button)
|
||||
return self:isPressed()
|
||||
end
|
13
SabrehavenOTClient/modules/corelib/ui/uicheckbox.lua
Normal file
13
SabrehavenOTClient/modules/corelib/ui/uicheckbox.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
-- @docclass
|
||||
UICheckBox = extends(UIWidget, "UICheckBox")
|
||||
|
||||
function UICheckBox.create()
|
||||
local checkbox = UICheckBox.internalCreate()
|
||||
checkbox:setFocusable(false)
|
||||
checkbox:setTextAlign(AlignLeft)
|
||||
return checkbox
|
||||
end
|
||||
|
||||
function UICheckBox:onClick()
|
||||
self:setChecked(not self:isChecked())
|
||||
end
|
184
SabrehavenOTClient/modules/corelib/ui/uicombobox.lua
Normal file
184
SabrehavenOTClient/modules/corelib/ui/uicombobox.lua
Normal file
@@ -0,0 +1,184 @@
|
||||
-- @docclass
|
||||
UIComboBox = extends(UIWidget, "UIComboBox")
|
||||
|
||||
function UIComboBox.create()
|
||||
local combobox = UIComboBox.internalCreate()
|
||||
combobox:setFocusable(false)
|
||||
combobox.options = {}
|
||||
combobox.currentIndex = -1
|
||||
combobox.mouseScroll = true
|
||||
combobox.menuScroll = false
|
||||
combobox.menuHeight = 100
|
||||
combobox.menuScrollStep = 0
|
||||
return combobox
|
||||
end
|
||||
|
||||
function UIComboBox:clearOptions()
|
||||
self.options = {}
|
||||
self.currentIndex = -1
|
||||
self:clearText()
|
||||
end
|
||||
|
||||
function UIComboBox:clear()
|
||||
return self:clearOptions()
|
||||
end
|
||||
|
||||
function UIComboBox:getOptionsCount()
|
||||
return #self.options
|
||||
end
|
||||
|
||||
function UIComboBox:isOption(text)
|
||||
if not self.options then return false end
|
||||
for i,v in ipairs(self.options) do
|
||||
if v.text == text then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function UIComboBox:setOption(text, dontSignal)
|
||||
self:setCurrentOption(text, dontSignal)
|
||||
end
|
||||
|
||||
function UIComboBox:setCurrentOption(text, dontSignal)
|
||||
if not self.options then return end
|
||||
for i,v in ipairs(self.options) do
|
||||
if v.text == text and self.currentIndex ~= i then
|
||||
self.currentIndex = i
|
||||
self:setText(text)
|
||||
if not dontSignal then
|
||||
signalcall(self.onOptionChange, self, text, v.data)
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIComboBox:updateCurrentOption(newText)
|
||||
self.options[self.currentIndex].text = newText
|
||||
self:setText(newText)
|
||||
end
|
||||
|
||||
function UIComboBox:setCurrentOptionByData(data, dontSignal)
|
||||
if not self.options then return end
|
||||
for i,v in ipairs(self.options) do
|
||||
if v.data == data and self.currentIndex ~= i then
|
||||
self.currentIndex = i
|
||||
self:setText(v.text)
|
||||
if not dontSignal then
|
||||
signalcall(self.onOptionChange, self, v.text, v.data)
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIComboBox:setCurrentIndex(index, dontSignal)
|
||||
if index >= 1 and index <= #self.options then
|
||||
local v = self.options[index]
|
||||
self.currentIndex = index
|
||||
self:setText(v.text)
|
||||
if not dontSignal then
|
||||
signalcall(self.onOptionChange, self, v.text, v.data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIComboBox:getCurrentOption()
|
||||
if table.haskey(self.options, self.currentIndex) then
|
||||
return self.options[self.currentIndex]
|
||||
end
|
||||
end
|
||||
|
||||
function UIComboBox:addOption(text, data)
|
||||
table.insert(self.options, { text = text, data = data })
|
||||
local index = #self.options
|
||||
if index == 1 then self:setCurrentOption(text) end
|
||||
return index
|
||||
end
|
||||
|
||||
function UIComboBox:removeOption(text)
|
||||
for i,v in ipairs(self.options) do
|
||||
if v.text == text then
|
||||
table.remove(self.options, i)
|
||||
if self.currentIndex == i then
|
||||
self:setCurrentIndex(1)
|
||||
elseif self.currentIndex > i then
|
||||
self.currentIndex = self.currentIndex - 1
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIComboBox:onMousePress(mousePos, mouseButton)
|
||||
local menu
|
||||
if self.menuScroll then
|
||||
menu = g_ui.createWidget(self:getStyleName() .. 'PopupScrollMenu')
|
||||
menu:setHeight(self.menuHeight)
|
||||
if self.menuScrollStep > 0 then
|
||||
menu:setScrollbarStep(self.menuScrollStep)
|
||||
end
|
||||
else
|
||||
menu = g_ui.createWidget(self:getStyleName() .. 'PopupMenu')
|
||||
end
|
||||
menu:setId(self:getId() .. 'PopupMenu')
|
||||
for i,v in ipairs(self.options) do
|
||||
menu:addOption(v.text, function() self:setCurrentOption(v.text) end)
|
||||
end
|
||||
menu:setWidth(self:getWidth())
|
||||
menu:display({ x = self:getX(), y = self:getY() + self:getHeight() })
|
||||
connect(menu, { onDestroy = function() self:setOn(false) end })
|
||||
self:setOn(true)
|
||||
return true
|
||||
end
|
||||
|
||||
function UIComboBox:onMouseWheel(mousePos, direction)
|
||||
if not self.mouseScroll or self.disableScroll then
|
||||
return false
|
||||
end
|
||||
if direction == MouseWheelUp and self.currentIndex > 1 then
|
||||
self:setCurrentIndex(self.currentIndex - 1)
|
||||
elseif direction == MouseWheelDown and self.currentIndex < #self.options then
|
||||
self:setCurrentIndex(self.currentIndex + 1)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function UIComboBox:onStyleApply(styleName, styleNode)
|
||||
if styleNode.options then
|
||||
for k,option in pairs(styleNode.options) do
|
||||
self:addOption(option)
|
||||
end
|
||||
end
|
||||
|
||||
if styleNode.data then
|
||||
for k,data in pairs(styleNode.data) do
|
||||
local option = self.options[k]
|
||||
if option then
|
||||
option.data = data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for name,value in pairs(styleNode) do
|
||||
if name == 'mouse-scroll' then
|
||||
self.mouseScroll = value
|
||||
elseif name == 'menu-scroll' then
|
||||
self.menuScroll = value
|
||||
elseif name == 'menu-height' then
|
||||
self.menuHeight = value
|
||||
elseif name == 'menu-scroll-step' then
|
||||
self.menuScrollStep = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIComboBox:setMouseScroll(scroll)
|
||||
self.mouseScroll = scroll
|
||||
end
|
||||
|
||||
function UIComboBox:canMouseScroll()
|
||||
return self.mouseScroll
|
||||
end
|
99
SabrehavenOTClient/modules/corelib/ui/uiimageview.lua
Normal file
99
SabrehavenOTClient/modules/corelib/ui/uiimageview.lua
Normal file
@@ -0,0 +1,99 @@
|
||||
-- @docclass
|
||||
UIImageView = extends(UIWidget, "UIImageView")
|
||||
|
||||
function UIImageView.create()
|
||||
local imageView = UIImageView.internalCreate()
|
||||
imageView.zoom = 1
|
||||
imageView.minZoom = math.pow(10, -2)
|
||||
imageView.maxZoom = math.pow(10, 2)
|
||||
imageView:setClipping(true)
|
||||
return imageView
|
||||
end
|
||||
|
||||
function UIImageView:getDefaultZoom()
|
||||
local width = self:getWidth()
|
||||
local height = self:getHeight()
|
||||
local textureWidth = self:getImageTextureWidth()
|
||||
local textureHeight = self:getImageTextureHeight()
|
||||
local zoomX = width / textureWidth
|
||||
local zoomY = height / textureHeight
|
||||
return math.min(zoomX, zoomY)
|
||||
end
|
||||
|
||||
function UIImageView:getImagePosition(x, y)
|
||||
x = x or self:getWidth() / 2
|
||||
y = y or self:getHeight() / 2
|
||||
local offsetX = self:getImageOffsetX()
|
||||
local offsetY = self:getImageOffsetY()
|
||||
local posX = (x - offsetX) / self.zoom
|
||||
local posY = (y - offsetY) / self.zoom
|
||||
return posX, posY
|
||||
end
|
||||
|
||||
function UIImageView:setImage(image)
|
||||
self:setImageSource(image)
|
||||
local zoom = self:getDefaultZoom()
|
||||
self:setZoom(zoom)
|
||||
self:center()
|
||||
end
|
||||
|
||||
function UIImageView:setZoom(zoom, x, y)
|
||||
zoom = math.max(math.min(zoom, self.maxZoom), self.minZoom)
|
||||
local posX, posY = self:getImagePosition(x, y)
|
||||
local textureWidth = self:getImageTextureWidth()
|
||||
local textureHeight = self:getImageTextureHeight()
|
||||
local imageWidth = textureWidth * zoom
|
||||
local imageHeight = textureHeight * zoom
|
||||
self:setImageWidth(imageWidth)
|
||||
self:setImageHeight(imageHeight)
|
||||
self.zoom = zoom
|
||||
self:move(posX, posY, x, y)
|
||||
end
|
||||
|
||||
function UIImageView:zoomIn(x, y)
|
||||
local zoom = self.zoom * 1.1
|
||||
self:setZoom(zoom, x, y)
|
||||
end
|
||||
|
||||
function UIImageView:zoomOut(x, y)
|
||||
local zoom = self.zoom / 1.1
|
||||
self:setZoom(zoom, x, y)
|
||||
end
|
||||
|
||||
function UIImageView:center()
|
||||
self:move(self:getImageTextureWidth() / 2, self:getImageTextureHeight() / 2)
|
||||
end
|
||||
|
||||
function UIImageView:move(x, y, centerX, centerY)
|
||||
x = math.max(math.min(x, self:getImageTextureWidth()), 0)
|
||||
y = math.max(math.min(y, self:getImageTextureHeight()), 0)
|
||||
local centerX = centerX or self:getWidth() / 2
|
||||
local centerY = centerY or self:getHeight() / 2
|
||||
local offsetX = centerX - x * self.zoom
|
||||
local offsetY = centerY - y * self.zoom
|
||||
self:setImageOffset({x=offsetX, y=offsetY})
|
||||
end
|
||||
|
||||
function UIImageView:onDragEnter(pos)
|
||||
return true
|
||||
end
|
||||
|
||||
function UIImageView:onDragMove(pos, moved)
|
||||
local posX, posY = self:getImagePosition()
|
||||
self:move(posX - moved.x / self.zoom, posY - moved.y / self.zoom)
|
||||
return true
|
||||
end
|
||||
|
||||
function UIImageView:onDragLeave(widget, pos)
|
||||
return true
|
||||
end
|
||||
|
||||
function UIImageView:onMouseWheel(mousePos, direction)
|
||||
local x = mousePos.x - self:getX()
|
||||
local y = mousePos.y - self:getY()
|
||||
if direction == MouseWheelUp then
|
||||
self:zoomIn(x, y)
|
||||
elseif direction == MouseWheelDown then
|
||||
self:zoomOut(x, y)
|
||||
end
|
||||
end
|
114
SabrehavenOTClient/modules/corelib/ui/uiinputbox.lua
Normal file
114
SabrehavenOTClient/modules/corelib/ui/uiinputbox.lua
Normal file
@@ -0,0 +1,114 @@
|
||||
if not UIWindow then dofile 'uiwindow' end
|
||||
|
||||
-- @docclass
|
||||
UIInputBox = extends(UIWindow, "UIInputBox")
|
||||
|
||||
function UIInputBox.create(title, okCallback, cancelCallback)
|
||||
local inputBox = UIInputBox.internalCreate()
|
||||
|
||||
inputBox:setText(title)
|
||||
inputBox.inputs = {}
|
||||
inputBox.onEnter = function()
|
||||
local results = {}
|
||||
for _,func in pairs(inputBox.inputs) do
|
||||
table.insert(results, func())
|
||||
end
|
||||
okCallback(unpack(results))
|
||||
inputBox:destroy()
|
||||
end
|
||||
inputBox.onEscape = function()
|
||||
if cancelCallback then
|
||||
cancelCallback()
|
||||
end
|
||||
inputBox:destroy()
|
||||
end
|
||||
|
||||
return inputBox
|
||||
end
|
||||
|
||||
function UIInputBox:addLabel(text)
|
||||
local label = g_ui.createWidget('InputBoxLabel', self)
|
||||
label:setText(text)
|
||||
return label
|
||||
end
|
||||
|
||||
function UIInputBox:addLineEdit(labelText, defaultText, maxLength)
|
||||
if labelText then self:addLabel(labelText) end
|
||||
local lineEdit = g_ui.createWidget('InputBoxLineEdit', self)
|
||||
if defaultText then lineEdit:setText(defaultText) end
|
||||
if maxLength then lineEdit:setMaxLength(maxLength) end
|
||||
table.insert(self.inputs, function() return lineEdit:getText() end)
|
||||
return lineEdit
|
||||
end
|
||||
|
||||
function UIInputBox:addTextEdit(labelText, defaultText, maxLength, visibleLines)
|
||||
if labelText then self:addLabel(labelText) end
|
||||
local textEdit = g_ui.createWidget('InputBoxTextEdit', self)
|
||||
if defaultText then textEdit:setText(defaultText) end
|
||||
if maxLength then textEdit:setMaxLength(maxLength) end
|
||||
visibleLines = visibleLines or 1
|
||||
textEdit:setHeight(textEdit:getHeight() * visibleLines)
|
||||
table.insert(self.inputs, function() return textEdit:getText() end)
|
||||
return textEdit
|
||||
end
|
||||
|
||||
function UIInputBox:addCheckBox(text, checked)
|
||||
local checkBox = g_ui.createWidget('InputBoxCheckBox', self)
|
||||
checkBox:setText(text)
|
||||
checkBox:setChecked(checked)
|
||||
table.insert(self.inputs, function() return checkBox:isChecked() end)
|
||||
return checkBox
|
||||
end
|
||||
|
||||
function UIInputBox:addComboBox(labelText, ...)
|
||||
if labelText then self:addLabel(labelText) end
|
||||
local comboBox = g_ui.createWidget('InputBoxComboBox', self)
|
||||
local options = {...}
|
||||
for i=1,#options do
|
||||
comboBox:addOption(options[i])
|
||||
end
|
||||
table.insert(self.inputs, function() return comboBox:getCurrentOption() end)
|
||||
return comboBox
|
||||
end
|
||||
|
||||
function UIInputBox:addSpinBox(labelText, minimum, maximum, value, step)
|
||||
if labelText then self:addLabel(labelText) end
|
||||
local spinBox = g_ui.createWidget('InputBoxSpinBox', self)
|
||||
spinBox:setMinimum(minimum)
|
||||
spinBox:setMaximum(maximum)
|
||||
spinBox:setValue(value)
|
||||
spinBox:setStep(step)
|
||||
table.insert(self.inputs, function() return spinBox:getValue() end)
|
||||
return spinBox
|
||||
end
|
||||
|
||||
function UIInputBox:display(okButtonText, cancelButtonText)
|
||||
okButtonText = okButtonText or tr('Ok')
|
||||
cancelButtonText = cancelButtonText or tr('Cancel')
|
||||
|
||||
local buttonsWidget = g_ui.createWidget('InputBoxButtonsPanel', self)
|
||||
local okButton = g_ui.createWidget('InputBoxButton', buttonsWidget)
|
||||
okButton:setText(okButtonText)
|
||||
okButton.onClick = self.onEnter
|
||||
|
||||
local cancelButton = g_ui.createWidget('InputBoxButton', buttonsWidget)
|
||||
cancelButton:setText(cancelButtonText)
|
||||
cancelButton.onClick = self.onEscape
|
||||
|
||||
buttonsWidget:setHeight(okButton:getHeight())
|
||||
|
||||
rootWidget:addChild(self)
|
||||
self:setStyle('InputBoxWindow')
|
||||
end
|
||||
|
||||
function displayTextInputBox(title, label, okCallback, cancelCallback)
|
||||
local inputBox = UIInputBox.create(title, okCallback, cancelCallback)
|
||||
inputBox:addLineEdit(label)
|
||||
inputBox:display()
|
||||
end
|
||||
|
||||
function displayNumberInputBox(title, label, okCallback, cancelCallback, min, max, value, step)
|
||||
local inputBox = UIInputBox.create(title, okCallback, cancelCallback)
|
||||
inputBox:addSpinBox(label, min, max, value, step)
|
||||
inputBox:display()
|
||||
end
|
10
SabrehavenOTClient/modules/corelib/ui/uilabel.lua
Normal file
10
SabrehavenOTClient/modules/corelib/ui/uilabel.lua
Normal file
@@ -0,0 +1,10 @@
|
||||
-- @docclass
|
||||
UILabel = extends(UIWidget, "UILabel")
|
||||
|
||||
function UILabel.create()
|
||||
local label = UILabel.internalCreate()
|
||||
label:setPhantom(true)
|
||||
label:setFocusable(false)
|
||||
label:setTextAlign(AlignLeft)
|
||||
return label
|
||||
end
|
96
SabrehavenOTClient/modules/corelib/ui/uimessagebox.lua
Normal file
96
SabrehavenOTClient/modules/corelib/ui/uimessagebox.lua
Normal file
@@ -0,0 +1,96 @@
|
||||
if not UIWindow then dofile 'uiwindow' end
|
||||
|
||||
-- @docclass
|
||||
UIMessageBox = extends(UIWindow, "UIMessageBox")
|
||||
|
||||
-- messagebox cannot be created from otui files
|
||||
UIMessageBox.create = nil
|
||||
|
||||
function UIMessageBox.display(title, message, buttons, onEnterCallback, onEscapeCallback)
|
||||
local messageBox = UIMessageBox.internalCreate()
|
||||
rootWidget:addChild(messageBox)
|
||||
|
||||
messageBox:setStyle('MainWindow')
|
||||
messageBox:setText(title)
|
||||
|
||||
local messageLabel = g_ui.createWidget('MessageBoxLabel', messageBox)
|
||||
messageLabel:setText(message)
|
||||
|
||||
local buttonsWidth = 0
|
||||
local buttonsHeight = 0
|
||||
|
||||
local anchor = AnchorRight
|
||||
if buttons.anchor then anchor = buttons.anchor end
|
||||
|
||||
local buttonHolder = g_ui.createWidget('MessageBoxButtonHolder', messageBox)
|
||||
buttonHolder:addAnchor(anchor, 'parent', anchor)
|
||||
|
||||
for i=1,#buttons do
|
||||
local button = messageBox:addButton(buttons[i].text, buttons[i].callback)
|
||||
if i == 1 then
|
||||
button:setMarginLeft(0)
|
||||
button:addAnchor(AnchorBottom, 'parent', AnchorBottom)
|
||||
button:addAnchor(AnchorLeft, 'parent', AnchorLeft)
|
||||
buttonsHeight = button:getHeight()
|
||||
else
|
||||
button:addAnchor(AnchorBottom, 'prev', AnchorBottom)
|
||||
button:addAnchor(AnchorLeft, 'prev', AnchorRight)
|
||||
end
|
||||
buttonsWidth = buttonsWidth + button:getWidth() + button:getMarginLeft()
|
||||
end
|
||||
|
||||
buttonHolder:setWidth(buttonsWidth)
|
||||
buttonHolder:setHeight(buttonsHeight)
|
||||
|
||||
if onEnterCallback then connect(messageBox, { onEnter = onEnterCallback }) end
|
||||
if onEscapeCallback then connect(messageBox, { onEscape = onEscapeCallback }) end
|
||||
|
||||
messageBox:setWidth(math.max(messageLabel:getWidth(), messageBox:getTextSize().width, buttonHolder:getWidth()) + messageBox:getPaddingLeft() + messageBox:getPaddingRight())
|
||||
messageBox:setHeight(messageLabel:getHeight() + messageBox:getPaddingTop() + messageBox:getPaddingBottom() + buttonHolder:getHeight() + buttonHolder:getMarginTop())
|
||||
return messageBox
|
||||
end
|
||||
|
||||
function displayInfoBox(title, message)
|
||||
local messageBox
|
||||
local defaultCallback = function() messageBox:ok() end
|
||||
messageBox = UIMessageBox.display(title, message, {{text='Ok', callback=defaultCallback}}, defaultCallback, defaultCallback)
|
||||
return messageBox
|
||||
end
|
||||
|
||||
function displayErrorBox(title, message)
|
||||
local messageBox
|
||||
local defaultCallback = function() messageBox:ok() end
|
||||
messageBox = UIMessageBox.display(title, message, {{text='Ok', callback=defaultCallback}}, defaultCallback, defaultCallback)
|
||||
return messageBox
|
||||
end
|
||||
|
||||
function displayCancelBox(title, message)
|
||||
local messageBox
|
||||
local defaultCallback = function() messageBox:cancel() end
|
||||
messageBox = UIMessageBox.display(title, message, {{text='Cancel', callback=defaultCallback}}, defaultCallback, defaultCallback)
|
||||
return messageBox
|
||||
end
|
||||
|
||||
function displayGeneralBox(title, message, buttons, onEnterCallback, onEscapeCallback)
|
||||
return UIMessageBox.display(title, message, buttons, onEnterCallback, onEscapeCallback)
|
||||
end
|
||||
|
||||
function UIMessageBox:addButton(text, callback)
|
||||
local buttonHolder = self:getChildById('buttonHolder')
|
||||
local button = g_ui.createWidget('MessageBoxButton', buttonHolder)
|
||||
button:setText(text)
|
||||
connect(button, { onClick = callback })
|
||||
return button
|
||||
end
|
||||
|
||||
function UIMessageBox:ok()
|
||||
signalcall(self.onOk, self)
|
||||
self.onOk = nil
|
||||
self:destroy()
|
||||
end
|
||||
|
||||
function UIMessageBox:cancel()
|
||||
signalcall(self.onCancel, self)
|
||||
self.onCancel = nil
|
||||
self:destroy()
|
||||
end
|
516
SabrehavenOTClient/modules/corelib/ui/uiminiwindow.lua
Normal file
516
SabrehavenOTClient/modules/corelib/ui/uiminiwindow.lua
Normal file
@@ -0,0 +1,516 @@
|
||||
-- @docclass
|
||||
UIMiniWindow = extends(UIWindow, "UIMiniWindow")
|
||||
|
||||
function UIMiniWindow.create()
|
||||
local miniwindow = UIMiniWindow.internalCreate()
|
||||
miniwindow.UIMiniWindowContainer = true
|
||||
return miniwindow
|
||||
end
|
||||
|
||||
function UIMiniWindow:open(dontSave)
|
||||
self:setVisible(true)
|
||||
|
||||
if not dontSave then
|
||||
self:setSettings({closed = false})
|
||||
end
|
||||
|
||||
signalcall(self.onOpen, self)
|
||||
end
|
||||
|
||||
function UIMiniWindow:close(dontSave)
|
||||
if not self:isExplicitlyVisible() then return end
|
||||
if self.forceOpen then return end
|
||||
self:setVisible(false)
|
||||
|
||||
if not dontSave then
|
||||
self:setSettings({closed = true})
|
||||
end
|
||||
|
||||
signalcall(self.onClose, self)
|
||||
end
|
||||
|
||||
function UIMiniWindow:minimize(dontSave)
|
||||
self:setOn(true)
|
||||
self:getChildById('contentsPanel'):hide()
|
||||
self:getChildById('miniwindowScrollBar'):hide()
|
||||
self:getChildById('bottomResizeBorder'):hide()
|
||||
if self.minimizeButton then
|
||||
self.minimizeButton:setOn(true)
|
||||
end
|
||||
self.maximizedHeight = self:getHeight()
|
||||
self:setHeight(self.minimizedHeight)
|
||||
|
||||
if not dontSave then
|
||||
self:setSettings({minimized = true})
|
||||
end
|
||||
|
||||
signalcall(self.onMinimize, self)
|
||||
end
|
||||
|
||||
function UIMiniWindow:maximize(dontSave)
|
||||
self:setOn(false)
|
||||
self:getChildById('contentsPanel'):show()
|
||||
self:getChildById('miniwindowScrollBar'):show()
|
||||
self:getChildById('bottomResizeBorder'):show()
|
||||
if self.minimizeButton then
|
||||
self.minimizeButton:setOn(false)
|
||||
end
|
||||
self:setHeight(self:getSettings('height') or self.maximizedHeight)
|
||||
|
||||
if not dontSave then
|
||||
self:setSettings({minimized = false})
|
||||
end
|
||||
|
||||
local parent = self:getParent()
|
||||
if parent and parent:getClassName() == 'UIMiniWindowContainer' then
|
||||
parent:fitAll(self)
|
||||
end
|
||||
|
||||
signalcall(self.onMaximize, self)
|
||||
end
|
||||
|
||||
function UIMiniWindow:lock(dontSave)
|
||||
local lockButton = self:getChildById('lockButton')
|
||||
if lockButton then
|
||||
lockButton:setOn(true)
|
||||
end
|
||||
self:setDraggable(false)
|
||||
if not dontsave then
|
||||
self:setSettings({locked = true})
|
||||
end
|
||||
|
||||
signalcall(self.onLockChange, self)
|
||||
end
|
||||
|
||||
function UIMiniWindow:unlock(dontSave)
|
||||
local lockButton = self:getChildById('lockButton')
|
||||
if lockButton then
|
||||
lockButton:setOn(false)
|
||||
end
|
||||
self:setDraggable(true)
|
||||
if not dontsave then
|
||||
self:setSettings({locked = false})
|
||||
end
|
||||
signalcall(self.onLockChange, self)
|
||||
end
|
||||
|
||||
function UIMiniWindow:setup()
|
||||
self:getChildById('closeButton').onClick =
|
||||
function()
|
||||
self:close()
|
||||
end
|
||||
if self.forceOpen then
|
||||
if self.closeButton then
|
||||
self.closeButton:hide()
|
||||
end
|
||||
end
|
||||
|
||||
if(self.minimizeButton) then
|
||||
self.minimizeButton.onClick =
|
||||
function()
|
||||
if self:isOn() then
|
||||
self:maximize()
|
||||
else
|
||||
self:minimize()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local lockButton = self:getChildById('lockButton')
|
||||
if lockButton then
|
||||
lockButton.onClick =
|
||||
function ()
|
||||
if self:isDraggable() then
|
||||
self:lock()
|
||||
else
|
||||
self:unlock()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:getChildById('miniwindowTopBar').onDoubleClick =
|
||||
function()
|
||||
if self:isOn() then
|
||||
self:maximize()
|
||||
else
|
||||
self:minimize()
|
||||
end
|
||||
end
|
||||
|
||||
local oldParent = self:getParent()
|
||||
|
||||
|
||||
local settings = {}
|
||||
if g_settings.getNodeSize('MiniWindows') < 100 then
|
||||
settings = g_settings.getNode('MiniWindows')
|
||||
end
|
||||
|
||||
if settings then
|
||||
local selfSettings = settings[self:getId()]
|
||||
if selfSettings then
|
||||
if selfSettings.parentId then
|
||||
local parent = rootWidget:recursiveGetChildById(selfSettings.parentId)
|
||||
if parent then
|
||||
if parent:getClassName() == 'UIMiniWindowContainer' and selfSettings.index and parent:isOn() then
|
||||
self.miniIndex = selfSettings.index
|
||||
parent:scheduleInsert(self, selfSettings.index)
|
||||
elseif selfSettings.position then
|
||||
self:setParent(parent, true)
|
||||
self:setPosition(topoint(selfSettings.position))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if selfSettings.minimized then
|
||||
self:minimize(true)
|
||||
else
|
||||
if selfSettings.height and self:isResizeable() then
|
||||
self:setHeight(selfSettings.height)
|
||||
elseif selfSettings.height and not self:isResizeable() then
|
||||
self:eraseSettings({height = true})
|
||||
end
|
||||
end
|
||||
if selfSettings.closed and not self.forceOpen and not self.containerWindow then
|
||||
self:close(true)
|
||||
end
|
||||
|
||||
if selfSettings.locked then
|
||||
self:lock(true)
|
||||
end
|
||||
else
|
||||
if not self.forceOpen and self.autoOpen ~= nil and (self.autoOpen == 0 or self.autoOpen == false) and not self.containerWindow then
|
||||
self:close(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local newParent = self:getParent()
|
||||
|
||||
self.miniLoaded = true
|
||||
|
||||
if self.save then
|
||||
if oldParent and oldParent:getClassName() == 'UIMiniWindowContainer' and not self.containerWindow then
|
||||
addEvent(function() oldParent:order() end)
|
||||
end
|
||||
if newParent and newParent:getClassName() == 'UIMiniWindowContainer' and newParent ~= oldParent then
|
||||
addEvent(function() newParent:order() end)
|
||||
end
|
||||
end
|
||||
|
||||
self:fitOnParent()
|
||||
end
|
||||
|
||||
function UIMiniWindow:onVisibilityChange(visible)
|
||||
self:fitOnParent()
|
||||
end
|
||||
|
||||
function UIMiniWindow:onDragEnter(mousePos)
|
||||
local parent = self:getParent()
|
||||
if not parent then return false end
|
||||
|
||||
if parent:getClassName() == 'UIMiniWindowContainer' then
|
||||
local containerParent = parent:getParent():getParent()
|
||||
parent:removeChild(self)
|
||||
containerParent:addChild(self)
|
||||
parent:saveChildren()
|
||||
end
|
||||
|
||||
local oldPos = self:getPosition()
|
||||
self.movingReference = { x = mousePos.x - oldPos.x, y = mousePos.y - oldPos.y }
|
||||
self:setPosition(oldPos)
|
||||
self.free = true
|
||||
return true
|
||||
end
|
||||
|
||||
function UIMiniWindow:onDragLeave(droppedWidget, mousePos)
|
||||
local children = rootWidget:recursiveGetChildrenByMarginPos(mousePos)
|
||||
local dropInPanel = 0
|
||||
for i=1,#children do
|
||||
local child = children[i]
|
||||
if child:getId():contains('gameLeftPanel') or child:getId():contains('gameRightPanel') then
|
||||
dropInPanel = 1
|
||||
if self.movedWidget then
|
||||
self.setMovedChildMargin(self.movedOldMargin or 0)
|
||||
self.movedWidget = nil
|
||||
self.setMovedChildMargin = nil
|
||||
self.movedOldMargin = nil
|
||||
self.movedIndex = nil
|
||||
end
|
||||
|
||||
UIWindow:onDragLeave(self, droppedWidget, mousePos)
|
||||
self:saveParent(self:getParent())
|
||||
break
|
||||
end
|
||||
end
|
||||
if dropInPanel == 0 then
|
||||
tmpp = self
|
||||
if(modules.game_interface.getLeftPanel():isVisible()) then
|
||||
if modules.game_interface.getRootPanel():getWidth() / 2 < mousePos.x then
|
||||
addEvent(function() tmpp:setParent(modules.game_interface.getRightPanel())
|
||||
if tmpp.movedWidget then
|
||||
tmpp.setMovedChildMargin(tmpp.movedOldMargin or 0)
|
||||
tmpp.movedWidget = nil
|
||||
tmpp.setMovedChildMargin = nil
|
||||
tmpp.movedOldMargin = nil
|
||||
tmpp.movedIndex = nil
|
||||
end
|
||||
|
||||
UIWindow:onDragLeave(tmpp, droppedWidget, mousePos)
|
||||
tmpp:saveParent(tmpp:getParent())
|
||||
end)
|
||||
else
|
||||
addEvent(function() tmpp:setParent(modules.game_interface.getLeftPanel())
|
||||
if tmpp.movedWidget then
|
||||
tmpp.setMovedChildMargin(tmpp.movedOldMargin or 0)
|
||||
tmpp.movedWidget = nil
|
||||
tmpp.setMovedChildMargin = nil
|
||||
tmpp.movedOldMargin = nil
|
||||
tmpp.movedIndex = nil
|
||||
end
|
||||
|
||||
UIWindow:onDragLeave(tmpp, droppedWidget, mousePos)
|
||||
tmpp:saveParent(tmpp:getParent())
|
||||
end)
|
||||
end
|
||||
else
|
||||
addEvent(function() tmpp:setParent(modules.game_interface.getRightPanel())
|
||||
if tmpp.movedWidget then
|
||||
tmpp.setMovedChildMargin(tmpp.movedOldMargin or 0)
|
||||
tmpp.movedWidget = nil
|
||||
tmpp.setMovedChildMargin = nil
|
||||
tmpp.movedOldMargin = nil
|
||||
tmpp.movedIndex = nil
|
||||
end
|
||||
|
||||
UIWindow:onDragLeave(tmpp, droppedWidget, mousePos)
|
||||
tmpp:saveParent(tmpp:getParent())
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIMiniWindow:onDragMove(mousePos, mouseMoved)
|
||||
local oldMousePosY = mousePos.y - mouseMoved.y
|
||||
local children = rootWidget:recursiveGetChildrenByMarginPos(mousePos)
|
||||
local overAnyWidget = false
|
||||
for i=1,#children do
|
||||
local child = children[i]
|
||||
if child:getParent():getClassName() == 'UIMiniWindowContainer' then
|
||||
overAnyWidget = true
|
||||
|
||||
local childCenterY = child:getY() + child:getHeight() / 2
|
||||
if child == self.movedWidget and mousePos.y < childCenterY and oldMousePosY < childCenterY then
|
||||
break
|
||||
end
|
||||
|
||||
if self.movedWidget then
|
||||
self.setMovedChildMargin(self.movedOldMargin or 0)
|
||||
self.setMovedChildMargin = nil
|
||||
end
|
||||
|
||||
if mousePos.y < childCenterY then
|
||||
self.movedOldMargin = child:getMarginTop()
|
||||
self.setMovedChildMargin = function(v) child:setMarginTop(v) end
|
||||
self.movedIndex = 0
|
||||
else
|
||||
self.movedOldMargin = child:getMarginBottom()
|
||||
self.setMovedChildMargin = function(v) child:setMarginBottom(v) end
|
||||
self.movedIndex = 1
|
||||
end
|
||||
|
||||
self.movedWidget = child
|
||||
self.setMovedChildMargin(self:getHeight())
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not overAnyWidget and self.movedWidget then
|
||||
self.setMovedChildMargin(self.movedOldMargin or 0)
|
||||
self.movedWidget = nil
|
||||
end
|
||||
|
||||
return UIWindow.onDragMove(self, mousePos, mouseMoved)
|
||||
end
|
||||
|
||||
function UIMiniWindow:onMousePress()
|
||||
local parent = self:getParent()
|
||||
if not parent then return false end
|
||||
if parent:getClassName() ~= 'UIMiniWindowContainer' then
|
||||
self:raise()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function UIMiniWindow:onFocusChange(focused)
|
||||
if not focused then return end
|
||||
local parent = self:getParent()
|
||||
if parent and parent:getClassName() ~= 'UIMiniWindowContainer' then
|
||||
self:raise()
|
||||
end
|
||||
end
|
||||
|
||||
function UIMiniWindow:onHeightChange(height)
|
||||
if not self:isOn() then
|
||||
self:setSettings({height = height})
|
||||
end
|
||||
self:fitOnParent()
|
||||
end
|
||||
|
||||
function UIMiniWindow:getSettings(name)
|
||||
if not self.save then return nil end
|
||||
local settings = g_settings.getNode('MiniWindows')
|
||||
if settings then
|
||||
local selfSettings = settings[self:getId()]
|
||||
if selfSettings then
|
||||
return selfSettings[name]
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function UIMiniWindow:setSettings(data)
|
||||
if not self.save then return end
|
||||
|
||||
local settings = g_settings.getNode('MiniWindows')
|
||||
if not settings then
|
||||
settings = {}
|
||||
end
|
||||
|
||||
local id = self:getId()
|
||||
if not settings[id] then
|
||||
settings[id] = {}
|
||||
end
|
||||
|
||||
for key,value in pairs(data) do
|
||||
settings[id][key] = value
|
||||
end
|
||||
|
||||
g_settings.setNode('MiniWindows', settings)
|
||||
end
|
||||
|
||||
function UIMiniWindow:eraseSettings(data)
|
||||
if not self.save then return end
|
||||
|
||||
local settings = g_settings.getNode('MiniWindows')
|
||||
if not settings then
|
||||
settings = {}
|
||||
end
|
||||
|
||||
local id = self:getId()
|
||||
if not settings[id] then
|
||||
settings[id] = {}
|
||||
end
|
||||
|
||||
for key,value in pairs(data) do
|
||||
settings[id][key] = nil
|
||||
end
|
||||
|
||||
g_settings.setNode('MiniWindows', settings)
|
||||
end
|
||||
|
||||
function UIMiniWindow:clearSettings()
|
||||
if not self.save then return end
|
||||
|
||||
local settings = g_settings.getNode('MiniWindows')
|
||||
if not settings then
|
||||
settings = {}
|
||||
end
|
||||
|
||||
local id = self:getId()
|
||||
settings[id] = {}
|
||||
|
||||
g_settings.setNode('MiniWindows', settings)
|
||||
end
|
||||
|
||||
function UIMiniWindow:saveParent(parent)
|
||||
local parent = self:getParent()
|
||||
if parent then
|
||||
if parent:getClassName() == 'UIMiniWindowContainer' then
|
||||
parent:saveChildren()
|
||||
else
|
||||
self:saveParentPosition(parent:getId(), self:getPosition())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIMiniWindow:saveParentPosition(parentId, position)
|
||||
local selfSettings = {}
|
||||
selfSettings.parentId = parentId
|
||||
selfSettings.position = pointtostring(position)
|
||||
self:setSettings(selfSettings)
|
||||
end
|
||||
|
||||
function UIMiniWindow:saveParentIndex(parentId, index)
|
||||
local selfSettings = {}
|
||||
selfSettings.parentId = parentId
|
||||
selfSettings.index = index
|
||||
self:setSettings(selfSettings)
|
||||
self.miniIndex = index
|
||||
end
|
||||
|
||||
function UIMiniWindow:disableResize()
|
||||
self:getChildById('bottomResizeBorder'):disable()
|
||||
end
|
||||
|
||||
function UIMiniWindow:enableResize()
|
||||
self:getChildById('bottomResizeBorder'):enable()
|
||||
end
|
||||
|
||||
function UIMiniWindow:fitOnParent()
|
||||
local parent = self:getParent()
|
||||
if self:isVisible() and parent and parent:getClassName() == 'UIMiniWindowContainer' then
|
||||
parent:fitAll(self)
|
||||
end
|
||||
end
|
||||
|
||||
function UIMiniWindow:setParent(parent, dontsave)
|
||||
UIWidget.setParent(self, parent)
|
||||
if not dontsave then
|
||||
self:saveParent(parent)
|
||||
end
|
||||
self:fitOnParent()
|
||||
end
|
||||
|
||||
function UIMiniWindow:setHeight(height)
|
||||
UIWidget.setHeight(self, height)
|
||||
signalcall(self.onHeightChange, self, height)
|
||||
end
|
||||
|
||||
function UIMiniWindow:setContentHeight(height)
|
||||
local contentsPanel = self:getChildById('contentsPanel')
|
||||
local minHeight = contentsPanel:getMarginTop() + contentsPanel:getMarginBottom() + contentsPanel:getPaddingTop() + contentsPanel:getPaddingBottom()
|
||||
|
||||
local resizeBorder = self:getChildById('bottomResizeBorder')
|
||||
resizeBorder:setParentSize(minHeight + height)
|
||||
end
|
||||
|
||||
function UIMiniWindow:setContentMinimumHeight(height)
|
||||
local contentsPanel = self:getChildById('contentsPanel')
|
||||
local minHeight = contentsPanel:getMarginTop() + contentsPanel:getMarginBottom() + contentsPanel:getPaddingTop() + contentsPanel:getPaddingBottom()
|
||||
|
||||
local resizeBorder = self:getChildById('bottomResizeBorder')
|
||||
resizeBorder:setMinimum(minHeight + height)
|
||||
end
|
||||
|
||||
function UIMiniWindow:setContentMaximumHeight(height)
|
||||
local contentsPanel = self:getChildById('contentsPanel')
|
||||
local minHeight = contentsPanel:getMarginTop() + contentsPanel:getMarginBottom() + contentsPanel:getPaddingTop() + contentsPanel:getPaddingBottom()
|
||||
|
||||
local resizeBorder = self:getChildById('bottomResizeBorder')
|
||||
resizeBorder:setMaximum(minHeight + height)
|
||||
end
|
||||
|
||||
function UIMiniWindow:getMinimumHeight()
|
||||
local resizeBorder = self:getChildById('bottomResizeBorder')
|
||||
return resizeBorder:getMinimum()
|
||||
end
|
||||
|
||||
function UIMiniWindow:getMaximumHeight()
|
||||
local resizeBorder = self:getChildById('bottomResizeBorder')
|
||||
return resizeBorder:getMaximum()
|
||||
end
|
||||
|
||||
function UIMiniWindow:isResizeable()
|
||||
local resizeBorder = self:getChildById('bottomResizeBorder')
|
||||
return resizeBorder:isExplicitlyVisible() and resizeBorder:isEnabled()
|
||||
end
|
228
SabrehavenOTClient/modules/corelib/ui/uiminiwindowcontainer.lua
Normal file
228
SabrehavenOTClient/modules/corelib/ui/uiminiwindowcontainer.lua
Normal file
@@ -0,0 +1,228 @@
|
||||
-- @docclass
|
||||
UIMiniWindowContainer = extends(UIWidget, "UIMiniWindowContainer")
|
||||
|
||||
function UIMiniWindowContainer.create()
|
||||
local container = UIMiniWindowContainer.internalCreate()
|
||||
container.scheduledWidgets = {}
|
||||
container:setFocusable(false)
|
||||
container:setPhantom(true)
|
||||
return container
|
||||
end
|
||||
|
||||
-- TODO: connect to window onResize event
|
||||
-- TODO: try to resize another widget?
|
||||
-- TODO: try to find another panel?
|
||||
function UIMiniWindowContainer:fitAll(noRemoveChild)
|
||||
if not self:isVisible() then
|
||||
return
|
||||
end
|
||||
|
||||
if not noRemoveChild then
|
||||
local children = self:getChildren()
|
||||
if #children > 0 then
|
||||
noRemoveChild = children[#children]
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local sumHeight = 0
|
||||
local children = self:getChildren()
|
||||
for i=1,#children do
|
||||
if children[i]:isVisible() then
|
||||
sumHeight = sumHeight + children[i]:getHeight()
|
||||
end
|
||||
end
|
||||
|
||||
local selfHeight = self:getHeight() - (self:getPaddingTop() + self:getPaddingBottom())
|
||||
if sumHeight <= selfHeight then
|
||||
return
|
||||
end
|
||||
|
||||
local removeChildren = {}
|
||||
|
||||
-- try to resize noRemoveChild
|
||||
local maximumHeight = selfHeight - (sumHeight - noRemoveChild:getHeight())
|
||||
if noRemoveChild:isResizeable() and noRemoveChild:getMinimumHeight() <= maximumHeight then
|
||||
sumHeight = sumHeight - noRemoveChild:getHeight() + maximumHeight
|
||||
addEvent(function() noRemoveChild:setHeight(maximumHeight) end)
|
||||
end
|
||||
|
||||
-- try to remove no-save widget
|
||||
for i=#children,1,-1 do
|
||||
if sumHeight <= selfHeight then
|
||||
break
|
||||
end
|
||||
|
||||
local child = children[i]
|
||||
if child ~= noRemoveChild and not child.save then
|
||||
local childHeight = child:getHeight()
|
||||
sumHeight = sumHeight - childHeight
|
||||
table.insert(removeChildren, child)
|
||||
end
|
||||
end
|
||||
|
||||
-- try to remove save widget, not forceOpen
|
||||
for i=#children,1,-1 do
|
||||
if sumHeight <= selfHeight then
|
||||
break
|
||||
end
|
||||
|
||||
local child = children[i]
|
||||
if child ~= noRemoveChild and child:isVisible() and not child.forceOpen then
|
||||
local childHeight = child:getHeight()
|
||||
sumHeight = sumHeight - childHeight
|
||||
table.insert(removeChildren, child)
|
||||
end
|
||||
end
|
||||
|
||||
-- try to remove save widget
|
||||
for i=#children,1,-1 do
|
||||
if sumHeight <= selfHeight then
|
||||
break
|
||||
end
|
||||
|
||||
local child = children[i]
|
||||
if child ~= noRemoveChild and child:isVisible() then
|
||||
local childHeight = child:getHeight() - 50
|
||||
sumHeight = sumHeight - childHeight
|
||||
table.insert(removeChildren, child)
|
||||
end
|
||||
end
|
||||
|
||||
-- close widgets
|
||||
for i=1,#removeChildren do
|
||||
if removeChildren[i].forceOpen then
|
||||
removeChildren[i]:minimize(true)
|
||||
else
|
||||
removeChildren[i]:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIMiniWindowContainer:onDrop(widget, mousePos)
|
||||
if widget.UIMiniWindowContainer then
|
||||
local oldParent = widget:getParent()
|
||||
if oldParent == self then
|
||||
return true
|
||||
end
|
||||
|
||||
if oldParent then
|
||||
oldParent:removeChild(widget)
|
||||
end
|
||||
|
||||
if widget.movedWidget then
|
||||
local index = self:getChildIndex(widget.movedWidget)
|
||||
self:insertChild(index + widget.movedIndex, widget)
|
||||
else
|
||||
self:addChild(widget)
|
||||
end
|
||||
|
||||
self:fitAll(widget)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function UIMiniWindowContainer:moveTo(newPanel)
|
||||
if not newPanel or newPanel == self then
|
||||
return
|
||||
end
|
||||
local children = self:getChildByIndex(1)
|
||||
while children do
|
||||
newPanel:addChild(children)
|
||||
children = self:getChildByIndex(1)
|
||||
end
|
||||
newPanel:fitAll()
|
||||
end
|
||||
|
||||
function UIMiniWindowContainer:swapInsert(widget, index)
|
||||
local oldParent = widget:getParent()
|
||||
local oldIndex = self:getChildIndex(widget)
|
||||
|
||||
if oldParent == self and oldIndex ~= index then
|
||||
local oldWidget = self:getChildByIndex(index)
|
||||
if oldWidget then
|
||||
self:removeChild(oldWidget)
|
||||
self:insertChild(oldIndex, oldWidget)
|
||||
end
|
||||
self:removeChild(widget)
|
||||
self:insertChild(index, widget)
|
||||
end
|
||||
end
|
||||
|
||||
function UIMiniWindowContainer:scheduleInsert(widget, index)
|
||||
if index - 1 > self:getChildCount() then
|
||||
if self.scheduledWidgets[index] then
|
||||
pdebug('replacing scheduled widget id ' .. widget:getId())
|
||||
end
|
||||
self.scheduledWidgets[index] = widget
|
||||
else
|
||||
local oldParent = widget:getParent()
|
||||
if oldParent ~= self then
|
||||
if oldParent then
|
||||
oldParent:removeChild(widget)
|
||||
end
|
||||
self:insertChild(index, widget)
|
||||
|
||||
while true do
|
||||
local placed = false
|
||||
for nIndex,nWidget in pairs(self.scheduledWidgets) do
|
||||
if nIndex - 1 <= self:getChildCount() then
|
||||
local oldParent = nWidget:getParent()
|
||||
if oldParent ~= self then
|
||||
if oldParent then
|
||||
oldParent:removeChild(nWidget)
|
||||
end
|
||||
self:insertChild(nIndex, nWidget)
|
||||
else
|
||||
self:moveChildToIndex(nWidget, nIndex)
|
||||
end
|
||||
self.scheduledWidgets[nIndex] = nil
|
||||
placed = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not placed then break end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIMiniWindowContainer:order()
|
||||
local children = self:getChildren()
|
||||
for i=1,#children do
|
||||
if not children[i].miniLoaded then return end
|
||||
end
|
||||
|
||||
table.sort(children, function(a, b)
|
||||
local indexA = a.miniIndex or a.autoOpen or 999
|
||||
local indexB = b.miniIndex or b.autoOpen or 999
|
||||
return indexA < indexB
|
||||
end)
|
||||
|
||||
self:reorderChildren(children)
|
||||
local ignoreIndex = 0
|
||||
for i=1,#children do
|
||||
if children[i].save then
|
||||
children[i].miniIndex = i - ignoreIndex
|
||||
else
|
||||
ignoreIndex = ignoreIndex + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIMiniWindowContainer:saveChildren()
|
||||
local children = self:getChildren()
|
||||
local ignoreIndex = 0
|
||||
for i=1,#children do
|
||||
if children[i].save then
|
||||
children[i]:saveParentIndex(self:getId(), i - ignoreIndex)
|
||||
else
|
||||
ignoreIndex = ignoreIndex + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIMiniWindowContainer:onGeometryChange()
|
||||
self:fitAll()
|
||||
end
|
505
SabrehavenOTClient/modules/corelib/ui/uimovabletabbar.lua
Normal file
505
SabrehavenOTClient/modules/corelib/ui/uimovabletabbar.lua
Normal file
@@ -0,0 +1,505 @@
|
||||
-- @docclass
|
||||
UIMoveableTabBar = extends(UIWidget, "UIMoveableTabBar")
|
||||
|
||||
-- private functions
|
||||
local function onTabClick(tab)
|
||||
tab.tabBar:selectTab(tab)
|
||||
end
|
||||
|
||||
local function updateMargins(tabBar)
|
||||
if #tabBar.tabs == 0 then return end
|
||||
|
||||
local currentMargin = 0
|
||||
for i = 1, #tabBar.tabs do
|
||||
tabBar.tabs[i]:setMarginLeft(currentMargin)
|
||||
currentMargin = currentMargin + tabBar.tabSpacing + tabBar.tabs[i]:getWidth()
|
||||
end
|
||||
end
|
||||
|
||||
local function updateNavigation(tabBar)
|
||||
if tabBar.prevNavigation then
|
||||
if #tabBar.preTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= 1 then
|
||||
tabBar.prevNavigation:enable()
|
||||
else
|
||||
tabBar.prevNavigation:disable()
|
||||
end
|
||||
end
|
||||
|
||||
if tabBar.nextNavigation then
|
||||
if #tabBar.postTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= #tabBar.tabs then
|
||||
tabBar.nextNavigation:enable()
|
||||
else
|
||||
tabBar.nextNavigation:disable()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function updateIndexes(tabBar, tab, xoff)
|
||||
local tabs = tabBar.tabs
|
||||
local currentMargin = 0
|
||||
local prevIndex = table.find(tabs, tab)
|
||||
local newIndex = prevIndex
|
||||
local xmid = xoff + tab:getWidth()/2
|
||||
for i = 1, #tabs do
|
||||
local nextTab = tabs[i]
|
||||
if xmid >= currentMargin + nextTab:getWidth()/2 then
|
||||
newIndex = table.find(tabs, nextTab)
|
||||
end
|
||||
currentMargin = currentMargin + tabBar.tabSpacing * (i - 1) + tabBar.tabs[i]:getWidth()
|
||||
end
|
||||
if newIndex ~= prevIndex then
|
||||
table.remove(tabs, table.find(tabs, tab))
|
||||
table.insert(tabs, newIndex, tab)
|
||||
end
|
||||
updateNavigation(tabBar)
|
||||
end
|
||||
|
||||
local function getMaxMargin(tabBar, tab)
|
||||
if #tabBar.tabs == 0 then return 0 end
|
||||
|
||||
local maxMargin = 0
|
||||
for i = 1, #tabBar.tabs do
|
||||
if tabBar.tabs[i] ~= tab then
|
||||
maxMargin = maxMargin + tabBar.tabs[i]:getWidth()
|
||||
end
|
||||
end
|
||||
return maxMargin + tabBar.tabSpacing * (#tabBar.tabs - 1)
|
||||
end
|
||||
|
||||
local function updateTabs(tabBar)
|
||||
if #tabBar.postTabs > 0 then
|
||||
local i = 1
|
||||
while i <= #tabBar.postTabs do
|
||||
local tab = tabBar.postTabs[i]
|
||||
if getMaxMargin(tabBar) + tab:getWidth() > tabBar:getWidth() then
|
||||
break
|
||||
end
|
||||
|
||||
table.remove(tabBar.postTabs, i)
|
||||
table.insert(tabBar.tabs, tab)
|
||||
tab:setVisible(true)
|
||||
end
|
||||
end
|
||||
if #tabBar.preTabs > 0 then
|
||||
for i = #tabBar.preTabs, 1, -1 do
|
||||
local tab = tabBar.preTabs[i]
|
||||
if getMaxMargin(tabBar) + tab:getWidth() > tabBar:getWidth() then
|
||||
break
|
||||
end
|
||||
|
||||
table.remove(tabBar.preTabs, i)
|
||||
table.insert(tabBar.tabs, 1, tab)
|
||||
tab:setVisible(true)
|
||||
end
|
||||
end
|
||||
updateNavigation(tabBar)
|
||||
updateMargins(tabBar)
|
||||
if not tabBar.currentTab and #tabBar.tabs > 0 then
|
||||
tabBar:selectTab(tabBar.tabs[1])
|
||||
end
|
||||
end
|
||||
|
||||
local function hideTabs(tabBar, fromBack, toArray, width)
|
||||
while #tabBar.tabs > 0 and getMaxMargin(tabBar) + width > tabBar:getWidth() do
|
||||
local index = fromBack and #tabBar.tabs or 1
|
||||
local tab = tabBar.tabs[index]
|
||||
table.remove(tabBar.tabs, index)
|
||||
if fromBack then
|
||||
table.insert(toArray, 1, tab)
|
||||
else
|
||||
table.insert(toArray, tab)
|
||||
end
|
||||
if tabBar.currentTab == tab then
|
||||
if #tabBar.tabs > 0 then
|
||||
tabBar:selectTab(tabBar.tabs[#tabBar.tabs])
|
||||
else
|
||||
tabBar.currentTab:setChecked(false)
|
||||
tabBar.currentTab = nil
|
||||
end
|
||||
end
|
||||
tab:setVisible(false)
|
||||
end
|
||||
end
|
||||
|
||||
local function showPreTab(tabBar)
|
||||
if #tabBar.preTabs == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
local tmpTab = tabBar.preTabs[#tabBar.preTabs]
|
||||
hideTabs(tabBar, true, tabBar.postTabs, tmpTab:getWidth())
|
||||
|
||||
table.remove(tabBar.preTabs, #tabBar.preTabs)
|
||||
table.insert(tabBar.tabs, 1, tmpTab)
|
||||
tmpTab:setVisible(true)
|
||||
return tmpTab
|
||||
end
|
||||
|
||||
local function showPostTab(tabBar)
|
||||
if #tabBar.postTabs == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
local tmpTab = tabBar.postTabs[1]
|
||||
hideTabs(tabBar, false, tabBar.preTabs, tmpTab:getWidth())
|
||||
|
||||
table.remove(tabBar.postTabs, 1)
|
||||
table.insert(tabBar.tabs, tmpTab)
|
||||
tmpTab:setVisible(true)
|
||||
return tmpTab
|
||||
end
|
||||
|
||||
local function onTabMousePress(tab, mousePos, mouseButton)
|
||||
if mouseButton == MouseRightButton then
|
||||
if tab.menuCallback then tab.menuCallback(tab, mousePos, mouseButton) end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local function onTabDragEnter(tab, mousePos)
|
||||
tab:raise()
|
||||
tab.hotSpot = mousePos.x - tab:getMarginLeft()
|
||||
tab.tabBar.selected = tab
|
||||
return true
|
||||
end
|
||||
|
||||
local function onTabDragLeave(tab)
|
||||
updateMargins(tab.tabBar)
|
||||
tab.tabBar.selected = nil
|
||||
return true
|
||||
end
|
||||
|
||||
local function onTabDragMove(tab, mousePos, mouseMoved)
|
||||
if tab == tab.tabBar.selected then
|
||||
local xoff = mousePos.x - tab.hotSpot
|
||||
|
||||
-- update indexes
|
||||
updateIndexes(tab.tabBar, tab, xoff)
|
||||
updateIndexes(tab.tabBar, tab, xoff)
|
||||
|
||||
-- update margins
|
||||
updateMargins(tab.tabBar)
|
||||
xoff = math.max(xoff, 0)
|
||||
xoff = math.min(xoff, getMaxMargin(tab.tabBar, tab))
|
||||
tab:setMarginLeft(xoff)
|
||||
end
|
||||
end
|
||||
|
||||
local function tabBlink(tab, step)
|
||||
local step = step or 0
|
||||
tab:setOn(not tab:isOn())
|
||||
|
||||
removeEvent(tab.blinkEvent)
|
||||
if step < 4 then
|
||||
tab.blinkEvent = scheduleEvent(function() tabBlink(tab, step+1) end, 500)
|
||||
else
|
||||
tab:setOn(true)
|
||||
tab.blinkEvent = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- public functions
|
||||
function UIMoveableTabBar.create()
|
||||
local tabbar = UIMoveableTabBar.internalCreate()
|
||||
tabbar:setFocusable(false)
|
||||
tabbar.tabs = {}
|
||||
tabbar.selected = nil -- dragged tab
|
||||
tabbar.tabSpacing = 0
|
||||
tabbar.tabsMoveable = false
|
||||
tabbar.preTabs = {}
|
||||
tabbar.postTabs = {}
|
||||
tabbar.prevNavigation = nil
|
||||
tabbar.nextNavigation = nil
|
||||
tabbar.onGeometryChange = function()
|
||||
hideTabs(tabbar, true, tabbar.postTabs, 0)
|
||||
updateTabs(tabbar)
|
||||
end
|
||||
return tabbar
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:onDestroy()
|
||||
if self.prevNavigation then
|
||||
self.prevNavigation:disable()
|
||||
end
|
||||
|
||||
if self.nextNavigation then
|
||||
self.nextNavigation:disable()
|
||||
end
|
||||
|
||||
self.nextNavigation = nil
|
||||
self.prevNavigation = nil
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:setContentWidget(widget)
|
||||
self.contentWidget = widget
|
||||
if #self.tabs > 0 then
|
||||
self.contentWidget:addChild(self.tabs[1].tabPanel)
|
||||
end
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:setTabSpacing(tabSpacing)
|
||||
self.tabSpacing = tabSpacing
|
||||
updateMargins(self)
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:addTab(text, panel, menuCallback)
|
||||
if panel == nil then
|
||||
panel = g_ui.createWidget(self:getStyleName() .. 'Panel')
|
||||
panel:setId('tabPanel')
|
||||
end
|
||||
|
||||
local tab = g_ui.createWidget(self:getStyleName() .. 'Button', self)
|
||||
panel.isTab = true
|
||||
tab.tabPanel = panel
|
||||
tab.tabBar = self
|
||||
tab:setId('tab')
|
||||
tab:setDraggable(self.tabsMoveable)
|
||||
tab:setText(text)
|
||||
tab:setWidth(tab:getTextSize().width + tab:getPaddingLeft() + tab:getPaddingRight())
|
||||
tab.menuCallback = menuCallback or nil
|
||||
tab.onClick = onTabClick
|
||||
tab.onMousePress = onTabMousePress
|
||||
tab.onDragEnter = onTabDragEnter
|
||||
tab.onDragLeave = onTabDragLeave
|
||||
tab.onDragMove = onTabDragMove
|
||||
tab.onDestroy = function() tab.tabPanel:destroy() end
|
||||
|
||||
if #self.tabs == 0 then
|
||||
self:selectTab(tab)
|
||||
tab:setMarginLeft(0)
|
||||
table.insert(self.tabs, tab)
|
||||
else
|
||||
local newMargin = self.tabSpacing * #self.tabs
|
||||
for i = 1, #self.tabs do
|
||||
newMargin = newMargin + self.tabs[i]:getWidth()
|
||||
end
|
||||
tab:setMarginLeft(newMargin)
|
||||
|
||||
hideTabs(self, true, self.postTabs, tab:getWidth())
|
||||
table.insert(self.tabs, tab)
|
||||
if #self.tabs == 1 then
|
||||
self:selectTab(tab)
|
||||
end
|
||||
updateMargins(self)
|
||||
end
|
||||
|
||||
updateNavigation(self)
|
||||
return tab
|
||||
end
|
||||
|
||||
-- Additional function to move the tab by lua
|
||||
function UIMoveableTabBar:moveTab(tab, units)
|
||||
local index = table.find(self.tabs, tab)
|
||||
if index == nil then return end
|
||||
|
||||
local focus = false
|
||||
if self.currentTab == tab then
|
||||
self:selectPrevTab()
|
||||
focus = true
|
||||
end
|
||||
|
||||
table.remove(self.tabs, index)
|
||||
|
||||
local newIndex = math.min(#self.tabs+1, math.max(index + units, 1))
|
||||
table.insert(self.tabs, newIndex, tab)
|
||||
if focus then self:selectTab(tab) end
|
||||
updateMargins(self)
|
||||
return newIndex
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:onStyleApply(styleName, styleNode)
|
||||
if styleNode['movable'] then
|
||||
self.tabsMoveable = styleNode['movable']
|
||||
end
|
||||
if styleNode['tab-spacing'] then
|
||||
self:setTabSpacing(styleNode['tab-spacing'])
|
||||
end
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:clearTabs()
|
||||
while #self.tabs > 0 do
|
||||
self:removeTab(self.tabs[#self.tabs])
|
||||
end
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:removeTab(tab)
|
||||
local tabTables = {self.tabs, self.preTabs, self.postTabs}
|
||||
local index = nil
|
||||
local tabTable = nil
|
||||
for i = 1, #tabTables do
|
||||
index = table.find(tabTables[i], tab)
|
||||
if index ~= nil then
|
||||
tabTable = tabTables[i]
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if tabTable == nil then
|
||||
return
|
||||
end
|
||||
table.remove(tabTable, index)
|
||||
if self.currentTab == tab then
|
||||
self:selectPrevTab()
|
||||
if #self.tabs == 1 then
|
||||
self.currentTab = nil
|
||||
end
|
||||
end
|
||||
if tab.blinkEvent then
|
||||
removeEvent(tab.blinkEvent)
|
||||
end
|
||||
updateTabs(self)
|
||||
tab:destroy()
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:getTab(text)
|
||||
for k,tab in pairs(self.tabs) do
|
||||
if tab:getText():lower() == text:lower() then
|
||||
return tab
|
||||
end
|
||||
end
|
||||
for k,tab in pairs(self.preTabs) do
|
||||
if tab:getText():lower() == text:lower() then
|
||||
return tab
|
||||
end
|
||||
end
|
||||
for k,tab in pairs(self.postTabs) do
|
||||
if tab:getText():lower() == text:lower() then
|
||||
return tab
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:selectTab(tab)
|
||||
if self.currentTab == tab then return end
|
||||
if self.contentWidget then
|
||||
local selectedWidget = self.contentWidget:getLastChild()
|
||||
if selectedWidget and selectedWidget.isTab then
|
||||
self.contentWidget:removeChild(selectedWidget)
|
||||
end
|
||||
self.contentWidget:addChild(tab.tabPanel)
|
||||
tab.tabPanel:fill('parent')
|
||||
end
|
||||
|
||||
if self.currentTab then
|
||||
self.currentTab:setChecked(false)
|
||||
end
|
||||
signalcall(self.onTabChange, self, tab)
|
||||
self.currentTab = tab
|
||||
tab:setChecked(true)
|
||||
tab:setOn(false)
|
||||
tab.blinking = false
|
||||
|
||||
if tab.blinkEvent then
|
||||
removeEvent(tab.blinkEvent)
|
||||
tab.blinkEvent = nil
|
||||
end
|
||||
|
||||
local parent = tab:getParent()
|
||||
parent:focusChild(tab, MouseFocusReason)
|
||||
updateNavigation(self)
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:selectNextTab()
|
||||
if self.currentTab == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local index = table.find(self.tabs, self.currentTab)
|
||||
if index == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local newIndex = index + 1
|
||||
if newIndex > #self.tabs then
|
||||
if #self.postTabs > 0 then
|
||||
local widget = showPostTab(self)
|
||||
self:selectTab(widget)
|
||||
else
|
||||
if #self.preTabs > 0 then
|
||||
for i = 1, #self.preTabs do
|
||||
showPreTab(self)
|
||||
end
|
||||
end
|
||||
|
||||
self:selectTab(self.tabs[1])
|
||||
end
|
||||
updateTabs(self)
|
||||
return
|
||||
end
|
||||
|
||||
local nextTab = self.tabs[newIndex]
|
||||
if not nextTab then
|
||||
return
|
||||
end
|
||||
|
||||
self:selectTab(nextTab)
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:selectPrevTab()
|
||||
if self.currentTab == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local index = table.find(self.tabs, self.currentTab)
|
||||
if index == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local newIndex = index - 1
|
||||
if newIndex <= 0 then
|
||||
if #self.preTabs > 0 then
|
||||
local widget = showPreTab(self)
|
||||
self:selectTab(widget)
|
||||
else
|
||||
if #self.postTabs > 0 then
|
||||
for i = 1, #self.postTabs do
|
||||
showPostTab(self)
|
||||
end
|
||||
end
|
||||
|
||||
self:selectTab(self.tabs[#self.tabs])
|
||||
end
|
||||
updateTabs(self)
|
||||
return
|
||||
end
|
||||
|
||||
local prevTab = self.tabs[newIndex]
|
||||
if not prevTab then
|
||||
return
|
||||
end
|
||||
|
||||
self:selectTab(prevTab)
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:blinkTab(tab)
|
||||
if tab:isChecked() then return end
|
||||
tab.blinking = true
|
||||
tabBlink(tab)
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:getTabPanel(tab)
|
||||
return tab.tabPanel
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:getCurrentTabPanel()
|
||||
if self.currentTab then
|
||||
return self.currentTab.tabPanel
|
||||
end
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:getCurrentTab()
|
||||
return self.currentTab
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:setNavigation(prevButton, nextButton)
|
||||
self.prevNavigation = prevButton
|
||||
self.nextNavigation = nextButton
|
||||
|
||||
if self.prevNavigation then
|
||||
self.prevNavigation.onClick = function() self:selectPrevTab() end
|
||||
end
|
||||
if self.nextNavigation then
|
||||
self.nextNavigation.onClick = function() self:selectNextTab() end
|
||||
end
|
||||
updateNavigation(self)
|
||||
end
|
122
SabrehavenOTClient/modules/corelib/ui/uipopupmenu.lua
Normal file
122
SabrehavenOTClient/modules/corelib/ui/uipopupmenu.lua
Normal file
@@ -0,0 +1,122 @@
|
||||
-- @docclass
|
||||
UIPopupMenu = extends(UIWidget, "UIPopupMenu")
|
||||
|
||||
local currentMenu
|
||||
|
||||
function UIPopupMenu.create()
|
||||
local menu = UIPopupMenu.internalCreate()
|
||||
local layout = UIVerticalLayout.create(menu)
|
||||
layout:setFitChildren(true)
|
||||
menu:setLayout(layout)
|
||||
menu.isGameMenu = false
|
||||
return menu
|
||||
end
|
||||
|
||||
function UIPopupMenu:display(pos)
|
||||
-- don't display if not options was added
|
||||
if self:getChildCount() == 0 then
|
||||
self:destroy()
|
||||
return
|
||||
end
|
||||
|
||||
if g_ui.isMouseGrabbed() then
|
||||
self:destroy()
|
||||
return
|
||||
end
|
||||
|
||||
if currentMenu then
|
||||
currentMenu:destroy()
|
||||
end
|
||||
|
||||
if pos == nil then
|
||||
pos = g_window.getMousePosition()
|
||||
end
|
||||
|
||||
rootWidget:addChild(self)
|
||||
self:setPosition(pos)
|
||||
self:grabMouse()
|
||||
self:focus()
|
||||
--self:grabKeyboard()
|
||||
currentMenu = self
|
||||
end
|
||||
|
||||
function UIPopupMenu:onGeometryChange(oldRect, newRect)
|
||||
local parent = self:getParent()
|
||||
if not parent then return end
|
||||
local ymax = parent:getY() + parent:getHeight()
|
||||
local xmax = parent:getX() + parent:getWidth()
|
||||
if newRect.y + newRect.height > ymax then
|
||||
local newy = ymax - newRect.height
|
||||
if newy > 0 and newy + newRect.height < ymax then self:setY(newy) end
|
||||
end
|
||||
if newRect.x + newRect.width > xmax then
|
||||
local newx = xmax - newRect.width
|
||||
if newx > 0 and newx + newRect.width < xmax then self:setX(newx) end
|
||||
end
|
||||
self:bindRectToParent()
|
||||
end
|
||||
|
||||
function UIPopupMenu:addOption(optionName, optionCallback, shortcut)
|
||||
local optionWidget = g_ui.createWidget(self:getStyleName() .. 'Button', self)
|
||||
optionWidget.onClick = function(widget)
|
||||
self:destroy()
|
||||
optionCallback()
|
||||
end
|
||||
optionWidget:setText(optionName)
|
||||
local width = optionWidget:getTextSize().width + optionWidget:getMarginLeft() + optionWidget:getMarginRight() + 15
|
||||
|
||||
if shortcut then
|
||||
local shortcutLabel = g_ui.createWidget(self:getStyleName() .. 'ShortcutLabel', optionWidget)
|
||||
shortcutLabel:setText(shortcut)
|
||||
width = width + shortcutLabel:getTextSize().width + shortcutLabel:getMarginLeft() + shortcutLabel:getMarginRight()
|
||||
end
|
||||
|
||||
self:setWidth(math.max(self:getWidth(), width))
|
||||
end
|
||||
|
||||
function UIPopupMenu:addSeparator()
|
||||
g_ui.createWidget(self:getStyleName() .. 'Separator', self)
|
||||
end
|
||||
|
||||
function UIPopupMenu:setGameMenu(state)
|
||||
self.isGameMenu = state
|
||||
end
|
||||
|
||||
function UIPopupMenu:onDestroy()
|
||||
if currentMenu == self then
|
||||
currentMenu = nil
|
||||
end
|
||||
self:ungrabMouse()
|
||||
end
|
||||
|
||||
function UIPopupMenu:onMousePress(mousePos, mouseButton)
|
||||
-- clicks outside menu area destroys the menu
|
||||
if not self:containsPoint(mousePos) then
|
||||
self:destroy()
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function UIPopupMenu:onKeyPress(keyCode, keyboardModifiers)
|
||||
if keyCode == KeyEscape then
|
||||
self:destroy()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- close all menus when the window is resized
|
||||
local function onRootGeometryUpdate()
|
||||
if currentMenu then
|
||||
currentMenu:destroy()
|
||||
end
|
||||
end
|
||||
|
||||
local function onGameEnd()
|
||||
if currentMenu and currentMenu.isGameMenu then
|
||||
currentMenu:destroy()
|
||||
end
|
||||
end
|
||||
|
||||
connect(rootWidget, { onGeometryChange = onRootGeometryUpdate })
|
||||
connect(g_game, { onGameEnd = onGameEnd } )
|
129
SabrehavenOTClient/modules/corelib/ui/uipopupscrollmenu.lua
Normal file
129
SabrehavenOTClient/modules/corelib/ui/uipopupscrollmenu.lua
Normal file
@@ -0,0 +1,129 @@
|
||||
-- @docclass
|
||||
UIPopupScrollMenu = extends(UIWidget, "UIPopupScrollMenu")
|
||||
|
||||
local currentMenu
|
||||
|
||||
function UIPopupScrollMenu.create()
|
||||
local menu = UIPopupScrollMenu.internalCreate()
|
||||
|
||||
local scrollArea = g_ui.createWidget('UIScrollArea', menu)
|
||||
scrollArea:setLayout(UIVerticalLayout.create(menu))
|
||||
scrollArea:setId('scrollArea')
|
||||
|
||||
local scrollBar = g_ui.createWidget('VerticalScrollBar', menu)
|
||||
scrollBar:setId('scrollBar')
|
||||
scrollBar.pixelsScroll = false
|
||||
|
||||
scrollBar:addAnchor(AnchorRight, 'parent', AnchorRight)
|
||||
scrollBar:addAnchor(AnchorTop, 'parent', AnchorTop)
|
||||
scrollBar:addAnchor(AnchorBottom, 'parent', AnchorBottom)
|
||||
|
||||
scrollArea:addAnchor(AnchorLeft, 'parent', AnchorLeft)
|
||||
scrollArea:addAnchor(AnchorTop, 'parent', AnchorTop)
|
||||
scrollArea:addAnchor(AnchorBottom, 'parent', AnchorBottom)
|
||||
scrollArea:addAnchor(AnchorRight, 'next', AnchorLeft)
|
||||
scrollArea:setVerticalScrollBar(scrollBar)
|
||||
|
||||
menu.scrollArea = scrollArea
|
||||
menu.scrollBar = scrollBar
|
||||
return menu
|
||||
end
|
||||
|
||||
function UIPopupScrollMenu:setScrollbarStep(step)
|
||||
self.scrollBar:setStep(step)
|
||||
end
|
||||
|
||||
function UIPopupScrollMenu:display(pos)
|
||||
-- don't display if not options was added
|
||||
if self.scrollArea:getChildCount() == 0 then
|
||||
self:destroy()
|
||||
return
|
||||
end
|
||||
|
||||
if g_ui.isMouseGrabbed() then
|
||||
self:destroy()
|
||||
return
|
||||
end
|
||||
|
||||
if currentMenu then
|
||||
currentMenu:destroy()
|
||||
end
|
||||
|
||||
if pos == nil then
|
||||
pos = g_window.getMousePosition()
|
||||
end
|
||||
|
||||
rootWidget:addChild(self)
|
||||
self:setPosition(pos)
|
||||
self:grabMouse()
|
||||
currentMenu = self
|
||||
end
|
||||
|
||||
function UIPopupScrollMenu:onGeometryChange(oldRect, newRect)
|
||||
local parent = self:getParent()
|
||||
if not parent then return end
|
||||
local ymax = parent:getY() + parent:getHeight()
|
||||
local xmax = parent:getX() + parent:getWidth()
|
||||
if newRect.y + newRect.height > ymax then
|
||||
local newy = newRect.y - newRect.height
|
||||
if newy > 0 and newy + newRect.height < ymax then self:setY(newy) end
|
||||
end
|
||||
if newRect.x + newRect.width > xmax then
|
||||
local newx = newRect.x - newRect.width
|
||||
if newx > 0 and newx + newRect.width < xmax then self:setX(newx) end
|
||||
end
|
||||
self:bindRectToParent()
|
||||
end
|
||||
|
||||
function UIPopupScrollMenu:addOption(optionName, optionCallback, shortcut)
|
||||
local optionWidget = g_ui.createWidget(self:getStyleName() .. 'Button', self.scrollArea)
|
||||
optionWidget.onClick = function(widget)
|
||||
self:destroy()
|
||||
optionCallback()
|
||||
end
|
||||
optionWidget:setText(optionName)
|
||||
local width = optionWidget:getTextSize().width + optionWidget:getMarginLeft() + optionWidget:getMarginRight() + 15
|
||||
|
||||
if shortcut then
|
||||
local shortcutLabel = g_ui.createWidget(self:getStyleName() .. 'ShortcutLabel', optionWidget)
|
||||
shortcutLabel:setText(shortcut)
|
||||
width = width + shortcutLabel:getTextSize().width + shortcutLabel:getMarginLeft() + shortcutLabel:getMarginRight()
|
||||
end
|
||||
|
||||
self:setWidth(math.max(self:getWidth(), width))
|
||||
end
|
||||
|
||||
function UIPopupScrollMenu:addSeparator()
|
||||
g_ui.createWidget(self:getStyleName() .. 'Separator', self.scrollArea)
|
||||
end
|
||||
|
||||
function UIPopupScrollMenu:onDestroy()
|
||||
if currentMenu == self then
|
||||
currentMenu = nil
|
||||
end
|
||||
self:ungrabMouse()
|
||||
end
|
||||
|
||||
function UIPopupScrollMenu:onMousePress(mousePos, mouseButton)
|
||||
-- clicks outside menu area destroys the menu
|
||||
if not self:containsPoint(mousePos) then
|
||||
self:destroy()
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function UIPopupScrollMenu:onKeyPress(keyCode, keyboardModifiers)
|
||||
if keyCode == KeyEscape then
|
||||
self:destroy()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- close all menus when the window is resized
|
||||
local function onRootGeometryUpdate()
|
||||
if currentMenu then
|
||||
currentMenu:destroy()
|
||||
end
|
||||
end
|
||||
connect(rootWidget, { onGeometryChange = onRootGeometryUpdate} )
|
99
SabrehavenOTClient/modules/corelib/ui/uiprogressbar.lua
Normal file
99
SabrehavenOTClient/modules/corelib/ui/uiprogressbar.lua
Normal file
@@ -0,0 +1,99 @@
|
||||
-- @docclass
|
||||
UIProgressBar = extends(UIWidget, "UIProgressBar")
|
||||
|
||||
function UIProgressBar.create()
|
||||
local progressbar = UIProgressBar.internalCreate()
|
||||
progressbar:setFocusable(false)
|
||||
progressbar:setOn(true)
|
||||
progressbar.min = 0
|
||||
progressbar.max = 100
|
||||
progressbar.value = 0
|
||||
progressbar.bgBorderLeft = 0
|
||||
progressbar.bgBorderRight = 0
|
||||
progressbar.bgBorderTop = 0
|
||||
progressbar.bgBorderBottom = 0
|
||||
return progressbar
|
||||
end
|
||||
|
||||
function UIProgressBar:setMinimum(minimum)
|
||||
self.minimum = minimum
|
||||
if self.value < minimum then
|
||||
self:setValue(minimum)
|
||||
end
|
||||
end
|
||||
|
||||
function UIProgressBar:setMaximum(maximum)
|
||||
self.maximum = maximum
|
||||
if self.value > maximum then
|
||||
self:setValue(maximum)
|
||||
end
|
||||
end
|
||||
|
||||
function UIProgressBar:setValue(value, minimum, maximum)
|
||||
if minimum then
|
||||
self:setMinimum(minimum)
|
||||
end
|
||||
|
||||
if maximum then
|
||||
self:setMaximum(maximum)
|
||||
end
|
||||
|
||||
self.value = math.max(math.min(value, self.maximum), self.minimum)
|
||||
self:updateBackground()
|
||||
end
|
||||
|
||||
function UIProgressBar:setPercent(percent)
|
||||
self:setValue(percent, 0, 100)
|
||||
end
|
||||
|
||||
function UIProgressBar:getPercent()
|
||||
return self.value
|
||||
end
|
||||
|
||||
function UIProgressBar:getPercentPixels()
|
||||
return (self.maximum - self.minimum) / self:getWidth()
|
||||
end
|
||||
|
||||
function UIProgressBar:getProgress()
|
||||
if self.minimum == self.maximum then return 1 end
|
||||
return (self.value - self.minimum) / (self.maximum - self.minimum)
|
||||
end
|
||||
|
||||
function UIProgressBar:updateBackground()
|
||||
if self:isOn() then
|
||||
local width = math.round(math.max((self:getProgress() * (self:getWidth() - self.bgBorderLeft - self.bgBorderRight)), 1))
|
||||
local height = self:getHeight() - self.bgBorderTop - self.bgBorderBottom
|
||||
local rect = { x = self.bgBorderLeft, y = self.bgBorderTop, width = width, height = height }
|
||||
self:setBackgroundRect(rect)
|
||||
end
|
||||
end
|
||||
|
||||
function UIProgressBar:onSetup()
|
||||
self:updateBackground()
|
||||
end
|
||||
|
||||
function UIProgressBar:onStyleApply(name, node)
|
||||
for name,value in pairs(node) do
|
||||
if name == 'background-border-left' then
|
||||
self.bgBorderLeft = tonumber(value)
|
||||
elseif name == 'background-border-right' then
|
||||
self.bgBorderRight = tonumber(value)
|
||||
elseif name == 'background-border-top' then
|
||||
self.bgBorderTop = tonumber(value)
|
||||
elseif name == 'background-border-bottom' then
|
||||
self.bgBorderBottom = tonumber(value)
|
||||
elseif name == 'background-border' then
|
||||
self.bgBorderLeft = tonumber(value)
|
||||
self.bgBorderRight = tonumber(value)
|
||||
self.bgBorderTop = tonumber(value)
|
||||
self.bgBorderBottom = tonumber(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIProgressBar:onGeometryChange(oldRect, newRect)
|
||||
if not self:isOn() then
|
||||
self:setHeight(0)
|
||||
end
|
||||
self:updateBackground()
|
||||
end
|
66
SabrehavenOTClient/modules/corelib/ui/uiradiogroup.lua
Normal file
66
SabrehavenOTClient/modules/corelib/ui/uiradiogroup.lua
Normal file
@@ -0,0 +1,66 @@
|
||||
-- @docclass
|
||||
UIRadioGroup = newclass("UIRadioGroup")
|
||||
|
||||
function UIRadioGroup.create()
|
||||
local radiogroup = UIRadioGroup.internalCreate()
|
||||
radiogroup.widgets = {}
|
||||
radiogroup.selectedWidget = nil
|
||||
return radiogroup
|
||||
end
|
||||
|
||||
function UIRadioGroup:destroy()
|
||||
for k,widget in pairs(self.widgets) do
|
||||
widget.onClick = nil
|
||||
end
|
||||
self.widgets = {}
|
||||
end
|
||||
|
||||
function UIRadioGroup:addWidget(widget)
|
||||
table.insert(self.widgets, widget)
|
||||
widget.onClick = function(widget) self:selectWidget(widget) end
|
||||
end
|
||||
|
||||
function UIRadioGroup:removeWidget(widget)
|
||||
if self.selectedWidget == widget then
|
||||
self:selectWidget(nil)
|
||||
end
|
||||
widget.onClick = nil
|
||||
table.removevalue(self.widgets, widget)
|
||||
end
|
||||
|
||||
function UIRadioGroup:selectWidget(selectedWidget, dontSignal)
|
||||
if selectedWidget == self.selectedWidget then return end
|
||||
|
||||
local previousSelectedWidget = self.selectedWidget
|
||||
self.selectedWidget = selectedWidget
|
||||
|
||||
if previousSelectedWidget then
|
||||
previousSelectedWidget:setChecked(false)
|
||||
end
|
||||
|
||||
if selectedWidget then
|
||||
selectedWidget:setChecked(true)
|
||||
end
|
||||
|
||||
if not dontSignal then
|
||||
signalcall(self.onSelectionChange, self, selectedWidget, previousSelectedWidget)
|
||||
end
|
||||
end
|
||||
|
||||
function UIRadioGroup:clearSelected()
|
||||
if not self.selectedWidget then return end
|
||||
|
||||
local previousSelectedWidget = self.selectedWidget
|
||||
self.selectedWidget:setChecked(false)
|
||||
self.selectedWidget = nil
|
||||
|
||||
signalcall(self.onSelectionChange, self, nil, previousSelectedWidget)
|
||||
end
|
||||
|
||||
function UIRadioGroup:getSelectedWidget()
|
||||
return self.selectedWidget
|
||||
end
|
||||
|
||||
function UIRadioGroup:getFirstWidget()
|
||||
return self.widgets[1]
|
||||
end
|
132
SabrehavenOTClient/modules/corelib/ui/uiresizeborder.lua
Normal file
132
SabrehavenOTClient/modules/corelib/ui/uiresizeborder.lua
Normal file
@@ -0,0 +1,132 @@
|
||||
-- @docclass
|
||||
UIResizeBorder = extends(UIWidget, "UIResizeBorder")
|
||||
|
||||
function UIResizeBorder.create()
|
||||
local resizeborder = UIResizeBorder.internalCreate()
|
||||
resizeborder:setFocusable(false)
|
||||
resizeborder.minimum = 0
|
||||
resizeborder.maximum = 1000
|
||||
return resizeborder
|
||||
end
|
||||
|
||||
function UIResizeBorder:onSetup()
|
||||
if self:getWidth() > self:getHeight() then
|
||||
self.vertical = true
|
||||
else
|
||||
self.vertical = false
|
||||
end
|
||||
end
|
||||
|
||||
function UIResizeBorder:onDestroy()
|
||||
if self.hovering then
|
||||
g_mouse.popCursor(self.cursortype)
|
||||
end
|
||||
end
|
||||
|
||||
function UIResizeBorder:onHoverChange(hovered)
|
||||
if hovered then
|
||||
if g_mouse.isCursorChanged() or g_mouse.isPressed() then return end
|
||||
if self:getWidth() > self:getHeight() then
|
||||
self.vertical = true
|
||||
self.cursortype = 'vertical'
|
||||
else
|
||||
self.vertical = false
|
||||
self.cursortype = 'horizontal'
|
||||
end
|
||||
g_mouse.pushCursor(self.cursortype)
|
||||
self.hovering = true
|
||||
if not self:isPressed() then
|
||||
g_effects.fadeIn(self)
|
||||
end
|
||||
else
|
||||
if not self:isPressed() and self.hovering then
|
||||
g_mouse.popCursor(self.cursortype)
|
||||
g_effects.fadeOut(self)
|
||||
self.hovering = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIResizeBorder:onMouseMove(mousePos, mouseMoved)
|
||||
if self:isPressed() then
|
||||
local parent = self:getParent()
|
||||
local newSize = 0
|
||||
if self.vertical then
|
||||
local delta = mousePos.y - self:getY() - self:getHeight()/2
|
||||
newSize = math.min(math.max(parent:getHeight() + delta, self.minimum), self.maximum)
|
||||
parent:setHeight(newSize)
|
||||
else
|
||||
local delta = mousePos.x - self:getX() - self:getWidth()/2
|
||||
newSize = math.min(math.max(parent:getWidth() + delta, self.minimum), self.maximum)
|
||||
parent:setWidth(newSize)
|
||||
end
|
||||
|
||||
self:checkBoundary(newSize)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function UIResizeBorder:onMouseRelease(mousePos, mouseButton)
|
||||
if not self:isHovered() then
|
||||
g_mouse.popCursor(self.cursortype)
|
||||
g_effects.fadeOut(self)
|
||||
self.hovering = false
|
||||
end
|
||||
end
|
||||
|
||||
function UIResizeBorder:onStyleApply(styleName, styleNode)
|
||||
for name,value in pairs(styleNode) do
|
||||
if name == 'maximum' then
|
||||
self:setMaximum(tonumber(value))
|
||||
elseif name == 'minimum' then
|
||||
self:setMinimum(tonumber(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIResizeBorder:onVisibilityChange(visible)
|
||||
if visible and self.maximum == self.minimum then
|
||||
self:hide()
|
||||
end
|
||||
end
|
||||
|
||||
function UIResizeBorder:setMaximum(maximum)
|
||||
self.maximum = maximum
|
||||
self:checkBoundary()
|
||||
end
|
||||
|
||||
function UIResizeBorder:setMinimum(minimum)
|
||||
self.minimum = minimum
|
||||
self:checkBoundary()
|
||||
end
|
||||
|
||||
function UIResizeBorder:getMaximum() return self.maximum end
|
||||
function UIResizeBorder:getMinimum() return self.minimum end
|
||||
|
||||
function UIResizeBorder:setParentSize(size)
|
||||
local parent = self:getParent()
|
||||
if self.vertical then
|
||||
parent:setHeight(size)
|
||||
else
|
||||
parent:setWidth(size)
|
||||
end
|
||||
self:checkBoundary(size)
|
||||
end
|
||||
|
||||
function UIResizeBorder:getParentSize()
|
||||
local parent = self:getParent()
|
||||
if self.vertical then
|
||||
return parent:getHeight()
|
||||
else
|
||||
return parent:getWidth()
|
||||
end
|
||||
end
|
||||
|
||||
function UIResizeBorder:checkBoundary(size)
|
||||
size = size or self:getParentSize()
|
||||
if self.maximum == self.minimum and size == self.maximum then
|
||||
self:hide()
|
||||
else
|
||||
self:show()
|
||||
end
|
||||
end
|
190
SabrehavenOTClient/modules/corelib/ui/uiscrollarea.lua
Normal file
190
SabrehavenOTClient/modules/corelib/ui/uiscrollarea.lua
Normal file
@@ -0,0 +1,190 @@
|
||||
-- @docclass
|
||||
UIScrollArea = extends(UIWidget, "UIScrollArea")
|
||||
|
||||
-- public functions
|
||||
function UIScrollArea.create()
|
||||
local scrollarea = UIScrollArea.internalCreate()
|
||||
scrollarea:setClipping(true)
|
||||
scrollarea.inverted = false
|
||||
scrollarea.alwaysScrollMaximum = false
|
||||
return scrollarea
|
||||
end
|
||||
|
||||
function UIScrollArea:onStyleApply(styleName, styleNode)
|
||||
for name,value in pairs(styleNode) do
|
||||
if name == 'vertical-scrollbar' then
|
||||
addEvent(function()
|
||||
local parent = self:getParent()
|
||||
if parent then
|
||||
self:setVerticalScrollBar(parent:getChildById(value))
|
||||
end
|
||||
end)
|
||||
elseif name == 'horizontal-scrollbar' then
|
||||
addEvent(function()
|
||||
local parent = self:getParent()
|
||||
if parent then
|
||||
self:setHorizontalScrollBar(self:getParent():getChildById(value))
|
||||
end
|
||||
end)
|
||||
elseif name == 'inverted-scroll' then
|
||||
self:setInverted(value)
|
||||
elseif name == 'always-scroll-maximum' then
|
||||
self:setAlwaysScrollMaximum(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIScrollArea:updateScrollBars()
|
||||
local scrollWidth = math.max(self:getChildrenRect().width - self:getPaddingRect().width, 0)
|
||||
local scrollHeight = math.max(self:getChildrenRect().height - self:getPaddingRect().height, 0)
|
||||
|
||||
local scrollbar = self.verticalScrollBar
|
||||
if scrollbar then
|
||||
if self.inverted then
|
||||
scrollbar:setMinimum(-scrollHeight)
|
||||
scrollbar:setMaximum(0)
|
||||
else
|
||||
scrollbar:setMinimum(0)
|
||||
scrollbar:setMaximum(scrollHeight)
|
||||
end
|
||||
end
|
||||
|
||||
local scrollbar = self.horizontalScrollBar
|
||||
if scrollbar then
|
||||
if self.inverted then
|
||||
scrollbar:setMinimum(-scrollWidth)
|
||||
scrollbar:setMaximum(0)
|
||||
else
|
||||
scrollbar:setMinimum(0)
|
||||
scrollbar:setMaximum(scrollWidth)
|
||||
end
|
||||
end
|
||||
|
||||
if self.lastScrollWidth ~= scrollWidth then
|
||||
self:onScrollWidthChange()
|
||||
end
|
||||
if self.lastScrollHeight ~= scrollHeight then
|
||||
self:onScrollHeightChange()
|
||||
end
|
||||
|
||||
self.lastScrollWidth = scrollWidth
|
||||
self.lastScrollHeight = scrollHeight
|
||||
end
|
||||
|
||||
function UIScrollArea:setVerticalScrollBar(scrollbar)
|
||||
self.verticalScrollBar = scrollbar
|
||||
connect(self.verticalScrollBar, 'onValueChange', function(scrollbar, value)
|
||||
local virtualOffset = self:getVirtualOffset()
|
||||
virtualOffset.y = value
|
||||
self:setVirtualOffset(virtualOffset)
|
||||
signalcall(self.onScrollChange, self, virtualOffset)
|
||||
end)
|
||||
self:updateScrollBars()
|
||||
end
|
||||
|
||||
function UIScrollArea:setHorizontalScrollBar(scrollbar)
|
||||
self.horizontalScrollBar = scrollbar
|
||||
connect(self.horizontalScrollBar, 'onValueChange', function(scrollbar, value)
|
||||
local virtualOffset = self:getVirtualOffset()
|
||||
virtualOffset.x = value
|
||||
self:setVirtualOffset(virtualOffset)
|
||||
signalcall(self.onScrollChange, self, virtualOffset)
|
||||
end)
|
||||
self:updateScrollBars()
|
||||
end
|
||||
|
||||
function UIScrollArea:setInverted(inverted)
|
||||
self.inverted = inverted
|
||||
end
|
||||
|
||||
function UIScrollArea:setAlwaysScrollMaximum(value)
|
||||
self.alwaysScrollMaximum = value
|
||||
end
|
||||
|
||||
function UIScrollArea:onLayoutUpdate()
|
||||
self:updateScrollBars()
|
||||
end
|
||||
|
||||
function UIScrollArea:onMouseWheel(mousePos, mouseWheel)
|
||||
if self.verticalScrollBar then
|
||||
if not self.verticalScrollBar:isOn() then
|
||||
return false
|
||||
end
|
||||
if mouseWheel == MouseWheelUp then
|
||||
local minimum = self.verticalScrollBar:getMinimum()
|
||||
if self.verticalScrollBar:getValue() <= minimum then
|
||||
return false
|
||||
end
|
||||
self.verticalScrollBar:decrement()
|
||||
else
|
||||
local maximum = self.verticalScrollBar:getMaximum()
|
||||
if self.verticalScrollBar:getValue() >= maximum then
|
||||
return false
|
||||
end
|
||||
self.verticalScrollBar:increment()
|
||||
end
|
||||
elseif self.horizontalScrollBar then
|
||||
if not self.horizontalScrollBar:isOn() then
|
||||
return false
|
||||
end
|
||||
if mouseWheel == MouseWheelUp then
|
||||
local maximum = self.horizontalScrollBar:getMaximum()
|
||||
if self.horizontalScrollBar:getValue() >= maximum then
|
||||
return false
|
||||
end
|
||||
self.horizontalScrollBar:increment()
|
||||
else
|
||||
local minimum = self.horizontalScrollBar:getMinimum()
|
||||
if self.horizontalScrollBar:getValue() <= minimum then
|
||||
return false
|
||||
end
|
||||
self.horizontalScrollBar:decrement()
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function UIScrollArea:ensureChildVisible(child)
|
||||
if child then
|
||||
local paddingRect = self:getPaddingRect()
|
||||
if self.verticalScrollBar then
|
||||
local deltaY = paddingRect.y - child:getY()
|
||||
if deltaY > 0 then
|
||||
self.verticalScrollBar:decrement(deltaY)
|
||||
end
|
||||
|
||||
deltaY = (child:getY() + child:getHeight()) - (paddingRect.y + paddingRect.height)
|
||||
if deltaY > 0 then
|
||||
self.verticalScrollBar:increment(deltaY)
|
||||
end
|
||||
elseif self.horizontalScrollBar then
|
||||
local deltaX = paddingRect.x - child:getX()
|
||||
if deltaX > 0 then
|
||||
self.horizontalScrollBar:decrement(deltaX)
|
||||
end
|
||||
|
||||
deltaX = (child:getX() + child:getWidth()) - (paddingRect.x + paddingRect.width)
|
||||
if deltaX > 0 then
|
||||
self.horizontalScrollBar:increment(deltaX)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIScrollArea:onChildFocusChange(focusedChild, oldFocused, reason)
|
||||
if focusedChild and (reason == MouseFocusReason or reason == KeyboardFocusReason) then
|
||||
self:ensureChildVisible(focusedChild)
|
||||
end
|
||||
end
|
||||
|
||||
function UIScrollArea:onScrollWidthChange()
|
||||
if self.alwaysScrollMaximum and self.horizontalScrollBar then
|
||||
self.horizontalScrollBar:setValue(self.horizontalScrollBar:getMaximum())
|
||||
end
|
||||
end
|
||||
|
||||
function UIScrollArea:onScrollHeightChange()
|
||||
if self.alwaysScrollMaximum and self.verticalScrollBar then
|
||||
self.verticalScrollBar:setValue(self.verticalScrollBar:getMaximum())
|
||||
end
|
||||
end
|
290
SabrehavenOTClient/modules/corelib/ui/uiscrollbar.lua
Normal file
290
SabrehavenOTClient/modules/corelib/ui/uiscrollbar.lua
Normal file
@@ -0,0 +1,290 @@
|
||||
-- @docclass
|
||||
UIScrollBar = extends(UIWidget, "UIScrollBar")
|
||||
|
||||
-- private functions
|
||||
local function calcValues(self)
|
||||
local slider = self:getChildById('sliderButton')
|
||||
local decrementButton = self:getChildById('decrementButton')
|
||||
local incrementButton = self:getChildById('incrementButton')
|
||||
|
||||
local pxrange, center
|
||||
if self.orientation == 'vertical' then
|
||||
pxrange = (self:getHeight() - decrementButton:getHeight() - decrementButton:getMarginTop() - decrementButton:getMarginBottom()
|
||||
- incrementButton:getHeight() - incrementButton:getMarginTop() - incrementButton:getMarginBottom())
|
||||
center = self:getY() + math.floor(self:getHeight() / 2)
|
||||
else -- horizontal
|
||||
pxrange = (self:getWidth() - decrementButton:getWidth() - decrementButton:getMarginLeft() - decrementButton:getMarginRight()
|
||||
- incrementButton:getWidth() - incrementButton:getMarginLeft() - incrementButton:getMarginRight())
|
||||
center = self:getX() + math.floor(self:getWidth() / 2)
|
||||
end
|
||||
|
||||
local range = self.maximum - self.minimum + 1
|
||||
|
||||
local proportion
|
||||
|
||||
if self.pixelsScroll then
|
||||
proportion = pxrange/(range+pxrange)
|
||||
else
|
||||
proportion = math.min(math.max(self.step, 1), range)/range
|
||||
end
|
||||
|
||||
local px = math.max(proportion * pxrange, 6)
|
||||
if g_app.isMobile() then
|
||||
px = math.max(proportion * pxrange, 24)
|
||||
end
|
||||
px = px - px % 2 + 1
|
||||
|
||||
local offset = 0
|
||||
if range == 0 or self.value == self.minimum then
|
||||
if self.orientation == 'vertical' then
|
||||
offset = -math.floor((self:getHeight() - px) / 2) + decrementButton:getMarginRect().height
|
||||
else
|
||||
offset = -math.floor((self:getWidth() - px) / 2) + decrementButton:getMarginRect().width
|
||||
end
|
||||
elseif range > 1 and self.value == self.maximum then
|
||||
if self.orientation == 'vertical' then
|
||||
offset = math.ceil((self:getHeight() - px) / 2) - incrementButton:getMarginRect().height
|
||||
else
|
||||
offset = math.ceil((self:getWidth() - px) / 2) - incrementButton:getMarginRect().width
|
||||
end
|
||||
elseif range > 1 then
|
||||
offset = (((self.value - self.minimum) / (range - 1)) - 0.5) * (pxrange - px)
|
||||
end
|
||||
|
||||
return range, pxrange, px, offset, center
|
||||
end
|
||||
|
||||
local function updateValueDisplay(widget)
|
||||
if widget == nil then return end
|
||||
|
||||
if widget:getShowValue() then
|
||||
widget:setText(widget:getValue() .. (widget:getSymbol() or ''))
|
||||
end
|
||||
end
|
||||
|
||||
local function updateSlider(self)
|
||||
local slider = self:getChildById('sliderButton')
|
||||
if slider == nil then return end
|
||||
|
||||
local range, pxrange, px, offset, center = calcValues(self)
|
||||
if self.orientation == 'vertical' then
|
||||
slider:setHeight(px)
|
||||
slider:setMarginTop(offset)
|
||||
else -- horizontal
|
||||
slider:setWidth(px)
|
||||
slider:setMarginLeft(offset)
|
||||
end
|
||||
updateValueDisplay(self)
|
||||
|
||||
local status = (self.maximum ~= self.minimum)
|
||||
|
||||
self:setOn(status)
|
||||
for _i,child in pairs(self:getChildren()) do
|
||||
child:setEnabled(status)
|
||||
end
|
||||
end
|
||||
|
||||
local function parseSliderPos(self, slider, pos, move)
|
||||
local delta, hotDistance
|
||||
if self.orientation == 'vertical' then
|
||||
delta = move.y
|
||||
hotDistance = pos.y - slider:getY()
|
||||
else
|
||||
delta = move.x
|
||||
hotDistance = pos.x - slider:getX()
|
||||
end
|
||||
|
||||
if (delta > 0 and hotDistance + delta > self.hotDistance) or
|
||||
(delta < 0 and hotDistance + delta < self.hotDistance) then
|
||||
local range, pxrange, px, offset, center = calcValues(self)
|
||||
local newvalue = self.value + delta * (range / (pxrange - px))
|
||||
self:setValue(newvalue)
|
||||
end
|
||||
end
|
||||
|
||||
local function parseSliderPress(self, slider, pos, button)
|
||||
if self.orientation == 'vertical' then
|
||||
self.hotDistance = pos.y - slider:getY()
|
||||
else
|
||||
self.hotDistance = pos.x - slider:getX()
|
||||
end
|
||||
end
|
||||
|
||||
-- public functions
|
||||
function UIScrollBar.create()
|
||||
local scrollbar = UIScrollBar.internalCreate()
|
||||
scrollbar:setFocusable(false)
|
||||
scrollbar.value = 0
|
||||
scrollbar.minimum = -999999
|
||||
scrollbar.maximum = 999999
|
||||
scrollbar.step = 1
|
||||
scrollbar.orientation = 'vertical'
|
||||
scrollbar.pixelsScroll = false
|
||||
scrollbar.showValue = false
|
||||
scrollbar.symbol = nil
|
||||
scrollbar.mouseScroll = true
|
||||
return scrollbar
|
||||
end
|
||||
|
||||
function UIScrollBar:onSetup()
|
||||
self.setupDone = true
|
||||
local sliderButton = self:getChildById('sliderButton')
|
||||
g_mouse.bindAutoPress(self:getChildById('decrementButton'), function() self:onDecrement() end, 300)
|
||||
g_mouse.bindAutoPress(self:getChildById('incrementButton'), function() self:onIncrement() end, 300)
|
||||
g_mouse.bindPressMove(sliderButton, function(mousePos, mouseMoved) parseSliderPos(self, sliderButton, mousePos, mouseMoved) end)
|
||||
g_mouse.bindPress(sliderButton, function(mousePos, mouseButton) parseSliderPress(self, sliderButton, mousePos, mouseButton) end)
|
||||
|
||||
updateSlider(self)
|
||||
end
|
||||
|
||||
function UIScrollBar:onStyleApply(styleName, styleNode)
|
||||
for name,value in pairs(styleNode) do
|
||||
if name == 'maximum' then
|
||||
self:setMaximum(tonumber(value))
|
||||
elseif name == 'minimum' then
|
||||
self:setMinimum(tonumber(value))
|
||||
elseif name == 'step' then
|
||||
self:setStep(tonumber(value))
|
||||
elseif name == 'orientation' then
|
||||
self:setOrientation(value)
|
||||
elseif name == 'value' then
|
||||
self:setValue(value)
|
||||
elseif name == 'pixels-scroll' then
|
||||
self.pixelsScroll = true
|
||||
elseif name == 'show-value' then
|
||||
self.showValue = true
|
||||
elseif name == 'symbol' then
|
||||
self.symbol = value
|
||||
elseif name == 'mouse-scroll' then
|
||||
self.mouseScroll = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIScrollBar:onDecrement()
|
||||
if g_keyboard.isCtrlPressed() then
|
||||
self:decrement(self.value)
|
||||
elseif g_keyboard.isShiftPressed() then
|
||||
self:decrement(10)
|
||||
else
|
||||
self:decrement()
|
||||
end
|
||||
end
|
||||
|
||||
function UIScrollBar:onIncrement()
|
||||
if g_keyboard.isCtrlPressed() then
|
||||
self:increment(self.maximum)
|
||||
elseif g_keyboard.isShiftPressed() then
|
||||
self:increment(10)
|
||||
else
|
||||
self:increment()
|
||||
end
|
||||
end
|
||||
|
||||
function UIScrollBar:decrement(count)
|
||||
count = count or self.step
|
||||
self:setValue(self.value - count)
|
||||
end
|
||||
|
||||
function UIScrollBar:increment(count)
|
||||
count = count or self.step
|
||||
self:setValue(self.value + count)
|
||||
end
|
||||
|
||||
function UIScrollBar:setMaximum(maximum)
|
||||
if maximum == self.maximum then return end
|
||||
self.maximum = maximum
|
||||
if self.minimum > maximum then
|
||||
self:setMinimum(maximum)
|
||||
end
|
||||
if self.value > maximum then
|
||||
self:setValue(maximum)
|
||||
else
|
||||
updateSlider(self)
|
||||
end
|
||||
end
|
||||
|
||||
function UIScrollBar:setMinimum(minimum)
|
||||
if minimum == self.minimum then return end
|
||||
self.minimum = minimum
|
||||
if self.maximum < minimum then
|
||||
self:setMaximum(minimum)
|
||||
end
|
||||
if self.value < minimum then
|
||||
self:setValue(minimum)
|
||||
else
|
||||
updateSlider(self)
|
||||
end
|
||||
end
|
||||
|
||||
function UIScrollBar:setRange(minimum, maximum)
|
||||
self:setMinimum(minimum)
|
||||
self:setMaximum(maximum)
|
||||
end
|
||||
|
||||
function UIScrollBar:setValue(value)
|
||||
value = math.max(math.min(value, self.maximum), self.minimum)
|
||||
if self.value == value then return end
|
||||
local delta = value - self.value
|
||||
self.value = value
|
||||
updateSlider(self)
|
||||
if self.setupDone then
|
||||
signalcall(self.onValueChange, self, math.round(value), delta)
|
||||
end
|
||||
end
|
||||
|
||||
function UIScrollBar:setMouseScroll(scroll)
|
||||
self.mouseScroll = scroll
|
||||
end
|
||||
|
||||
function UIScrollBar:setStep(step)
|
||||
self.step = step
|
||||
end
|
||||
|
||||
function UIScrollBar:setOrientation(orientation)
|
||||
self.orientation = orientation
|
||||
end
|
||||
|
||||
function UIScrollBar:setText(text)
|
||||
local valueLabel = self:getChildById('valueLabel')
|
||||
if valueLabel then
|
||||
valueLabel:setText(text)
|
||||
end
|
||||
end
|
||||
|
||||
function UIScrollBar:onGeometryChange()
|
||||
updateSlider(self)
|
||||
end
|
||||
|
||||
function UIScrollBar:onMouseWheel(mousePos, mouseWheel)
|
||||
if not self.mouseScroll or not self:isOn() or self.disableScroll then
|
||||
return false
|
||||
end
|
||||
if mouseWheel == MouseWheelUp then
|
||||
if self.orientation == 'vertical' then
|
||||
if self.value <= self.minimum then return false end
|
||||
self:decrement()
|
||||
else
|
||||
if self.value >= self.maximum then return false end
|
||||
self:increment()
|
||||
end
|
||||
else
|
||||
if self.orientation == 'vertical' then
|
||||
if self.value >= self.maximum then return false end
|
||||
self:increment()
|
||||
else
|
||||
if self.value <= self.minimum then return false end
|
||||
self:decrement()
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function UIScrollBar:getMaximum() return self.maximum end
|
||||
function UIScrollBar:getMinimum() return self.minimum end
|
||||
function UIScrollBar:getValue() return math.round(self.value) end
|
||||
function UIScrollBar:getStep() return self.step end
|
||||
function UIScrollBar:getOrientation() return self.orientation end
|
||||
function UIScrollBar:getShowValue() return self.showValue end
|
||||
function UIScrollBar:getSymbol() return self.symbol end
|
||||
function UIScrollBar:getMouseScroll() return self.mouseScroll end
|
191
SabrehavenOTClient/modules/corelib/ui/uispinbox.lua
Normal file
191
SabrehavenOTClient/modules/corelib/ui/uispinbox.lua
Normal file
@@ -0,0 +1,191 @@
|
||||
-- @docclass
|
||||
UISpinBox = extends(UITextEdit, "UISpinBox")
|
||||
|
||||
function UISpinBox.create()
|
||||
local spinbox = UISpinBox.internalCreate()
|
||||
spinbox:setFocusable(false)
|
||||
spinbox:setValidCharacters('0123456789')
|
||||
spinbox.displayButtons = true
|
||||
spinbox.minimum = 0
|
||||
spinbox.maximum = 1
|
||||
spinbox.value = 0
|
||||
spinbox.step = 1
|
||||
spinbox.firstchange = true
|
||||
spinbox.mouseScroll = true
|
||||
spinbox:setText("1")
|
||||
spinbox:setValue(1)
|
||||
return spinbox
|
||||
end
|
||||
|
||||
function UISpinBox:onSetup()
|
||||
g_mouse.bindAutoPress(self:getChildById('up'), function() self:up() end, 300)
|
||||
g_mouse.bindAutoPress(self:getChildById('down'), function() self:down() end, 300)
|
||||
end
|
||||
|
||||
function UISpinBox:onMouseWheel(mousePos, direction)
|
||||
if not self.mouseScroll or self.disableScroll then
|
||||
return false
|
||||
end
|
||||
if direction == MouseWheelUp then
|
||||
self:up()
|
||||
elseif direction == MouseWheelDown then
|
||||
self:down()
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function UISpinBox:onKeyPress()
|
||||
if self.firstchange then
|
||||
self.firstchange = false
|
||||
self:setText('')
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function UISpinBox:onTextChange(text, oldText)
|
||||
if text:len() == 0 then
|
||||
self:setValue(self.minimum)
|
||||
return
|
||||
end
|
||||
|
||||
local number = tonumber(text)
|
||||
if not number then
|
||||
self:setText(number)
|
||||
return
|
||||
else
|
||||
if number < self.minimum then
|
||||
self:setText(self.minimum)
|
||||
return
|
||||
elseif number > self.maximum then
|
||||
self:setText(self.maximum)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
self:setValue(number)
|
||||
end
|
||||
|
||||
function UISpinBox:onValueChange(value)
|
||||
-- nothing to do
|
||||
end
|
||||
|
||||
function UISpinBox:onFocusChange(focused)
|
||||
if not focused then
|
||||
if self:getText():len() == 0 then
|
||||
self:setText(self.minimum)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UISpinBox:onStyleApply(styleName, styleNode)
|
||||
for name, value in pairs(styleNode) do
|
||||
if name == 'maximum' then
|
||||
self.maximum = value
|
||||
addEvent(function() self:setMaximum(value) end)
|
||||
elseif name == 'minimum' then
|
||||
self.minimum = value
|
||||
addEvent(function() self:setMinimum(value) end)
|
||||
elseif name == 'mouse-scroll' then
|
||||
addEvent(function() self:setMouseScroll(value) end)
|
||||
elseif name == 'buttons' then
|
||||
addEvent(function()
|
||||
if value then
|
||||
self:showButtons()
|
||||
else
|
||||
self:hideButtons()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UISpinBox:showButtons()
|
||||
self:getChildById('up'):show()
|
||||
self:getChildById('down'):show()
|
||||
self.displayButtons = true
|
||||
end
|
||||
|
||||
function UISpinBox:hideButtons()
|
||||
self:getChildById('up'):hide()
|
||||
self:getChildById('down'):hide()
|
||||
self.displayButtons = false
|
||||
end
|
||||
|
||||
function UISpinBox:up()
|
||||
self:setValue(self.value + self.step)
|
||||
end
|
||||
|
||||
function UISpinBox:down()
|
||||
self:setValue(self.value - self.step)
|
||||
end
|
||||
|
||||
function UISpinBox:setValue(value, dontSignal)
|
||||
if type(value) == "string" then
|
||||
value = tonumber(value)
|
||||
end
|
||||
value = value or 0
|
||||
value = math.max(math.min(self.maximum, value), self.minimum)
|
||||
|
||||
if value == self.value then return end
|
||||
|
||||
self.value = value
|
||||
if self:getText():len() > 0 then
|
||||
self:setText(value)
|
||||
end
|
||||
|
||||
local upButton = self:getChildById('up')
|
||||
local downButton = self:getChildById('down')
|
||||
if upButton then
|
||||
upButton:setEnabled(self.maximum ~= self.minimum and self.value ~= self.maximum)
|
||||
end
|
||||
if downButton then
|
||||
downButton:setEnabled(self.maximum ~= self.minimum and self.value ~= self.minimum)
|
||||
end
|
||||
|
||||
if not dontSignal then
|
||||
signalcall(self.onValueChange, self, value)
|
||||
end
|
||||
end
|
||||
|
||||
function UISpinBox:getValue()
|
||||
return self.value
|
||||
end
|
||||
|
||||
function UISpinBox:setMinimum(minimum)
|
||||
minimum = minimum or -9223372036854775808
|
||||
self.minimum = minimum
|
||||
if self.minimum > self.maximum then
|
||||
self.maximum = self.minimum
|
||||
end
|
||||
if self.value < minimum then
|
||||
self:setValue(minimum)
|
||||
end
|
||||
end
|
||||
|
||||
function UISpinBox:getMinimum()
|
||||
return self.minimum
|
||||
end
|
||||
|
||||
function UISpinBox:setMaximum(maximum)
|
||||
maximum = maximum or 9223372036854775807
|
||||
self.maximum = maximum
|
||||
if self.value > maximum then
|
||||
self:setValue(maximum)
|
||||
end
|
||||
end
|
||||
|
||||
function UISpinBox:getMaximum()
|
||||
return self.maximum
|
||||
end
|
||||
|
||||
function UISpinBox:setStep(step)
|
||||
self.step = step or 1
|
||||
end
|
||||
|
||||
function UISpinBox:setMouseScroll(mouseScroll)
|
||||
self.mouseScroll = mouseScroll
|
||||
end
|
||||
|
||||
function UISpinBox:getMouseScroll()
|
||||
return self.mouseScroll
|
||||
end
|
85
SabrehavenOTClient/modules/corelib/ui/uisplitter.lua
Normal file
85
SabrehavenOTClient/modules/corelib/ui/uisplitter.lua
Normal file
@@ -0,0 +1,85 @@
|
||||
-- @docclass
|
||||
UISplitter = extends(UIWidget, "UISplitter")
|
||||
|
||||
function UISplitter.create()
|
||||
local splitter = UISplitter.internalCreate()
|
||||
splitter:setFocusable(false)
|
||||
splitter.relativeMargin = 'bottom'
|
||||
return splitter
|
||||
end
|
||||
|
||||
function UISplitter:onHoverChange(hovered)
|
||||
-- Check if margin can be changed
|
||||
local margin = (self.vertical and self:getMarginBottom() or self:getMarginRight())
|
||||
if hovered and (self:canUpdateMargin(margin + 1) ~= margin or self:canUpdateMargin(margin - 1) ~= margin) then
|
||||
if g_mouse.isCursorChanged() or g_mouse.isPressed() then return end
|
||||
if self:getWidth() > self:getHeight() then
|
||||
self.vertical = true
|
||||
self.cursortype = 'vertical'
|
||||
else
|
||||
self.vertical = false
|
||||
self.cursortype = 'horizontal'
|
||||
end
|
||||
self.hovering = true
|
||||
g_mouse.pushCursor(self.cursortype)
|
||||
if not self:isPressed() then
|
||||
g_effects.fadeIn(self)
|
||||
end
|
||||
else
|
||||
if not self:isPressed() and self.hovering then
|
||||
g_mouse.popCursor(self.cursortype)
|
||||
g_effects.fadeOut(self)
|
||||
self.hovering = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UISplitter:onMouseMove(mousePos, mouseMoved)
|
||||
if self:isPressed() then
|
||||
--local currentmargin, newmargin, delta
|
||||
if self.vertical then
|
||||
local delta = mousePos.y - self:getY() - self:getHeight()/2
|
||||
local newMargin = self:canUpdateMargin(self:getMarginBottom() - delta)
|
||||
local currentMargin = self:getMarginBottom()
|
||||
if newMargin ~= currentMargin then
|
||||
self.newMargin = newMargin
|
||||
if not self.event or self.event:isExecuted() then
|
||||
self.event = addEvent(function()
|
||||
self:setMarginBottom(self.newMargin)
|
||||
end)
|
||||
end
|
||||
end
|
||||
else
|
||||
local delta = mousePos.x - self:getX() - self:getWidth()/2
|
||||
local newMargin = self:canUpdateMargin(self:getMarginRight() - delta)
|
||||
local currentMargin = self:getMarginRight()
|
||||
if newMargin ~= currentMargin then
|
||||
self.newMargin = newMargin
|
||||
if not self.event or self.event:isExecuted() then
|
||||
self.event = addEvent(function()
|
||||
self:setMarginRight(self.newMargin)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function UISplitter:onMouseRelease(mousePos, mouseButton)
|
||||
if not self:isHovered() then
|
||||
g_mouse.popCursor(self.cursortype)
|
||||
g_effects.fadeOut(self)
|
||||
self.hovering = false
|
||||
end
|
||||
end
|
||||
|
||||
function UISplitter:onStyleApply(styleName, styleNode)
|
||||
if styleNode['relative-margin'] then
|
||||
self.relativeMargin = styleNode['relative-margin']
|
||||
end
|
||||
end
|
||||
|
||||
function UISplitter:canUpdateMargin(newMargin)
|
||||
return newMargin
|
||||
end
|
163
SabrehavenOTClient/modules/corelib/ui/uitabbar.lua
Normal file
163
SabrehavenOTClient/modules/corelib/ui/uitabbar.lua
Normal file
@@ -0,0 +1,163 @@
|
||||
-- @docclass
|
||||
UITabBar = extends(UIWidget, "UITabBar")
|
||||
|
||||
-- private functions
|
||||
local function onTabClick(tab)
|
||||
tab.tabBar:selectTab(tab)
|
||||
end
|
||||
|
||||
local function onTabMouseRelease(tab, mousePos, mouseButton)
|
||||
if mouseButton == MouseRightButton and tab:containsPoint(mousePos) then
|
||||
signalcall(tab.tabBar.onTabLeftClick, tab.tabBar, tab)
|
||||
end
|
||||
end
|
||||
|
||||
-- public functions
|
||||
function UITabBar.create()
|
||||
local tabbar = UITabBar.internalCreate()
|
||||
tabbar:setFocusable(false)
|
||||
tabbar.tabs = {}
|
||||
return tabbar
|
||||
end
|
||||
|
||||
function UITabBar:onSetup()
|
||||
self.buttonsPanel = self:getChildById('buttonsPanel')
|
||||
end
|
||||
|
||||
function UITabBar:setContentWidget(widget)
|
||||
self.contentWidget = widget
|
||||
if #self.tabs > 0 then
|
||||
self.contentWidget:addChild(self.tabs[1].tabPanel)
|
||||
end
|
||||
end
|
||||
|
||||
function UITabBar:addTab(text, panel, icon)
|
||||
if panel == nil then
|
||||
panel = g_ui.createWidget(self:getStyleName() .. 'Panel')
|
||||
panel:setId('tabPanel')
|
||||
end
|
||||
|
||||
local tab = g_ui.createWidget(self:getStyleName() .. 'Button', self.buttonsPanel)
|
||||
|
||||
panel.isTab = true
|
||||
tab.tabPanel = panel
|
||||
tab.tabBar = self
|
||||
tab:setId('tab')
|
||||
tab:setText(text)
|
||||
tab:setWidth(tab:getTextSize().width + tab:getPaddingLeft() + tab:getPaddingRight())
|
||||
tab.onClick = onTabClick
|
||||
tab.onMouseRelease = onTabMouseRelease
|
||||
tab.onDestroy = function() tab.tabPanel:destroy() end
|
||||
|
||||
table.insert(self.tabs, tab)
|
||||
if #self.tabs == 1 then
|
||||
self:selectTab(tab)
|
||||
end
|
||||
|
||||
local tabStyle = {}
|
||||
tabStyle['icon-source'] = icon
|
||||
tab:mergeStyle(tabStyle)
|
||||
|
||||
return tab
|
||||
end
|
||||
|
||||
function UITabBar:addButton(text, func, icon)
|
||||
local button = g_ui.createWidget(self:getStyleName() .. 'Button', self.buttonsPanel)
|
||||
button:setText(text)
|
||||
|
||||
local style = {}
|
||||
style['icon-source'] = icon
|
||||
button:mergeStyle(style)
|
||||
|
||||
button.onClick = func
|
||||
return button
|
||||
end
|
||||
|
||||
function UITabBar:removeTab(tab)
|
||||
local index = table.find(self.tabs, tab)
|
||||
if index == nil then return end
|
||||
if self.currentTab == tab then
|
||||
self:selectPrevTab()
|
||||
end
|
||||
table.remove(self.tabs, index)
|
||||
tab:destroy()
|
||||
end
|
||||
|
||||
function UITabBar:getTab(text)
|
||||
for k,tab in pairs(self.tabs) do
|
||||
if tab:getText():lower() == text:lower() then
|
||||
return tab
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UITabBar:selectTab(tab)
|
||||
if self.currentTab == tab then return end
|
||||
if self.contentWidget then
|
||||
local selectedWidget = self.contentWidget:getLastChild()
|
||||
if selectedWidget and selectedWidget.isTab then
|
||||
self.contentWidget:removeChild(selectedWidget)
|
||||
end
|
||||
self.contentWidget:addChild(tab.tabPanel)
|
||||
tab.tabPanel:fill('parent')
|
||||
end
|
||||
|
||||
if self.currentTab then
|
||||
self.currentTab:setChecked(false)
|
||||
end
|
||||
signalcall(self.onTabChange, self, tab)
|
||||
self.currentTab = tab
|
||||
tab:setChecked(true)
|
||||
tab:setOn(false)
|
||||
|
||||
local parent = tab:getParent()
|
||||
if parent then
|
||||
parent:focusChild(tab, MouseFocusReason)
|
||||
end
|
||||
end
|
||||
|
||||
function UITabBar:selectNextTab()
|
||||
if self.currentTab == nil then return end
|
||||
local index = table.find(self.tabs, self.currentTab)
|
||||
if index == nil then return end
|
||||
local nextTab = self.tabs[index + 1] or self.tabs[1]
|
||||
if not nextTab then return end
|
||||
self:selectTab(nextTab)
|
||||
end
|
||||
|
||||
function UITabBar:selectPrevTab()
|
||||
if self.currentTab == nil then return end
|
||||
local index = table.find(self.tabs, self.currentTab)
|
||||
if index == nil then return end
|
||||
local prevTab = self.tabs[index - 1] or self.tabs[#self.tabs]
|
||||
if not prevTab then return end
|
||||
self:selectTab(prevTab)
|
||||
end
|
||||
|
||||
function UITabBar:getTabPanel(tab)
|
||||
return tab.tabPanel
|
||||
end
|
||||
|
||||
function UITabBar:getCurrentTabPanel()
|
||||
if self.currentTab then
|
||||
return self.currentTab.tabPanel
|
||||
end
|
||||
end
|
||||
|
||||
function UITabBar:getCurrentTab()
|
||||
return self.currentTab
|
||||
end
|
||||
|
||||
function UITabBar:getTabs()
|
||||
return self.tabs
|
||||
end
|
||||
|
||||
function UITabBar:getTabsPanel()
|
||||
return table.collect(self.tabs, function(_,tab) return tab.tabPanel end)
|
||||
end
|
||||
|
||||
function UITabBar:clearTabs()
|
||||
while #self.tabs > 0 do
|
||||
self:removeTab(self.tabs[#self.tabs])
|
||||
end
|
||||
end
|
432
SabrehavenOTClient/modules/corelib/ui/uitable.lua
Normal file
432
SabrehavenOTClient/modules/corelib/ui/uitable.lua
Normal file
@@ -0,0 +1,432 @@
|
||||
-- @docclass
|
||||
--[[
|
||||
TODO:
|
||||
* Make table headers more robust.
|
||||
* Get dynamic row heights working with text wrapping.
|
||||
]]
|
||||
|
||||
TABLE_SORTING_ASC = 0
|
||||
TABLE_SORTING_DESC = 1
|
||||
|
||||
UITable = extends(UIWidget, "UITable")
|
||||
|
||||
-- Initialize default values
|
||||
function UITable.create()
|
||||
local table = UITable.internalCreate()
|
||||
table.headerRow = nil
|
||||
table.headerColumns = {}
|
||||
table.dataSpace = nil
|
||||
table.rows = {}
|
||||
table.rowBaseStyle = nil
|
||||
table.columns = {}
|
||||
table.columnWidth = {}
|
||||
table.columBaseStyle = nil
|
||||
table.headerRowBaseStyle = nil
|
||||
table.headerColumnBaseStyle = nil
|
||||
table.selectedRow = nil
|
||||
table.defaultColumnWidth = 80
|
||||
table.sortColumn = -1
|
||||
table.sortType = TABLE_SORTING_ASC
|
||||
table.autoSort = false
|
||||
|
||||
return table
|
||||
end
|
||||
|
||||
-- Clear table values
|
||||
function UITable:onDestroy()
|
||||
for _,row in pairs(self.rows) do
|
||||
row.onClick = nil
|
||||
end
|
||||
self.rows = {}
|
||||
self.columns = {}
|
||||
self.headerRow = nil
|
||||
self.headerColumns = {}
|
||||
self.columnWidth = {}
|
||||
self.selectedRow = nil
|
||||
|
||||
if self.dataSpace then
|
||||
self.dataSpace:destroyChildren()
|
||||
self.dataSpace = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Detect if a header is already defined
|
||||
function UITable:onSetup()
|
||||
local header = self:getChildById('header')
|
||||
if header then
|
||||
self:setHeader(header)
|
||||
end
|
||||
end
|
||||
|
||||
-- Parse table related styles
|
||||
function UITable:onStyleApply(styleName, styleNode)
|
||||
for name, value in pairs(styleNode) do
|
||||
if value ~= false then
|
||||
if name == 'table-data' then
|
||||
addEvent(function()
|
||||
self:setTableData(self:getParent():getChildById(value))
|
||||
end)
|
||||
elseif name == 'column-style' then
|
||||
addEvent(function()
|
||||
self:setColumnStyle(value)
|
||||
end)
|
||||
elseif name == 'row-style' then
|
||||
addEvent(function()
|
||||
self:setRowStyle(value)
|
||||
end)
|
||||
elseif name == 'header-column-style' then
|
||||
addEvent(function()
|
||||
self:setHeaderColumnStyle(value)
|
||||
end)
|
||||
elseif name == 'header-row-style' then
|
||||
addEvent(function()
|
||||
self:setHeaderRowStyle(value)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UITable:setColumnWidth(width)
|
||||
if self:hasHeader() then return end
|
||||
self.columnWidth = width
|
||||
end
|
||||
|
||||
function UITable:setDefaultColumnWidth(width)
|
||||
self.defaultColumnWidth = width
|
||||
end
|
||||
|
||||
-- Check if the table has a header
|
||||
function UITable:hasHeader()
|
||||
return self.headerRow ~= nil
|
||||
end
|
||||
|
||||
-- Clear all rows
|
||||
function UITable:clearData()
|
||||
if not self.dataSpace then
|
||||
return
|
||||
end
|
||||
self.dataSpace:destroyChildren()
|
||||
self.selectedRow = nil
|
||||
self.columns = {}
|
||||
self.rows = {}
|
||||
end
|
||||
|
||||
-- Set existing child as header
|
||||
function UITable:setHeader(headerWidget)
|
||||
self:removeHeader()
|
||||
|
||||
if self.dataSpace then
|
||||
local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop()
|
||||
self.dataSpace:applyStyle({ height = newHeight })
|
||||
end
|
||||
|
||||
self.headerColumns = {}
|
||||
self.columnWidth = {}
|
||||
for colId, column in pairs(headerWidget:getChildren()) do
|
||||
column.colId = colId
|
||||
column.table = self
|
||||
table.insert(self.columnWidth, column:getWidth())
|
||||
table.insert(self.headerColumns, column)
|
||||
end
|
||||
|
||||
self.headerRow = headerWidget
|
||||
end
|
||||
|
||||
-- Create and add header from table data
|
||||
function UITable:addHeader(data)
|
||||
if not data or type(data) ~= 'table' then
|
||||
g_logger.error('UITable:addHeaderRow - table columns must be provided in a table')
|
||||
return
|
||||
end
|
||||
|
||||
self:removeHeader()
|
||||
|
||||
-- build header columns
|
||||
local columns = {}
|
||||
for colId, column in pairs(data) do
|
||||
local col = g_ui.createWidget(self.headerColumnBaseStyle)
|
||||
col.colId = colId
|
||||
col.table = self
|
||||
for type, value in pairs(column) do
|
||||
if type == 'width' then
|
||||
col:setWidth(value)
|
||||
elseif type == 'height' then
|
||||
col:setHeight(value)
|
||||
elseif type == 'text' then
|
||||
col:setText(value)
|
||||
elseif type == 'onClick' then
|
||||
col.onClick = value
|
||||
end
|
||||
end
|
||||
table.insert(columns, col)
|
||||
end
|
||||
|
||||
-- create a new header
|
||||
local headerRow = g_ui.createWidget(self.headerRowBaseStyle, self)
|
||||
local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop()
|
||||
self.dataSpace:applyStyle({ height = newHeight })
|
||||
|
||||
headerRow:setId('header')
|
||||
self.headerColumns = {}
|
||||
self.columnWidth = {}
|
||||
for _, column in pairs(columns) do
|
||||
headerRow:addChild(column)
|
||||
table.insert(self.columnWidth, column:getWidth())
|
||||
table.insert(self.headerColumns, column)
|
||||
end
|
||||
|
||||
self.headerRow = headerRow
|
||||
return headerRow
|
||||
end
|
||||
|
||||
-- Remove header
|
||||
function UITable:removeHeader()
|
||||
if self:hasHeader() then
|
||||
if self.dataSpace then
|
||||
local newHeight = self.dataSpace:getHeight()+self.headerRow:getHeight()+self.dataSpace:getMarginTop()
|
||||
self.dataSpace:applyStyle({ height = newHeight })
|
||||
end
|
||||
self.headerColumns = {}
|
||||
self.columnWidth = {}
|
||||
self.headerRow:destroy()
|
||||
self.headerRow = nil
|
||||
end
|
||||
end
|
||||
|
||||
function UITable:addRow(data, height)
|
||||
if not self.dataSpace then
|
||||
g_logger.error('UITable:addRow - table data space has not been set, cannot add rows.')
|
||||
return
|
||||
end
|
||||
if not data or type(data) ~= 'table' then
|
||||
g_logger.error('UITable:addRow - table columns must be provided in a table.')
|
||||
return
|
||||
end
|
||||
|
||||
local row = g_ui.createWidget(self.rowBaseStyle)
|
||||
row.table = self
|
||||
if height then row:setHeight(height) end
|
||||
|
||||
local rowId = #self.rows + 1
|
||||
row.rowId = rowId
|
||||
row:setId('row'..rowId)
|
||||
row:updateBackgroundColor()
|
||||
|
||||
self.columns[rowId] = {}
|
||||
for colId, column in pairs(data) do
|
||||
local col = g_ui.createWidget(self.columBaseStyle, row)
|
||||
if column.width then
|
||||
col:setWidth(column.width)
|
||||
else
|
||||
col:setWidth(self.columnWidth[colId] or self.defaultColumnWidth)
|
||||
end
|
||||
if column.height then
|
||||
col:setHeight(column.height)
|
||||
end
|
||||
if column.text then
|
||||
col:setText(column.text)
|
||||
end
|
||||
if column.sortvalue then
|
||||
col.sortvalue = column.sortvalue
|
||||
else
|
||||
col.sortvalue = column.text or 0
|
||||
end
|
||||
table.insert(self.columns[rowId], col)
|
||||
end
|
||||
|
||||
self.dataSpace:addChild(row)
|
||||
table.insert(self.rows, row)
|
||||
|
||||
if self.autoSort then
|
||||
self:sort()
|
||||
end
|
||||
|
||||
return row
|
||||
end
|
||||
|
||||
-- Update row indices and background color
|
||||
function UITable:updateRows()
|
||||
for rowId = 1, #self.rows do
|
||||
local row = self.rows[rowId]
|
||||
row.rowId = rowId
|
||||
row:setId('row'..rowId)
|
||||
row:updateBackgroundColor()
|
||||
end
|
||||
end
|
||||
|
||||
-- Removes the given row widget from the table
|
||||
function UITable:removeRow(row)
|
||||
if self.selectedRow == row then
|
||||
self:selectRow(nil)
|
||||
end
|
||||
row.onClick = nil
|
||||
row.table = nil
|
||||
table.remove(self.columns, row.rowId)
|
||||
table.remove(self.rows, row.rowId)
|
||||
self.dataSpace:removeChild(row)
|
||||
self:updateRows()
|
||||
end
|
||||
|
||||
function UITable:toggleSorting(enabled)
|
||||
self.autoSort = enabled
|
||||
end
|
||||
|
||||
function UITable:setSorting(colId, sortType)
|
||||
self.headerColumns[colId]:focus()
|
||||
|
||||
if sortType then
|
||||
self.sortType = sortType
|
||||
elseif self.sortColumn == colId then
|
||||
if self.sortType == TABLE_SORTING_ASC then
|
||||
self.sortType = TABLE_SORTING_DESC
|
||||
else
|
||||
self.sortType = TABLE_SORTING_ASC
|
||||
end
|
||||
else
|
||||
self.sortType = TABLE_SORTING_ASC
|
||||
end
|
||||
self.sortColumn = colId
|
||||
end
|
||||
|
||||
function UITable:sort()
|
||||
if self.sortColumn <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
if self.sortType == TABLE_SORTING_ASC then
|
||||
table.sort(self.rows, function(rowA, b)
|
||||
return rowA:getChildByIndex(self.sortColumn).sortvalue < b:getChildByIndex(self.sortColumn).sortvalue
|
||||
end)
|
||||
else
|
||||
table.sort(self.rows, function(rowA, b)
|
||||
return rowA:getChildByIndex(self.sortColumn).sortvalue > b:getChildByIndex(self.sortColumn).sortvalue
|
||||
end)
|
||||
end
|
||||
|
||||
if self.dataSpace then
|
||||
for _, child in pairs(self.dataSpace:getChildren()) do
|
||||
self.dataSpace:removeChild(child)
|
||||
end
|
||||
end
|
||||
|
||||
self:updateRows()
|
||||
self.columns = {}
|
||||
for _, row in pairs(self.rows) do
|
||||
if self.dataSpace then
|
||||
self.dataSpace:addChild(row)
|
||||
end
|
||||
|
||||
self.columns[row.rowId] = {}
|
||||
for _, column in pairs(row:getChildren()) do
|
||||
table.insert(self.columns[row.rowId], column)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UITable:selectRow(selectedRow)
|
||||
if selectedRow == self.selectedRow then return end
|
||||
|
||||
local previousSelectedRow = self.selectedRow
|
||||
self.selectedRow = selectedRow
|
||||
|
||||
if previousSelectedRow then
|
||||
previousSelectedRow:setChecked(false)
|
||||
end
|
||||
|
||||
if selectedRow then
|
||||
selectedRow:setChecked(true)
|
||||
end
|
||||
|
||||
signalcall(self.onSelectionChange, self, selectedRow, previousSelectedRow)
|
||||
end
|
||||
|
||||
function UITable:setTableData(tableData)
|
||||
local headerHeight = 0
|
||||
if self.headerRow then
|
||||
headerHeight = self.headerRow:getHeight()
|
||||
end
|
||||
|
||||
self.dataSpace = tableData
|
||||
self.dataSpace:applyStyle({ height = self:getHeight()-headerHeight-self:getMarginTop() })
|
||||
end
|
||||
|
||||
function UITable:setRowStyle(style, dontUpdate)
|
||||
self.rowBaseStyle = style
|
||||
|
||||
if not dontUpdate then
|
||||
for _, row in pairs(self.rows) do
|
||||
row:setStyle(style)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UITable:setColumnStyle(style, dontUpdate)
|
||||
self.columBaseStyle = style
|
||||
|
||||
if not dontUpdate then
|
||||
for _, columns in pairs(self.columns) do
|
||||
for _, col in pairs(columns) do
|
||||
col:setStyle(style)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UITable:setHeaderRowStyle(style)
|
||||
self.headerRowBaseStyle = style
|
||||
if self.headerRow then
|
||||
self.headerRow:setStyle(style)
|
||||
end
|
||||
end
|
||||
|
||||
function UITable:setHeaderColumnStyle(style)
|
||||
self.headerColumnBaseStyle = style
|
||||
for _, col in pairs(self.headerColumns) do
|
||||
col:setStyle(style)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
UITableRow = extends(UIWidget, "UITableRow")
|
||||
|
||||
function UITableRow:onFocusChange(focused)
|
||||
if focused then
|
||||
if self.table then self.table:selectRow(self) end
|
||||
end
|
||||
end
|
||||
|
||||
function UITableRow:onStyleApply(styleName, styleNode)
|
||||
for name,value in pairs(styleNode) do
|
||||
if name == 'even-background-color' then
|
||||
self.evenBackgroundColor = value
|
||||
elseif name == 'odd-background-color' then
|
||||
self.oddBackgroundColor = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UITableRow:updateBackgroundColor()
|
||||
self.backgroundColor = nil
|
||||
|
||||
local isEven = (self.rowId % 2 == 0)
|
||||
if isEven and self.evenBackgroundColor then
|
||||
self.backgroundColor = self.evenBackgroundColor
|
||||
elseif not isEven and self.oddBackgroundColor then
|
||||
self.backgroundColor = self.oddBackgroundColor
|
||||
end
|
||||
|
||||
if self.backgroundColor then
|
||||
self:mergeStyle({ ['background-color'] = self.backgroundColor })
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
UITableHeaderColumn = extends(UIButton, "UITableHeaderColumn")
|
||||
|
||||
function UITableHeaderColumn:onClick()
|
||||
if self.table then
|
||||
self.table:setSorting(self.colId)
|
||||
self.table:sort()
|
||||
end
|
||||
end
|
78
SabrehavenOTClient/modules/corelib/ui/uitextedit.lua
Normal file
78
SabrehavenOTClient/modules/corelib/ui/uitextedit.lua
Normal file
@@ -0,0 +1,78 @@
|
||||
function UITextEdit:onStyleApply(styleName, styleNode)
|
||||
for name,value in pairs(styleNode) do
|
||||
if name == 'vertical-scrollbar' then
|
||||
addEvent(function()
|
||||
self:setVerticalScrollBar(self:getParent():getChildById(value))
|
||||
end)
|
||||
elseif name == 'horizontal-scrollbar' then
|
||||
addEvent(function()
|
||||
self:setHorizontalScrollBar(self:getParent():getChildById(value))
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UITextEdit:onMouseWheel(mousePos, mouseWheel)
|
||||
if self.verticalScrollBar and self:isMultiline() then
|
||||
if mouseWheel == MouseWheelUp then
|
||||
self.verticalScrollBar:decrement()
|
||||
else
|
||||
self.verticalScrollBar:increment()
|
||||
end
|
||||
return true
|
||||
elseif self.horizontalScrollBar then
|
||||
if mouseWheel == MouseWheelUp then
|
||||
self.horizontalScrollBar:increment()
|
||||
else
|
||||
self.horizontalScrollBar:decrement()
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function UITextEdit:onTextAreaUpdate(virtualOffset, virtualSize, totalSize)
|
||||
self:updateScrollBars()
|
||||
end
|
||||
|
||||
function UITextEdit:setVerticalScrollBar(scrollbar)
|
||||
self.verticalScrollBar = scrollbar
|
||||
self.verticalScrollBar.onValueChange = function(scrollbar, value)
|
||||
local virtualOffset = self:getTextVirtualOffset()
|
||||
virtualOffset.y = value
|
||||
self:setTextVirtualOffset(virtualOffset)
|
||||
end
|
||||
self:updateScrollBars()
|
||||
end
|
||||
|
||||
function UITextEdit:setHorizontalScrollBar(scrollbar)
|
||||
self.horizontalScrollBar = scrollbar
|
||||
self.horizontalScrollBar.onValueChange = function(scrollbar, value)
|
||||
local virtualOffset = self:getTextVirtualOffset()
|
||||
virtualOffset.x = value
|
||||
self:setTextVirtualOffset(virtualOffset)
|
||||
end
|
||||
self:updateScrollBars()
|
||||
end
|
||||
|
||||
function UITextEdit:updateScrollBars()
|
||||
local scrollSize = self:getTextTotalSize()
|
||||
local scrollWidth = math.max(scrollSize.width - self:getTextVirtualSize().width, 0)
|
||||
local scrollHeight = math.max(scrollSize.height - self:getTextVirtualSize().height, 0)
|
||||
|
||||
local scrollbar = self.verticalScrollBar
|
||||
if scrollbar then
|
||||
scrollbar:setMinimum(0)
|
||||
scrollbar:setMaximum(scrollHeight)
|
||||
scrollbar:setValue(self:getTextVirtualOffset().y)
|
||||
end
|
||||
|
||||
local scrollbar = self.horizontalScrollBar
|
||||
if scrollbar then
|
||||
scrollbar:setMinimum(0)
|
||||
scrollbar:setMaximum(scrollWidth)
|
||||
scrollbar:setValue(self:getTextVirtualOffset().x)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- todo: ontext change, focus to cursor
|
21
SabrehavenOTClient/modules/corelib/ui/uiwidget.lua
Normal file
21
SabrehavenOTClient/modules/corelib/ui/uiwidget.lua
Normal file
@@ -0,0 +1,21 @@
|
||||
-- @docclass UIWidget
|
||||
|
||||
function UIWidget:setMargin(...)
|
||||
local params = {...}
|
||||
if #params == 1 then
|
||||
self:setMarginTop(params[1])
|
||||
self:setMarginRight(params[1])
|
||||
self:setMarginBottom(params[1])
|
||||
self:setMarginLeft(params[1])
|
||||
elseif #params == 2 then
|
||||
self:setMarginTop(params[1])
|
||||
self:setMarginRight(params[2])
|
||||
self:setMarginBottom(params[1])
|
||||
self:setMarginLeft(params[2])
|
||||
elseif #params == 4 then
|
||||
self:setMarginTop(params[1])
|
||||
self:setMarginRight(params[2])
|
||||
self:setMarginBottom(params[3])
|
||||
self:setMarginLeft(params[4])
|
||||
end
|
||||
end
|
46
SabrehavenOTClient/modules/corelib/ui/uiwindow.lua
Normal file
46
SabrehavenOTClient/modules/corelib/ui/uiwindow.lua
Normal file
@@ -0,0 +1,46 @@
|
||||
-- @docclass
|
||||
UIWindow = extends(UIWidget, "UIWindow")
|
||||
|
||||
function UIWindow.create()
|
||||
local window = UIWindow.internalCreate()
|
||||
window:setTextAlign(AlignTopCenter)
|
||||
window:setDraggable(true)
|
||||
window:setAutoFocusPolicy(AutoFocusFirst)
|
||||
return window
|
||||
end
|
||||
|
||||
function UIWindow:onKeyDown(keyCode, keyboardModifiers)
|
||||
if keyboardModifiers == KeyboardNoModifier then
|
||||
if keyCode == KeyEnter then
|
||||
signalcall(self.onEnter, self)
|
||||
elseif keyCode == KeyEscape then
|
||||
signalcall(self.onEscape, self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UIWindow:onFocusChange(focused)
|
||||
if focused then self:raise() end
|
||||
end
|
||||
|
||||
function UIWindow:onDragEnter(mousePos)
|
||||
if self.static then
|
||||
return false
|
||||
end
|
||||
self:breakAnchors()
|
||||
self.movingReference = { x = mousePos.x - self:getX(), y = mousePos.y - self:getY() }
|
||||
return true
|
||||
end
|
||||
|
||||
function UIWindow:onDragLeave(droppedWidget, mousePos)
|
||||
-- TODO: auto detect and reconnect anchors
|
||||
end
|
||||
|
||||
function UIWindow:onDragMove(mousePos, mouseMoved)
|
||||
if self.static then
|
||||
return
|
||||
end
|
||||
local pos = { x = mousePos.x - self.movingReference.x, y = mousePos.y - self.movingReference.y }
|
||||
self:setPosition(pos)
|
||||
self:bindRectToParent()
|
||||
end
|
376
SabrehavenOTClient/modules/corelib/util.lua
Normal file
376
SabrehavenOTClient/modules/corelib/util.lua
Normal file
@@ -0,0 +1,376 @@
|
||||
-- @docfuncs @{
|
||||
|
||||
function print(...)
|
||||
local msg = ""
|
||||
local args = {...}
|
||||
local appendSpace = #args > 1
|
||||
for i,v in ipairs(args) do
|
||||
msg = msg .. tostring(v)
|
||||
if appendSpace and i < #args then
|
||||
msg = msg .. ' '
|
||||
end
|
||||
end
|
||||
g_logger.log(LogInfo, msg)
|
||||
end
|
||||
|
||||
function pinfo(msg)
|
||||
g_logger.log(LogInfo, msg)
|
||||
end
|
||||
|
||||
function perror(msg)
|
||||
g_logger.log(LogError, msg)
|
||||
end
|
||||
|
||||
function pwarning(msg)
|
||||
g_logger.log(LogWarning, msg)
|
||||
end
|
||||
|
||||
function pdebug(msg)
|
||||
g_logger.log(LogDebug, msg)
|
||||
end
|
||||
|
||||
function fatal(msg)
|
||||
g_logger.log(LogFatal, msg)
|
||||
end
|
||||
|
||||
function exit()
|
||||
g_app.exit()
|
||||
end
|
||||
|
||||
function quit()
|
||||
g_app.exit()
|
||||
end
|
||||
|
||||
function connect(object, arg1, arg2, arg3)
|
||||
local signalsAndSlots
|
||||
local pushFront
|
||||
if type(arg1) == 'string' then
|
||||
signalsAndSlots = { [arg1] = arg2 }
|
||||
pushFront = arg3
|
||||
else
|
||||
signalsAndSlots = arg1
|
||||
pushFront = arg2
|
||||
end
|
||||
|
||||
for signal,slot in pairs(signalsAndSlots) do
|
||||
if not object[signal] then
|
||||
local mt = getmetatable(object)
|
||||
if mt and type(object) == 'userdata' then
|
||||
object[signal] = function(...)
|
||||
return signalcall(mt[signal], ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not object[signal] then
|
||||
object[signal] = slot
|
||||
elseif type(object[signal]) == 'function' then
|
||||
object[signal] = { object[signal] }
|
||||
end
|
||||
|
||||
if type(slot) ~= 'function' then
|
||||
perror(debug.traceback('unable to connect a non function value'))
|
||||
end
|
||||
|
||||
if type(object[signal]) == 'table' then
|
||||
if pushFront then
|
||||
table.insert(object[signal], 1, slot)
|
||||
else
|
||||
table.insert(object[signal], #object[signal]+1, slot)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function disconnect(object, arg1, arg2)
|
||||
local signalsAndSlots
|
||||
if type(arg1) == 'string' then
|
||||
if arg2 == nil then
|
||||
object[arg1] = nil
|
||||
return
|
||||
end
|
||||
signalsAndSlots = { [arg1] = arg2 }
|
||||
elseif type(arg1) == 'table' then
|
||||
signalsAndSlots = arg1
|
||||
else
|
||||
perror(debug.traceback('unable to disconnect'))
|
||||
end
|
||||
|
||||
for signal,slot in pairs(signalsAndSlots) do
|
||||
if not object[signal] then
|
||||
elseif type(object[signal]) == 'function' then
|
||||
if object[signal] == slot then
|
||||
object[signal] = nil
|
||||
end
|
||||
elseif type(object[signal]) == 'table' then
|
||||
for k,func in pairs(object[signal]) do
|
||||
if func == slot then
|
||||
table.remove(object[signal], k)
|
||||
|
||||
if #object[signal] == 1 then
|
||||
object[signal] = object[signal][1]
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function newclass(name)
|
||||
if not name then
|
||||
perror(debug.traceback('new class has no name.'))
|
||||
end
|
||||
|
||||
local class = {}
|
||||
function class.internalCreate()
|
||||
local instance = {}
|
||||
for k,v in pairs(class) do
|
||||
instance[k] = v
|
||||
end
|
||||
return instance
|
||||
end
|
||||
class.create = class.internalCreate
|
||||
class.__class = name
|
||||
class.getClassName = function() return name end
|
||||
return class
|
||||
end
|
||||
|
||||
function extends(base, name)
|
||||
if not name then
|
||||
perror(debug.traceback('extended class has no name.'))
|
||||
end
|
||||
|
||||
local derived = {}
|
||||
function derived.internalCreate()
|
||||
local instance = base.create()
|
||||
for k,v in pairs(derived) do
|
||||
instance[k] = v
|
||||
end
|
||||
return instance
|
||||
end
|
||||
derived.create = derived.internalCreate
|
||||
derived.__class = name
|
||||
derived.getClassName = function() return name end
|
||||
return derived
|
||||
end
|
||||
|
||||
function runinsandbox(func, ...)
|
||||
if type(func) == 'string' then
|
||||
func, err = loadfile(resolvepath(func, 2))
|
||||
if not func then
|
||||
error(err)
|
||||
end
|
||||
end
|
||||
local env = { }
|
||||
local oldenv = getfenv(0)
|
||||
setmetatable(env, { __index = oldenv } )
|
||||
setfenv(0, env)
|
||||
func(...)
|
||||
setfenv(0, oldenv)
|
||||
return env
|
||||
end
|
||||
|
||||
function loadasmodule(name, file)
|
||||
file = file or resolvepath(name, 2)
|
||||
if package.loaded[name] then
|
||||
return package.loaded[name]
|
||||
end
|
||||
local env = runinsandbox(file)
|
||||
package.loaded[name] = env
|
||||
return env
|
||||
end
|
||||
|
||||
local function module_loader(modname)
|
||||
local module = g_modules.getModule(modname)
|
||||
if not module then
|
||||
return '\n\tno module \'' .. modname .. '\''
|
||||
end
|
||||
return function()
|
||||
if not module:load() then
|
||||
error('unable to load required module ' .. modname)
|
||||
end
|
||||
return module:getSandbox()
|
||||
end
|
||||
end
|
||||
table.insert(package.loaders, 1, module_loader)
|
||||
|
||||
function import(table)
|
||||
assert(type(table) == 'table')
|
||||
local env = getfenv(2)
|
||||
for k,v in pairs(table) do
|
||||
env[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
function export(what, key)
|
||||
if key ~= nil then
|
||||
_G[key] = what
|
||||
else
|
||||
for k,v in pairs(what) do
|
||||
_G[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function unexport(key)
|
||||
if type(key) == 'table' then
|
||||
for _k,v in pairs(key) do
|
||||
_G[v] = nil
|
||||
end
|
||||
else
|
||||
_G[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function getfsrcpath(depth)
|
||||
depth = depth or 2
|
||||
local info = debug.getinfo(1+depth, "Sn")
|
||||
local path
|
||||
if info.short_src then
|
||||
path = info.short_src:match("(.*)/.*")
|
||||
end
|
||||
if not path then
|
||||
path = '/'
|
||||
elseif path:sub(0, 1) ~= '/' then
|
||||
path = '/' .. path
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
function resolvepath(filePath, depth)
|
||||
if not filePath then return nil end
|
||||
depth = depth or 1
|
||||
if filePath then
|
||||
if filePath:sub(0, 1) ~= '/' then
|
||||
local basepath = getfsrcpath(depth+1)
|
||||
if basepath:sub(#basepath) ~= '/' then basepath = basepath .. '/' end
|
||||
return basepath .. filePath
|
||||
else
|
||||
return filePath
|
||||
end
|
||||
else
|
||||
local basepath = getfsrcpath(depth+1)
|
||||
if basepath:sub(#basepath) ~= '/' then basepath = basepath .. '/' end
|
||||
return basepath
|
||||
end
|
||||
end
|
||||
|
||||
function toboolean(v)
|
||||
if type(v) == 'string' then
|
||||
v = v:trim():lower()
|
||||
if v == '1' or v == 'true' then
|
||||
return true
|
||||
end
|
||||
elseif type(v) == 'number' then
|
||||
if v == 1 then
|
||||
return true
|
||||
end
|
||||
elseif type(v) == 'boolean' then
|
||||
return v
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function fromboolean(boolean)
|
||||
if boolean then
|
||||
return 'true'
|
||||
else
|
||||
return 'false'
|
||||
end
|
||||
end
|
||||
|
||||
function booleantonumber(boolean)
|
||||
if boolean then
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
function numbertoboolean(number)
|
||||
if number ~= 0 then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function protectedcall(func, ...)
|
||||
local status, ret = pcall(func, ...)
|
||||
if status then
|
||||
return ret
|
||||
end
|
||||
|
||||
perror(ret)
|
||||
return false
|
||||
end
|
||||
|
||||
function signalcall(param, ...)
|
||||
if type(param) == 'function' then
|
||||
local status, ret = pcall(param, ...)
|
||||
if status then
|
||||
return ret
|
||||
else
|
||||
perror(ret)
|
||||
end
|
||||
elseif type(param) == 'table' then
|
||||
for k,v in pairs(param) do
|
||||
local status, ret = pcall(v, ...)
|
||||
if status then
|
||||
if ret then return true end
|
||||
else
|
||||
perror(ret)
|
||||
end
|
||||
end
|
||||
elseif param ~= nil then
|
||||
error('attempt to call a non function value')
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function tr(s, ...)
|
||||
return string.format(s, ...)
|
||||
end
|
||||
|
||||
function getOppositeAnchor(anchor)
|
||||
if anchor == AnchorLeft then
|
||||
return AnchorRight
|
||||
elseif anchor == AnchorRight then
|
||||
return AnchorLeft
|
||||
elseif anchor == AnchorTop then
|
||||
return AnchorBottom
|
||||
elseif anchor == AnchorBottom then
|
||||
return AnchorTop
|
||||
elseif anchor == AnchorVerticalCenter then
|
||||
return AnchorHorizontalCenter
|
||||
elseif anchor == AnchorHorizontalCenter then
|
||||
return AnchorVerticalCenter
|
||||
end
|
||||
return anchor
|
||||
end
|
||||
|
||||
function makesingleton(obj)
|
||||
local singleton = {}
|
||||
if obj.getClassName then
|
||||
for key,value in pairs(_G[obj:getClassName()]) do
|
||||
if type(value) == 'function' then
|
||||
singleton[key] = function(...) return value(obj, ...) end
|
||||
end
|
||||
end
|
||||
end
|
||||
return singleton
|
||||
end
|
||||
|
||||
function comma_value(amount)
|
||||
local formatted = amount
|
||||
while true do
|
||||
formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
|
||||
if (k==0) then
|
||||
break
|
||||
end
|
||||
end
|
||||
return formatted
|
||||
end
|
||||
|
||||
-- @}
|
Reference in New Issue
Block a user