Refactoring and flexibility changes

* Split game module into game and game_interface
* Move core_lib to corelib
* Move miniwindow to corelib
* Introduce init.lua script for initializing the client, giving much more flexibility
* OTClient is no longer Application derived and is much simpler
This commit is contained in:
Eduardo Bart
2012-06-19 21:15:56 -03:00
parent 9e72860178
commit 8761220deb
115 changed files with 448 additions and 363 deletions

312
modules/corelib/const.lua Normal file
View File

@@ -0,0 +1,312 @@
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
KeyboardNoModifier = 0
KeyboardCtrlModifier = 1
KeyboardAltModifier = 2
KeyboardCtrlAltModifier = 3
KeyboardShiftModifier = 4
KeyboardCtrlShiftModifier = 5
KeyboardAltShiftModifier = 6
KeyboardCtrlAltShiftModifier = 7
MouseNoButton = 0
MouseLeftButton = 1
MouseRightButton = 2
MouseMidButton = 3
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
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'
}
SpeakSay = 1
SpeakWhisper = 2
SpeakYell = 3
SpeakBroadcast = 4
SpeakPrivate = 5
SpeakPrivateRed = 6
SpeakPrivatePlayerToNpc = 7
SpeakPrivateNpcToPlayer = 8
SpeakChannelYellow = 9
SpeakChannelWhite = 10
SpeakChannelRed = 11
SpeakChannelOrange = 12
SpeakMonsterSay = 13
SpeakMonsterYell = 14
FightOffensive = 1
FightBalanced = 2
FightDefensive = 3
DontChase = 0
ChaseOpponent = 1

View File

@@ -0,0 +1,21 @@
Module
name: corelib
description: Contains core lua classes, functions and constants used by other modules
author: OTClient team
website: www.otclient.info
reloadable: false
@onLoad: |
dofiles 'ext'
dofiles 'math'
dofile 'const'
dofile 'util'
dofile 'globals'
dofile 'settings'
dofile 'keyboard'
dofile 'mouse'
dofiles 'ui'
dofiles 'widgets'

View File

@@ -0,0 +1,6 @@
function os.execute(command)
local f = assert(io.popen(command, 'r'))
local data = assert(f:read('*a'))
f:close()
print(data)
end

View File

@@ -0,0 +1,45 @@
function string.split(s, delim)
local start = 1
local results = {}
while true do
local pos = string.find(s, delim, start, true)
if not pos then
break
end
table.insert(results, string.sub(s, start, pos-1))
start = pos + string.len(delim)
end
table.insert(results, string.sub(s, start))
table.removevalue(results, '')
return results
end
function string.starts(s, start)
return string.sub(s, 1, #start) == start
end
function string.trim(s)
return string.match(s, '^%s*(.*%S)') or ''
end
function string.explode(str, sep, limit)
if(type(sep) ~= 'string' or tostring(str):len() == 0 or sep:len() == 0) then
return {}
end
local i, pos, tmp, t = 0, 1, "", {}
for s, e in function() return string.find(str, sep, pos) end do
tmp = str: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 = str:sub(pos):trim()
table.insert(t, tmp)
return t
end

View File

@@ -0,0 +1,57 @@
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.copy(t)
local res = {}
for k,v in pairs(t) do
res[k] = v
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)
for k,v in pairs(t) do
if v == value then return k end
end
end
function table.removevalue(t, value)
for k,v in pairs(t) do
if v == value then
table.remove(t, k)
break
end
end
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

View File

