#include "ui_defs.lua" Ui.weakRef = function(data) local weak = setmetatable({content=data}, {__mode="v"}) return function() return weak.content end end Ui._ObjectHelpers = { addInnerChild = function(o, v, key) local helpers = Ui._ObjectHelpers o.children = o.children or {} table.insert(o.children, v) v.parent = o v._childIdx = #o.children local keyType = type(key) local nameToAdd = keyType == 'string' and key or v.id if nameToAdd and type(nameToAdd) == "string" and nameToAdd ~= "" then o[nameToAdd] = v end helpers.setRootRecursive(v, o) return v end, wrapDraw = function(obj) obj.originalDraw = obj.draw obj.draw = function(self) if self.onInputChanged then local curDevice = LastInputDevice() if curDevice ~= self.lastInputDevice then self.lastInputDevice = curDevice self:onInputChanged(curDevice) end end if not self.visible then return end if self.resizeToParent and self.parent then local hPad = (self.parent.padding and self.parent.padding.left + self.parent.padding.right or 0) local vPad = (self.parent.padding and self.parent.padding.top + self.parent.padding.bottom or 0) self.w = self.parent.w - hPad - (self.margin.left + self.margin.right) self.h = self.parent.h - vPad - (self.margin.top + self.margin.bottom) end UiPush() if self.ignoreNavigation then UiIgnoreNavigation() end self:onPreDraw() UiPush() UiColorFilter(unpack(self.colorFilter)) self.w = self.w or 0 self.h = self.h or 0 if UiIsMouseInRect(self.w, self.h) and UiReceivesInput() then if not self.drawState.hovered then self:onMouseEnter() end self.drawState.hovered = true self:onHovered() if InputPressed("lmb") and not self.onPressedOverride then local x, y = UiGetCanvasMousePos() if UiIsInClipRegion(x, y) then self.drawState.pressed = true; self:onPressed() end elseif InputReleased("lmb") and self.drawState.pressed then self.drawState.pressed = false; self:onReleased() end elseif self.drawState.hovered then self.dbgmsg("MouseLeave for " .. tostring(self.id)) self.drawState.hovered = false self:onMouseLeave() end if self.debug then --[[FIXME:Doesn't work very well because it doesn't take alignment into account, so a more intrusive way to debug within the components themselves or find an even better solution is needed. ]] self:debugdraw(self.w,self.h) end self:originalDraw() UiPop() self:onPostDraw() UiPop() end end, tableFieldCopy = function(from, to, key) if to[key] ~= nil then for kk, vv in pairs(from[key]) do if type(vv) ~= 'table' then to[key][kk] = vv else Ui._ObjectHelpers.tableFieldCopy(from[key], to[key], kk) end end else to[key] = from[key] end from[key] = nil end, isConfigurationField = function(fieldName, fieldValue) return type(fieldValue) == 'table' and not fieldValue._uiObjectFlag and fieldName ~= 'children' end, setRootRecursive = function(obj, r) rawset(obj, "root", r) if obj.children then for _, child in ipairs(obj.children) do Ui._ObjectHelpers.setRootRecursive(child, r) end end end, addToCreationWaitList = function(obj) local helpers = Ui._ObjectHelpers if not helpers.waitCreationList then helpers.waitCreationList = {} end table.insert(helpers.waitCreationList, obj) end, } Ui.Object = { debug = false, --base UI object should't be serialized nonserializable = true, new = function(self, innerObject) local helpers = Ui._ObjectHelpers local o = innerObject or {} o.drawState = o.drawState or { hovered = false; pressed = false; } local base if self == Ui.Object then base = { id = "UnnamedObject", x = 0, y = 0, w = 0, h = 0, visible = true, ignoreNavigation = false, margin = { top = 0, left = 0, right = 0, bottom = 0 }, colorFilter = { 1, 1, 1, 1}, resizeToParent = false, lastInputDevice = 0, signals = {}, children = {}, _uiObjectFlag = true, _isDrawWrapped = false, } setmetatable(base, { __index = Ui.Object }) else base = self end -- Updates the base object using data from the innerObject. -- All data from innerObject is copied to the base; -- innerObject serves merely as a configuration. for k,v in pairs(o) do if type(v) == 'table' and v._uiObjectFlag then helpers.addInnerChild(base, v, k) elseif helpers.isConfigurationField(k, v) and type(base[k]) == 'table' then helpers.tableFieldCopy(o, base, k) else base[k] = v end end if not base._isDrawWrapped and base.draw then helpers.wrapDraw(base) base._isDrawWrapped = true end if base.onCreate then if helpers.isUiLoaded then base:onCreate() else helpers.addToCreationWaitList(base) end end if innerObject.onLanguageChanged then table.insert(Ui._UiObjectLangChangedListeners, Ui.weakRef(base)) end return base end, append = function(self, child) return Ui._ObjectHelpers.addInnerChild(self, child) end, clear = function(self) self.children = {} end, inject = function(self, fields) local fieldsType = type(fields) if fieldsType == "string" then self[fields] = self:findById(fields) elseif fieldsType == "table" then for k, v in ipairs(fields) do self:inject(v) end end end, show = function(self, needsShow) self.visible = needsShow or true end, hide = function(self) self.visible = false end, getSize = function(self) return self.w, self.h end, getScaledSize = function(self) local sx, sy = UiGetScale() local w, h = self:getSize() local sw = (sx ~= 0) and w / sx or 0 local sh = (sy ~= 0) and h / sy or 0 return sw, sh end, size = function(self) local sw, sh = self:getScaledSize() return { w = sw, h = sh } end, dbgmsg = function(self, msg) --DebugPrint(msg) end, debugdraw = function(o, w, h) UiPush() local r,g,b = randomColor() o.debugColor = o.debugColor or Ui.Colorf(r,g,b,0.2) o.debugColor:set() UiRect(w,h) UiPop() end, findById = function(self, id) if self.id == id then return self end if self.children then for _, child in ipairs(self.children) do local found = child:findById(id) if found then return found end end end return nil end, apply = function(self, data) for k,v in pairs(data) do Ui._ObjectHelpers.tableFieldCopy(data, self, k) end end, connect = function(self, signal, target, slot) if not self.signals[signal] then self.signals[signal] = {} end self.signals[signal][slot] = {target, slot} end, emit = function(self, signal, ...) if self.signals[signal] then for _, slot in pairs(self.signals[signal]) do local o, m = unpack(slot) m(o,...) end end end, ----------------------------- --- -- @brief Synchronizes specific properties of a child component with its -- parent, implementing a simple data-binding mechanism. In its -- current implementation, only dimensions (width and height) are -- synchronized. -- @param self Ui.Object: The child component whose properties are to be -- synchronized. -- @note For future flexibility, consider passing an additional parameter -- listing the properties to be synchronized. -- @usage Used in Ui.TextInput, where `fromParent` is invoked in the -- `onPreDraw` method to synchronize dimensions before rendering. fromParent = function(self) if self.parent then self.w = self.parent.w self.h = self.parent.h end end, -- EVENTS onPressed = function(self) self.dbgmsg("Pressed on object with id = "..tostring(self.id)) end, onReleased = function(self) self.dbgmsg("Released on object with id = "..tostring(self.id)) end, onHovered = function(self) self.dbgmsg("Hovered object with id = "..tostring(self.id)) end, onMouseEnter = function(self) self.dbgmsg("Hovered object with id = "..tostring(self.id)) end, onMouseLeave = function(self) self.dbgmsg("Hovered object with id = "..tostring(self.id)) end, onPreDraw = function(self) end, onPostDraw = function(self) end } RegisterListenerTo("LanguageChanged", "__UiObjectLanguageChanged") Ui._UiObjectLangChangedListeners = {} function __UiObjectLanguageChanged(newLanguage) for _, v in ipairs(Ui._UiObjectLangChangedListeners) do if v() ~= nil then v():onLanguageChanged(newLanguage) end end end RegisterListenerTo("UiOnLoaded", "__UiOnLoaded") function __UiOnLoaded() local helpers = Ui._ObjectHelpers helpers.isUiLoaded = true if not helpers.waitCreationList then return end for i=1,#helpers.waitCreationList do helpers.waitCreationList[i]:onCreate() end helpers.waitCreationList = {} end