mirror of
				https://github.com/ErikasKontenis/SabrehavenServer.git
				synced 2025-10-30 19:56:22 +01:00 
			
		
		
		
	commit client
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
	 ErikasKontenis
					ErikasKontenis