@@ -0,0 +1,87 @@
rootWidget = g_ui.getRootWidget()
importStyle = g_ui.importStyle
importFont = g_fonts.importFont
setDefaultFont = g_fonts.setDefaultFont
-- G is used as a global table to save variables in memory between reloads
G = G or {}
function loadUI(otui, parent)
local otuiFilePath = resolvepath(otui, 2)
return g_ui.loadUI(otuiFilePath, parent)
end
function displayUI(otui, parent)
parent = parent or rootWidget
local otuiFilePath = resolvepath(otui, 2)
return g_ui.loadUI(otuiFilePath, parent)
end
function createWidget(stylename, parent)
if type(parent) == 'string' then
parent = rootWidget:recursiveGetChildById(parent)
end
local widget = g_ui.createWidgetFromStyle(stylename, parent)
return widget
end
function scheduleEvent(callback, delay)
local event = g_eventDispatcher.scheduleEvent(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 event = g_eventDispatcher.addEvent(callback, front)
-- must hold a reference to the callback, otherwise it would be collected
event._callback = callback
return event
end
function cycleEvent(callback, front)
local event = g_eventDispatcher.cycleEvent(callback, front)
-- 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
function reloadModule(name)
local module = g_modules.getModule(name)
if module then
module:reload()
end
end
function reloadModules()
g_modules.reloadModules()
end

View File

@@ -0,0 +1,146 @@
Keyboard = {}
-- private functions
function translateKeyCombo(keyCombo)
if not keyCombo or #keyCombo == 0 then return nil end
table.sort(keyCombo)
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 retranslateKeyComboDesc(keyComboDesc)
if keyComboDesc == nil then
error('Unable to translate key combo \'' .. 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
table.sort(keyCombo)
return translateKeyCombo(keyCombo)
end
local function onWidgetKeyDown(widget, keyCode, keyboardModifiers)
if keyCode == KeyUnknown then return false end
local keyComboDesc = determineKeyComboDesc(keyCode, keyboardModifiers)
local callback = widget.boundKeyDownCombos[keyComboDesc]
if callback then
callback()
return true
end
return false
end
local function onWidgetKeyPress(widget, keyCode, keyboardModifiers, autoRepeatTicks)
if keyCode == KeyUnknown then return false end
local keyComboDesc = determineKeyComboDesc(keyCode, keyboardModifiers)
local comboConf = widget.boundKeyPressCombos[keyComboDesc]
if comboConf and (autoRepeatTicks >= comboConf.autoRepeatDelay or autoRepeatTicks == 0) and comboConf.callback then
comboConf.callback()
return true
end
return false
end
local function connectKeyDownEvent(widget)
if widget.boundKeyDownCombos then return end
connect(widget, { onKeyDown = onWidgetKeyDown })
widget.boundKeyDownCombos = {}
end
local function connectKeyPressEvent(widget)
if widget.boundKeyPressCombos then return end
connect(widget, { onKeyPress = onWidgetKeyPress })
widget.boundKeyPressCombos = {}
end
-- public functions
function Keyboard.bindKeyDown(keyComboDesc, callback, widget)
widget = widget or rootWidget
connectKeyDownEvent(widget)
local keyComboDesc = retranslateKeyComboDesc(keyComboDesc)
widget.boundKeyDownCombos[keyComboDesc] = callback
end
function Keyboard.bindKeyPress(keyComboDesc, callback, widget, autoRepeatDelay)
autoRepeatDelay = autoRepeatDelay or 500
widget = widget or rootWidget
connectKeyPressEvent(widget)
local keyComboDesc = retranslateKeyComboDesc(keyComboDesc)
widget.boundKeyPressCombos[keyComboDesc] = { callback = callback, autoRepeatDelay = autoRepeatDelay }
widget:setAutoRepeatDelay(math.min(autoRepeatDelay, widget:getAutoRepeatDelay()))
end
function Keyboard.unbindKeyDown(keyComboDesc, widget)
widget = widget or rootWidget
if widget.boundKeyDownCombos == nil then return end
local keyComboDesc = retranslateKeyComboDesc(keyComboDesc)
if keyComboDesc then
widget.boundKeyDownCombos[keyComboDesc] = nil
end
end
function Keyboard.unbindKeyPress(keyComboDesc, widget)
widget = widget or rootWidget
if widget.boundKeyPressCombos == nil then return end
local keyComboDesc = retranslateKeyComboDesc(keyComboDesc)
if keyComboDesc then
widget.boundKeyPressCombos[keyComboDesc] = nil
end
end
function Keyboard.getModifiers()
return g_window.getKeyboardModifiers()
end
function Keyboard.isCtrlPressed()
return bit32.band(g_window.getKeyboardModifiers(), KeyboardCtrlModifier) ~= 0
end
function Keyboard.isAltPressed()
return bit32.band(g_window.getKeyboardModifiers(), KeyboardAltModifier) ~= 0
end
function Keyboard.isShiftPressed()
return bit32.band(g_window.getKeyboardModifiers(), KeyboardShiftModifier) ~= 0
end

View File

@@ -0,0 +1 @@
Color = {}

View File

@@ -0,0 +1,8 @@
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

View File

@@ -0,0 +1 @@
Point = {}

View File

@@ -0,0 +1 @@
Rect = {}

View File

@@ -0,0 +1 @@
Size = {}

53
modules/corelib/mouse.lua Normal file
View File

@@ -0,0 +1,53 @@
Mouse = {}
local cursorChanged = false
function Mouse.setTargetCursor()
g_window.setMouseCursor('/cursors/targetcursor.png', {x=9,y=9})
cursorChanged = true
end
function Mouse.setHorizontalCursor()
g_window.setMouseCursor('/cursors/horizontal.png', {x=9,y=4})
cursorChanged = true
end
function Mouse.setVerticalCursor()
g_window.setMouseCursor('/cursors/vertical.png', {x=4,y=9})
cursorChanged = true
end
function Mouse.restoreCursor()
g_window.restoreMouseCursor()
cursorChanged = false
end
function Mouse.isCursorChanged()
return cursorChanged
end
function Mouse.isPressed(button)
if not button then button = MouseLeftButton end
return g_window.isMouseButtonPressed(button)
end
function Mouse.bindAutoPress(widget, callback)
connect(widget, { onMousePress = function(widget, mousePos, mouseButton)
callback()
periodicalEvent(function()
callback()
end, function()
return widget:isPressed()
end, 30, 300)
return true
end })
end
function Mouse.bindPressMove(widget, callback)
connect(widget, { onMouseMove = function(widget, mousePos, mouseMoved)
if widget:isPressed() then
callback(mousePos, mouseMoved)
end
return true
end })
end

View File

@@ -0,0 +1,82 @@
Settings = {}
Settings.exists = g_configs.exists
Settings.setNode = g_configs.setNode
Settings.addNode = g_configs.addNode
Settings.getNode = g_configs.getNode
Settings.remove = g_configs.remove
Settings.setList = g_configs.setList
Settings.getList = g_configs.getList
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 Settings.set(key, value)
g_configs.set(key, convertSettingValue(value))
end
function Settings.setDefault(key, value)
if Settings.exists(key) then return false end
Settings.set(key, value)
return true
end
function Settings.get(key, default)
if not Settings.exists(key) and default ~= nil then
Settings.set(key, default)
end
return g_configs.get(key)
end
function Settings.getString(key, default)
return Settings.get(key, default)
end
function Settings.getInteger(key, default)
return tonumber(Settings.get(key, default))
end
function Settings.getNumber(key, default)
return tonumber(Settings.get(key, default))
end
function Settings.getBoolean(key, default)
return toboolean(Settings.get(key, default))
end
function Settings.getPoint(key, default)
return topoint(Settings.get(key, default))
end
function Settings.getRect(key, default)
return torect(Settings.get(key, default))
end
function Settings.getSize(key, default)
return tosize(Settings.get(key, default))
end
function Settings.getColor(key, default)
return tocolor(Settings.get(key, default))
end
function Settings.getColor(key, default)
return tocolor(Settings.get(key, default))
end

View File

@@ -0,0 +1,36 @@
Effects = {}
function 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()
Effects.fadeIn(widget, time, elapsed + 30)
end, 30)
else
widget.fadeEvent = nil
end
end
function 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()
Effects.fadeOut(widget, time, elapsed + 30)
end, 30)
else
widget.fadeEvent = nil
end
end
function Effects.cancelFade(widget)
removeEvent(widget.fadeEvent)
widget.fadeEvent = nil
end

