diff --git a/src/elements/Accordion.lua b/src/elements/Accordion.lua index a3e8f14..ac72fb5 100644 --- a/src/elements/Accordion.lua +++ b/src/elements/Accordion.lua @@ -174,14 +174,14 @@ end --- @param expanded boolean Whether the panel starts expanded (default: false) --- @return table panelContainer The container for this panel function Accordion:newPanel(title, expanded) - local panels = self.get("panels") or {} + local panels = self.getResolved("panels") or {} local panelId = #panels + 1 local panelContainer = self:addContainer() panelContainer.set("x", 1) panelContainer.set("y", 1) - panelContainer.set("width", self.get("width")) - panelContainer.set("height", self.get("height")) + panelContainer.set("width", self.getResolved("width")) + panelContainer.set("height", self.getResolved("height")) panelContainer.set("visible", expanded or false) panelContainer.set("ignoreOffset", true) @@ -202,11 +202,11 @@ Accordion.addPanel = Accordion.newPanel --- @shortDescription Updates the layout of all panels (positions and visibility) --- @private function Accordion:updatePanelLayout() - local panels = self.get("panels") or {} - local headerHeight = self.get("panelHeaderHeight") or 1 + local panels = self.getResolved("panels") or {} + local headerHeight = self.getResolved("panelHeaderHeight") or 1 local currentY = 1 - local width = self.get("width") - local accordionHeight = self.get("height") + local width = self.getResolved("width") + local accordionHeight = self.getResolved("height") for _, panel in ipairs(panels) do local contentY = currentY + headerHeight @@ -238,7 +238,7 @@ function Accordion:updatePanelLayout() local totalHeight = currentY - 1 local maxOffset = math.max(0, totalHeight - accordionHeight) - local currentOffset = self.get("offsetY") + local currentOffset = self.getResolved("offsetY") if currentOffset > maxOffset then self.set("offsetY", maxOffset) @@ -251,8 +251,8 @@ end --- @param panelId number The ID of the panel to toggle --- @return Accordion self For method chaining function Accordion:togglePanel(panelId) - local panels = self.get("panels") or {} - local allowMultiple = self.get("allowMultiple") + local panels = self.getResolved("panels") or {} + local allowMultiple = self.getResolved("allowMultiple") for i, panel in ipairs(panels) do if panel.id == panelId then @@ -279,8 +279,8 @@ end --- @param panelId number The ID of the panel to expand --- @return Accordion self For method chaining function Accordion:expandPanel(panelId) - local panels = self.get("panels") or {} - local allowMultiple = self.get("allowMultiple") + local panels = self.getResolved("panels") or {} + local allowMultiple = self.getResolved("allowMultiple") for i, panel in ipairs(panels) do if panel.id == panelId then @@ -309,7 +309,7 @@ end --- @param panelId number The ID of the panel to collapse --- @return Accordion self For method chaining function Accordion:collapsePanel(panelId) - local panels = self.get("panels") or {} + local panels = self.getResolved("panels") or {} for _, panel in ipairs(panels) do if panel.id == panelId then @@ -329,7 +329,7 @@ end --- @param panelId number The ID of the panel --- @return table? container The panel's container or nil function Accordion:getPanel(panelId) - local panels = self.get("panels") or {} + local panels = self.getResolved("panels") or {} for _, panel in ipairs(panels) do if panel.id == panelId then return panel.container @@ -342,8 +342,8 @@ end --- @return table metrics Panel layout information --- @private function Accordion:_getPanelMetrics() - local panels = self.get("panels") or {} - local headerHeight = self.get("panelHeaderHeight") or 1 + local panels = self.getResolved("panels") or {} + local headerHeight = self.getResolved("panelHeaderHeight") or 1 local positions = {} local currentY = 1 @@ -381,7 +381,7 @@ function Accordion:mouse_click(button, x, y) end local relX, relY = VisualElement.getRelativePosition(self, x, y) - local offsetY = self.get("offsetY") + local offsetY = self.getResolved("offsetY") local adjustedY = relY + offsetY local metrics = self:_getPanelMetrics() @@ -400,12 +400,12 @@ end function Accordion:mouse_scroll(direction, x, y) if VisualElement.mouse_scroll(self, direction, x, y) then local metrics = self:_getPanelMetrics() - local accordionHeight = self.get("height") + local accordionHeight = self.getResolved("height") local totalHeight = metrics.totalHeight local maxOffset = math.max(0, totalHeight - accordionHeight) if maxOffset > 0 then - local currentOffset = self.get("offsetY") + local currentOffset = self.getResolved("offsetY") local newOffset = currentOffset + direction newOffset = math.max(0, math.min(maxOffset, newOffset)) self.set("offsetY", newOffset) @@ -422,17 +422,17 @@ end function Accordion:render() VisualElement.render(self) - local width = self.get("width") - local offsetY = self.get("offsetY") + local width = self.getResolved("width") + local offsetY = self.getResolved("offsetY") local metrics = self:_getPanelMetrics() for _, panelInfo in ipairs(metrics.positions) do - local bgColor = panelInfo.expanded and self.get("expandedHeaderBackground") or self.get("headerBackground") - local fgColor = panelInfo.expanded and self.get("expandedHeaderTextColor") or self.get("headerTextColor") + local bgColor = panelInfo.expanded and self.getResolved("expandedHeaderBackground") or self.getResolved("headerBackground") + local fgColor = panelInfo.expanded and self.getResolved("expandedHeaderTextColor") or self.getResolved("headerTextColor") local headerY = panelInfo.headerY - offsetY - if headerY >= 1 and headerY <= self.get("height") then + if headerY >= 1 and headerY <= self.getResolved("height") then VisualElement.multiBlit( self, 1, @@ -450,16 +450,16 @@ function Accordion:render() end end - if not self.get("childrenSorted") then + if not self.getResolved("childrenSorted") then self:sortChildren() end - if not self.get("childrenEventsSorted") then + if not self.getResolved("childrenEventsSorted") then for eventName in pairs(self._values.childrenEvents or {}) do self:sortChildrenEvents(eventName) end end - for _, child in ipairs(self.get("visibleChildren") or {}) do + for _, child in ipairs(self.getResolved("visibleChildren") or {}) do if child == self then error("CIRCULAR REFERENCE DETECTED!") return diff --git a/src/elements/BarChart.lua b/src/elements/BarChart.lua index b4f31d8..28d3872 100644 --- a/src/elements/BarChart.lua +++ b/src/elements/BarChart.lua @@ -54,11 +54,11 @@ end function BarChart:render() VisualElement.render(self) - local width = self.get("width") - local height = self.get("height") - local minVal = self.get("minValue") - local maxVal = self.get("maxValue") - local series = self.get("series") + local width = self.getResolved("width") + local height = self.getResolved("height") + local minVal = self.getResolved("minValue") + local maxVal = self.getResolved("maxValue") + local series = self.getResolved("series") local activeSeriesCount = 0 local seriesList = {} diff --git a/src/elements/BaseElement.lua b/src/elements/BaseElement.lua index cd7738b..a7e4cac 100644 --- a/src/elements/BaseElement.lua +++ b/src/elements/BaseElement.lua @@ -225,7 +225,7 @@ end --- @param priority? number Optional priority override --- @return BaseElement self function BaseElement:setState(stateName, priority) - local states = self.get("states") + local states = self.getResolved("states") if not priority and self._registeredStates[stateName] then priority = self._registeredStates[stateName].priority @@ -309,6 +309,145 @@ function BaseElement:updateConditionalStates() return self end +--- Registers a responsive state that reacts to parent size changes +--- @shortDescription Registers a state that responds to parent dimensions +--- @param stateName string The name of the state +--- @param condition string|function Condition as string expression or function: function(element) return boolean end +--- @param options? table|number Options table with 'priority' and 'observe', or just priority number +--- @return BaseElement self +function BaseElement:registerResponsiveState(stateName, condition, options) + local priority = 100 + local observeList = {} + if type(options) == "number" then + priority = options + elseif type(options) == "table" then + priority = options.priority or 100 + observeList = options.observe or {} + end + + local conditionFunc + local isStringExpr = type(condition) == "string" + + if isStringExpr then + conditionFunc = self:_parseResponsiveExpression(condition) + + local autoDeps = self:_detectDependencies(condition) + for _, dep in ipairs(autoDeps) do + table.insert(observeList, dep) + end + else + conditionFunc = condition + end + self:registerState(stateName, conditionFunc, priority) + + for _, observeInfo in ipairs(observeList) do + local element = observeInfo.element or observeInfo[1] + local property = observeInfo.property or observeInfo[2] + if element and property then + element:observe(property, function() + self:updateConditionalStates() + end) + end + end + self:updateConditionalStates() + + return self +end + +--- Parses a responsive expression string into a function +--- @private +--- @param expr string The expression to parse +--- @return function conditionFunc The parsed condition function +function BaseElement:_parseResponsiveExpression(expr) + local protectedNames = { + colors = true, + math = true, + clamp = true, + round = true + } + + local mathEnv = { + clamp = function(val, min, max) + return math.min(math.max(val, min), max) + end, + round = function(val) + return math.floor(val + 0.5) + end, + floor = math.floor, + ceil = math.ceil, + abs = math.abs + } + + expr = expr:gsub("([%w_]+)%.([%w_]+)", function(obj, prop) + if protectedNames[obj] or tonumber(obj) then + return obj.."."..prop + end + return string.format('__getProperty("%s", "%s")', obj, prop) + end) + + local element = self + local env = setmetatable({ + colors = colors, + math = math, + tostring = tostring, + tonumber = tonumber, + __getProperty = function(objName, propName) + if objName == "self" then + if element._properties[propName] then + return element.get(propName) + end + elseif objName == "parent" then + if element.parent and element.parent._properties[propName] then + return element.parent.get(propName) + end + else + local target = element:getBaseFrame():getChild(objName) + if target and target._properties[propName] then + return target.get(propName) + end + end + return nil + end + }, { __index = mathEnv }) + + local func, err = load("return "..expr, "responsive", "t", env) + if not func then + error("Invalid responsive expression: " .. err) + end + + return function(self) + local ok, result = pcall(func) + return ok and result or false + end +end + +--- Detects dependencies in a responsive expression +--- @private +--- @param expr string The expression to analyze +--- @return table dependencies List of {element, property} pairs +function BaseElement:_detectDependencies(expr) + local deps = {} + local protectedNames = {colors = true, math = true, clamp = true, round = true} + + for ref, prop in expr:gmatch("([%w_]+)%.([%w_]+)") do + if not protectedNames[ref] and not tonumber(ref) then + local element + if ref == "self" then + element = self + elseif ref == "parent" then + element = self.parent + else + element = self:getBaseFrame():getChild(ref) + end + + if element then + table.insert(deps, {element = element, property = prop}) + end + end + end + return deps +end + --- Removes a state from the registry --- @shortDescription Removes state definition --- @param stateName string The state to remove @@ -325,9 +464,9 @@ end --- @param ... any Additional arguments to pass to the callbacks --- @return table self The BaseElement instance function BaseElement:fireEvent(event, ...) - if self.get("eventCallbacks")[event] then + if self.getResolved("eventCallbacks")[event] then local lastResult - for _, callback in ipairs(self.get("eventCallbacks")[event]) do + for _, callback in ipairs(self.getResolved("eventCallbacks")[event]) do lastResult = callback(self, ...) end return lastResult @@ -341,7 +480,7 @@ end --- @return boolean? handled Whether the event was handled --- @protected function BaseElement:dispatchEvent(event, ...) - if self.get("enabled") == false then + if self.getResolved("enabled") == false then return false end if self[event] then diff --git a/src/elements/BigFont.lua b/src/elements/BigFont.lua index d57c9f2..1610cbd 100644 --- a/src/elements/BigFont.lua +++ b/src/elements/BigFont.lua @@ -171,12 +171,12 @@ BigFont.__index = BigFont ---@property text string BigFont The text string to display in enlarged format BigFont.defineProperty(BigFont, "text", {default = "BigFont", type = "string", canTriggerRender = true, setter=function(self, value) - self.bigfontText = makeText(self.get("fontSize"), value, self.get("foreground"), self.get("background")) + self.bigfontText = makeText(self.getResolved("fontSize"), value, self.getResolved("foreground"), self.getResolved("background")) return value end}) ---@property fontSize number 1 Scale factor for text size (1-3, where 1 is 3x3 pixels per character) BigFont.defineProperty(BigFont, "fontSize", {default = 1, type = "number", canTriggerRender = true, setter=function(self, value) - self.bigfontText = makeText(value, self.get("text"), self.get("foreground"), self.get("background")) + self.bigfontText = makeText(value, self.getResolved("text"), self.getResolved("foreground"), self.getResolved("background")) return value end}) @@ -200,10 +200,10 @@ function BigFont:init(props, basalt) VisualElement.init(self, props, basalt) self.set("type", "BigFont") self:observe("background", function(self, value) - self.bigfontText = makeText(self.get("fontSize"), self.get("text"), self.get("foreground"), value) + self.bigfontText = makeText(self.getResolved("fontSize"), self.getResolved("text"), self.getResolved("foreground"), value) end) self:observe("foreground", function(self, value) - self.bigfontText = makeText(self.get("fontSize"), self.get("text"), value, self.get("background")) + self.bigfontText = makeText(self.getResolved("fontSize"), self.getResolved("text"), value, self.getResolved("background")) end) end @@ -212,11 +212,12 @@ end function BigFont:render() VisualElement.render(self) if(self.bigfontText)then - local x, y = self.get("x"), self.get("y") + local x, y = self.getResolved("x"), self.getResolved("y") + local width = self.getResolved("width") for i = 1, #self.bigfontText[1] do - local text = self.bigfontText[1][i]:sub(1, self.get("width")) - local fg = self.bigfontText[2][i]:sub(1, self.get("width")) - local bg = self.bigfontText[3][i]:sub(1, self.get("width")) + local text = self.bigfontText[1][i]:sub(1, width) + local fg = self.bigfontText[2][i]:sub(1, width) + local bg = self.bigfontText[3][i]:sub(1, width) self:blit(x, y + i - 1, text, fg, bg) end end diff --git a/src/elements/Breadcrumb.lua b/src/elements/Breadcrumb.lua index 6dca8ea..d4cc614 100644 --- a/src/elements/Breadcrumb.lua +++ b/src/elements/Breadcrumb.lua @@ -44,10 +44,10 @@ end --- @param y number --- @return boolean handled function Breadcrumb:mouse_click(button, x, y) - if not self.get("clickable") then return false end + if not self.getResolved("clickable") then return false end if VisualElement.mouse_click(self, button, x, y) then - local path = self.get("path") - local separator = self.get("separator") + local path = self.getResolved("path") + local separator = self.getResolved("separator") local cursorX = 1 for i, segment in ipairs(path) do @@ -81,11 +81,11 @@ end --- @shortDescription Renders the breadcrumb trail --- @protected function Breadcrumb:render() - local path = self.get("path") - local separator = self.get("separator") - local fg = self.get("foreground") - local clickable = self.get("clickable") - local width = self.get("width") + local path = self.getResolved("path") + local separator = self.getResolved("separator") + local fg = self.getResolved("foreground") + local clickable = self.getResolved("clickable") + local width = self.getResolved("width") local fullText = "" for i, segment in ipairs(path) do @@ -95,8 +95,8 @@ function Breadcrumb:render() end end - if self.get("autoSize") then - self.set("width", #fullText) + if self.getResolved("autoSize") then + self.getResolved("width", #fullText) else if #fullText > width then local ellipsis = "... > " diff --git a/src/elements/Button.lua b/src/elements/Button.lua index b4aefaf..671525f 100644 --- a/src/elements/Button.lua +++ b/src/elements/Button.lua @@ -62,8 +62,8 @@ end function Button:render() VisualElement.render(self) local text = self.getResolved("text") - text = text:sub(1, self.get("width")) - local xO, yO = getCenteredPosition(text, self.get("width"), self.get("height")) + text = text:sub(1, self.getResolved("width")) + local xO, yO = getCenteredPosition(text, self.getResolved("width"), self.getResolved("height")) self:textFg(xO, yO, text, self.getResolved("foreground")) end diff --git a/src/elements/CheckBox.lua b/src/elements/CheckBox.lua index b783e4f..d1458cd 100644 --- a/src/elements/CheckBox.lua +++ b/src/elements/CheckBox.lua @@ -24,18 +24,18 @@ CheckBox.__index = CheckBox CheckBox.defineProperty(CheckBox, "checked", {default = false, type = "boolean", canTriggerRender = true}) ---@property text string empty Text shown when the checkbox is unchecked CheckBox.defineProperty(CheckBox, "text", {default = " ", type = "string", canTriggerRender = true, setter=function(self, value) - local checkedText = self.get("checkedText") + local checkedText = self.getResolved("checkedText") local width = math.max(#value, #checkedText) - if(self.get("autoSize"))then + if(self.getResolved("autoSize"))then self.set("width", width) end return value end}) ---@property checkedText string x Text shown when the checkbox is checked CheckBox.defineProperty(CheckBox, "checkedText", {default = "x", type = "string", canTriggerRender = true, setter=function(self, value) - local text = self.get("text") + local text = self.getResolved("text") local width = math.max(#value, #text) - if(self.get("autoSize"))then + if(self.getResolved("autoSize"))then self.set("width", width) end return value @@ -74,7 +74,7 @@ end --- @protected function CheckBox:mouse_click(button, x, y) if VisualElement.mouse_click(self, button, x, y) then - self.set("checked", not self.get("checked")) + self.set("checked", not self.getResolved("checked")) return true end return false @@ -88,7 +88,7 @@ function CheckBox:render() local checked = self.getResolved("checked") local defaultText = self.getResolved("text") local checkedText = self.getResolved("checkedText") - local text = string.sub(checked and checkedText or defaultText, 1, self.get("width")) + local text = string.sub(checked and checkedText or defaultText, 1, self.getResolved("width")) self:textFg(1, 1, text, self.getResolved("foreground")) end diff --git a/src/elements/Collection.lua b/src/elements/Collection.lua index 814e3b8..6ff793b 100644 --- a/src/elements/Collection.lua +++ b/src/elements/Collection.lua @@ -56,7 +56,7 @@ function Collection:addItem(itemData) end local entry = CollectionEntry.new(self, itemData, self._entrySchema) - table.insert(self.get("items"), entry) + table.insert(self.getResolved("items"), entry) self:updateRender() return entry end @@ -67,7 +67,7 @@ end --- @return Collection self The Collection instance --- @usage Collection:removeItem(1) function Collection:removeItem(index) - local items = self.get("items") + local items = self.getResolved("items") if type(index) == "number" then table.remove(items, index) else @@ -98,7 +98,7 @@ end --- @usage local selected = Collection:getSelectedItems() function Collection:getSelectedItems() local selected = {} - for i, item in ipairs(self.get("items")) do + for i, item in ipairs(self.getResolved("items")) do if type(item) == "table" and item.selected then local selectedItem = item selectedItem.index = i @@ -112,7 +112,7 @@ end --- @shortDescription Gets first selected item --- @return table? selected The first item function Collection:getSelectedItem() - local items = self.get("items") + local items = self.getResolved("items") for i, item in ipairs(items) do if type(item) == "table" and item.selected then return item @@ -122,7 +122,7 @@ function Collection:getSelectedItem() end function Collection:selectItem(index) - local items = self.get("items") + local items = self.getResolved("items") if type(index) == "number" then if items[index] and type(items[index]) == "table" then items[index].selected = true @@ -142,7 +142,7 @@ function Collection:selectItem(index) end function Collection:unselectItem(index) - local items = self.get("items") + local items = self.getResolved("items") if type(index) == "number" then if items[index] and type(items[index]) == "table" then items[index].selected = false @@ -162,7 +162,7 @@ function Collection:unselectItem(index) end function Collection:clearItemSelection() - local items = self.get("items") + local items = self.getResolved("items") for i, item in ipairs(items) do item.selected = false end @@ -175,7 +175,7 @@ end --- @return number? index The index of the first selected item, or nil if none selected --- @usage local index = Collection:getSelectedIndex() function Collection:getSelectedIndex() - local items = self.get("items") + local items = self.getResolved("items") for i, item in ipairs(items) do if type(item) == "table" and item.selected then return i @@ -188,7 +188,7 @@ end --- @shortDescription Selects the next item --- @return Collection self The Collection instance function Collection:selectNext() - local items = self.get("items") + local items = self.getResolved("items") local currentIndex = self:getSelectedIndex() if not currentIndex then @@ -196,7 +196,7 @@ function Collection:selectNext() self:selectItem(1) end elseif currentIndex < #items then - if not self.get("multiSelection") then + if not self.getResolved("multiSelection") then self:clearItemSelection() end self:selectItem(currentIndex + 1) @@ -210,7 +210,7 @@ end --- @shortDescription Selects the previous item --- @return Collection self The Collection instance function Collection:selectPrevious() - local items = self.get("items") + local items = self.getResolved("items") local currentIndex = self:getSelectedIndex() if not currentIndex then @@ -218,7 +218,7 @@ function Collection:selectPrevious() self:selectItem(#items) end elseif currentIndex > 1 then - if not self.get("multiSelection") then + if not self.getResolved("multiSelection") then self:clearItemSelection() end self:selectItem(currentIndex - 1) diff --git a/src/elements/ComboBox.lua b/src/elements/ComboBox.lua index 5298521..fb8257d 100644 --- a/src/elements/ComboBox.lua +++ b/src/elements/ComboBox.lua @@ -78,10 +78,10 @@ end --- @shortDescription Filters items for auto-complete --- @private function ComboBox:getFilteredItems() - local allItems = self.get("items") or {} - local currentText = self.get("text"):lower() + local allItems = self.getResolved("items") or {} + local currentText = self.getResolved("text"):lower() - if not self.get("autoComplete") or #currentText == 0 then + if not self.getResolved("autoComplete") or #currentText == 0 then return allItems end @@ -106,15 +106,15 @@ end --- @shortDescription Updates dropdown with filtered items --- @private function ComboBox:updateFilteredDropdown() - if not self.get("autoComplete") then return end + if not self.getResolved("autoComplete") then return end local filteredItems = self:getFilteredItems() - local shouldOpen = #filteredItems > 0 and #self.get("text") > 0 + local shouldOpen = #filteredItems > 0 and #self.getResolved("text") > 0 if shouldOpen then self:setState("opened") self.set("manuallyOpened", false) - local dropdownHeight = self.get("dropdownHeight") or 5 + local dropdownHeight = self.getResolved("dropdownHeight") or 5 local actualHeight = math.min(dropdownHeight, #filteredItems) self.set("height", 1 + actualHeight) else @@ -128,15 +128,15 @@ end --- @shortDescription Updates the viewport --- @private function ComboBox:updateViewport() - local text = self.get("text") - local cursorPos = self.get("cursorPos") - local width = self.get("width") - local dropSymbol = self.get("dropSymbol") + local text = self.getResolved("text") + local cursorPos = self.getResolved("cursorPos") + local width = self.getResolved("width") + local dropSymbol = self.getResolved("dropSymbol") local textWidth = width - #dropSymbol if textWidth < 1 then textWidth = 1 end - local viewOffset = self.get("viewOffset") + local viewOffset = self.getResolved("viewOffset") if cursorPos - viewOffset > textWidth then viewOffset = cursorPos - textWidth @@ -151,18 +151,18 @@ end --- @shortDescription Handles character input --- @param char string The character that was typed function ComboBox:char(char) - if not self.get("editable") then return end + if not self.getResolved("editable") then return end if not self:hasState("focused") then return end - local text = self.get("text") - local cursorPos = self.get("cursorPos") + local text = self.getResolved("text") + local cursorPos = self.getResolved("cursorPos") local newText = text:sub(1, cursorPos - 1) .. char .. text:sub(cursorPos) self.set("text", newText) self.set("cursorPos", cursorPos + 1) self:updateViewport() - if self.get("autoComplete") then + if self.getResolved("autoComplete") then self:updateFilteredDropdown() else self:updateRender() @@ -174,11 +174,11 @@ end --- @param key number The key code that was pressed --- @param held boolean Whether the key is being held function ComboBox:key(key, held) - if not self.get("editable") then return end + if not self.getResolved("editable") then return end if not self:hasState("focused") then return end - local text = self.get("text") - local cursorPos = self.get("cursorPos") + local text = self.getResolved("text") + local cursorPos = self.getResolved("cursorPos") if key == keys.left then self.set("cursorPos", math.max(1, cursorPos - 1)) @@ -193,7 +193,7 @@ function ComboBox:key(key, held) self.set("cursorPos", cursorPos - 1) self:updateViewport() - if self.get("autoComplete") then + if self.getResolved("autoComplete") then self:updateFilteredDropdown() else self:updateRender() @@ -205,7 +205,7 @@ function ComboBox:key(key, held) self.set("text", newText) self:updateViewport() - if self.get("autoComplete") then + if self.getResolved("autoComplete") then self:updateFilteredDropdown() else self:updateRender() @@ -238,8 +238,8 @@ function ComboBox:mouse_click(button, x, y) if not VisualElement.mouse_click(self, button, x, y) then return false end local relX, relY = self:getRelativePosition(x, y) - local width = self.get("width") - local dropSymbol = self.get("dropSymbol") + local width = self.getResolved("width") + local dropSymbol = self.getResolved("dropSymbol") local isOpen = self:hasState("opened") if relY == 1 then @@ -250,8 +250,8 @@ function ComboBox:mouse_click(button, x, y) self.set("manuallyOpened", false) else self:setState("opened") - local allItems = self.get("items") or {} - local dropdownHeight = self.get("dropdownHeight") or 5 + local allItems = self.getResolved("items") or {} + local dropdownHeight = self.getResolved("dropdownHeight") or 5 local actualHeight = math.min(dropdownHeight, #allItems) self.set("height", 1 + actualHeight) self.set("manuallyOpened", true) @@ -260,17 +260,17 @@ function ComboBox:mouse_click(button, x, y) return true end - if relX <= width - #dropSymbol and self.get("editable") then - local text = self.get("text") - local viewOffset = self.get("viewOffset") + if relX <= width - #dropSymbol and self.getResolved("editable") then + local text = self.getResolved("text") + local viewOffset = self.getResolved("viewOffset") local maxPos = #text + 1 local targetPos = math.min(maxPos, viewOffset + relX) self.set("cursorPos", targetPos) if not isOpen then self:setState("opened") - local allItems = self.get("items") or {} - local dropdownHeight = self.get("dropdownHeight") or 5 + local allItems = self.getResolved("items") or {} + local dropdownHeight = self.getResolved("dropdownHeight") or 5 local actualHeight = math.min(dropdownHeight, #allItems) self.set("height", 1 + actualHeight) self.set("manuallyOpened", true) @@ -299,14 +299,14 @@ function ComboBox:mouse_up(button, x, y) if self:hasState("opened") then local relX, relY = self:getRelativePosition(x, y) - if relY > 1 and self.get("selectable") and not self._scrollBarDragging then - local itemIndex = (relY - 1) + self.get("offset") + if relY > 1 and self.getResolved("selectable") and not self._scrollBarDragging then + local itemIndex = (relY - 1) + self.getResolved("offset") local items - if self.get("autoComplete") and not self.get("manuallyOpened") then + if self.getResolved("autoComplete") and not self.getResolved("manuallyOpened") then items = self:getFilteredItems() else - items = self.get("items") + items = self.getResolved("items") end if itemIndex <= #items then @@ -316,8 +316,8 @@ function ComboBox:mouse_up(button, x, y) items[itemIndex] = item end - if not self.get("multiSelection") then - for _, otherItem in ipairs(self.get("items")) do + if not self.getResolved("multiSelection") then + for _, otherItem in ipairs(self.getResolved("items")) do if type(otherItem) == "table" then otherItem.selected = false end @@ -357,12 +357,12 @@ end function ComboBox:render() VisualElement.render(self) - local text = self.get("text") - local width = self.get("width") - local dropSymbol = self.get("dropSymbol") + local text = self.getResolved("text") + local width = self.getResolved("width") + local dropSymbol = self.getResolved("dropSymbol") local isFocused = self:hasState("focused") local isOpen = self:hasState("opened") - local viewOffset = self.get("viewOffset") + local viewOffset = self.getResolved("viewOffset") local selectedText = self.getResolved("selectedText") local bg = self.getResolved("background") local fg = self.getResolved("foreground") @@ -387,8 +387,8 @@ function ComboBox:render() string.rep(tHex[fg], width), string.rep(tHex[bg], width)) - if isFocused and self.get("editable") then - local cursorPos = self.get("cursorPos") + if isFocused and self.getResolved("editable") then + local cursorPos = self.getResolved("cursorPos") local cursorX = cursorPos - viewOffset if cursorX >= 1 and cursorX <= textWidth then self:setCursor(cursorX, 1, true, fg) @@ -396,14 +396,14 @@ function ComboBox:render() end if isOpen then - local actualHeight = self.get("height") - local items = self.get("items") + local actualHeight = self.getResolved("height") + local items = self.getResolved("items") - if self.get("autoComplete") and not self.get("manuallyOpened") then + if self.getResolved("autoComplete") and not self.getResolved("manuallyOpened") then items = self:getFilteredItems() end - local dropdownHeight = math.min(self.get("dropdownHeight"), #items) + local dropdownHeight = math.min(self.getResolved("dropdownHeight"), #items) local originalItems = self._values.items self._values.items = items @@ -418,8 +418,8 @@ function ComboBox:render() string.rep(tHex[fg], width), string.rep(tHex[bg], width)) - if isFocused and self.get("editable") then - local cursorPos = self.get("cursorPos") + if isFocused and self.getResolved("editable") then + local cursorPos = self.getResolved("cursorPos") local cursorX = cursorPos - viewOffset if cursorX >= 1 and cursorX <= textWidth then self:setCursor(cursorX, 1, true, fg) diff --git a/src/elements/Container.lua b/src/elements/Container.lua index 1910f3b..b4bf7c8 100644 --- a/src/elements/Container.lua +++ b/src/elements/Container.lua @@ -121,8 +121,8 @@ function Container:isChildVisible(child) if not child:isType("VisualElement") then return false end if(child.get("visible") == false)then return false end if(child._destroyed)then return false end - local containerW, containerH = self.get("width"), self.get("height") - local offsetX, offsetY = self.get("offsetX"), self.get("offsetY") + local containerW, containerH = self.getResolved("width"), self.getResolved("height") + local offsetX, offsetY = self.getResolved("offsetX"), self.getResolved("offsetY") local childX, childY = child.get("x"), child.get("y") local childW, childH = child.get("width"), child.get("height") @@ -353,7 +353,7 @@ local function convertMousePosition(self, event, ...) local args = {...} if event and event:find("mouse_") then local button, absX, absY = ... - local xOffset, yOffset = self.get("offsetX"), self.get("offsetY") + local xOffset, yOffset = self.getResolved("offsetX"), self.getResolved("offsetY") local relX, relY = self:getRelativePosition(absX + xOffset, absY + yOffset) args = {button, relX, relY} end @@ -368,13 +368,13 @@ end --- @return boolean handled Whether the event was handled --- @return table? child The child that handled the event function Container:callChildrenEvent(visibleOnly, event, ...) - if visibleOnly and not self.get("childrenEventsSorted") then + if visibleOnly and not self.getResolved("childrenEventsSorted") then for evt in pairs(self._values.childrenEvents) do self:sortChildrenEvents(evt) end end - local children = visibleOnly and self.get("visibleChildrenEvents") or self.get("childrenEvents") + local children = visibleOnly and self.getResolved("visibleChildrenEvents") or self.getResolved("childrenEvents") if children[event] then local events = children[event] for i = #events, 1, -1 do @@ -510,8 +510,8 @@ end --- @return boolean handled Whether the event was handled --- @protected function Container:key(key) - if self.get("focusedChild") then - return self.get("focusedChild"):dispatchEvent("key", key) + if self.getResolved("focusedChild") then + return self.getResolved("focusedChild"):dispatchEvent("key", key) end return true end @@ -521,8 +521,8 @@ end --- @return boolean handled Whether the event was handled --- @protected function Container:char(char) - if self.get("focusedChild") then - return self.get("focusedChild"):dispatchEvent("char", char) + if self.getResolved("focusedChild") then + return self.getResolved("focusedChild"):dispatchEvent("char", char) end return true end @@ -532,8 +532,8 @@ end --- @return boolean handled Whether the event was handled --- @protected function Container:key_up(key) - if self.get("focusedChild") then - return self.get("focusedChild"):dispatchEvent("key_up", key) + if self.getResolved("focusedChild") then + return self.getResolved("focusedChild"):dispatchEvent("key_up", key) end return true end @@ -549,7 +549,7 @@ end --- @return Container self The container instance --- @protected function Container:multiBlit(x, y, width, height, text, fg, bg) - local w, h = self.get("width"), self.get("height") + local w, h = self.getResolved("width"), self.getResolved("height") width = x < 1 and math.min(width + x - 1, w) or math.min(width, math.max(0, w - x + 1)) height = y < 1 and math.min(height + y - 1, h) or math.min(height, math.max(0, h - y + 1)) @@ -568,7 +568,7 @@ end --- @return Container self The container instance --- @protected function Container:textFg(x, y, text, fg) - local w, h = self.get("width"), self.get("height") + local w, h = self.getResolved("width"), self.getResolved("height") if y < 1 or y > h then return self end @@ -589,7 +589,7 @@ end --- @return Container self The container instance --- @protected function Container:textBg(x, y, text, bg) - local w, h = self.get("width"), self.get("height") + local w, h = self.getResolved("width"), self.getResolved("height") if y < 1 or y > h then return self end @@ -603,7 +603,7 @@ function Container:textBg(x, y, text, bg) end function Container:drawText(x, y, text) - local w, h = self.get("width"), self.get("height") + local w, h = self.getResolved("width"), self.getResolved("height") if y < 1 or y > h then return self end @@ -617,7 +617,7 @@ function Container:drawText(x, y, text) end function Container:drawFg(x, y, fg) - local w, h = self.get("width"), self.get("height") + local w, h = self.getResolved("width"), self.getResolved("height") if y < 1 or y > h then return self end @@ -630,7 +630,7 @@ function Container:drawFg(x, y, fg) end function Container:drawBg(x, y, bg) - local w, h = self.get("width"), self.get("height") + local w, h = self.getResolved("width"), self.getResolved("height") if y < 1 or y > h then return self end @@ -651,7 +651,7 @@ end --- @return Container self The container instance --- @protected function Container:blit(x, y, text, fg, bg) - local w, h = self.get("width"), self.get("height") + local w, h = self.getResolved("width"), self.getResolved("height") if y < 1 or y > h then return self end @@ -674,15 +674,15 @@ end --- @protected function Container:render() VisualElement.render(self) - if not self.get("childrenSorted")then + if not self.getResolved("childrenSorted")then self:sortChildren() end - if not self.get("childrenEventsSorted")then + if not self.getResolved("childrenEventsSorted")then for event in pairs(self._values.childrenEvents) do self:sortChildrenEvents(event) end end - for _, child in ipairs(self.get("visibleChildren")) do + for _, child in ipairs(self.getResolved("visibleChildren")) do if child == self then errorManager.error("CIRCULAR REFERENCE DETECTED!") return diff --git a/src/elements/ContextMenu.lua b/src/elements/ContextMenu.lua index 3e4d1f5..c88064f 100644 --- a/src/elements/ContextMenu.lua +++ b/src/elements/ContextMenu.lua @@ -148,8 +148,8 @@ end --- @shortDescription Calculates menu size based on items --- @private function ContextMenu:calculateSize() - local items = self.get("items") - local itemHeight = self.get("itemHeight") + local items = self.getResolved("items") + local itemHeight = self.getResolved("itemHeight") if #items == 0 then self.set("width", 10) @@ -195,7 +195,7 @@ function ContextMenu:close() self.set("isOpen", false) self.set("visible", false) - local openSubmenu = self.get("openSubmenu") + local openSubmenu = self.getResolved("openSubmenu") if openSubmenu and openSubmenu.menu then openSubmenu.menu:close() end @@ -225,8 +225,8 @@ end --- @return table? item Item data or nil --- @private function ContextMenu:getItemAt(y) - local items = self.get("items") - local itemHeight = self.get("itemHeight") + local items = self.getResolved("items") + local itemHeight = self.getResolved("itemHeight") local index = math.floor((y - 1) / itemHeight) + 1 @@ -243,20 +243,20 @@ function ContextMenu:createSubmenu(submenuItems, parentItem) local submenu = self.parent:addContextMenu() submenu:setItems(submenuItems) - submenu.set("background", self.get("background")) - submenu.set("foreground", self.get("foreground")) + submenu.set("background", self.getResolved("background")) + submenu.set("foreground", self.getResolved("foreground")) submenu.parentMenu = self - local parentX = self.get("x") - local parentY = self.get("y") - local parentWidth = self.get("width") - local itemHeight = self.get("itemHeight") + local parentX = self.getResolved("x") + local parentY = self.getResolved("y") + local parentWidth = self.getResolved("width") + local itemHeight = self.getResolved("itemHeight") local itemIndex = parentItem._index or 1 submenu.set("x", parentX + parentWidth) submenu.set("y", parentY + (itemIndex - 1) * itemHeight) - submenu.set("z", self.get("z") + 1) + submenu.set("z", self.getResolved("z") + 1) return submenu end @@ -278,7 +278,7 @@ function ContextMenu:mouse_click(button, x, y) end if item.submenu then - local openSubmenu = self.get("openSubmenu") + local openSubmenu = self.getResolved("openSubmenu") if openSubmenu and openSubmenu.index == index then openSubmenu.menu:close() self.set("openSubmenu", nil) @@ -312,12 +312,12 @@ end --- @shortDescription Renders the ContextMenu --- @protected function ContextMenu:render() - local items = self.get("items") - local width = self.get("width") - local height = self.get("height") - local itemHeight = self.get("itemHeight") - local menuBg = self.get("background") - local menuFg = self.get("foreground") + local items = self.getResolved("items") + local width = self.getResolved("width") + local height = self.getResolved("height") + local itemHeight = self.getResolved("itemHeight") + local menuBg = self.getResolved("background") + local menuFg = self.getResolved("foreground") for i, item in ipairs(items) do local y = (i - 1) * itemHeight + 1 @@ -342,16 +342,16 @@ function ContextMenu:render() end end - if not self.get("childrenSorted") then + if not self.getResolved("childrenSorted") then self:sortChildren() end - if not self.get("childrenEventsSorted") then + if not self.getResolved("childrenEventsSorted") then for eventName in pairs(self._values.childrenEvents or {}) do self:sortChildrenEvents(eventName) end end - for _, child in ipairs(self.get("visibleChildren") or {}) do + for _, child in ipairs(self.getResolved("visibleChildren") or {}) do if child == self then error("CIRCULAR REFERENCE DETECTED!") return diff --git a/src/elements/Dialog.lua b/src/elements/Dialog.lua index 264d97d..0900af3 100644 --- a/src/elements/Dialog.lua +++ b/src/elements/Dialog.lua @@ -62,7 +62,7 @@ function Dialog:show() self:center() self.set("visible", true) -- Auto-focus when modal - if self.get("modal") then + if self.getResolved("modal") then self:setFocused(true) end return self @@ -91,22 +91,22 @@ function Dialog:alert(title, message, callback) self:addLabel({ text = message, x = 2, y = 3, - width = self.get("width") - 3, + width = self.getResolved("width") - 3, height = 3, foreground = colors.white }) local btnWidth = 10 - local btnX = math.floor((self.get("width") - btnWidth) / 2) + 1 + local btnX = math.floor((self.getResolved("width") - btnWidth) / 2) + 1 self:addButton({ text = "OK", x = btnX, - y = self.get("height") - 2, + y = self.getResolved("height") - 2, width = btnWidth, height = 1, - background = self.get("primaryColor"), - foreground = self.get("buttonForeground") + background = self.getResolved("primaryColor"), + foreground = self.getResolved("buttonForeground") }):onClick(function() if callback then callback() end self:close() @@ -129,7 +129,7 @@ function Dialog:confirm(title, message, callback) self:addLabel({ text = message, x = 2, y = 3, - width = self.get("width") - 3, + width = self.getResolved("width") - 3, height = 3, foreground = colors.white }) @@ -137,16 +137,16 @@ function Dialog:confirm(title, message, callback) local btnWidth = 10 local spacing = 2 local totalWidth = btnWidth * 2 + spacing - local startX = math.floor((self.get("width") - totalWidth) / 2) + 1 + local startX = math.floor((self.getResolved("width") - totalWidth) / 2) + 1 self:addButton({ text = "Cancel", x = startX, - y = self.get("height") - 2, + y = self.getResolved("height") - 2, width = btnWidth, height = 1, - background = self.get("secondaryColor"), - foreground = self.get("buttonForeground") + background = self.getResolved("secondaryColor"), + foreground = self.getResolved("buttonForeground") }):onClick(function() if callback then callback(false) end self:close() @@ -155,11 +155,11 @@ function Dialog:confirm(title, message, callback) self:addButton({ text = "OK", x = startX + btnWidth + spacing, - y = self.get("height") - 2, + y = self.getResolved("height") - 2, width = btnWidth, height = 1, - background = self.get("primaryColor"), - foreground = self.get("buttonForeground") + background = self.getResolved("primaryColor"), + foreground = self.getResolved("buttonForeground") }):onClick(function() if callback then callback(true) end self:close() @@ -188,7 +188,7 @@ function Dialog:prompt(title, message, default, callback) local input = self:addInput({ x = 2, y = 5, - width = self.get("width") - 3, + width = self.getResolved("width") - 3, height = 1, defaultText = default or "", background = colors.white, @@ -198,16 +198,16 @@ function Dialog:prompt(title, message, default, callback) local btnWidth = 10 local spacing = 2 local totalWidth = btnWidth * 2 + spacing - local startX = math.floor((self.get("width") - totalWidth) / 2) + 1 + local startX = math.floor((self.getResolved("width") - totalWidth) / 2) + 1 self:addButton({ text = "Cancel", x = startX, - y = self.get("height") - 2, + y = self.getResolved("height") - 2, width = btnWidth, height = 1, - background = self.get("secondaryColor"), - foreground = self.get("buttonForeground") + background = self.getResolved("secondaryColor"), + foreground = self.getResolved("buttonForeground") }):onClick(function() if callback then callback(nil) end self:close() @@ -216,11 +216,11 @@ function Dialog:prompt(title, message, default, callback) self:addButton({ text = "OK", x = startX + btnWidth + spacing, - y = self.get("height") - 2, + y = self.getResolved("height") - 2, width = btnWidth, height = 1, - background = self.get("primaryColor"), - foreground = self.get("buttonForeground") + background = self.getResolved("primaryColor"), + foreground = self.getResolved("buttonForeground") }):onClick(function() if callback then callback(input.get("text") or "") end self:close() @@ -235,9 +235,9 @@ end function Dialog:render() Frame.render(self) - local title = self.get("title") + local title = self.getResolved("title") if title ~= "" then - local width = self.get("width") + local width = self.getResolved("width") local titleText = title:sub(1, width - 4) self:textFg(2, 2, titleText, colors.white) end @@ -247,7 +247,7 @@ end --- @shortDescription Handles mouse click events --- @protected function Dialog:mouse_click(button, x, y) - if self.get("modal") then + if self.getResolved("modal") then if self:isInBounds(x, y) then return Frame.mouse_click(self, button, x, y) end @@ -260,7 +260,7 @@ end --- @shortDescription Handles mouse drag events --- @protected function Dialog:mouse_drag(button, x, y) - if self.get("modal") then + if self.getResolved("modal") then if self:isInBounds(x, y) then return Frame.mouse_drag and Frame.mouse_drag(self, button, x, y) or false end @@ -273,7 +273,7 @@ end --- @shortDescription Handles mouse up events --- @protected function Dialog:mouse_up(button, x, y) - if self.get("modal") then + if self.getResolved("modal") then if self:isInBounds(x, y) then return Frame.mouse_up and Frame.mouse_up(self, button, x, y) or false end @@ -286,7 +286,7 @@ end --- @shortDescription Handles mouse scroll events --- @protected function Dialog:mouse_scroll(direction, x, y) - if self.get("modal") then + if self.getResolved("modal") then if self:isInBounds(x, y) then return Frame.mouse_scroll and Frame.mouse_scroll(self, direction, x, y) or false end diff --git a/src/elements/Display.lua b/src/elements/Display.lua index 47f0265..5653dfe 100644 --- a/src/elements/Display.lua +++ b/src/elements/Display.lua @@ -50,7 +50,7 @@ end function Display:init(props, basalt) VisualElement.init(self, props, basalt) self.set("type", "Display") - self._window = window.create(basalt.getActiveFrame():getTerm(), 1, 1, self.get("width"), self.get("height"), false) + self._window = window.create(basalt.getActiveFrame():getTerm(), 1, 1, self.getResolved("width"), self.getResolved("height"), false) local reposition = self._window.reposition local blit = self._window.blit local write = self._window.write @@ -63,7 +63,7 @@ function Display:init(props, basalt) end self._window.getPosition = function(self) - return self.get("x"), self.get("y") + return self.getResolved("x"), self.getResolved("y") end self._window.setVisible = function(visible) @@ -71,7 +71,7 @@ function Display:init(props, basalt) end self._window.isVisible = function(self) - return self.get("visible") + return self.getResolved("visible") end self._window.blit = function(x, y, text, fg, bg) blit(x, y, text, fg, bg) @@ -85,13 +85,13 @@ function Display:init(props, basalt) self:observe("width", function(self, width) local window = self._window if window then - window.reposition(1, 1, width, self.get("height")) + window.reposition(1, 1, width, self.getResolved("height")) end end) self:observe("height", function(self, height) local window = self._window if window then - window.reposition(1, 1, self.get("width"), height) + window.reposition(1, 1, self.getResolved("width"), height) end end) end diff --git a/src/elements/DropDown.lua b/src/elements/DropDown.lua index 936f92d..62843bd 100644 --- a/src/elements/DropDown.lua +++ b/src/elements/DropDown.lua @@ -105,7 +105,7 @@ function DropDown:mouse_click(button, x, y) self.set("height", 1) self:unsetState("opened") else - self.set("height", 1 + math.min(self.get("dropdownHeight"), #self.get("items"))) + self.set("height", 1 + math.min(self.getResolved("dropdownHeight"), #self.getResolved("items"))) self:setState("opened") end return true @@ -138,9 +138,9 @@ function DropDown:mouse_up(button, x, y) if self:hasState("opened") then local relX, relY = self:getRelativePosition(x, y) - if relY > 1 and self.get("selectable") and not self._scrollBarDragging then - local itemIndex = (relY - 1) + self.get("offset") - local items = self.get("items") + if relY > 1 and self.getResolved("selectable") and not self._scrollBarDragging then + local itemIndex = (relY - 1) + self.getResolved("offset") + local items = self.getResolved("items") if itemIndex <= #items then local item = items[itemIndex] @@ -149,7 +149,7 @@ function DropDown:mouse_up(button, x, y) items[itemIndex] = item end - if not self.get("multiSelection") then + if not self.getResolved("multiSelection") then for _, otherItem in ipairs(items) do if type(otherItem) == "table" then otherItem.selected = false @@ -185,26 +185,28 @@ end function DropDown:render() VisualElement.render(self) - local text = self.get("selectedText") + local width = self.getResolved("width") + local height = self.getResolved("height") + local text = self.getResolved("selectedText") local isOpen = self:hasState("opened") local selectedItems = self:getSelectedItems() if #selectedItems > 0 then local selectedItem = selectedItems[1] text = selectedItem.text or "" - text = text:sub(1, self.get("width") - 2) + text = text:sub(1, width - 2) end if isOpen then - local actualHeight = self.get("height") - local dropdownHeight = math.min(self.get("dropdownHeight"), #self.get("items")) + local actualHeight = height + local dropdownHeight = math.min(self.getResolved("dropdownHeight"), #self.getResolved("items")) self.set("height", dropdownHeight) List.render(self, 1) self.set("height", actualHeight) end - self:blit(1, 1, text .. string.rep(" ", self.get("width") - #text - 1) .. (isOpen and "\31" or "\17"), - string.rep(tHex[self.getResolved("foreground")], self.get("width")), - string.rep(tHex[self.getResolved("background")], self.get("width"))) + self:blit(1, 1, text .. string.rep(" ", width - #text - 1) .. (isOpen and "\31" or "\17"), + string.rep(tHex[self.getResolved("foreground")], width), + string.rep(tHex[self.getResolved("background")], width)) end --- Called when the DropDown gains focus diff --git a/src/elements/Frame.lua b/src/elements/Frame.lua index 006e7a7..0dedd21 100644 --- a/src/elements/Frame.lua +++ b/src/elements/Frame.lua @@ -53,21 +53,21 @@ end --- @protected function Frame:mouse_click(button, x, y) if self:isInBounds(x, y) then - if self.get("draggable") then + if self.getResolved("draggable") then local relX, relY = self:getRelativePosition(x, y) - local draggingMap = self.get("draggingMap") + local draggingMap = self.getResolved("draggingMap") for _, map in ipairs(draggingMap) do local width = map.width or 1 local height = map.height or 1 if type(width) == "string" and width == "width" then - width = self.get("width") + width = self.getResolved("width") elseif type(width) == "function" then width = width(self) end if type(height) == "string" and height == "height" then - height = self.get("height") + height = self.getResolved("height") elseif type(height) == "function" then height = height(self) end @@ -75,8 +75,8 @@ function Frame:mouse_click(button, x, y) local mapY = map.y or 1 if relX >= map.x and relX <= map.x + width - 1 and relY >= mapY and relY <= mapY + height - 1 then - self.dragStartX = x - self.get("x") - self.dragStartY = y - self.get("y") + self.dragStartX = x - self.getResolved("x") + self.dragStartY = y - self.getResolved("y") self.dragging = true return true end @@ -126,7 +126,7 @@ end --- @protected function Frame:getChildrenHeight() local maxHeight = 0 - local children = self.get("children") + local children = self.getResolved("children") for _, child in ipairs(children) do if child.get("visible") then @@ -147,7 +147,7 @@ local function convertMousePosition(self, event, ...) local args = {...} if event and event:find("mouse_") then local button, absX, absY = ... - local xOffset, yOffset = self.get("offsetX"), self.get("offsetY") + local xOffset, yOffset = self.getResolved("offsetX"), self.getResolved("offsetY") local relX, relY = self:getRelativePosition(absX + xOffset, absY + yOffset) args = {button, relX, relY} end @@ -167,11 +167,11 @@ function Frame:mouse_scroll(direction, x, y) if success then return true end - if self.get("scrollable") then - local height = self.get("height") + if self.getResolved("scrollable") then + local height = self.getResolved("height") local childrenHeight = self:getChildrenHeight() - local currentOffset = self.get("offsetY") + local currentOffset = self.getResolved("offsetY") local maxScroll = math.max(0, childrenHeight - height) local newOffset = currentOffset + direction diff --git a/src/elements/Graph.lua b/src/elements/Graph.lua index 1e06a38..9ae097a 100644 --- a/src/elements/Graph.lua +++ b/src/elements/Graph.lua @@ -60,13 +60,13 @@ end --- @param pointCount number The number of points in the series --- @return Graph self The graph instance function Graph:addSeries(name, symbol, bgCol, fgCol, pointCount) - local series = self.get("series") + local series = self.getResolved("series") table.insert(series, { name = name, symbol = symbol or " ", bgColor = bgCol or colors.white, fgColor = fgCol or colors.black, - pointCount = pointCount or self.get("width"), + pointCount = pointCount or self.getResolved("width"), data = {}, visible = true }) @@ -78,7 +78,7 @@ end --- @param name string The name of the series --- @return Graph self The graph instance function Graph:removeSeries(name) - local series = self.get("series") + local series = self.getResolved("series") for i, s in ipairs(series) do if s.name == name then table.remove(series, i) @@ -93,7 +93,7 @@ end --- @param name string The name of the series --- @return table? series The series function Graph:getSeries(name) - local series = self.get("series") + local series = self.getResolved("series") for _, s in ipairs(series) do if s.name == name then return s @@ -107,7 +107,7 @@ end --- @param visible boolean Whether the series should be visible --- @return Graph self The graph instance function Graph:changeSeriesVisibility(name, visible) - local series = self.get("series") + local series = self.getResolved("series") for _, s in ipairs(series) do if s.name == name then s.visible = visible @@ -123,7 +123,7 @@ end --- @param value number The value of the point --- @return Graph self The graph instance function Graph:addPoint(name, value) - local series = self.get("series") + local series = self.getResolved("series") for _, s in ipairs(series) do if s.name == name then @@ -142,7 +142,7 @@ end --- @param name string The name of the series --- @return Graph self The graph instance function Graph:focusSeries(name) - local series = self.get("series") + local series = self.getResolved("series") for index, s in ipairs(series) do if s.name == name then table.remove(series, index) @@ -159,7 +159,7 @@ end --- @param count number The number of points in the series --- @return Graph self The graph instance function Graph:setSeriesPointCount(name, count) - local series = self.get("series") + local series = self.getResolved("series") for _, s in ipairs(series) do if s.name == name then s.pointCount = count @@ -178,7 +178,7 @@ end --- @param name? string The name of the series --- @return Graph self The graph instance function Graph:clear(seriesName) - local series = self.get("series") + local series = self.getResolved("series") if seriesName then for _, s in ipairs(series) do if s.name == seriesName then @@ -199,11 +199,11 @@ end function Graph:render() VisualElement.render(self) - local width = self.get("width") - local height = self.get("height") - local minVal = self.get("minValue") - local maxVal = self.get("maxValue") - local series = self.get("series") + local width = self.getResolved("width") + local height = self.getResolved("height") + local minVal = self.getResolved("minValue") + local maxVal = self.getResolved("maxValue") + local series = self.getResolved("series") for _, s in pairs(series) do if(s.visible)then diff --git a/src/elements/Image.lua b/src/elements/Image.lua index 80c79ca..5f8c82b 100644 --- a/src/elements/Image.lua +++ b/src/elements/Image.lua @@ -53,7 +53,7 @@ end --- @param height number The new height of the image --- @return Image self The Image instance function Image:resizeImage(width, height) - local frames = self.get("bimg") + local frames = self.getResolved("bimg") for frameIndex, frame in ipairs(frames) do local newFrame = {} @@ -86,7 +86,7 @@ end --- @return number width The width of the image --- @return number height The height of the image function Image:getImageSize() - local bimg = self.get("bimg") + local bimg = self.getResolved("bimg") if not bimg[1] or not bimg[1][1] then return 0, 0 end return #bimg[1][1][1], #bimg[1] end @@ -99,7 +99,7 @@ end --- @return number? bg Background color --- @return string? char Character at position function Image:getPixelData(x, y) - local frame = self.get("bimg")[self.get("currentFrame")] + local frame = self.getResolved("bimg")[self.getResolved("currentFrame")] if not frame or not frame[y] then return end local text = frame[y][1] @@ -116,10 +116,10 @@ function Image:getPixelData(x, y) end local function ensureFrame(self, y) - local frame = self.get("bimg")[self.get("currentFrame")] + local frame = self.getResolved("bimg")[self.getResolved("currentFrame")] if not frame then frame = {} - self.get("bimg")[self.get("currentFrame")] = frame + self.getResolved("bimg")[self.getResolved("currentFrame")] = frame end if not frame[y] then frame[y] = {"", "", ""} @@ -128,9 +128,9 @@ local function ensureFrame(self, y) end local function updateFrameSize(self, neededWidth, neededHeight) - if not self.get("autoResize") then return end + if not self.getResolved("autoResize") then return end - local frames = self.get("bimg") + local frames = self.getResolved("bimg") local maxWidth = neededWidth local maxHeight = neededHeight @@ -164,13 +164,13 @@ end --- @return Image self The Image instance function Image:setText(x, y, text) if type(text) ~= "string" or #text < 1 or x < 1 or y < 1 then return self end - if not self.get("autoResize")then + if not self.getResolved("autoResize")then local imgWidth, imgHeight = self:getImageSize() if y > imgHeight then return self end end local frame = ensureFrame(self, y) - if self.get("autoResize") then + if self.getResolved("autoResize") then updateFrameSize(self, x + #text - 1, y) else local maxLen = #frame[y][1] @@ -193,7 +193,7 @@ end --- @return string text The text at the specified position function Image:getText(x, y, length) if not x or not y then return "" end - local frame = self.get("bimg")[self.get("currentFrame")] + local frame = self.getResolved("bimg")[self.getResolved("currentFrame")] if not frame or not frame[y] then return "" end local text = frame[y][1] @@ -214,13 +214,13 @@ end --- @return Image self The Image instance function Image:setFg(x, y, pattern) if type(pattern) ~= "string" or #pattern < 1 or x < 1 or y < 1 then return self end - if not self.get("autoResize")then + if not self.getResolved("autoResize")then local imgWidth, imgHeight = self:getImageSize() if y > imgHeight then return self end end local frame = ensureFrame(self, y) - if self.get("autoResize") then + if self.getResolved("autoResize") then updateFrameSize(self, x + #pattern - 1, y) else local maxLen = #frame[y][2] @@ -243,7 +243,7 @@ end --- @return string fg The foreground color pattern function Image:getFg(x, y, length) if not x or not y then return "" end - local frame = self.get("bimg")[self.get("currentFrame")] + local frame = self.getResolved("bimg")[self.getResolved("currentFrame")] if not frame or not frame[y] then return "" end local fg = frame[y][2] @@ -264,13 +264,13 @@ end --- @return Image self The Image instance function Image:setBg(x, y, pattern) if type(pattern) ~= "string" or #pattern < 1 or x < 1 or y < 1 then return self end - if not self.get("autoResize")then + if not self.getResolved("autoResize")then local imgWidth, imgHeight = self:getImageSize() if y > imgHeight then return self end end local frame = ensureFrame(self, y) - if self.get("autoResize") then + if self.getResolved("autoResize") then updateFrameSize(self, x + #pattern - 1, y) else local maxLen = #frame[y][3] @@ -293,7 +293,7 @@ end --- @return string bg The background color pattern function Image:getBg(x, y, length) if not x or not y then return "" end - local frame = self.get("bimg")[self.get("currentFrame")] + local frame = self.getResolved("bimg")[self.getResolved("currentFrame")] if not frame or not frame[y] then return "" end local bg = frame[y][3] @@ -325,10 +325,10 @@ end --- @shortDescription Advances to the next frame in the animation --- @return Image self The Image instance function Image:nextFrame() - if not self.get("bimg").animation then return self end + if not self.getResolved("bimg").animation then return self end - local frames = self.get("bimg") - local current = self.get("currentFrame") + local frames = self.getResolved("bimg") + local current = self.getResolved("currentFrame") local next = current + 1 if next > #frames then next = 1 end @@ -340,7 +340,7 @@ end --- @shortDescription Adds a new frame to the image --- @return Image self The Image instance function Image:addFrame() - local frames = self.get("bimg") + local frames = self.getResolved("bimg") local width = frames.width or #frames[1][1][1] local height = frames.height or #frames[1] local frame = {} @@ -360,7 +360,7 @@ end --- @param frame table The new frame data --- @return Image self The Image instance function Image:updateFrame(frameIndex, frame) - local frames = self.get("bimg") + local frames = self.getResolved("bimg") frames[frameIndex] = frame self:updateRender() return self @@ -371,8 +371,8 @@ end --- @param frameIndex number The index of the frame to get --- @return table frame The frame data function Image:getFrame(frameIndex) - local frames = self.get("bimg") - return frames[frameIndex or self.get("currentFrame")] + local frames = self.getResolved("bimg") + return frames[frameIndex or self.getResolved("currentFrame")] end --- Gets the metadata of the image @@ -380,7 +380,7 @@ end --- @return table metadata The metadata of the image function Image:getMetadata() local metadata = {} - local bimg = self.get("bimg") + local bimg = self.getResolved("bimg") for k,v in pairs(bimg)do if(type(v)=="string")then metadata[k] = v @@ -401,7 +401,7 @@ function Image:setMetadata(key, value) end return self end - local bimg = self.get("bimg") + local bimg = self.getResolved("bimg") if(type(value)=="string")then bimg[key] = value end @@ -413,13 +413,13 @@ end function Image:render() VisualElement.render(self) - local frame = self.get("bimg")[self.get("currentFrame")] + local frame = self.getResolved("bimg")[self.getResolved("currentFrame")] if not frame then return end - local offsetX = self.get("offsetX") - local offsetY = self.get("offsetY") - local elementWidth = self.get("width") - local elementHeight = self.get("height") + local offsetX = self.getResolved("offsetX") + local offsetY = self.getResolved("offsetY") + local elementWidth = self.getResolved("width") + local elementHeight = self.getResolved("height") for y = 1, elementHeight do local frameY = y + offsetY diff --git a/src/elements/Input.lua b/src/elements/Input.lua index cc101f1..3c59054 100644 --- a/src/elements/Input.lua +++ b/src/elements/Input.lua @@ -62,7 +62,7 @@ end --- @param blink boolean Whether the cursor should blink --- @param color number The color of the cursor function Input:setCursor(x, y, blink, color) - x = math.min(self.get("width"), math.max(1, x)) + x = math.min(self.getResolved("width"), math.max(1, x)) return VisualElement.setCursor(self, x, y, blink, color) end @@ -72,10 +72,10 @@ end --- @protected function Input:char(char) if not self:hasState("focused") then return false end - local text = self.get("text") - local pos = self.get("cursorPos") - local maxLength = self.get("maxLength") - local pattern = self.get("pattern") + local text = self.getResolved("text") + local pos = self.getResolved("cursorPos") + local maxLength = self.getResolved("maxLength") + local pattern = self.getResolved("pattern") if maxLength and #text >= maxLength then return false end if pattern and not char:match(pattern) then return false end @@ -84,8 +84,8 @@ function Input:char(char) self.set("cursorPos", pos + 1) self:updateViewport() - local relPos = self.get("cursorPos") - self.get("viewOffset") - self:setCursor(relPos, 1, true, self.get("cursorColor") or self.get("foreground")) + local relPos = self.getResolved("cursorPos") - self.getResolved("viewOffset") + self:setCursor(relPos, 1, true, self.getResolved("cursorColor") or self.getResolved("foreground")) VisualElement.char(self, char) return true end @@ -96,10 +96,10 @@ end --- @protected function Input:key(key, held) if not self:hasState("focused") then return false end - local pos = self.get("cursorPos") - local text = self.get("text") - local viewOffset = self.get("viewOffset") - local width = self.get("width") + local pos = self.getResolved("cursorPos") + local text = self.getResolved("text") + local viewOffset = self.getResolved("viewOffset") + local width = self.getResolved("width") if key == keys.left then if pos > 1 then @@ -124,7 +124,7 @@ function Input:key(key, held) end end - local relativePos = self.get("cursorPos") - self.get("viewOffset") + local relativePos = self.getResolved("cursorPos") - self.getResolved("viewOffset") self:setCursor(relativePos, 1, true, self.getResolved("cursorColor") or self.getResolved("foreground")) VisualElement.key(self, key, held) return true @@ -139,8 +139,8 @@ end function Input:mouse_click(button, x, y) if VisualElement.mouse_click(self, button, x, y) then local relX, relY = self:getRelativePosition(x, y) - local text = self.get("text") - local viewOffset = self.get("viewOffset") + local text = self.getResolved("text") + local viewOffset = self.getResolved("viewOffset") local maxPos = #text + 1 local targetPos = math.min(maxPos, viewOffset + relX) @@ -158,10 +158,10 @@ end --- @shortDescription Updates the input's viewport --- @return Input self The updated instance function Input:updateViewport() - local width = self.get("width") - local cursorPos = self.get("cursorPos") - local viewOffset = self.get("viewOffset") - local textLength = #self.get("text") + local width = self.getResolved("width") + local cursorPos = self.getResolved("cursorPos") + local viewOffset = self.getResolved("viewOffset") + local textLength = #self.getResolved("text") if cursorPos - viewOffset >= width then self.set("viewOffset", cursorPos - width + 1) @@ -169,7 +169,7 @@ function Input:updateViewport() self.set("viewOffset", cursorPos - 1) end - self.set("viewOffset", math.max(0, math.min(self.get("viewOffset"), textLength - width + 1))) + self.set("viewOffset", math.max(0, math.min(self.getResolved("viewOffset"), textLength - width + 1))) return self end @@ -178,7 +178,7 @@ end --- @protected function Input:focus() VisualElement.focus(self) - self:setCursor(self.get("cursorPos") - self.get("viewOffset"), 1, true, self.getResolved("cursorColor") or self.getResolved("foreground")) + self:setCursor(self.getResolved("cursorPos") - self.getResolved("viewOffset"), 1, true, self.getResolved("cursorColor") or self.getResolved("foreground")) self:updateRender() end @@ -194,10 +194,10 @@ end --- @protected function Input:paste(content) if not self:hasState("focused") then return false end - local text = self.get("text") - local pos = self.get("cursorPos") - local maxLength = self.get("maxLength") - local pattern = self.get("pattern") + local text = self.getResolved("text") + local pos = self.getResolved("cursorPos") + local maxLength = self.getResolved("maxLength") + local pattern = self.getResolved("pattern") local newText = text:sub(1, pos - 1) .. content .. text:sub(pos) if maxLength and #newText > maxLength then newText = newText:sub(1, maxLength) @@ -214,10 +214,10 @@ end --- @protected function Input:render() local text = self.getResolved("text") - local viewOffset = self.get("viewOffset") + local viewOffset = self.getResolved("viewOffset") local placeholder = self.getResolved("placeholder") local focused = self:hasState("focused") - local width, height = self.get("width"), self.get("height") + local width, height = self.getResolved("width"), self.getResolved("height") local replaceChar = self.getResolved("replaceChar") self:multiBlit(1, 1, width, height, " ", tHex[self.getResolved("foreground")], tHex[self.getResolved("background")]) @@ -227,7 +227,7 @@ function Input:render() end if(focused) then - self:setCursor(self.get("cursorPos") - viewOffset, 1, true, self.getResolved("cursorColor") or self.getResolved("foreground")) + self:setCursor(self.getResolved("cursorPos") - viewOffset, 1, true, self.getResolved("cursorColor") or self.getResolved("foreground")) end local visibleText = text:sub(viewOffset + 1, viewOffset + width) diff --git a/src/elements/Label.lua b/src/elements/Label.lua index 22a3e1a..164bd0b 100644 --- a/src/elements/Label.lua +++ b/src/elements/Label.lua @@ -11,10 +11,10 @@ Label.__index = Label ---@property text string Label The text content to display. Can be a string or a function that returns a string Label.defineProperty(Label, "text", {default = "Label", type = "string", canTriggerRender = true, setter = function(self, value) if(type(value)=="function")then value = value() end - if(self.get("autoSize"))then + if(self.getResolved("autoSize"))then self.set("width", #value) else - self.set("height", #wrapText(value, self.get("width"))) + self.set("height", #wrapText(value, self.getResolved("width"))) end return value end}) @@ -22,9 +22,9 @@ end}) ---@property autoSize boolean true Whether the label should automatically resize its width based on the text content Label.defineProperty(Label, "autoSize", {default = true, type = "boolean", canTriggerRender = true, setter = function(self, value) if(value)then - self.set("width", #self.get("text")) + self.set("width", #self.getResolved("text")) else - self.set("height", #wrapText(self.get("text"), self.get("width"))) + self.set("height", #wrapText(self.getResolved("text"), self.getResolved("width"))) end return value end}) @@ -61,8 +61,8 @@ end --- @shortDescription Gets the wrapped lines of the Label --- @return table wrappedText The wrapped lines of the Label function Label:getWrappedText() - local text = self.get("text") - local wrappedText = wrapText(text, self.get("width")) + local text = self.getResolved("text") + local wrappedText = wrapText(text, self.getResolved("width")) return wrappedText end @@ -70,13 +70,13 @@ end --- @protected function Label:render() VisualElement.render(self) - local text = self.get("text") - if(self.get("autoSize"))then - self:textFg(1, 1, text, self.get("foreground")) + local text = self.getResolved("text") + if(self.getResolved("autoSize"))then + self:textFg(1, 1, text, self.getResolved("foreground")) else - local wrappedText = wrapText(text, self.get("width")) + local wrappedText = wrapText(text, self.getResolved("width")) for i, line in ipairs(wrappedText) do - self:textFg(1, i, line, self.get("foreground")) + self:textFg(1, i, line, self.getResolved("foreground")) end end end diff --git a/src/elements/LineChart.lua b/src/elements/LineChart.lua index db20619..dd1b24d 100644 --- a/src/elements/LineChart.lua +++ b/src/elements/LineChart.lua @@ -53,7 +53,7 @@ local function drawLine(self, x1, y1, x2, y2, symbol, bgColor, fgColor) local t = steps == 0 and 0 or i / steps local x = math.floor(x1 + dx * t) local y = math.floor(y1 + dy * t) - if x >= 1 and x <= self.get("width") and y >= 1 and y <= self.get("height") then + if x >= 1 and x <= self.getResolved("width") and y >= 1 and y <= self.getResolved("height") then self:blit(x, y, symbol, tHex[bgColor], tHex[fgColor]) end end @@ -64,11 +64,11 @@ end function LineChart:render() VisualElement.render(self) - local width = self.get("width") - local height = self.get("height") - local minVal = self.get("minValue") - local maxVal = self.get("maxValue") - local series = self.get("series") + local width = self.getResolved("width") + local height = self.getResolved("height") + local minVal = self.getResolved("minValue") + local maxVal = self.getResolved("maxValue") + local series = self.getResolved("series") for _, s in pairs(series) do if(s.visible)then diff --git a/src/elements/List.lua b/src/elements/List.lua index 51ba3f8..c1c9614 100644 --- a/src/elements/List.lua +++ b/src/elements/List.lua @@ -14,7 +14,7 @@ List.defineProperty(List, "offset", { type = "number", canTriggerRender = true, setter = function(self, value) - local maxOffset = math.max(0, #self.get("items") - self.get("height")) + local maxOffset = math.max(0, #self.getResolved("items") - self.getResolved("height")) return math.min(maxOffset, math.max(0, value)) end }) @@ -86,15 +86,15 @@ function List:init(props, basalt) self.set("type", "List") self:observe("items", function() - local maxOffset = math.max(0, #self.get("items") - self.get("height")) - if self.get("offset") > maxOffset then + local maxOffset = math.max(0, #self.getResolved("items") - self.getResolved("height")) + if self.getResolved("offset") > maxOffset then self.set("offset", maxOffset) end end) self:observe("height", function() - local maxOffset = math.max(0, #self.get("items") - self.get("height")) - if self.get("offset") > maxOffset then + local maxOffset = math.max(0, #self.getResolved("items") - self.getResolved("height")) + if self.getResolved("offset") > maxOffset then self.set("offset", maxOffset) end end) @@ -111,16 +111,16 @@ end function List:mouse_click(button, x, y) if Collection.mouse_click(self, button, x, y) then local relX, relY = self:getRelativePosition(x, y) - local width = self.get("width") - local items = self.get("items") - local height = self.get("height") - local showScrollBar = self.get("showScrollBar") + local width = self.getResolved("width") + local items = self.getResolved("items") + local height = self.getResolved("height") + local showScrollBar = self.getResolved("showScrollBar") if showScrollBar and #items > height and relX == width then local maxOffset = #items - height local handleSize = math.max(1, math.floor((height / #items) * height)) - local currentPercent = maxOffset > 0 and (self.get("offset") / maxOffset * 100) or 0 + local currentPercent = maxOffset > 0 and (self.getResolved("offset") / maxOffset * 100) or 0 local handlePos = math.floor((currentPercent / 100) * (height - handleSize)) + 1 if relY >= handlePos and relY < handlePos + handleSize then @@ -134,12 +134,12 @@ function List:mouse_click(button, x, y) return true end - if self.get("selectable") then - local adjustedIndex = relY + self.get("offset") + if self.getResolved("selectable") then + local adjustedIndex = relY + self.getResolved("offset") if adjustedIndex <= #items then local item = items[adjustedIndex] - if not self.get("multiSelection") then + if not self.getResolved("multiSelection") then for _, otherItem in ipairs(items) do if type(otherItem) == "table" then otherItem.selected = false @@ -170,8 +170,8 @@ end function List:mouse_drag(button, x, y) if self._scrollBarDragging then local _, relY = self:getRelativePosition(x, y) - local items = self.get("items") - local height = self.get("height") + local items = self.getResolved("items") + local height = self.getResolved("height") local handleSize = math.max(1, math.floor((height / #items) * height)) local maxOffset = #items - height relY = math.max(1, math.min(height, relY)) @@ -209,8 +209,8 @@ end --- @protected function List:mouse_scroll(direction, x, y) if Collection.mouse_scroll(self, direction, x, y) then - local offset = self.get("offset") - local maxOffset = math.max(0, #self.get("items") - self.get("height")) + local offset = self.getResolved("offset") + local maxOffset = math.max(0, #self.getResolved("items") - self.getResolved("height")) offset = math.min(maxOffset, math.max(0, offset + direction)) self.set("offset", offset) @@ -233,7 +233,7 @@ end --- @shortDescription Scrolls the list to the bottom --- @return List self The List instance function List:scrollToBottom() - local maxOffset = math.max(0, #self.get("items") - self.get("height")) + local maxOffset = math.max(0, #self.getResolved("items") - self.getResolved("height")) self.set("offset", maxOffset) return self end @@ -252,8 +252,8 @@ end --- @return List self The List instance --- @usage list:scrollToItem(5) function List:scrollToItem(index) - local height = self.get("height") - local offset = self.get("offset") + local height = self.getResolved("height") + local offset = self.getResolved("offset") if index < offset + 1 then self.set("offset", math.max(0, index - 1)) @@ -270,8 +270,8 @@ end --- @return boolean Whether the event was handled --- @protected function List:key(keyCode) - if Collection.key(self, keyCode) and self.get("selectable") then - local items = self.get("items") + if Collection.key(self, keyCode) and self.getResolved("selectable") then + local items = self.getResolved("items") local currentIndex = self:getSelectedIndex() if keyCode == keys.up then @@ -297,14 +297,14 @@ function List:key(keyCode) self:scrollToBottom() return true elseif keyCode == keys.pageUp then - local height = self.get("height") + local height = self.getResolved("height") local newIndex = math.max(1, (currentIndex or 1) - height) self:clearItemSelection() self:selectItem(newIndex) self:scrollToItem(newIndex) return true elseif keyCode == keys.pageDown then - local height = self.get("height") + local height = self.getResolved("height") local newIndex = math.min(#items, (currentIndex or 1) + height) self:clearItemSelection() self:selectItem(newIndex) @@ -321,19 +321,19 @@ function List:render(vOffset) vOffset = vOffset or 0 Collection.render(self) - local items = self.get("items") - local height = self.get("height") - local offset = self.get("offset") - local width = self.get("width") + local items = self.getResolved("items") + local height = self.getResolved("height") + local offset = self.getResolved("offset") + local width = self.getResolved("width") local listBg = self.getResolved("background") local listFg = self.getResolved("foreground") - local showScrollBar = self.get("showScrollBar") + local showScrollBar = self.getResolved("showScrollBar") local needsScrollBar = showScrollBar and #items > height local contentWidth = needsScrollBar and width - 1 or width if #items == 0 then - local emptyText = self.get("emptyText") + local emptyText = self.getResolved("emptyText") local y = math.floor(height / 2) + vOffset local x = math.max(1, math.floor((width - #emptyText) / 2) + 1) diff --git a/src/elements/Menu.lua b/src/elements/Menu.lua index d271b1f..d0fa98d 100644 --- a/src/elements/Menu.lua +++ b/src/elements/Menu.lua @@ -28,7 +28,7 @@ Menu.defineProperty(Menu, "horizontalOffset", { type = "number", canTriggerRender = true, setter = function(self, value) - local maxOffset = math.max(0, self:getTotalWidth() - self.get("width")) + local maxOffset = math.max(0, self:getTotalWidth() - self.getResolved("width")) return math.min(maxOffset, math.max(0, value)) end }) @@ -59,7 +59,7 @@ function Menu:init(props, basalt) self.set("type", "Menu") self:observe("items", function() - local maxWidth = self.get("maxWidth") + local maxWidth = self.getResolved("maxWidth") if maxWidth then self.set("width", math.min(maxWidth, self:getTotalWidth()), true) else @@ -74,8 +74,8 @@ end --- @shortDescription Calculates total width of menu items --- @return number totalWidth The total width of all items function Menu:getTotalWidth() - local items = self.get("items") - local spacing = self.get("spacing") + local items = self.getResolved("items") + local spacing = self.getResolved("spacing") local totalWidth = 0 for i, item in ipairs(items) do @@ -97,10 +97,10 @@ end --- @protected function Menu:render() VisualElement.render(self) - local viewportWidth = self.get("width") - local spacing = self.get("spacing") - local offset = self.get("horizontalOffset") - local items = self.get("items") + local viewportWidth = self.getResolved("width") + local spacing = self.getResolved("spacing") + local offset = self.getResolved("horizontalOffset") + local items = self.getResolved("items") local itemPositions = {} local currentX = 1 @@ -143,13 +143,13 @@ function Menu:render() if #visibleText > 0 then local isSelected = item.selected - local fg = item.selectable == false and self.get("separatorColor") or - (isSelected and (item.selectedForeground or self.get("selectedForeground")) or - (item.foreground or self.get("foreground"))) + local fg = item.selectable == false and self.getResolved("separatorColor") or + (isSelected and (item.selectedForeground or self.getResolved("selectedForeground")) or + (item.foreground or self.getResolved("foreground"))) local bg = isSelected and - (item.selectedBackground or self.get("selectedBackground")) or - (item.background or self.get("background")) + (item.selectedBackground or self.getResolved("selectedBackground")) or + (item.background or self.getResolved("background")) self:blit(visibleStart, 1, visibleText, string.rep(tHex[fg], #visibleText), @@ -168,8 +168,8 @@ function Menu:render() if spacingWidth > 0 then local spacingText = string.rep(" ", spacingWidth) self:blit(visibleSpacingStart, 1, spacingText, - string.rep(tHex[self.get("foreground")], spacingWidth), - string.rep(tHex[self.get("background")], spacingWidth)) + string.rep(tHex[self.getResolved("foreground")], spacingWidth), + string.rep(tHex[self.getResolved("background")], spacingWidth)) end end end @@ -181,11 +181,11 @@ end --- @protected function Menu:mouse_click(button, x, y) if not VisualElement.mouse_click(self, button, x, y) then return false end - if(self.get("selectable") == false) then return false end + if(self.getResolved("selectable") == false) then return false end local relX = select(1, self:getRelativePosition(x, y)) - local offset = self.get("horizontalOffset") - local spacing = self.get("spacing") - local items = self.get("items") + local offset = self.getResolved("horizontalOffset") + local spacing = self.getResolved("spacing") + local items = self.getResolved("items") local virtualX = relX + offset local currentX = 1 @@ -200,7 +200,7 @@ function Menu:mouse_click(button, x, y) items[i] = item end - if not self.get("multiSelection") then + if not self.getResolved("multiSelection") then for _, otherItem in ipairs(items) do if type(otherItem) == "table" then otherItem.selected = false @@ -230,8 +230,8 @@ end --- @protected function Menu:mouse_scroll(direction, x, y) if VisualElement.mouse_scroll(self, direction, x, y) then - local offset = self.get("horizontalOffset") - local maxOffset = math.max(0, self:getTotalWidth() - self.get("width")) + local offset = self.getResolved("horizontalOffset") + local maxOffset = math.max(0, self:getTotalWidth() - self.getResolved("width")) offset = math.min(maxOffset, math.max(0, offset + (direction * 3))) self.set("horizontalOffset", offset) diff --git a/src/elements/Program.lua b/src/elements/Program.lua index 287e7c4..35e748b 100644 --- a/src/elements/Program.lua +++ b/src/elements/Program.lua @@ -254,15 +254,15 @@ function Program:init(props, basalt) VisualElement.init(self, props, basalt) self.set("type", "Program") self:observe("width", function(self, width) - local program = self.get("program") + local program = self.getResolved("program") if program then - program:resize(width, self.get("height")) + program:resize(width, self.getResolved("height")) end end) self:observe("height", function(self, height) - local program = self.get("program") + local program = self.getResolved("program") if program then - program:resize(self.get("width"), height) + program:resize(self.getResolved("width"), height) end end) return self @@ -280,7 +280,7 @@ function Program:execute(path, env, addEnvironment, ...) local program = BasaltProgram.new(self, env, addEnvironment) self.set("program", program) program:setArgs(...) - program:run(path, self.get("width"), self.get("height"), ...) + program:run(path, self.getResolved("width"), self.getResolved("height"), ...) self:updateRender() return self end @@ -289,7 +289,7 @@ end --- @shortDescription Stops the program --- @return Program self The Program instance function Program:stop() - local program = self.get("program") + local program = self.getResolved("program") if program then program:stop() self.set("running", false) @@ -332,7 +332,7 @@ end --- @return any result The event result --- @protected function Program:dispatchEvent(event, ...) - local program = self.get("program") + local program = self.getResolved("program") local result = VisualElement.dispatchEvent(self, event, ...) if program then program:resume(event, ...) @@ -350,7 +350,7 @@ end --- @protected function Program:focus() if(VisualElement.focus(self))then - local program = self.get("program") + local program = self.getResolved("program") if program then local cursorBlink = program.window.getCursorBlink() local cursorX, cursorY = program.window.getCursorPos() @@ -363,7 +363,7 @@ end --- @protected function Program:render() VisualElement.render(self) - local program = self.get("program") + local program = self.getResolved("program") if program then local _, height = program.window.getSize() for y = 1, height do diff --git a/src/elements/ProgressBar.lua b/src/elements/ProgressBar.lua index 4f85f2c..f005f12 100644 --- a/src/elements/ProgressBar.lua +++ b/src/elements/ProgressBar.lua @@ -47,29 +47,30 @@ end --- @protected function ProgressBar:render() VisualElement.render(self) - local width = self.get("width") - local height = self.get("height") - local progress = math.min(100, math.max(0, self.get("progress"))) + local width = self.getResolved("width") + local height = self.getResolved("height") + local progress = math.min(100, math.max(0, self.getResolved("progress"))) local fillWidth = math.floor((width * progress) / 100) local fillHeight = math.floor((height * progress) / 100) - local direction = self.get("direction") - local progressColor = self.get("progressColor") + local direction = self.getResolved("direction") + local progressColor = self.getResolved("progressColor") + local foreground = self.getResolved("foreground") if direction == "right" then - self:multiBlit(1, 1, fillWidth, height, " ", tHex[self.get("foreground")], tHex[progressColor]) + self:multiBlit(1, 1, fillWidth, height, " ", tHex[foreground], tHex[progressColor]) elseif direction == "left" then - self:multiBlit(width - fillWidth + 1, 1, fillWidth, height, " ", tHex[self.get("foreground")], tHex[progressColor]) + self:multiBlit(width - fillWidth + 1, 1, fillWidth, height, " ", tHex[foreground], tHex[progressColor]) elseif direction == "up" then - self:multiBlit(1, height - fillHeight + 1, width, fillHeight, " ", tHex[self.get("foreground")], tHex[progressColor]) + self:multiBlit(1, height - fillHeight + 1, width, fillHeight, " ", tHex[foreground], tHex[progressColor]) elseif direction == "down" then - self:multiBlit(1, 1, width, fillHeight, " ", tHex[self.get("foreground")], tHex[progressColor]) + self:multiBlit(1, 1, width, fillHeight, " ", tHex[foreground], tHex[progressColor]) end - if self.get("showPercentage") then + if self.getResolved("showPercentage") then local text = tostring(progress).."%" local x = math.floor((width - #text) / 2) + 1 local y = math.floor((height - 1) / 2) + 1 - self:textFg(x, y, text, self.get("foreground")) + self:textFg(x, y, text, foreground) end end diff --git a/src/elements/ScrollBar.lua b/src/elements/ScrollBar.lua index 51eeffa..fd29995 100644 --- a/src/elements/ScrollBar.lua +++ b/src/elements/ScrollBar.lua @@ -80,8 +80,8 @@ function ScrollBar:attach(element, config) self.set("maxValue", config.max or 100) element:observe(config.property, function(_, value) if value then - local min = self.get("minValue") - local max = self.get("maxValue") + local min = self.getResolved("minValue") + local max = self.getResolved("maxValue") if min == max then return end self.set("value", math.floor( @@ -96,28 +96,28 @@ end --- @shortDescription Updates the attached element's property based on the ScrollBar value --- @return ScrollBar self The ScrollBar instance function ScrollBar:updateAttachedElement() - local element = self.get("attachedElement") + local element = self.getResolved("attachedElement") if not element then return end - local value = self.get("value") - local min = self.get("minValue") - local max = self.get("maxValue") + local value = self.getResolved("value") + local min = self.getResolved("minValue") + local max = self.getResolved("maxValue") if type(min) == "function" then min = min() end if type(max) == "function" then max = max() end local mappedValue = min + (value / 100) * (max - min) - element.set(self.get("attachedProperty"), math.floor(mappedValue + 0.5)) + element.set(self.getResolved("attachedProperty"), math.floor(mappedValue + 0.5)) return self end local function getScrollbarSize(self) - return self.get("orientation") == "vertical" and self.get("height") or self.get("width") + return self.getResolved("orientation") == "vertical" and self.getResolved("height") or self.getResolved("width") end local function getRelativeScrollPosition(self, x, y) local relX, relY = self:getRelativePosition(x, y) - return self.get("orientation") == "vertical" and relY or relX + return self.getResolved("orientation") == "vertical" and relY or relX end --- @shortDescription Handles mouse click events @@ -129,8 +129,8 @@ end function ScrollBar:mouse_click(button, x, y) if VisualElement.mouse_click(self, button, x, y) then local size = getScrollbarSize(self) - local value = self.get("value") - local handleSize = self.get("handleSize") + local value = self.getResolved("value") + local handleSize = self.getResolved("handleSize") local handlePos = math.floor((value / 100) * (size - handleSize)) + 1 local relPos = getRelativeScrollPosition(self, x, y) @@ -155,8 +155,8 @@ end function ScrollBar:mouse_drag(button, x, y) if(VisualElement.mouse_drag(self, button, x, y))then local size = getScrollbarSize(self) - local handleSize = self.get("handleSize") - local dragMultiplier = self.get("dragMultiplier") + local handleSize = self.getResolved("handleSize") + local dragMultiplier = self.getResolved("dragMultiplier") local relPos = getRelativeScrollPosition(self, x, y) relPos = math.max(1, math.min(size, relPos)) @@ -179,8 +179,8 @@ end function ScrollBar:mouse_scroll(direction, x, y) if not self:isInBounds(x, y) then return false end direction = direction > 0 and -1 or 1 - local step = self.get("step") - local currentValue = self.get("value") + local step = self.getResolved("step") + local currentValue = self.getResolved("value") local newValue = currentValue - direction * step self.set("value", math.min(100, math.max(0, newValue))) @@ -194,21 +194,23 @@ function ScrollBar:render() VisualElement.render(self) local size = getScrollbarSize(self) - local value = self.get("value") - local handleSize = self.get("handleSize") - local symbol = self.get("symbol") - local symbolColor = self.get("symbolColor") - local symbolBackgroundColor = self.get("symbolBackgroundColor") - local bgSymbol = self.get("backgroundSymbol") - local isVertical = self.get("orientation") == "vertical" + local value = self.getResolved("value") + local handleSize = self.getResolved("handleSize") + local symbol = self.getResolved("symbol") + local symbolColor = self.getResolved("symbolColor") + local symbolBackgroundColor = self.getResolved("symbolBackgroundColor") + local bgSymbol = self.getResolved("backgroundSymbol") + local isVertical = self.getResolved("orientation") == "vertical" + local foreground = self.getResolved("foreground") + local background = self.getResolved("background") local handlePos = math.floor((value / 100) * (size - handleSize)) + 1 for i = 1, size do if isVertical then - self:blit(1, i, bgSymbol, tHex[self.get("foreground")], tHex[self.get("background")]) + self:blit(1, i, bgSymbol, tHex[foreground], tHex[background]) else - self:blit(i, 1, bgSymbol, tHex[self.get("foreground")], tHex[self.get("background")]) + self:blit(i, 1, bgSymbol, tHex[foreground], tHex[background]) end end diff --git a/src/elements/ScrollFrame.lua b/src/elements/ScrollFrame.lua index 2f682c0..84aac64 100644 --- a/src/elements/ScrollFrame.lua +++ b/src/elements/ScrollFrame.lua @@ -110,7 +110,7 @@ ScrollFrame.defineProperty(ScrollFrame, "contentWidth", { type = "number", getter = function(self) local maxWidth = 0 - local children = self.get("children") + local children = self.getResolved("children") for _, child in ipairs(children) do local childX = child.get("x") local childWidth = child.get("width") @@ -129,7 +129,7 @@ ScrollFrame.defineProperty(ScrollFrame, "contentHeight", { type = "number", getter = function(self) local maxHeight = 0 - local children = self.get("children") + local children = self.getResolved("children") for _, child in ipairs(children) do local childY = child.get("y") local childHeight = child.get("height") @@ -182,11 +182,11 @@ end function ScrollFrame:mouse_click(button, x, y) if Container.mouse_click(self, button, x, y) then local relX, relY = self:getRelativePosition(x, y) - local width = self.get("width") - local height = self.get("height") - local showScrollBar = self.get("showScrollBar") - local contentWidth = self.get("contentWidth") - local contentHeight = self.get("contentHeight") + local width = self.getResolved("width") + local height = self.getResolved("height") + local showScrollBar = self.getResolved("showScrollBar") + local contentWidth = self.getResolved("contentWidth") + local contentHeight = self.getResolved("contentHeight") local needsHorizontalScrollBar = showScrollBar and contentWidth > width local viewportHeight = needsHorizontalScrollBar and height - 1 or height local needsVerticalScrollBar = showScrollBar and contentHeight > viewportHeight @@ -197,7 +197,7 @@ function ScrollFrame:mouse_click(button, x, y) local handleSize = math.max(1, math.floor((viewportHeight / contentHeight) * scrollHeight)) local maxOffset = contentHeight - viewportHeight - local currentPercent = maxOffset > 0 and (self.get("offsetY") / maxOffset * 100) or 0 + local currentPercent = maxOffset > 0 and (self.getResolved("offsetY") / maxOffset * 100) or 0 local handlePos = math.floor((currentPercent / 100) * (scrollHeight - handleSize)) + 1 if relY >= handlePos and relY < handlePos + handleSize then @@ -216,7 +216,7 @@ function ScrollFrame:mouse_click(button, x, y) local handleSize = math.max(1, math.floor((viewportWidth / contentWidth) * scrollWidth)) local maxOffset = contentWidth - viewportWidth - local currentPercent = maxOffset > 0 and (self.get("offsetX") / maxOffset * 100) or 0 + local currentPercent = maxOffset > 0 and (self.getResolved("offsetX") / maxOffset * 100) or 0 local handlePos = math.floor((currentPercent / 100) * (scrollWidth - handleSize)) + 1 if relX >= handlePos and relX < handlePos + handleSize then @@ -245,11 +245,11 @@ end function ScrollFrame:mouse_drag(button, x, y) if self._scrollBarDragging then local _, relY = self:getRelativePosition(x, y) - local height = self.get("height") - local contentWidth = self.get("contentWidth") - local contentHeight = self.get("contentHeight") - local width = self.get("width") - local needsHorizontalScrollBar = self.get("showScrollBar") and contentWidth > width + local height = self.getResolved("height") + local contentWidth = self.getResolved("contentWidth") + local contentHeight = self.getResolved("contentHeight") + local width = self.getResolved("width") + local needsHorizontalScrollBar = self.getResolved("showScrollBar") and contentWidth > width local viewportHeight = needsHorizontalScrollBar and height - 1 or height local scrollHeight = viewportHeight @@ -268,13 +268,13 @@ function ScrollFrame:mouse_drag(button, x, y) if self._hScrollBarDragging then local relX, _ = self:getRelativePosition(x, y) - local width = self.get("width") - local contentWidth = self.get("contentWidth") - local contentHeight = self.get("contentHeight") - local height = self.get("height") - local needsHorizontalScrollBar = self.get("showScrollBar") and contentWidth > width + local width = self.getResolved("width") + local contentWidth = self.getResolved("contentWidth") + local contentHeight = self.getResolved("contentHeight") + local height = self.getResolved("height") + local needsHorizontalScrollBar = self.getResolved("showScrollBar") and contentWidth > width local viewportHeight = needsHorizontalScrollBar and height - 1 or height - local needsVerticalScrollBar = self.get("showScrollBar") and contentHeight > viewportHeight + local needsVerticalScrollBar = self.getResolved("showScrollBar") and contentHeight > viewportHeight local viewportWidth = needsVerticalScrollBar and width - 1 or width local scrollWidth = viewportWidth local handleSize = math.max(1, math.floor((viewportWidth / contentWidth) * scrollWidth)) @@ -325,7 +325,7 @@ end --- @protected function ScrollFrame:mouse_scroll(direction, x, y) if self:isInBounds(x, y) then - local xOffset, yOffset = self.get("offsetX"), self.get("offsetY") + local xOffset, yOffset = self.getResolved("offsetX"), self.getResolved("offsetY") local relX, relY = self:getRelativePosition(x + xOffset, y + yOffset) local success, child = self:callChildrenEvent(true, "mouse_scroll", direction, relX, relY) @@ -333,16 +333,16 @@ function ScrollFrame:mouse_scroll(direction, x, y) return true end - local height = self.get("height") - local width = self.get("width") - local offsetY = self.get("offsetY") - local offsetX = self.get("offsetX") - local contentWidth = self.get("contentWidth") - local contentHeight = self.get("contentHeight") + local height = self.getResolved("height") + local width = self.getResolved("width") + local offsetY = self.getResolved("offsetY") + local offsetX = self.getResolved("offsetX") + local contentWidth = self.getResolved("contentWidth") + local contentHeight = self.getResolved("contentHeight") - local needsHorizontalScrollBar = self.get("showScrollBar") and contentWidth > width + local needsHorizontalScrollBar = self.getResolved("showScrollBar") and contentWidth > width local viewportHeight = needsHorizontalScrollBar and height - 1 or height - local needsVerticalScrollBar = self.get("showScrollBar") and contentHeight > viewportHeight + local needsVerticalScrollBar = self.getResolved("showScrollBar") and contentHeight > viewportHeight local viewportWidth = needsVerticalScrollBar and width - 1 or width if needsVerticalScrollBar then @@ -366,13 +366,13 @@ end function ScrollFrame:render() Container.render(self) - local height = self.get("height") - local width = self.get("width") - local offsetY = self.get("offsetY") - local offsetX = self.get("offsetX") - local showScrollBar = self.get("showScrollBar") - local contentWidth = self.get("contentWidth") - local contentHeight = self.get("contentHeight") + local height = self.getResolved("height") + local width = self.getResolved("width") + local offsetY = self.getResolved("offsetY") + local offsetX = self.getResolved("offsetX") + local showScrollBar = self.getResolved("showScrollBar") + local contentWidth = self.getResolved("contentWidth") + local contentHeight = self.getResolved("contentHeight") local needsHorizontalScrollBar = showScrollBar and contentWidth > width local viewportHeight = needsHorizontalScrollBar and height - 1 or height local needsVerticalScrollBar = showScrollBar and contentHeight > viewportHeight @@ -382,10 +382,10 @@ function ScrollFrame:render() local scrollHeight = viewportHeight local handleSize = math.max(1, math.floor((viewportHeight / contentHeight) * scrollHeight)) local maxOffset = contentHeight - viewportHeight - local scrollBarBg = self.get("scrollBarBackgroundSymbol") - local scrollBarColor = self.get("scrollBarColor") - local scrollBarBgColor = self.get("scrollBarBackgroundColor") - local scrollBarBg2Color = self.get("scrollBarBackgroundColor2") + local scrollBarBg = self.getResolved("scrollBarBackgroundSymbol") + local scrollBarColor = self.getResolved("scrollBarColor") + local scrollBarBgColor = self.getResolved("scrollBarBackgroundColor") + local scrollBarBg2Color = self.getResolved("scrollBarBackgroundColor2") local currentPercent = maxOffset > 0 and (offsetY / maxOffset * 100) or 0 local handlePos = math.floor((currentPercent / 100) * (scrollHeight - handleSize)) + 1 @@ -403,10 +403,10 @@ function ScrollFrame:render() local scrollWidth = viewportWidth local handleSize = math.max(1, math.floor((viewportWidth / contentWidth) * scrollWidth)) local maxOffset = contentWidth - viewportWidth - local scrollBarBg = self.get("scrollBarBackgroundSymbol") - local scrollBarColor = self.get("scrollBarColor") - local scrollBarBgColor = self.get("scrollBarBackgroundColor") - local scrollBarBg2Color = self.get("scrollBarBackgroundColor2") + local scrollBarBg = self.getResolved("scrollBarBackgroundSymbol") + local scrollBarColor = self.getResolved("scrollBarColor") + local scrollBarBgColor = self.getResolved("scrollBarBackgroundColor") + local scrollBarBg2Color = self.getResolved("scrollBarBackgroundColor2") local currentPercent = maxOffset > 0 and (offsetX / maxOffset * 100) or 0 local handlePos = math.floor((currentPercent / 100) * (scrollWidth - handleSize)) + 1 @@ -421,7 +421,7 @@ function ScrollFrame:render() end if needsVerticalScrollBar and needsHorizontalScrollBar then - local background = self.get("background") + local background = self.getResolved("background") self:blit(width, height, " ", tHex[background], tHex[background]) end end diff --git a/src/elements/SideNav.lua b/src/elements/SideNav.lua index ef8089d..7eb3a7a 100644 --- a/src/elements/SideNav.lua +++ b/src/elements/SideNav.lua @@ -154,7 +154,7 @@ end --- @param title string The title of the navigation item --- @return table tabHandler The navigation item handler proxy for adding elements function SideNav:newTab(title) - local tabs = self.get("tabs") or {} + local tabs = self.getResolved("tabs") or {} local tabId = #tabs + 1 table.insert(tabs, { @@ -164,7 +164,7 @@ function SideNav:newTab(title) self.set("tabs", tabs) - if not self.get("activeTab") then + if not self.getResolved("activeTab") then self.set("activeTab", tabId) end self:updateTabVisibility() @@ -215,7 +215,7 @@ end --- @return table element The created element function SideNav:addElement(elementType, tabId) local element = Container.addElement(self, elementType) - local targetTab = tabId or self.get("activeTab") + local targetTab = tabId or self.getResolved("activeTab") if targetTab then element._tabId = targetTab self:updateTabVisibility() @@ -230,7 +230,7 @@ end function SideNav:addChild(child) Container.addChild(self, child) if not child._tabId then - local tabs = self.get("tabs") or {} + local tabs = self.getResolved("tabs") or {} if #tabs > 0 then child._tabId = 1 self:updateTabVisibility() @@ -249,7 +249,7 @@ end --- @shortDescription Sets the active navigation item --- @param tabId number The ID of the navigation item to activate function SideNav:setActiveTab(tabId) - local oldTab = self.get("activeTab") + local oldTab = self.getResolved("activeTab") if oldTab == tabId then return self end self.set("activeTab", tabId) self:updateTabVisibility() @@ -266,7 +266,7 @@ function SideNav:isChildVisible(child) return false end if child._tabId then - return child._tabId == self.get("activeTab") + return child._tabId == self.getResolved("activeTab") end return true end @@ -280,11 +280,11 @@ function SideNav:getContentXOffset() end function SideNav:_getSidebarMetrics() - local tabs = self.get("tabs") or {} - local height = self.get("height") or 1 - local sidebarWidth = self.get("sidebarWidth") or 12 - local scrollOffset = self.get("sidebarScrollOffset") or 0 - local sidebarPos = self.get("sidebarPosition") or "left" + local tabs = self.getResolved("tabs") or {} + local height = self.getResolved("height") or 1 + local sidebarWidth = self.getResolved("sidebarWidth") or 12 + local scrollOffset = self.getResolved("sidebarScrollOffset") or 0 + local sidebarPos = self.getResolved("sidebarPosition") or "left" local positions = {} local actualY = 1 @@ -348,7 +348,7 @@ function SideNav:mouse_click(button, x, y) local baseRelX, baseRelY = VisualElement.getRelativePosition(self, x, y) local metrics = self:_getSidebarMetrics() - local width = self.get("width") or 1 + local width = self.getResolved("width") or 1 local inSidebar = false if metrics.sidebarPosition == "right" then @@ -373,7 +373,7 @@ end function SideNav:getRelativePosition(x, y) local metrics = self:_getSidebarMetrics() - local width = self.get("width") or 1 + local width = self.getResolved("width") or 1 if x == nil or y == nil then return VisualElement.getRelativePosition(self) @@ -456,7 +456,7 @@ function SideNav:mouse_up(button, x, y) end local baseRelX, baseRelY = VisualElement.getRelativePosition(self, x, y) local metrics = self:_getSidebarMetrics() - local width = self.get("width") or 1 + local width = self.getResolved("width") or 1 local inSidebar = false if metrics.sidebarPosition == "right" then @@ -475,7 +475,7 @@ function SideNav:mouse_release(button, x, y) VisualElement.mouse_release(self, button, x, y) local baseRelX, baseRelY = VisualElement.getRelativePosition(self, x, y) local metrics = self:_getSidebarMetrics() - local width = self.get("width") or 1 + local width = self.getResolved("width") or 1 local inSidebar = false if metrics.sidebarPosition == "right" then @@ -494,7 +494,7 @@ function SideNav:mouse_move(_, x, y) if VisualElement.mouse_move(self, _, x, y) then local baseRelX, baseRelY = VisualElement.getRelativePosition(self, x, y) local metrics = self:_getSidebarMetrics() - local width = self.get("width") or 1 + local width = self.getResolved("width") or 1 local inSidebar = false if metrics.sidebarPosition == "right" then @@ -519,7 +519,7 @@ function SideNav:mouse_drag(button, x, y) if VisualElement.mouse_drag(self, button, x, y) then local baseRelX, baseRelY = VisualElement.getRelativePosition(self, x, y) local metrics = self:_getSidebarMetrics() - local width = self.get("width") or 1 + local width = self.getResolved("width") or 1 local inSidebar = false if metrics.sidebarPosition == "right" then @@ -542,7 +542,7 @@ end --- @return SideNav self For method chaining function SideNav:scrollSidebar(direction) local metrics = self:_getSidebarMetrics() - local currentOffset = self.get("sidebarScrollOffset") or 0 + local currentOffset = self.getResolved("sidebarScrollOffset") or 0 local maxScroll = metrics.maxScroll or 0 local newOffset = currentOffset + (direction * 2) @@ -556,7 +556,7 @@ function SideNav:mouse_scroll(direction, x, y) if VisualElement.mouse_scroll(self, direction, x, y) then local baseRelX, baseRelY = VisualElement.getRelativePosition(self, x, y) local metrics = self:_getSidebarMetrics() - local width = self.get("width") or 1 + local width = self.getResolved("width") or 1 local inSidebar = false if metrics.sidebarPosition == "right" then @@ -603,23 +603,25 @@ end --- @protected function SideNav:render() VisualElement.render(self) - local height = self.get("height") + local height = self.getResolved("height") + local foreground = self.getResolved("foreground") + local sidebarBackground = self.getResolved("sidebarBackground") local metrics = self:_getSidebarMetrics() local sidebarW = metrics.sidebarWidth or 12 for y = 1, height do - VisualElement.multiBlit(self, 1, y, sidebarW, 1, " ", tHex[self.get("foreground")], tHex[self.get("sidebarBackground")]) + VisualElement.multiBlit(self, 1, y, sidebarW, 1, " ", tHex[foreground], tHex[sidebarBackground]) end - local activeTab = self.get("activeTab") + local activeTab = self.getResolved("activeTab") for _, pos in ipairs(metrics.positions) do - local bgColor = (pos.id == activeTab) and self.get("activeTabBackground") or self.get("sidebarBackground") - local fgColor = (pos.id == activeTab) and self.get("activeTabTextColor") or self.get("foreground") + local bgColor = (pos.id == activeTab) and self.getResolved("activeTabBackground") or sidebarBackground + local fgColor = (pos.id == activeTab) and self.getResolved("activeTabTextColor") or foreground local itemHeight = pos.displayHeight or (pos.y2 - pos.y1 + 1) for dy = 0, itemHeight - 1 do - VisualElement.multiBlit(self, 1, pos.y1 + dy, sidebarW, 1, " ", tHex[self.get("foreground")], tHex[bgColor]) + VisualElement.multiBlit(self, 1, pos.y1 + dy, sidebarW, 1, " ", tHex[foreground], tHex[bgColor]) end local displayTitle = pos.title @@ -630,16 +632,16 @@ function SideNav:render() VisualElement.textFg(self, 2, pos.y1, displayTitle, fgColor) end - if not self.get("childrenSorted") then + if not self.getResolved("childrenSorted") then self:sortChildren() end - if not self.get("childrenEventsSorted") then + if not self.getResolved("childrenEventsSorted") then for eventName in pairs(self._values.childrenEvents or {}) do self:sortChildrenEvents(eventName) end end - for _, child in ipairs(self.get("visibleChildren") or {}) do + for _, child in ipairs(self.getResolved("visibleChildren") or {}) do if child == self then error("CIRCULAR REFERENCE DETECTED!") return end child:render() child:postRender() diff --git a/src/elements/Slider.lua b/src/elements/Slider.lua index fe73f9a..19787bc 100644 --- a/src/elements/Slider.lua +++ b/src/elements/Slider.lua @@ -59,9 +59,9 @@ end --- @return number value The current value (0 to max) --- @usage local value = slider:getValue() function Slider:getValue() - local step = self.get("step") - local max = self.get("max") - local maxSteps = self.get("horizontal") and self.get("width") or self.get("height") + local step = self.getResolved("step") + local max = self.getResolved("max") + local maxSteps = self.getResolved("horizontal") and self.getResolved("width") or self.getResolved("height") return math.floor((step - 1) * (max / (maxSteps - 1))) end @@ -74,8 +74,8 @@ end function Slider:mouse_click(button, x, y) if self:isInBounds(x, y) then local relX, relY = self:getRelativePosition(x, y) - local pos = self.get("horizontal") and relX or relY - local maxSteps = self.get("horizontal") and self.get("width") or self.get("height") + local pos = self.getResolved("horizontal") and relX or relY + local maxSteps = self.getResolved("horizontal") and self.getResolved("width") or self.getResolved("height") self.set("step", math.min(maxSteps, math.max(1, pos))) self:updateRender() @@ -93,8 +93,8 @@ Slider.mouse_drag = Slider.mouse_click --- @protected function Slider:mouse_scroll(direction, x, y) if self:isInBounds(x, y) then - local step = self.get("step") - local maxSteps = self.get("horizontal") and self.get("width") or self.get("height") + local step = self.getResolved("step") + local maxSteps = self.getResolved("horizontal") and self.getResolved("width") or self.getResolved("height") self.set("step", math.min(maxSteps, math.max(1, step + direction))) self:updateRender() return true @@ -106,23 +106,23 @@ end --- @protected function Slider:render() VisualElement.render(self) - local width = self.get("width") - local height = self.get("height") - local horizontal = self.get("horizontal") - local step = self.get("step") + local width = self.getResolved("width") + local height = self.getResolved("height") + local horizontal = self.getResolved("horizontal") + local step = self.getResolved("step") local barChar = horizontal and "\140" or " " local text = string.rep(barChar, horizontal and width or height) if horizontal then - self:textFg(1, 1, text, self.get("barColor")) - self:textBg(step, 1, " ", self.get("sliderColor")) + self:textFg(1, 1, text, self.getResolved("barColor")) + self:textBg(step, 1, " ", self.getResolved("sliderColor")) else - local bg = self.get("background") + local bg = self.getResolved("background") for y = 1, height do self:textBg(1, y, " ", bg) end - self:textBg(1, step, " ", self.get("sliderColor")) + self:textBg(1, step, " ", self.getResolved("sliderColor")) end end diff --git a/src/elements/Switch.lua b/src/elements/Switch.lua index a0d8c5d..e8979c6 100644 --- a/src/elements/Switch.lua +++ b/src/elements/Switch.lua @@ -53,7 +53,7 @@ end --- @protected function Switch:mouse_click(button, x, y) if VisualElement.mouse_click(self, button, x, y) then - self.set("checked", not self.get("checked")) + self.set("checked", not self.getResolved("checked")) return true end return false @@ -62,20 +62,21 @@ end --- @shortDescription Renders the Switch --- @protected function Switch:render() - local checked = self.get("checked") - local text = self.get("text") - local switchWidth = self.get("width") - local switchHeight = self.get("height") + local checked = self.getResolved("checked") + local text = self.getResolved("text") + local switchWidth = self.getResolved("width") + local switchHeight = self.getResolved("height") + local foreground = self.getResolved("foreground") - local bgColor = checked and self.get("onBackground") or self.get("offBackground") - self:multiBlit(1, 1, switchWidth, switchHeight, " ", tHex[self.get("foreground")], tHex[bgColor]) + local bgColor = checked and self.getResolved("onBackground") or self.getResolved("offBackground") + self:multiBlit(1, 1, switchWidth, switchHeight, " ", tHex[foreground], tHex[bgColor]) local sliderSize = math.floor(switchWidth / 2) local sliderStart = checked and (switchWidth - sliderSize + 1) or 1 - self:multiBlit(sliderStart, 1, sliderSize, switchHeight, " ", tHex[self.get("foreground")], tHex[self.get("background")]) + self:multiBlit(sliderStart, 1, sliderSize, switchHeight, " ", tHex[foreground], tHex[self.getResolved("background")]) if text ~= "" then - self:textFg(switchWidth + 2, 1, text, self.get("foreground")) + self:textFg(switchWidth + 2, 1, text, foreground) end end diff --git a/src/elements/TabControl.lua b/src/elements/TabControl.lua index 7ed7d53..af2213c 100644 --- a/src/elements/TabControl.lua +++ b/src/elements/TabControl.lua @@ -155,7 +155,7 @@ end --- @param title string The title of the tab --- @return table tabHandler The tab handler proxy for adding elements to the new tab function TabControl:newTab(title) - local tabs = self.get("tabs") or {} + local tabs = self.getResolved("tabs") or {} local tabId = #tabs + 1 table.insert(tabs, { @@ -165,7 +165,7 @@ function TabControl:newTab(title) self.set("tabs", tabs) - if not self.get("activeTab") then + if not self.getResolved("activeTab") then self.set("activeTab", tabId) end self:updateTabVisibility() @@ -216,7 +216,7 @@ end --- @return table element The created element function TabControl:addElement(elementType, tabId) local element = Container.addElement(self, elementType) - local targetTab = tabId or self.get("activeTab") + local targetTab = tabId or self.getResolved("activeTab") if targetTab then element._tabId = targetTab self:updateTabVisibility() @@ -231,7 +231,7 @@ end function TabControl:addChild(child) Container.addChild(self, child) if not child._tabId then - local tabs = self.get("tabs") or {} + local tabs = self.getResolved("tabs") or {} if #tabs > 0 then child._tabId = 1 self:updateTabVisibility() @@ -250,7 +250,7 @@ end --- @shortDescription Sets the active tab --- @param tabId number The ID of the tab to activate function TabControl:setActiveTab(tabId) - local oldTab = self.get("activeTab") + local oldTab = self.getResolved("activeTab") if oldTab == tabId then return self end self.set("activeTab", tabId) self:updateTabVisibility() @@ -267,7 +267,7 @@ function TabControl:isChildVisible(child) return false end if child._tabId then - return child._tabId == self.get("activeTab") + return child._tabId == self.getResolved("activeTab") end return true end @@ -281,15 +281,15 @@ function TabControl:getContentYOffset() end function TabControl:_getHeaderMetrics() - local tabs = self.get("tabs") or {} - local width = self.get("width") or 1 - local minTabH = self.get("tabHeight") or 1 - local scrollable = self.get("scrollableTab") + local tabs = self.getResolved("tabs") or {} + local width = self.getResolved("width") or 1 + local minTabH = self.getResolved("tabHeight") or 1 + local scrollable = self.getResolved("scrollableTab") local positions = {} if scrollable then - local scrollOffset = self.get("tabScrollOffset") or 0 + local scrollOffset = self.getResolved("tabScrollOffset") or 0 local actualX = 1 local totalWidth = 0 @@ -500,10 +500,10 @@ end --- @param direction number -1 to scroll left, 1 to scroll right --- @return TabControl self For method chaining function TabControl:scrollTabs(direction) - if not self.get("scrollableTab") then return self end + if not self.getResolved("scrollableTab") then return self end local metrics = self:_getHeaderMetrics() - local currentOffset = self.get("tabScrollOffset") or 0 + local currentOffset = self.getResolved("tabScrollOffset") or 0 local maxScroll = metrics.maxScroll or 0 local newOffset = currentOffset + (direction * 5) @@ -517,7 +517,7 @@ function TabControl:mouse_scroll(direction, x, y) if VisualElement.mouse_scroll(self, direction, x, y) then local headerH = self:_getHeaderMetrics().headerHeight - if self.get("scrollableTab") and y == self.get("y") then + if self.getResolved("scrollableTab") and y == self.getResolved("y") then self:scrollTabs(direction) return true end @@ -549,18 +549,20 @@ end --- @protected function TabControl:render() VisualElement.render(self) - local width = self.get("width") + local width = self.getResolved("width") + local foreground = self.getResolved("foreground") + local headerBackground = self.getResolved("headerBackground") local metrics = self:_getHeaderMetrics() local headerH = metrics.headerHeight or 1 - VisualElement.multiBlit(self, 1, 1, width, headerH, " ", tHex[self.get("foreground")], tHex[self.get("headerBackground")]) - local activeTab = self.get("activeTab") + VisualElement.multiBlit(self, 1, 1, width, headerH, " ", tHex[foreground], tHex[headerBackground]) + local activeTab = self.getResolved("activeTab") for _, pos in ipairs(metrics.positions) do - local bgColor = (pos.id == activeTab) and self.get("activeTabBackground") or self.get("headerBackground") - local fgColor = (pos.id == activeTab) and self.get("activeTabTextColor") or self.get("foreground") + local bgColor = (pos.id == activeTab) and self.getResolved("activeTabBackground") or headerBackground + local fgColor = (pos.id == activeTab) and self.getResolved("activeTabTextColor") or foreground - VisualElement.multiBlit(self, pos.x1, pos.line, pos.displayWidth or (pos.x2 - pos.x1 + 1), 1, " ", tHex[self.get("foreground")], tHex[bgColor]) + VisualElement.multiBlit(self, pos.x1, pos.line, pos.displayWidth or (pos.x2 - pos.x1 + 1), 1, " ", tHex[foreground], tHex[bgColor]) local displayTitle = pos.title local textStartInTitle = 1 + (pos.startClip or 0) @@ -576,16 +578,16 @@ function TabControl:render() end end - if not self.get("childrenSorted") then + if not self.getResolved("childrenSorted") then self:sortChildren() end - if not self.get("childrenEventsSorted") then + if not self.getResolved("childrenEventsSorted") then for eventName in pairs(self._values.childrenEvents or {}) do self:sortChildrenEvents(eventName) end end - for _, child in ipairs(self.get("visibleChildren") or {}) do + for _, child in ipairs(self.getResolved("visibleChildren") or {}) do if child == self then error("CIRCULAR REFERENCE DETECTED!") return end child:render() child:postRender() diff --git a/src/elements/Table.lua b/src/elements/Table.lua index 5133332..14aba8e 100644 --- a/src/elements/Table.lua +++ b/src/elements/Table.lua @@ -70,7 +70,7 @@ Table.defineProperty(Table, "offset", { type = "number", canTriggerRender = true, setter = function(self, value) - local maxOffset = math.max(0, #self.get("items") - (self.get("height") - 1)) + local maxOffset = math.max(0, #self.getResolved("items") - (self.getResolved("height") - 1)) return math.min(maxOffset, math.max(0, value)) end }) @@ -127,8 +127,8 @@ function Table:init(props, basalt) self.set("type", "Table") self:observe("sortColumn", function() - if self.get("sortColumn") then - self:sortByColumn(self.get("sortColumn")) + if self.getResolved("sortColumn") then + self:sortByColumn(self.getResolved("sortColumn")) end end) @@ -155,7 +155,7 @@ end --- @param rowIndex number The index of the row to remove --- @return Table self The Table instance function Table:removeRow(rowIndex) - local items = self.get("items") + local items = self.getResolved("items") if items[rowIndex] then table.remove(items, rowIndex) self.set("items", items) @@ -168,7 +168,7 @@ end --- @param rowIndex number The index of the row --- @return table? row The row data or nil function Table:getRow(rowIndex) - local items = self.get("items") + local items = self.getResolved("items") return items[rowIndex] end @@ -179,7 +179,7 @@ end --- @param value any The new value --- @return Table self The Table instance function Table:updateCell(rowIndex, colIndex, value) - local items = self.get("items") + local items = self.getResolved("items") if items[rowIndex] and items[rowIndex].cells then items[rowIndex].cells[colIndex] = value self.set("items", items) @@ -191,7 +191,7 @@ end --- @shortDescription Gets the currently selected row data --- @return table? row The selected row or nil function Table:getSelectedRow() - local items = self.get("items") + local items = self.getResolved("items") for _, item in ipairs(items) do local isSelected = item._data and item._data.selected or item.selected if isSelected then @@ -215,7 +215,7 @@ end --- @param width number|string The width of the column (number, "auto", or "30%") --- @return Table self The Table instance function Table:addColumn(name, width) - local columns = self.get("columns") + local columns = self.getResolved("columns") table.insert(columns, {name = name, width = width}) self.set("columns", columns) return self @@ -227,7 +227,7 @@ end --- @param sortFn function Function that takes (rowA, rowB) and returns comparison result --- @return Table self The Table instance function Table:setColumnSortFunction(columnIndex, sortFn) - local customSorts = self.get("customSortFunction") + local customSorts = self.getResolved("customSortFunction") customSorts[columnIndex] = sortFn self.set("customSortFunction", customSorts) return self @@ -270,7 +270,7 @@ end --- @shortDescription Gets all rows as array of cell arrays --- @return table data Array of row cell arrays function Table:getData() - local items = self.get("items") + local items = self.getResolved("items") local data = {} for _, item in ipairs(items) do @@ -359,9 +359,9 @@ end --- @param fn function? Optional custom sorting function --- @return Table self The Table instance function Table:sortByColumn(columnIndex, fn) - local items = self.get("items") - local direction = self.get("sortDirection") - local customSorts = self.get("customSortFunction") + local items = self.getResolved("items") + local direction = self.getResolved("sortDirection") + local customSorts = self.getResolved("customSortFunction") local sortFn = fn or customSorts[columnIndex] @@ -429,10 +429,10 @@ function Table:mouse_click(button, x, y) if not Collection.mouse_click(self, button, x, y) then return false end local relX, relY = self:getRelativePosition(x, y) - local width = self.get("width") - local height = self.get("height") - local items = self.get("items") - local showScrollBar = self.get("showScrollBar") + local width = self.getResolved("width") + local height = self.getResolved("height") + local items = self.getResolved("items") + local showScrollBar = self.getResolved("showScrollBar") local visibleRows = height - 1 if showScrollBar and #items > visibleRows and relX == width and relY > 1 then @@ -440,7 +440,7 @@ function Table:mouse_click(button, x, y) local maxOffset = #items - visibleRows local handleSize = math.max(1, math.floor((visibleRows / #items) * scrollBarHeight)) - local currentPercent = maxOffset > 0 and (self.get("offset") / maxOffset * 100) or 0 + local currentPercent = maxOffset > 0 and (self.getResolved("offset") / maxOffset * 100) or 0 local handlePos = math.floor((currentPercent / 100) * (scrollBarHeight - handleSize)) + 1 local scrollBarRelY = relY - 1 @@ -457,15 +457,15 @@ function Table:mouse_click(button, x, y) end if relY == 1 then - local columns = self.get("columns") + local columns = self.getResolved("columns") local calculatedColumns = self:calculateColumnWidths(columns, width) local currentX = 1 for i, col in ipairs(calculatedColumns) do local colWidth = col.visibleWidth or col.width or 10 if relX >= currentX and relX < currentX + colWidth then - if self.get("sortColumn") == i then - self.set("sortDirection", self.get("sortDirection") == "asc" and "desc" or "asc") + if self.getResolved("sortColumn") == i then + self.set("sortDirection", self.getResolved("sortDirection") == "asc" and "desc" or "asc") else self.set("sortColumn", i) self.set("sortDirection", "asc") @@ -480,7 +480,7 @@ function Table:mouse_click(button, x, y) end if relY > 1 then - local rowIndex = relY - 2 + self.get("offset") + local rowIndex = relY - 2 + self.getResolved("offset") if rowIndex >= 0 and rowIndex < #items then local actualIndex = rowIndex + 1 @@ -514,8 +514,8 @@ end function Table:mouse_drag(button, x, y) if self._scrollBarDragging then local _, relY = self:getRelativePosition(x, y) - local items = self.get("items") - local height = self.get("height") + local items = self.getResolved("items") + local height = self.getResolved("height") local visibleRows = height - 1 local scrollBarHeight = height - 1 local handleSize = math.max(1, math.floor((visibleRows / #items) * scrollBarHeight)) @@ -549,11 +549,11 @@ end --- @protected function Table:mouse_scroll(direction, x, y) if Collection.mouse_scroll(self, direction, x, y) then - local items = self.get("items") - local height = self.get("height") + local items = self.getResolved("items") + local height = self.getResolved("height") local visibleRows = height - 1 -- Subtract header local maxOffset = math.max(0, #items - visibleRows) - local newOffset = math.min(maxOffset, math.max(0, self.get("offset") + direction)) + local newOffset = math.min(maxOffset, math.max(0, self.getResolved("offset") + direction)) self.set("offset", newOffset) self:updateRender() @@ -570,9 +570,11 @@ function Table:render() local items = self.getResolved("items") local sortCol = self.getResolved("sortColumn") local offset = self.getResolved("offset") - local height = self.get("height") - local width = self.get("width") + local height = self.getResolved("height") + local width = self.getResolved("width") local showScrollBar = self.getResolved("showScrollBar") + local background = self.getResolved("background") + local foreground = self.getResolved("foreground") local visibleRows = height - 1 local needsScrollBar = showScrollBar and #items > visibleRows @@ -595,14 +597,14 @@ function Table:render() if i > lastVisibleColumn then break end local text = col.name if i == sortCol then - text = text .. (self.get("sortDirection") == "asc" and "\30" or "\31") + text = text .. (self.getResolved("sortDirection") == "asc" and "\30" or "\31") end - self:textFg(currentX, 1, text:sub(1, col.visibleWidth), self.get("headerColor")) + self:textFg(currentX, 1, text:sub(1, col.visibleWidth), self.getResolved("headerColor")) currentX = currentX + col.visibleWidth end if currentX <= contentWidth then - self:textBg(currentX, 1, string.rep(" ", contentWidth - currentX + 1), self.get("background")) + self:textBg(currentX, 1, string.rep(" ", contentWidth - currentX + 1), background) end for y = 2, height do @@ -615,7 +617,7 @@ function Table:render() if cells then currentX = 1 - local bg = isSelected and self.get("selectedBackground") or self.get("background") + local bg = isSelected and self.getResolved("selectedBackground") or background for i, col in ipairs(calculatedColumns) do if i > lastVisibleColumn then break end @@ -625,7 +627,7 @@ function Table:render() paddedText = string.sub(paddedText, 1, col.visibleWidth - 1) .. " " end local finalText = string.sub(paddedText, 1, col.visibleWidth) - local finalForeground = string.rep(tHex[self.get("foreground")], col.visibleWidth) + local finalForeground = string.rep(tHex[foreground], col.visibleWidth) local finalBackground = string.rep(tHex[bg], col.visibleWidth) self:blit(currentX, y, finalText, finalForeground, finalBackground) @@ -638,8 +640,8 @@ function Table:render() end else self:blit(1, y, string.rep(" ", contentWidth), - string.rep(tHex[self.get("foreground")], contentWidth), - string.rep(tHex[self.get("background")], contentWidth)) + string.rep(tHex[foreground], contentWidth), + string.rep(tHex[background], contentWidth)) end end @@ -655,7 +657,6 @@ function Table:render() local scrollBarBg = self.getResolved("scrollBarBackground") local scrollBarColor = self.getResolved("scrollBarColor") local scrollBarBgColor = self.getResolved("scrollBarBackgroundColor") - local foreground = self.getResolved("foreground") for i = 2, height do self:blit(width, i, scrollBarBg, tHex[foreground], tHex[scrollBarBgColor]) diff --git a/src/elements/TextBox.lua b/src/elements/TextBox.lua index b5da16e..f036427 100644 --- a/src/elements/TextBox.lua +++ b/src/elements/TextBox.lua @@ -94,20 +94,20 @@ local function autoCompleteVisible(self) end local function getBorderPadding(self) - return self.get("autoCompleteShowBorder") and 1 or 0 + return self.getResolved("autoCompleteShowBorder") and 1 or 0 end local function updateAutoCompleteStyles(self) local frame = self._autoCompleteFrame local list = self._autoCompleteList if not frame or frame._destroyed then return end - frame:setBackground(self.get("autoCompleteBackground")) - frame:setForeground(self.get("autoCompleteForeground")) + frame:setBackground(self.getResolved("autoCompleteBackground")) + frame:setForeground(self.getResolved("autoCompleteForeground")) if list and not list._destroyed then - list:setBackground(self.get("autoCompleteBackground")) - list:setForeground(self.get("autoCompleteForeground")) - list:setSelectedBackground(self.get("autoCompleteSelectedBackground")) - list:setSelectedForeground(self.get("autoCompleteSelectedForeground")) + list:setBackground(self.getResolved("autoCompleteBackground")) + list:setForeground(self.getResolved("autoCompleteForeground")) + list:setSelectedBackground(self.getResolved("autoCompleteSelectedBackground")) + list:setSelectedForeground(self.getResolved("autoCompleteSelectedForeground")) list:updateRender() end layoutAutoCompleteList(self) @@ -165,9 +165,9 @@ local function applyAutoCompleteSelection(self, item) local insertText = entry.insert or entry.text or "" if insertText == "" then return end - local lines = self.get("lines") - local cursorY = self.get("cursorY") - local cursorX = self.get("cursorX") + local lines = self.getResolved("lines") + local cursorY = self.getResolved("cursorY") + local cursorX = self.getResolved("cursorX") local line = lines[cursorY] or "" local startIndex = self._autoCompleteTokenStart or cursorX if startIndex < 1 then startIndex = 1 end @@ -184,7 +184,7 @@ local function applyAutoCompleteSelection(self, item) end local function ensureAutoCompleteUI(self) - if not self.get("autoCompleteEnabled") then return nil end + if not self.getResolved("autoCompleteEnabled") then return nil end local frame = self._autoCompleteFrame if frame and not frame._destroyed then return self._autoCompleteList @@ -194,15 +194,15 @@ local function ensureAutoCompleteUI(self) if not base or not base.addFrame then return nil end frame = base:addFrame({ - width = self.get("width"), + width = self.getResolved("width"), height = 1, x = 1, y = 1, visible = false, - background = self.get("autoCompleteBackground"), - foreground = self.get("autoCompleteForeground"), + background = self.getResolved("autoCompleteBackground"), + foreground = self.getResolved("autoCompleteForeground"), ignoreOffset = true, - z = self.get("z") + self.get("autoCompleteZOffset"), + z = self.getResolved("z") + self.getResolved("autoCompleteZOffset"), }) frame:setIgnoreOffset(true) frame:setVisible(false) @@ -215,16 +215,16 @@ local function ensureAutoCompleteUI(self) height = math.max(1, frame.get("height") - padding * 2), selectable = true, multiSelection = false, - background = self.get("autoCompleteBackground"), - foreground = self.get("autoCompleteForeground"), + background = self.getResolved("autoCompleteBackground"), + foreground = self.getResolved("autoCompleteForeground"), }) - list:setSelectedBackground(self.get("autoCompleteSelectedBackground")) - list:setSelectedForeground(self.get("autoCompleteSelectedForeground")) + list:setSelectedBackground(self.getResolved("autoCompleteSelectedBackground")) + list:setSelectedForeground(self.getResolved("autoCompleteSelectedForeground")) list:setOffset(0) list:onSelect(function(_, index, selectedItem) if not autoCompleteVisible(self) then return end setAutoCompleteSelection(self, index) - if self.get("autoCompleteAcceptOnClick") then + if self.getResolved("autoCompleteAcceptOnClick") then applyAutoCompleteSelection(self, selectedItem) end end) @@ -272,12 +272,12 @@ updateAutoCompleteBorder = function(self) frame._autoCompleteBorderCommand = nil end - if not self.get("autoCompleteShowBorder") then + if not self.getResolved("autoCompleteShowBorder") then frame:updateRender() return end - local borderColor = self.get("autoCompleteBorderColor") or colors.black + local borderColor = self.getResolved("autoCompleteBorderColor") or colors.black local commandIndex = canvas:addCommand(function(element) local width = element.get("width") or 0 @@ -303,12 +303,12 @@ updateAutoCompleteBorder = function(self) end local function getTokenInfo(self) - local lines = self.get("lines") - local cursorY = self.get("cursorY") - local cursorX = self.get("cursorX") + local lines = self.getResolved("lines") + local cursorY = self.getResolved("cursorY") + local cursorX = self.getResolved("cursorX") local line = lines[cursorY] or "" local uptoCursor = line:sub(1, math.max(cursorX - 1, 0)) - local pattern = self.get("autoCompleteTokenPattern") or "[%w_]+" + local pattern = self.getResolved("autoCompleteTokenPattern") or "[%w_]+" local token = "" if pattern ~= "" then @@ -355,7 +355,7 @@ local function iterateSuggestions(source, handler) end local function gatherSuggestions(self, token) - local provider = self.get("autoCompleteProvider") + local provider = self.getResolved("autoCompleteProvider") local source = {} if provider then local ok, result = pcall(provider, self, token) @@ -363,11 +363,11 @@ local function gatherSuggestions(self, token) source = result end else - source = self.get("autoCompleteItems") or {} + source = self.getResolved("autoCompleteItems") or {} end local suggestions = {} - local caseInsensitive = self.get("autoCompleteCaseInsensitive") + local caseInsensitive = self.getResolved("autoCompleteCaseInsensitive") local target = caseInsensitive and token:lower() or token iterateSuggestions(source, function(entry) local normalized = normalizeSuggestion(entry) @@ -379,7 +379,7 @@ local function gatherSuggestions(self, token) end end) - local maxItems = self.get("autoCompleteMaxItems") + local maxItems = self.getResolved("autoCompleteMaxItems") if #suggestions > maxItems then while #suggestions > maxItems do table.remove(suggestions) @@ -403,8 +403,8 @@ local function measureSuggestionWidth(self, suggestions) end end - local limit = self.get("autoCompleteMaxWidth") - local maxWidth = self.get("width") + local limit = self.getResolved("autoCompleteMaxWidth") + local maxWidth = self.getResolved("width") if limit and limit > 0 then maxWidth = math.min(maxWidth, limit) end @@ -430,7 +430,7 @@ local function placeAutoCompleteFrame(self, visibleCount, width) local list = self._autoCompleteList if not frame or frame._destroyed then return end local border = getBorderPadding(self) - local contentWidth = math.max(1, width or self.get("width")) + local contentWidth = math.max(1, width or self.getResolved("width")) local contentHeight = math.max(1, visibleCount or 1) local base = self:getBaseFrame() @@ -457,17 +457,17 @@ local function placeAutoCompleteFrame(self, visibleCount, width) local frameWidth = contentWidth + border * 2 local frameHeight = contentHeight + border * 2 local originX, originY = self:calculatePosition() - local scrollX = self.get("scrollX") or 0 - local scrollY = self.get("scrollY") or 0 - local tokenStart = (self._autoCompleteTokenStart or self.get("cursorX")) + local scrollX = self.getResolved("scrollX") or 0 + local scrollY = self.getResolved("scrollY") or 0 + local tokenStart = (self._autoCompleteTokenStart or self.getResolved("cursorX")) local column = tokenStart - scrollX - column = math.max(1, math.min(self.get("width"), column)) + column = math.max(1, math.min(self.getResolved("width"), column)) - local cursorRow = self.get("cursorY") - scrollY - cursorRow = math.max(1, math.min(self.get("height"), cursorRow)) + local cursorRow = self.getResolved("cursorY") - scrollY + cursorRow = math.max(1, math.min(self.getResolved("height"), cursorRow)) - local offsetX = self.get("autoCompleteOffsetX") - local offsetY = self.get("autoCompleteOffsetY") + local offsetX = self.getResolved("autoCompleteOffsetX") + local offsetY = self.getResolved("autoCompleteOffsetY") local baseX = originX + column - 1 + offsetX local x = baseX - border @@ -520,7 +520,7 @@ local function placeAutoCompleteFrame(self, visibleCount, width) frame:setPosition(x, y) frame:setWidth(frameWidth) frame:setHeight(frameHeight) - frame:setZ(self.get("z") + self.get("autoCompleteZOffset")) + frame:setZ(self.getResolved("z") + self.getResolved("autoCompleteZOffset")) layoutAutoCompleteList(self, contentWidth, contentHeight) @@ -531,7 +531,7 @@ local function placeAutoCompleteFrame(self, visibleCount, width) end local function refreshAutoComplete(self) - if not self.get("autoCompleteEnabled") then + if not self.getResolved("autoCompleteEnabled") then hideAutoComplete(self, true) return end @@ -544,7 +544,7 @@ local function refreshAutoComplete(self) self._autoCompleteToken = token self._autoCompleteTokenStart = startIndex - if #token < self.get("autoCompleteMinChars") then + if #token < self.getResolved("autoCompleteMinChars") then hideAutoComplete(self) return end @@ -576,7 +576,7 @@ end local function handleAutoCompleteKey(self, key) if not autoCompleteVisible(self) then return false end - if key == keys.tab or (key == keys.enter and self.get("autoCompleteAcceptOnEnter")) then + if key == keys.tab or (key == keys.enter and self.getResolved("autoCompleteAcceptOnEnter")) then applyAutoCompleteSelection(self) return true elseif key == keys.up then @@ -593,7 +593,7 @@ local function handleAutoCompleteKey(self, key) local height = (self._autoCompleteList and self._autoCompleteList.get("height")) or 1 setAutoCompleteSelection(self, (self._autoCompleteIndex or 1) + height) return true - elseif key == keys.escape and self.get("autoCompleteCloseOnEscape") then + elseif key == keys.escape and self.getResolved("autoCompleteCloseOnEscape") then hideAutoComplete(self) return true end @@ -647,7 +647,7 @@ function TextBox:init(props, basalt) self.set("type", "TextBox") local function refreshIfEnabled() - if self.get("autoCompleteEnabled") and self:hasState("focused") then + if self.getResolved("autoCompleteEnabled") and self:hasState("focused") then refreshAutoComplete(self) end end @@ -659,7 +659,7 @@ function TextBox:init(props, basalt) local function reposition() if autoCompleteVisible(self) then local suggestions = rawget(self, "_autoCompleteSuggestions") or {} - placeAutoCompleteFrame(self, math.max(#suggestions, 1), rawget(self, "_autoCompletePopupWidth") or self.get("width")) + placeAutoCompleteFrame(self, math.max(#suggestions, 1), rawget(self, "_autoCompletePopupWidth") or self.getResolved("width")) end end @@ -690,12 +690,12 @@ function TextBox:init(props, basalt) self:observe("autoCompleteZOffset", function() if self._autoCompleteFrame and not self._autoCompleteFrame._destroyed then - self._autoCompleteFrame:setZ(self.get("z") + self.get("autoCompleteZOffset")) + self._autoCompleteFrame:setZ(self.getResolved("z") + self.getResolved("autoCompleteZOffset")) end end) self:observe("z", function() if self._autoCompleteFrame and not self._autoCompleteFrame._destroyed then - self._autoCompleteFrame:setZ(self.get("z") + self.get("autoCompleteZOffset")) + self._autoCompleteFrame:setZ(self.getResolved("z") + self.getResolved("autoCompleteZOffset")) end end) @@ -749,7 +749,7 @@ end --- @param color number The color to apply --- @return TextBox self The TextBox instance function TextBox:addSyntaxPattern(pattern, color) - table.insert(self.get("syntaxPatterns"), {pattern = pattern, color = color}) + table.insert(self.getResolved("syntaxPatterns"), {pattern = pattern, color = color}) return self end @@ -757,7 +757,7 @@ end --- @param index number The index of the pattern to remove --- @return TextBox self function TextBox:removeSyntaxPattern(index) - local patterns = self.get("syntaxPatterns") or {} + local patterns = self.getResolved("syntaxPatterns") or {} if type(index) ~= "number" then return self end if index >= 1 and index <= #patterns then table.remove(patterns, index) @@ -776,9 +776,9 @@ function TextBox:clearSyntaxPatterns() end local function insertChar(self, char) - local lines = self.get("lines") - local cursorX = self.get("cursorX") - local cursorY = self.get("cursorY") + local lines = self.getResolved("lines") + local cursorX = self.getResolved("cursorX") + local cursorY = self.getResolved("cursorY") local currentLine = lines[cursorY] lines[cursorY] = currentLine:sub(1, cursorX-1) .. char .. currentLine:sub(cursorX) self.set("cursorX", cursorX + 1) @@ -793,9 +793,9 @@ local function insertText(self, text) end local function newLine(self) - local lines = self.get("lines") - local cursorX = self.get("cursorX") - local cursorY = self.get("cursorY") + local lines = self.getResolved("lines") + local cursorX = self.getResolved("cursorX") + local cursorY = self.getResolved("cursorY") local currentLine = lines[cursorY] local restOfLine = currentLine:sub(cursorX) @@ -809,9 +809,9 @@ local function newLine(self) end local function backspace(self) - local lines = self.get("lines") - local cursorX = self.get("cursorX") - local cursorY = self.get("cursorY") + local lines = self.getResolved("lines") + local cursorX = self.getResolved("cursorX") + local cursorY = self.getResolved("cursorY") local currentLine = lines[cursorY] if cursorX > 1 then @@ -832,12 +832,12 @@ end --- @shortDescription Updates the viewport to keep the cursor in view --- @return TextBox self The TextBox instance function TextBox:updateViewport() - local cursorX = self.get("cursorX") - local cursorY = self.get("cursorY") - local scrollX = self.get("scrollX") - local scrollY = self.get("scrollY") - local width = self.get("width") - local height = self.get("height") + local cursorX = self.getResolved("cursorX") + local cursorY = self.getResolved("cursorY") + local scrollX = self.getResolved("scrollX") + local scrollY = self.getResolved("scrollY") + local width = self.getResolved("width") + local height = self.getResolved("height") -- Horizontal scrolling if cursorX - scrollX > width then @@ -860,14 +860,14 @@ end --- @return boolean handled Whether the event was handled --- @protected function TextBox:char(char) - if not self.get("editable") or not self:hasState("focused") then return false end + if not self.getResolved("editable") or not self:hasState("focused") then return false end -- Auto-pair logic only triggers for single characters - local autoPair = self.get("autoPairEnabled") + local autoPair = self.getResolved("autoPairEnabled") if autoPair and #char == 1 then - local map = self.get("autoPairCharacters") or {} - local lines = self.get("lines") - local cursorX = self.get("cursorX") - local cursorY = self.get("cursorY") + local map = self.getResolved("autoPairCharacters") or {} + local lines = self.getResolved("lines") + local cursorX = self.getResolved("cursorX") + local cursorY = self.getResolved("cursorY") local line = lines[cursorY] or "" local afterChar = line:sub(cursorX, cursorX) @@ -876,22 +876,22 @@ function TextBox:char(char) if closing then -- If skip closing and same closing already directly after, just insert opening? insertChar(self, char) - if self.get("autoPairSkipClosing") then + if self.getResolved("autoPairSkipClosing") then if afterChar ~= closing then insertChar(self, closing) -- Move cursor back inside pair - self.set("cursorX", self.get("cursorX") - 1) + self.set("cursorX", self.getResolved("cursorX") - 1) end else insertChar(self, closing) - self.set("cursorX", self.get("cursorX") - 1) + self.set("cursorX", self.getResolved("cursorX") - 1) end refreshAutoComplete(self) return true end -- If typed char is a closing we might want to overtype - if self.get("autoPairOverType") then + if self.getResolved("autoPairOverType") then for open, close in pairs(map) do if char == close and afterChar == close then -- move over instead of inserting @@ -913,24 +913,24 @@ end --- @return boolean handled Whether the event was handled --- @protected function TextBox:key(key) - if not self.get("editable") or not self:hasState("focused") then return false end + if not self.getResolved("editable") or not self:hasState("focused") then return false end if handleAutoCompleteKey(self, key) then return true end - local lines = self.get("lines") - local cursorX = self.get("cursorX") - local cursorY = self.get("cursorY") + local lines = self.getResolved("lines") + local cursorX = self.getResolved("cursorX") + local cursorY = self.getResolved("cursorY") if key == keys.enter then -- Smart newline between matching braces/brackets if enabled - if self.get("autoPairEnabled") and self.get("autoPairNewlineIndent") then - local lines = self.get("lines") - local cursorX = self.get("cursorX") - local cursorY = self.get("cursorY") + if self.getResolved("autoPairEnabled") and self.getResolved("autoPairNewlineIndent") then + local lines = self.getResolved("lines") + local cursorX = self.getResolved("cursorX") + local cursorY = self.getResolved("cursorY") local line = lines[cursorY] or "" local before = line:sub(1, cursorX - 1) local after = line:sub(cursorX) - local pairMap = self.get("autoPairCharacters") or {} + local pairMap = self.getResolved("autoPairCharacters") or {} local inverse = {} for o,c in pairs(pairMap) do inverse[c]=o end local prevChar = before:sub(-1) @@ -989,9 +989,9 @@ function TextBox:mouse_scroll(direction, x, y) return true end if self:isInBounds(x, y) then - local scrollY = self.get("scrollY") - local height = self.get("height") - local lines = self.get("lines") + local scrollY = self.getResolved("scrollY") + local height = self.getResolved("height") + local lines = self.getResolved("lines") local maxScroll = math.max(0, #lines - height + 2) @@ -1013,11 +1013,11 @@ end function TextBox:mouse_click(button, x, y) if VisualElement.mouse_click(self, button, x, y) then local relX, relY = self:getRelativePosition(x, y) - local scrollX = self.get("scrollX") - local scrollY = self.get("scrollY") + local scrollX = self.getResolved("scrollX") + local scrollY = self.getResolved("scrollY") local targetY = (relY or 0) + (scrollY or 0) - local lines = self.get("lines") or {} + local lines = self.getResolved("lines") or {} -- clamp and validate before indexing to avoid nil errors if targetY < 1 then targetY = 1 end @@ -1042,7 +1042,7 @@ end --- @shortDescription Handles paste events --- @protected function TextBox:paste(text) - if not self.get("editable") or not self:hasState("focused") then return false end + if not self.getResolved("editable") or not self:hasState("focused") then return false end for char in text:gmatch(".") do if char == "\n" then @@ -1078,13 +1078,13 @@ end --- @shortDescription Gets the text of the TextBox --- @return string text The text of the TextBox function TextBox:getText() - return table.concat(self.get("lines"), "\n") + return table.concat(self.getResolved("lines"), "\n") end local function applySyntaxHighlighting(self, line) local text = line - local colors = string.rep(tHex[self.get("foreground")], #text) - local patterns = self.get("syntaxPatterns") + local colors = string.rep(tHex[self.getResolved("foreground")], #text) + local patterns = self.getResolved("syntaxPatterns") for _, syntax in ipairs(patterns) do local start = 1 @@ -1111,13 +1111,15 @@ end function TextBox:render() VisualElement.render(self) - local lines = self.get("lines") - local scrollX = self.get("scrollX") - local scrollY = self.get("scrollY") - local width = self.get("width") - local height = self.get("height") - local fg = tHex[self.get("foreground")] - local bg = tHex[self.get("background")] + local lines = self.getResolved("lines") + local scrollX = self.getResolved("scrollX") + local scrollY = self.getResolved("scrollY") + local width = self.getResolved("width") + local height = self.getResolved("height") + local foreground = self.getResolved("foreground") + local background = self.getResolved("background") + local fg = tHex[foreground] + local bg = tHex[background] for y = 1, height do local lineNum = y + scrollY @@ -1130,17 +1132,17 @@ function TextBox:render() local padLen = width - #text if padLen > 0 then text = text .. string.rep(" ", padLen) - colors = colors .. string.rep(tHex[self.get("foreground")], padLen) + colors = colors .. string.rep(tHex[foreground], padLen) end self:blit(1, y, text, colors, string.rep(bg, #text)) end if self:hasState("focused") then - local relativeX = self.get("cursorX") - scrollX - local relativeY = self.get("cursorY") - scrollY + local relativeX = self.getResolved("cursorX") - scrollX + local relativeY = self.getResolved("cursorY") - scrollY if relativeX >= 1 and relativeX <= width and relativeY >= 1 and relativeY <= height then - self:setCursor(relativeX, relativeY, true, self.get("cursorColor") or self.get("foreground")) + self:setCursor(relativeX, relativeY, true, self.getResolved("cursorColor") or foreground) end end end diff --git a/src/elements/Timer.lua b/src/elements/Timer.lua index c8fd129..5365ebb 100644 --- a/src/elements/Timer.lua +++ b/src/elements/Timer.lua @@ -44,7 +44,7 @@ end function Timer:start() if not self.running then self.running = true - local time = self.get("interval") + local time = self.getResolved("interval") self.timerId = os.startTimer(time) end return self @@ -70,12 +70,12 @@ function Timer:dispatchEvent(event, ...) local timerId = select(1, ...) if timerId == self.timerId then self.action() - local amount = self.get("amount") + local amount = self.getResolved("amount") if amount > 0 then self.set("amount", amount - 1) end if amount ~= 0 then - self.timerId = os.startTimer(self.get("interval")) + self.timerId = os.startTimer(self.getResolved("interval")) end end end diff --git a/src/elements/Toast.lua b/src/elements/Toast.lua index 1eefbb1..90d5268 100644 --- a/src/elements/Toast.lua +++ b/src/elements/Toast.lua @@ -76,7 +76,7 @@ function Toast:show(titleOrMessage, messageOrDuration, duration) if type(messageOrDuration) == "string" then title = titleOrMessage message = messageOrDuration - dur = duration or self.get("duration") + dur = duration or self.getResolved("duration") elseif type(messageOrDuration) == "number" then title = "" message = titleOrMessage @@ -84,7 +84,7 @@ function Toast:show(titleOrMessage, messageOrDuration, duration) else title = "" message = titleOrMessage - dur = self.get("duration") + dur = self.getResolved("duration") end self.set("title", title) @@ -96,7 +96,7 @@ function Toast:show(titleOrMessage, messageOrDuration, duration) self._hideTimerId = nil end - if self.get("autoHide") and dur > 0 then + if self.getResolved("autoHide") and dur > 0 then self._hideTimerId = os.startTimer(dur) end @@ -179,12 +179,12 @@ end --- @protected function Toast:render() VisualElement.render(self) - if not self.get("active") then + if not self.getResolved("active") then return end - local width = self.get("width") - local height = self.get("height") + local width = self.getResolved("width") + local height = self.getResolved("height") local title = self.getResolved("title") local message = self.getResolved("message") local toastType = self.getResolved("toastType") diff --git a/src/elements/Tree.lua b/src/elements/Tree.lua index ebbff6f..2e9be01 100644 --- a/src/elements/Tree.lua +++ b/src/elements/Tree.lua @@ -129,7 +129,7 @@ Tree.__index = Tree ---@property nodes table {} The tree structure containing node objects with {text, children} properties Tree.defineProperty(Tree, "nodes", {default = {}, type = "table", canTriggerRender = true, setter = function(self, value) if #value > 0 then - self.get("expandedNodes")[value[1]] = true + self.getResolved("expandedNodes")[value[1]] = true end return value end}) @@ -210,7 +210,7 @@ end --- @param node table The node to expand --- @return Tree self The Tree instance function Tree:expandNode(node) - self.get("expandedNodes")[node] = true + self.getResolved("expandedNodes")[node] = true self:updateRender() return self end @@ -220,7 +220,7 @@ end --- @param node table The node to collapse --- @return Tree self The Tree instance function Tree:collapseNode(node) - self.get("expandedNodes")[node] = nil + self.getResolved("expandedNodes")[node] = nil self:updateRender() return self end @@ -230,7 +230,7 @@ end --- @param node table The node to toggle --- @return Tree self The Tree instance function Tree:toggleNode(node) - if self.get("expandedNodes")[node] then + if self.getResolved("expandedNodes")[node] then self:collapseNode(node) else self:expandNode(node) @@ -248,10 +248,10 @@ end function Tree:mouse_click(button, x, y) if VisualElement.mouse_click(self, button, x, y) then local relX, relY = self:getRelativePosition(x, y) - local width = self.get("width") - local height = self.get("height") - local flatNodes = flattenTree(self.get("nodes"), self.get("expandedNodes")) - local showScrollBar = self.get("showScrollBar") + local width = self.getResolved("width") + local height = self.getResolved("height") + local flatNodes = flattenTree(self.getResolved("nodes"), self.getResolved("expandedNodes")) + local showScrollBar = self.getResolved("showScrollBar") local maxContentWidth, _ = self:getNodeSize() local needsHorizontalScrollBar = showScrollBar and maxContentWidth > width local contentHeight = needsHorizontalScrollBar and height - 1 or height @@ -262,7 +262,7 @@ function Tree:mouse_click(button, x, y) local handleSize = math.max(1, math.floor((contentHeight / #flatNodes) * scrollHeight)) local maxOffset = #flatNodes - contentHeight - local currentPercent = maxOffset > 0 and (self.get("offset") / maxOffset * 100) or 0 + local currentPercent = maxOffset > 0 and (self.getResolved("offset") / maxOffset * 100) or 0 local handlePos = math.floor((currentPercent / 100) * (scrollHeight - handleSize)) + 1 if relY >= handlePos and relY < handlePos + handleSize then @@ -281,7 +281,7 @@ function Tree:mouse_click(button, x, y) local handleSize = math.max(1, math.floor((contentWidth / maxContentWidth) * contentWidth)) local maxOffset = maxContentWidth - contentWidth - local currentPercent = maxOffset > 0 and (self.get("horizontalOffset") / maxOffset * 100) or 0 + local currentPercent = maxOffset > 0 and (self.getResolved("horizontalOffset") / maxOffset * 100) or 0 local handlePos = math.floor((currentPercent / 100) * (contentWidth - handleSize)) + 1 if relX >= handlePos and relX < handlePos + handleSize then @@ -295,7 +295,7 @@ function Tree:mouse_click(button, x, y) return true end - local visibleIndex = relY + self.get("offset") + local visibleIndex = relY + self.getResolved("offset") if flatNodes[visibleIndex] then local nodeInfo = flatNodes[visibleIndex] @@ -331,10 +331,10 @@ end function Tree:mouse_drag(button, x, y) if self._scrollBarDragging then local _, relY = self:getRelativePosition(x, y) - local flatNodes = flattenTree(self.get("nodes"), self.get("expandedNodes")) - local height = self.get("height") + local flatNodes = flattenTree(self.getResolved("nodes"), self.getResolved("expandedNodes")) + local height = self.getResolved("height") local maxContentWidth, _ = self:getNodeSize() - local needsHorizontalScrollBar = self.get("showScrollBar") and maxContentWidth > self.get("width") + local needsHorizontalScrollBar = self.getResolved("showScrollBar") and maxContentWidth > self.getResolved("width") local contentHeight = needsHorizontalScrollBar and height - 1 or height local scrollHeight = contentHeight local handleSize = math.max(1, math.floor((contentHeight / #flatNodes) * scrollHeight)) @@ -352,13 +352,13 @@ function Tree:mouse_drag(button, x, y) if self._hScrollBarDragging then local relX, _ = self:getRelativePosition(x, y) - local width = self.get("width") + local width = self.getResolved("width") local maxContentWidth, _ = self:getNodeSize() - local flatNodes = flattenTree(self.get("nodes"), self.get("expandedNodes")) - local height = self.get("height") - local needsHorizontalScrollBar = self.get("showScrollBar") and maxContentWidth > width + local flatNodes = flattenTree(self.getResolved("nodes"), self.getResolved("expandedNodes")) + local height = self.getResolved("height") + local needsHorizontalScrollBar = self.getResolved("showScrollBar") and maxContentWidth > width local contentHeight = needsHorizontalScrollBar and height - 1 or height - local needsVerticalScrollBar = self.get("showScrollBar") and #flatNodes > contentHeight + local needsVerticalScrollBar = self.getResolved("showScrollBar") and #flatNodes > contentHeight local contentWidth = needsVerticalScrollBar and width - 1 or width local handleSize = math.max(1, math.floor((contentWidth / maxContentWidth) * contentWidth)) local maxOffset = maxContentWidth - contentWidth @@ -406,15 +406,15 @@ end --- @protected function Tree:mouse_scroll(direction, x, y) if VisualElement.mouse_scroll(self, direction, x, y) then - local flatNodes = flattenTree(self.get("nodes"), self.get("expandedNodes")) - local height = self.get("height") - local width = self.get("width") - local showScrollBar = self.get("showScrollBar") + local flatNodes = flattenTree(self.getResolved("nodes"), self.getResolved("expandedNodes")) + local height = self.getResolved("height") + local width = self.getResolved("width") + local showScrollBar = self.getResolved("showScrollBar") local maxContentWidth, _ = self:getNodeSize() local needsHorizontalScrollBar = showScrollBar and maxContentWidth > width local contentHeight = needsHorizontalScrollBar and height - 1 or height local maxScroll = math.max(0, #flatNodes - contentHeight) - local newScroll = math.min(maxScroll, math.max(0, self.get("offset") + direction)) + local newScroll = math.min(maxScroll, math.max(0, self.getResolved("offset") + direction)) self.set("offset", newScroll) return true @@ -428,8 +428,8 @@ end --- @return number height The height of the tree function Tree:getNodeSize() local width, height = 0, 0 - local flatNodes = flattenTree(self.get("nodes"), self.get("expandedNodes")) - local expandedNodes = self.get("expandedNodes") + local flatNodes = flattenTree(self.getResolved("nodes"), self.getResolved("expandedNodes")) + local expandedNodes = self.getResolved("expandedNodes") for _, nodeInfo in ipairs(flatNodes) do local node = nodeInfo.node @@ -453,14 +453,14 @@ end function Tree:render() VisualElement.render(self) - local flatNodes = flattenTree(self.get("nodes"), self.get("expandedNodes")) - local height = self.get("height") - local width = self.get("width") - local selectedNode = self.get("selectedNode") - local expandedNodes = self.get("expandedNodes") - local offset = self.get("offset") - local horizontalOffset = self.get("horizontalOffset") - local showScrollBar = self.get("showScrollBar") + local flatNodes = flattenTree(self.getResolved("nodes"), self.getResolved("expandedNodes")) + local height = self.getResolved("height") + local width = self.getResolved("width") + local selectedNode = self.getResolved("selectedNode") + local expandedNodes = self.getResolved("expandedNodes") + local offset = self.getResolved("offset") + local horizontalOffset = self.getResolved("horizontalOffset") + local showScrollBar = self.getResolved("showScrollBar") local maxContentWidth, _ = self:getNodeSize() local needsHorizontalScrollBar = showScrollBar and maxContentWidth > width local contentHeight = needsHorizontalScrollBar and height - 1 or height @@ -480,8 +480,8 @@ function Tree:render() end local isSelected = node == selectedNode - local _bg = isSelected and self.get("selectedBackgroundColor") or (node.background or node.bg or self.get("background")) - local _fg = isSelected and self.get("selectedForegroundColor") or (node.foreground or node.fg or self.get("foreground")) + local _bg = isSelected and self.getResolved("selectedBackgroundColor") or (node.background or node.bg or self.getResolved("background")) + local _fg = isSelected and self.getResolved("selectedForegroundColor") or (node.foreground or node.fg or self.getResolved("foreground")) local fullText = indent .. symbol .. " " .. (node.text or "Node") local text = sub(fullText, horizontalOffset + 1, horizontalOffset + contentWidth) @@ -492,7 +492,7 @@ function Tree:render() self:blit(1, y, paddedText, fg, bg) else - self:blit(1, y, string.rep(" ", contentWidth), tHex[self.get("foreground")]:rep(contentWidth), tHex[self.get("background")]:rep(contentWidth)) + self:blit(1, y, string.rep(" ", contentWidth), tHex[self.getResolved("foreground")]:rep(contentWidth), tHex[self.getResolved("background")]:rep(contentWidth)) end end @@ -537,7 +537,7 @@ function Tree:render() end if needsVerticalScrollBar and needsHorizontalScrollBar then - self:blit(width, height, " ", tHex[foreground], tHex[self.get("background")]) + self:blit(width, height, " ", tHex[foreground], tHex[self.getResolved("background")]) end end diff --git a/src/elements/VisualElement.lua b/src/elements/VisualElement.lua index ff7c165..e0affa0 100644 --- a/src/elements/VisualElement.lua +++ b/src/elements/VisualElement.lua @@ -162,7 +162,7 @@ end --- @param offset number The offset to apply (negative = inside, positive = outside, fractional = percentage) --- @return VisualElement self The element instance function VisualElement:setConstraint(property, targetElement, targetProperty, offset) - local constraints = self.get("constraints") + local constraints = self.getResolved("constraints") if constraints[property] then self:_removeConstraintObservers(property, constraints[property]) end @@ -187,7 +187,7 @@ end --- @param value any The value to set for the property --- @return VisualElement self The element instance function VisualElement:setLayoutConfigProperty(key, value) - local layoutConfig = self.get("layoutConfig") + local layoutConfig = self.getResolved("layoutConfig") layoutConfig[key] = value self.set("layoutConfig", layoutConfig) return self @@ -198,7 +198,7 @@ end --- @param key string The layout config property to get --- @return any value The value of the property, or nil if not set function VisualElement:getLayoutConfigProperty(key) - local layoutConfig = self.get("layoutConfig") + local layoutConfig = self.getResolved("layoutConfig") return layoutConfig[key] end @@ -207,7 +207,7 @@ end --- @return VisualElement self The element instance function VisualElement:resolveAllConstraints() if not self._constraintsDirty then return self end - local constraints = self.get("constraints") + local constraints = self.getResolved("constraints") if not constraints or not next(constraints) then return self end local order = {"width", "height", "left", "right", "top", "bottom", "x", "y", "centerX", "centerY"} @@ -236,7 +236,7 @@ function VisualElement:_applyConstraintValue(property, value, constraints) self.set("width", width) self.set("x", leftValue) else - local width = self.get("width") + local width = self.getResolved("width") self.set("x", value - width + 1) end elseif property == "bottom" then @@ -246,14 +246,14 @@ function VisualElement:_applyConstraintValue(property, value, constraints) self.set("height", height) self.set("y", topValue) else - local height = self.get("height") + local height = self.getResolved("height") self.set("y", value - height + 1) end elseif property == "centerX" then - local width = self.get("width") + local width = self.getResolved("width") self.set("x", value - math.floor(width / 2)) elseif property == "centerY" then - local height = self.get("height") + local height = self.getResolved("height") self.set("y", value - math.floor(height / 2)) elseif property == "width" then self.set("width", value) @@ -351,7 +351,7 @@ end --- @param property string The property of the constraint to remove --- @return VisualElement self The element instance function VisualElement:removeConstraint(property) - local constraints = self.get("constraints") + local constraints = self.getResolved("constraints") constraints[property] = nil self.set("constraints", constraints) self:updateConstraints() @@ -362,7 +362,7 @@ end --- @shortDescription Updates all constraints, recalculating positions and sizes --- @return VisualElement self The element instance function VisualElement:updateConstraints() - local constraints = self.get("constraints") + local constraints = self.getResolved("constraints") for property, constraint in pairs(constraints) do local value = self:_resolveConstraint(property, constraint) @@ -372,16 +372,16 @@ function VisualElement:updateConstraints() elseif property == "y" or property == "top" then self.set("y", value) elseif property == "right" then - local width = self.get("width") + local width = self.getResolved("width") self.set("x", value - width + 1) elseif property == "bottom" then - local height = self.get("height") + local height = self.getResolved("height") self.set("y", value - height + 1) elseif property == "centerX" then - local width = self.get("width") + local width = self.getResolved("width") self.set("x", value - math.floor(width / 2)) elseif property == "centerY" then - local height = self.get("height") + local height = self.getResolved("height") self.set("y", value - math.floor(height / 2)) elseif property == "width" then self.set("width", value) @@ -403,7 +403,7 @@ function VisualElement:_resolveConstraint(property, constraint) end if not targetEl then - return self.get(property) or 1 + return self.getResolved(property) or 1 end local value @@ -761,9 +761,9 @@ end --- @param y number The y position to check --- @return boolean isInBounds Whether the coordinates are within the bounds of the element function VisualElement:isInBounds(x, y) - local xPos, yPos = self.get("x"), self.get("y") - local width, height = self.get("width"), self.get("height") - if(self.get("ignoreOffset"))then + local xPos, yPos = self.getResolved("x"), self.getResolved("y") + local width, height = self.getResolved("width"), self.getResolved("height") + if(self.getResolved("ignoreOffset"))then if(self.parent)then x = x - self.parent.get("offsetX") y = y - self.parent.get("offsetY") @@ -824,7 +824,7 @@ end --- @protected function VisualElement:mouse_move(_, x, y) if(x==nil)or(y==nil)then return false end - local hover = self.get("hover") + local hover = self.getResolved("hover") if(self:isInBounds(x, y))then if(not hover)then self.set("hover", true) @@ -1000,8 +1000,8 @@ end --- @return number y The y position function VisualElement:calculatePosition() self:resolveAllConstraints() - local x, y = self.get("x"), self.get("y") - if not self.get("ignoreOffset") then + local x, y = self.getResolved("x"), self.getResolved("y") + if not self.getResolved("ignoreOffset") then if self.parent ~= nil then local xO, yO = self.parent.get("offsetX"), self.parent.get("offsetY") x = x - xO @@ -1018,7 +1018,7 @@ end ---@return number x The absolute x position ---@return number y The absolute y position function VisualElement:getAbsolutePosition(x, y) - local xPos, yPos = self.get("x"), self.get("y") + local xPos, yPos = self.getResolved("x"), self.getResolved("y") if(x ~= nil) then xPos = xPos + x - 1 end @@ -1045,7 +1045,7 @@ end --- @return number y The relative y position function VisualElement:getRelativePosition(x, y) if (x == nil) or (y == nil) then - x, y = self.get("x"), self.get("y") + x, y = self.getResolved("x"), self.getResolved("y") end local parentX, parentY = 1, 1 @@ -1053,7 +1053,7 @@ function VisualElement:getRelativePosition(x, y) parentX, parentY = self.parent:getRelativePosition() end - local elementX, elementY = self.get("x"), self.get("y") + local elementX, elementY = self.getResolved("x"), self.getResolved("y") return x - (elementX - 1) - (parentX - 1), y - (elementY - 1) - (parentY - 1) end @@ -1094,7 +1094,7 @@ end --- @protected function VisualElement:render() if(not self.getResolved("backgroundEnabled"))then return end - local width, height = self.get("width"), self.get("height") + local width, height = self.getResolved("width"), self.getResolved("height") local fgHex = tHex[self.getResolved("foreground")] local bgHex = tHex[self.getResolved("background")] local bTop, bBottom, bLeft, bRight = diff --git a/src/plugins/responsive.lua b/src/plugins/responsive.lua new file mode 100644 index 0000000..3176254 --- /dev/null +++ b/src/plugins/responsive.lua @@ -0,0 +1,157 @@ +local errorManager = require("errorManager") +---@configDefault false + +--- This is the responsive plugin. It provides a fluent builder API for creating responsive states with an intuitive when/apply/otherwise syntax. +---@class BaseElement +local BaseElement = {} + +--- Creates a responsive builder for defining responsive states +--- @shortDescription Creates a responsive state builder +--- @param self BaseElement The element to create the builder for +--- @return ResponsiveBuilder builder The responsive builder instance +function BaseElement:responsive() + local builder = { + _element = self, + _rules = {}, + _currentStateName = nil, + _currentCondition = nil, + _stateCounter = 0 + } + + --- Defines a condition for responsive behavior + --- @param condition string|function The condition as string expression or function + --- @return ResponsiveBuilder self For method chaining + function builder:when(condition) + if self._currentCondition then + errorManager.header = "Responsive Builder Error" + errorManager.error("Previous when() must be followed by apply() before starting a new when()") + end + + self._stateCounter = self._stateCounter + 1 + self._currentStateName = "__responsive_" .. self._stateCounter + self._currentCondition = condition + + return self + end + + --- Applies properties when the current condition is met + --- @param properties table The properties to apply {property = value, ...} + --- @return ResponsiveBuilder self For method chaining + function builder:apply(properties) + if not self._currentCondition then + errorManager.header = "Responsive Builder Error" + errorManager.error("apply() must follow a when() call") + end + + if type(properties) ~= "table" then + errorManager.header = "Responsive Builder Error" + errorManager.error("apply() requires a table of properties") + end + + self._element:registerResponsiveState( + self._currentStateName, + self._currentCondition, + 100 + ) + + for propName, value in pairs(properties) do + local capitalizedName = propName:sub(1,1):upper() .. propName:sub(2) + local setter = "set" .. capitalizedName .. "State" + + if self._element[setter] then + self._element[setter](self._element, self._currentStateName, value) + else + errorManager.header = "Responsive Builder Error" + errorManager.error("Unknown property: " .. propName) + end + end + + table.insert(self._rules, { + stateName = self._currentStateName, + condition = self._currentCondition, + properties = properties + }) + + self._currentCondition = nil + self._currentStateName = nil + + return self + end + + --- Defines a fallback condition (else case) + --- @param properties table The properties to apply when no other conditions match + --- @return ResponsiveBuilder self For method chaining + function builder:otherwise(properties) + if self._currentCondition then + errorManager.header = "Responsive Builder Error" + errorManager.error("otherwise() cannot be used after when() without apply()") + end + + if type(properties) ~= "table" then + errorManager.header = "Responsive Builder Error" + errorManager.error("otherwise() requires a table of properties") + end + + self._stateCounter = self._stateCounter + 1 + local otherwiseStateName = "__responsive_otherwise_" .. self._stateCounter + + local otherRules = {} + for _, rule in ipairs(self._rules) do + table.insert(otherRules, rule.condition) + end + + local otherwiseCondition + if type(otherRules[1]) == "string" then + local negatedExprs = {} + for _, cond in ipairs(otherRules) do + table.insert(negatedExprs, "not (" .. cond .. ")") + end + otherwiseCondition = table.concat(negatedExprs, " and ") + else + otherwiseCondition = function(elem) + for _, cond in ipairs(otherRules) do + if cond(elem) then + return false + end + end + return true + end + end + + self._element:registerResponsiveState( + otherwiseStateName, + otherwiseCondition, + 50 + ) + + for propName, value in pairs(properties) do + local capitalizedName = propName:sub(1,1):upper() .. propName:sub(2) + local setter = "set" .. capitalizedName .. "State" + + if self._element[setter] then + self._element[setter](self._element, otherwiseStateName, value) + else + errorManager.header = "Responsive Builder Error" + errorManager.error("Unknown property: " .. propName) + end + end + + return self + end + + --- Completes the builder (optional, for clarity) + --- @return BaseElement element The original element + function builder:done() + if self._currentCondition then + errorManager.header = "Responsive Builder Error" + errorManager.error("Unfinished when() without apply()") + end + return self._element + end + + return builder +end + +return { + BaseElement = BaseElement +} \ No newline at end of file