View File

@@ -0,0 +1,44 @@
RadioGroup = newclass()
function RadioGroup.create()
local radiogroup = RadioGroup.internalCreate()
radiogroup.widgets = {}
return radiogroup
end
function RadioGroup:destroy()
for k,widget in pairs(self.widgets) do
widget.onClick = nil
end
self.widgets = {}
end
function RadioGroup:addWidget(widget)
table.insert(self.widgets, widget)
widget.onClick = function(widget) self:selectWidget(widget) end
end
function RadioGroup:removeWidget(widget)
if self.selectedWidget == widget then
self:selectWidget(nil)
end
widget.onClick = nil
table.removevalue(self.widgets, widget)
end
function RadioGroup:selectWidget(selectedWidget)
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
signalcall(self.onSelectionChange, self, selectedWidget, previousSelectedWidget)
end

View File

@@ -0,0 +1,96 @@
ToolTip = {}
-- private variables
local toolTipLabel
local currentHoveredWidget
-- private functions
local function moveToolTip(tooltip)
if not tooltip:isVisible() or tooltip:getOpacity() < 0.1 then return end
local pos = g_window.getMousePosition()
pos.y = pos.y + 1
local xdif = g_window.getSize().width - (pos.x + tooltip:getWidth())
if xdif < 2 then
pos.x = pos.x - tooltip:getWidth() - 3
else
pos.x = pos.x + 10
end
tooltip:setPosition(pos)
end
local function onWidgetHoverChange(widget, hovered)
if hovered then
if widget.tooltip and not Mouse.isPressed() then
ToolTip.display(widget.tooltip)
currentHoveredWidget = widget
end
else
if widget == currentHoveredWidget then
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 ToolTip.init()
connect(UIWidget, { onStyleApply = onWidgetStyleApply,
onHoverChange = onWidgetHoverChange})
addEvent(function()
toolTipLabel = createWidget('UILabel', rootWidget)
toolTipLabel:setId('toolTip')
toolTipLabel:setBackgroundColor('#111111cc')
toolTipLabel:setTextAlign(AlignCenter)
toolTipLabel:hide()
toolTipLabel.onMouseMove = moveToolTip
end)
end
function ToolTip.terminate()
disconnect(UIWidget, { onStyleApply = onWidgetStyleApply,
onHoverChange = onWidgetHoverChange })
currentHoveredWidget = nil
toolTipLabel:destroy()
toolTipLabel = nil
ToolTip = nil
end
function ToolTip.display(text)
if text == nil 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()
Effects.fadeIn(toolTipLabel, 100)
moveToolTip(toolTipLabel)
end
function ToolTip.hide()
Effects.fadeOut(toolTipLabel, 100)
end
-- UIWidget extensions
function UIWidget:setTooltip(text)
self.tooltip = text
end
function UIWidget:getTooltip()
return self.tooltip
end
ToolTip.init()
connect(g_app, { onTerminate = ToolTip.terminate })

176
modules/corelib/util.lua Normal file
View File

@@ -0,0 +1,176 @@
function print(...)
local msg = ""
for i,v in ipairs({...}) do
msg = msg .. tostring(v) .. "\t"
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
exit = g_app.exit
quit = g_app.exit
function connect(object, signalsAndSlots, pushFront)
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(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, signalsAndSlots)
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()
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
return class
end
function extends(base)
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
return derived
end
function newenv()
local env = { }
setmetatable(env, { __index = _G} )
return env
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)
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(str)
str = str:trim():lower()
if str == '1' or str == 'true' then
return true
end
return false
end
local oldtonumber = tonumber
function tonumber(v)
if v == nil then return 0 end
return oldtonumber(v)
end
function signalcall(param, ...)
if type(param) == 'function' then
return param(...)
elseif type(param) == 'table' then
for k,v in pairs(param) do
if v(...) then
return true
end
end
elseif func ~= nil then
error('attempt to call a non function value')
end
return false
end
function tr(s)
return s
end

View File

@@ -0,0 +1,7 @@
UIButton = extends(UIWidget)
function UIButton.create()
local button = UIButton.internalCreate()
button:setFocusable(false)
return button
end

View File

@@ -0,0 +1,12 @@
UICheckBox = extends(UIWidget)
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

View File

@@ -0,0 +1,70 @@
UIComboBox = extends(UIWidget)
function UIComboBox.create()
local combobox = UIComboBox.internalCreate()
combobox.options = {}
combobox.currentIndex = -1
return combobox
end
function UIComboBox:setCurrentOption(text)
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)
self:onOptionChange(text, v.data)
return
end
end
end
function UIComboBox:setCurrentIndex(index)
if index >= 1 and index <= #self.options then
local v = self.options[index]
self.currentIndex = index
self:setText(v.text)
self:onOptionChange(v.text, v.data)
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:onMousePress(mousePos, mouseButton)
local menu = createWidget(self:getStyleName() .. 'PopupMenu')
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 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
end
function UIComboBox:onOptionChange(optionText, optionData)
-- nothing todo
end

View File

@@ -0,0 +1,9 @@
UILabel = extends(UIWidget)
function UILabel.create()
local label = UILabel.internalCreate()
label:setPhantom(true)
label:setFocusable(false)
label:setTextAlign(AlignLeft)
return label
end

View File

@@ -0,0 +1,67 @@
if not UIWindow then dofile 'uiwindow' end
UIMessageBox = extends(UIWindow)
MessageBoxOk = 1
MessageBoxCancel = 2
-- messagebox cannot be created from otui files
UIMessageBox.create = nil
function UIMessageBox.display(title, message, flags)
local messagebox = UIMessageBox.internalCreate()
rootWidget:addChild(messagebox)
messagebox:setStyle('MessageBoxWindow')
messagebox:setText(title)
local messageLabel = createWidget('MessageBoxLabel', messagebox)
messageLabel:setText(message)
messageLabel:resizeToText()
messagebox:setWidth(math.max(messageLabel:getWidth() + 48, messagebox:getTextSize().width + 20))
messagebox:setHeight(math.max(messageLabel:getHeight() + 64, messagebox:getHeight()))
-- setup messagebox first button
local buttonRight = createWidget('MessageBoxRightButton', messagebox)
if flags == MessageBoxOk then
buttonRight:setText('Ok')
connect(buttonRight, { onClick = function(self) self:getParent():ok() end })
connect(messagebox, { onEnter = function(self) self:ok() end })
connect(messagebox, { onEscape = function(self) self:ok() end })
elseif flags == MessageBoxCancel then
buttonRight:setText('Cancel')
connect(buttonRight, { onClick = function(self) self:getParent():cancel() end })
connect(messagebox, { onEnter = function(self) self:cancel() end })
connect(messagebox, { onEscape = function(self) self:cancel() end })
end
messagebox:lock()
return messagebox
end
function displayInfoBox(title, message)
return UIMessageBox.display(title, message, MessageBoxOk)
end
function displayErrorBox(title, message)
return UIMessageBox.display(title, message, MessageBoxOk)
end
function displayCancelBox(title, message)
return UIMessageBox.display(title, message, MessageBoxCancel)
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

View File

@@ -0,0 +1,122 @@
UIMiniWindow = extends(UIWindow)
function UIMiniWindow.create()
local miniwindow = UIMiniWindow.internalCreate()
return miniwindow
end
function UIMiniWindow:onSetup()
self:getChildById('closeButton').onClick = function() signalcall(self.onClose, self) end
self:getChildById('minimizeButton').onClick = function() signalcall(self.onMinimize, self) end
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()
parent:removeChild(self)
containerParent:addChild(self)
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: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(0)
self.setMovedChildMargin = nil
end
if mousePos.y < childCenterY then
self.setMovedChildMargin = function(v) child:setMarginTop(v) end
self.movedIndex = 0
else
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(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:onDragLeave(droppedWidget, mousePos)
if self.movedWidget then
self.setMovedChildMargin(0)
self.movedWidget = nil
self.setMovedChildMargin = nil
self.movedIndex = nil
end
end
function UIMiniWindow:onFocusChange(focused)
-- miniwindows only raises when its outside MiniWindowContainers
if not focused then return end
local parent = self:getParent()
if parent and parent:getClassName() ~= 'UIMiniWindowContainer' then
self:raise()
end
end
function UIMiniWindow:onClose()
end
function UIMiniWindow:onMinimize()
if self:isOn() then
self:setOn(false)
self:getChildById('contentsPanel'):show()
self:getChildById('miniwindowScrollBar'):show()
self:getChildById('bottomResizeBorder'):show()
self:getChildById('minimizeButton'):setOn(false)
self:setHeight(self.savedHeight)
else
self.savedHeight = self:getHeight()
self:setHeight(self.minimizedHeight)
self:setOn(true)
self:getChildById('contentsPanel'):hide()
self:getChildById('miniwindowScrollBar'):hide()
self:getChildById('bottomResizeBorder'):hide()
self:getChildById('minimizeButton'):setOn(true)
end
end
function UIMiniWindow:getClassName()
return 'UIMiniWindow'
end

View File

@@ -0,0 +1,32 @@
UIMiniWindowContainer = extends(UIWidget)
function UIMiniWindowContainer.create()
local container = UIMiniWindowContainer.internalCreate()
container:setFocusable(false)
container:setPhantom(true)
return container
end
function UIMiniWindowContainer:onDrop(widget, mousePos)
if widget:getClassName() == 'UIMiniWindow' then
local oldParent = widget:getParent()
if oldParent == self then
return true
end
oldParent:removeChild(widget)
if widget.movedWidget then
local index = self:getChildIndex(widget.movedWidget)
self:insertChild(index + widget.movedIndex, widget)
else
self:addChild(widget)
end
return true
end
end
function UIMiniWindowContainer:getClassName()
return 'UIMiniWindowContainer'
end

View File

@@ -0,0 +1,79 @@
UIPopupMenu = extends(UIWidget)
local currentMenu
function UIPopupMenu.create()
local menu = UIPopupMenu.internalCreate()
local layout = UIVerticalLayout.create(menu)
layout:setFitChildren(true)
menu:setLayout(layout)
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 currentMenu then
currentMenu:destroy()
end
rootWidget:addChild(self)
self:setPosition(pos)
self:grabMouse()
self:grabKeyboard()
currentMenu = self
end
function UIPopupMenu:onGeometryChange()
self:bindRectToParent()
end
function UIPopupMenu:addOption(optionName, optionCallback)
local optionWidget = createWidget(self:getStyleName() .. 'Button', self)
local lastOptionWidget = self:getLastChild()
optionWidget.onClick = function(self)
optionCallback()
self:getParent():destroy()
end
optionWidget:setText(optionName)
local width = optionWidget:getTextSize().width + optionWidget:getMarginLeft() + optionWidget:getMarginRight() + 6
self:setWidth(math.max(self:getWidth(), width))
end
function UIPopupMenu:addSeparator()
createWidget(self:getStyleName() .. 'Separator', self)
end
function UIPopupMenu:onDestroy()
if currentMenu == self then
currentMenu = nil
end
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
connect(rootWidget, { onGeometryChange = onRootGeometryUpdate} )

View File

@@ -0,0 +1,35 @@
UIProgressBar = extends(UIWidget)
function UIProgressBar.create()
local progressbar = UIProgressBar.internalCreate()
progressbar:setFocusable(false)
progressbar:setPhantom(true)
progressbar.percent = 0
progressbar:updateBackground()
return progressbar
end
function UIProgressBar:setPercent(percent)
self.percent = math.max(math.min(percent, 100), 0)
self:updateBackground()
end
function UIProgressBar:getPercent()
return self.percent
end
function UIProgressBar:getPercentPixels()
return 100 / self:getWidth()
end
function UIProgressBar:updateBackground()
local width = math.round(math.max((self.percent * self:getWidth())/100, 1))
local height = self:getHeight()
self:setBackgroundSize({width=width, height=height})
end
function UIProgressBar:onGeometryChange(oldRect, newRect)
self:updateBackground()
end

View File

@@ -0,0 +1,92 @@
UIResizeBorder = extends(UIWidget)
function UIResizeBorder.create()
local resizeborder = UIResizeBorder.internalCreate()
resizeborder:setFocusable(false)
resizeborder.minimum = 0
resizeborder.maximum = 1000
return resizeborder
end
function UIResizeBorder:onHoverChange(hovered)
if hovered then
if Mouse.isCursorChanged() or Mouse.isPressed() then return end
if self:getWidth() > self:getHeight() then
Mouse.setVerticalCursor()
self.vertical = true
else
Mouse.setHorizontalCursor()
self.vertical = false
end
self.hovering = true
if not self:isPressed() then
Effects.fadeIn(self)
end
else
if not self:isPressed() and self.hovering then
Mouse.restoreCursor()
Effects.fadeOut(self)
self.hovering = false
end
end
end
function UIResizeBorder:onMouseMove(mousePos, mouseMoved)
if self:isPressed() then
if self.vertical then
local delta = mousePos.y - self:getY() - self:getHeight()/2
local parent = self:getParent()
local newsize = math.min(math.max(parent:getHeight() + delta, self.minimum), self.maximum)
if newsize ~= currentMargin then
self.newsize = newsize
if not self.event or self.event:isExecuted() then
self.event = addEvent(function()
parent:setHeight(self.newsize)
end)
end
end
else
local delta = mousePos.x - self:getX() - self:getWidth()/2
local parent = self:getParent()
local newsize = math.min(math.max(parent:getWidth() + delta, self.minimum), self.maximum)
if newsize ~= currentMargin then
self.newsize = newsize
if not self.event or self.event:isExecuted() then
self.event = addEvent(function()
parent:setWidth(self.newsize)
end)
end
end
end
return true
end
end
function UIResizeBorder:onMouseRelease(mousePos, mouseButton)
if not self:isHovered() then
Mouse.restoreCursor()
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:setMaximum(maximum)
self.maximum = maximum
end
function UIResizeBorder:setMinimum(minimum)
self.minimum = minimum
end
function UIResizeBorder:getMaximum() return self.maximum end
function UIResizeBorder:getMinimum() return self.minimum end

View File

@@ -0,0 +1,100 @@
UIScrollArea = extends(UIWidget)
-- public functions
function UIScrollArea.create()
local scrollarea = UIScrollArea.internalCreate()
scrollarea:setClipping(true)
scrollarea.inverted = false
return scrollarea
end
function UIScrollArea: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)
elseif name == 'inverted-scroll' then
self:setInverted(value)
end
end
end
function UIScrollArea:updateScrollBars()
local offset = { x = 0, y = 0 }
local scrollheight = math.max(self:getChildrenRect().height - self:getPaddingRect().height, 0)
local scrollwidth = math.max(self:getChildrenRect().width - self:getPaddingRect().width, 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)
else
scrollbar:setMaximum(scrollwidth)
end
end
end
function UIScrollArea:setVerticalScrollBar(scrollbar)
self.verticalScrollBar = scrollbar
self.verticalScrollBar.onValueChange = function(scrollbar, value)
local virtualOffset = self:getVirtualOffset()
virtualOffset.y = value
self:setVirtualOffset(virtualOffset)
end
self:updateScrollBars()
end
function UIScrollArea:setHorizontalScrollBar(scrollbar)
self.horizontalScrollBar = scrollbar
self:updateScrollBars()
end
function UIScrollArea:setInverted(inverted)
self.inverted = inverted
end
function UIScrollArea:onLayoutUpdate()
self:updateScrollBars()
end
function UIScrollArea:onMouseWheel(mousePos, mouseWheel)
if self.verticalScrollBar then
if mouseWheel == MouseWheelUp then
self.verticalScrollBar:decrement()
else
self.verticalScrollBar:increment()
end
end
return true
end
function UIScrollArea:onChildFocusChange(focusedChild, oldFocused, reason)
if focusedChild and (reason == MouseFocusReason or reason == KeyboardFocusReason) then
local paddingRect = self:getPaddingRect()
local delta = paddingRect.y - focusedChild:getY()
if delta > 0 then
self.verticalScrollBar:decrement(delta)
end
delta = (focusedChild:getY() + focusedChild:getHeight()) - (paddingRect.y + paddingRect.height)
if delta > 0 then
self.verticalScrollBar:increment(delta)
end
end
end

View File

@@ -0,0 +1,207 @@
UIScrollBar = extends(UIWidget)
-- 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() + 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, 10)
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 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
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, pos)
local point
if self.orientation == 'vertical' then
point = pos.y
else
point = pos.x
end
local range, pxrange, px, offset, center = calcValues(self)
offset = math.min(math.max(point - center, -pxrange/2), pxrange/2)
local newvalue = math.floor(((offset / (pxrange - px)) + 0.5) * (range - 1)) + self.minimum
self:setValue(newvalue)
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
return scrollbar
end
function UIScrollBar:onSetup()
self.setupDone = true
signalcall(self.onValueChange, self, self.value)
Mouse.bindAutoPress(self:getChildById('decrementButton'), function() self:decrement() end)
Mouse.bindAutoPress(self:getChildById('incrementButton'), function() self:increment() end)
Mouse.bindPressMove(self:getChildById('sliderButton'), function(mousePos, mouseMoved) parseSliderPos(self, mousePos) 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
end
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.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.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, value, delta)
end
end
function UIScrollBar:setStep(step)
self.step = step
end
function UIScrollBar:setOrientation(orientation)
self.orientation = orientation
end
function UIScrollBar:onGeometryChange()
updateSlider(self)
end
function UIScrollBar:onMouseWheel(mousePos, mouseWheel)
if mouseWheel == MouseWheelUp then
if self.orientation == 'vertical' then
self:decrement()
else
self:increment()
end
else
if self.orientation == 'vertical' then
self:increment()
else
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 self.value end
function UIScrollBar:getStep() return self.step end
function UIScrollBar:getOrientation() return self.orientation end

View File

@@ -0,0 +1,75 @@
UISpinBox = extends(UITextEdit)
function UISpinBox.create()
local spinbox = UISpinBox.internalCreate()
spinbox:setValidCharacters('0123456789')
spinbox.minimum = 0
spinbox.maximum = 0
spinbox.value = 0
spinbox:setText("0")
return spinbox
end
function UISpinBox:onMouseWheel(mousePos, direction)
if direction == MouseWheelUp then
self:setValue(self.value + 1)
elseif direction == MouseWheelDown then
self:setValue(self.value - 1)
end
return true
end
function UISpinBox:onTextChange(text, oldText)
if text:len() == 0 then
self:setValue(self.minimum)
return
end
local number = tonumber(text)
if not number or number > self.maximum or number < self.minimum then
self:setText(oldText)
return
end
self:setValue(number)
end
function UISpinBox:onValueChange(value)
-- nothing todo
end
function UISpinBox:onStyleApply(styleName, styleNode)
for name, value in pairs(styleNode) do
if name == 'maximum' then
self:setMaximum(value)
elseif name == 'minimum' then
self:setMinimum(value)
end
end
end
function UISpinBox:setValue(value)
value = math.max(math.min(self.maximum, value), self.minimum)
if value == self.value then return end
if self:getText():len() > 0 then
self:setText(value)
end
self.value = value
signalcall(self.onValueChange, self, value)
end
function UISpinBox:setMinimum(minimum)
self.minimum = minimum
if self.value < minimum then
self:setValue(minimum)
end
end
function UISpinBox:setMaximum(maximum)
self.maximum = maximum
if self.value > maximum then
self:setValue(maximum)
end
end
function UISpinBox:getValue() return self.value end

View File

@@ -0,0 +1,81 @@
UISplitter = extends(UIWidget)
function UISplitter.create()
local splitter = UISplitter.internalCreate()
splitter:setFocusable(false)
splitter.relativeMargin = 'bottom'
return splitter
end
function UISplitter:onHoverChange(hovered)
if hovered then
if Mouse.isCursorChanged() or Mouse.isPressed() then return end
if self:getWidth() > self:getHeight() then
Mouse.setVerticalCursor()
self.vertical = true
else
Mouse.setHorizontalCursor()
self.vertical = false
end
self.hovering = true
if not self:isPressed() then
Effects.fadeIn(self)
end
else
if not self:isPressed() and self.hovering then
Mouse.restoreCursor()
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
Mouse.restoreCursor()
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

View File

@@ -0,0 +1,131 @@
UITabBar = extends(UIWidget)
-- private functions
local function onTabClick(tab)
tab.tabBar:selectTab(tab)
end
local function tabBlink(tab)
if not tab.blinking then return end
tab:setOn(not tab:isOn())
tab.blinkEvent = scheduleEvent(function() tabBlink(tab) end, 500)
end
-- public functions
function UITabBar.create()
local tabbar = UITabBar.internalCreate()
tabbar:setFocusable(false)
tabbar.tabs = {}
return tabbar
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)
if panel == nil then
panel = createWidget(self:getStyleName() .. 'Panel')
panel:setId('tabPanel')
end
local tab = createWidget(self:getStyleName() .. 'Button', self)
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.onDestroy = function() tab.tabPanel:destroy() end
table.insert(self.tabs, tab)
if #self.tabs == 1 then
self:selectTab(tab)
end
return tab
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)
if tab.blinkEvent then
removeEvent(tab.blinkEvent)
end
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)
tab.blinking = false
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:blinkTab(tab)
if tab:isChecked() or tab.blinking then return end
tab.blinking = true
tabBlink(tab)
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

View File

@@ -0,0 +1,19 @@
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

View File

@@ -0,0 +1,38 @@
UIWindow = extends(UIWidget)
function UIWindow.create()
local window = UIWindow.internalCreate()
window:setTextAlign(AlignTopCenter)
window:setDragable(true)
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)
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)
local pos = { x = mousePos.x - self.movingReference.x, y = mousePos.y - self.movingReference.y }
self:setPosition(pos)
self:bindRectToParent()
end