Merge branch 'main' into main

This commit is contained in:
Robert Jelic
2025-11-05 02:03:22 +01:00
committed by GitHub
43 changed files with 8111 additions and 7549 deletions

10065
BasaltLS.lua

File diff suppressed because it is too large Load Diff

View File

@@ -1,509 +1,517 @@
return {
["categories"] = {
["core"] = {
["description"] = "Core Files",
["libraries"] = {
["files"] = {
["main"] = {
["size"] = 19883,
["path"] = "main.lua",
["collectionentry"] = {
["description"] = "",
["requires"] = {
},
["description"] = "",
["default"] = true,
["path"] = "libraries/collectionentry.lua",
["size"] = 3551,
},
["errorManager"] = {
["size"] = 3789,
["path"] = "errorManager.lua",
["utils"] = {
["description"] = "",
["requires"] = {
},
["description"] = "",
["default"] = true,
["path"] = "libraries/utils.lua",
["size"] = 2661,
},
["elementManager"] = {
["size"] = 15411,
["path"] = "elementManager.lua",
["expect"] = {
["description"] = "",
["requires"] = {
},
["description"] = "",
["default"] = true,
["path"] = "libraries/expect.lua",
["size"] = 846,
},
["render"] = {
["size"] = 12422,
["path"] = "render.lua",
["colorHex"] = {
["description"] = "",
["requires"] = {
},
["description"] = "",
["default"] = true,
},
["propertySystem"] = {
["size"] = 18186,
["path"] = "propertySystem.lua",
["requires"] = {
},
["description"] = "",
["default"] = true,
},
["layoutManager"] = {
["size"] = 3634,
["path"] = "layoutManager.lua",
["requires"] = {
},
["description"] = "",
["default"] = true,
},
["init"] = {
["size"] = 622,
["path"] = "init.lua",
["requires"] = {
},
["description"] = "",
["default"] = true,
},
["log"] = {
["size"] = 3142,
["path"] = "log.lua",
["requires"] = {
},
["description"] = "",
["default"] = true,
["path"] = "libraries/colorHex.lua",
["size"] = 132,
},
},
["description"] = "Libraries",
},
["core"] = {
["files"] = {
["render"] = {
["description"] = "",
["requires"] = {
},
["default"] = true,
["path"] = "render.lua",
["size"] = 12422,
},
["errorManager"] = {
["description"] = "",
["requires"] = {
},
["default"] = true,
["path"] = "errorManager.lua",
["size"] = 3789,
},
["main"] = {
["description"] = "",
["requires"] = {
},
["default"] = true,
["path"] = "main.lua",
["size"] = 19883,
},
["init"] = {
["description"] = "",
["requires"] = {
},
["default"] = true,
["path"] = "init.lua",
["size"] = 622,
},
["log"] = {
["description"] = "",
["requires"] = {
},
["default"] = true,
["path"] = "log.lua",
["size"] = 3142,
},
["layoutManager"] = {
["description"] = "",
["requires"] = {
},
["default"] = true,
["path"] = "layoutManager.lua",
["size"] = 3634,
},
["elementManager"] = {
["description"] = "",
["requires"] = {
},
["default"] = true,
["path"] = "elementManager.lua",
["size"] = 15411,
},
["propertySystem"] = {
["description"] = "",
["requires"] = {
},
["default"] = true,
["path"] = "propertySystem.lua",
["size"] = 18307,
},
},
["description"] = "Core Files",
},
["plugins"] = {
["files"] = {
["xml"] = {
["description"] = "",
["requires"] = {
},
["default"] = false,
["path"] = "plugins/xml.lua",
["size"] = 14068,
},
["canvas"] = {
["description"] = "",
["requires"] = {
},
["default"] = false,
["path"] = "plugins/canvas.lua",
["size"] = 7897,
},
["debug"] = {
["description"] = "",
["requires"] = {
},
["default"] = false,
["path"] = "plugins/debug.lua",
["size"] = 6274,
},
["reactive"] = {
["description"] = "",
["requires"] = {
},
["default"] = false,
["path"] = "plugins/reactive.lua",
["size"] = 11893,
},
["responsive"] = {
["description"] = "",
["requires"] = {
},
["default"] = false,
["path"] = "plugins/responsive.lua",
["size"] = 5529,
},
["theme"] = {
["description"] = "",
["requires"] = {
},
["default"] = false,
["path"] = "plugins/theme.lua",
["size"] = 6801,
},
["animation"] = {
["description"] = "",
["requires"] = {
},
["default"] = false,
["path"] = "plugins/animation.lua",
["size"] = 18446,
},
["benchmark"] = {
["description"] = "",
["requires"] = {
[1] = "VisualElement",
},
["default"] = false,
["path"] = "plugins/benchmark.lua",
["size"] = 12604,
},
},
["description"] = "Plugins",
},
["elements"] = {
["description"] = "UI Elements",
["files"] = {
["Program"] = {
["size"] = 12753,
["path"] = "elements/Program.lua",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "",
["default"] = false,
},
["LineChart"] = {
["size"] = 3172,
["path"] = "elements/LineChart.lua",
["requires"] = {
},
["description"] = "",
["default"] = true,
},
["BarChart"] = {
["size"] = 3507,
["path"] = "elements/BarChart.lua",
["requires"] = {
},
["description"] = "",
["default"] = true,
},
["Timer"] = {
["size"] = 2938,
["path"] = "elements/Timer.lua",
["requires"] = {
[1] = "BaseElement",
},
["description"] = "",
["default"] = false,
},
["List"] = {
["size"] = 15474,
["path"] = "elements/List.lua",
["requires"] = {
[1] = "Collection",
},
["description"] = "A scrollable list of selectable items",
["default"] = true,
},
["Image"] = {
["size"] = 15076,
["path"] = "elements/Image.lua",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "",
["default"] = true,
},
["Input"] = {
["size"] = 9200,
["path"] = "elements/Input.lua",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "A text input field with various features",
["default"] = true,
},
["Collection"] = {
["size"] = 7778,
["path"] = "elements/Collection.lua",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "A collection of items",
["default"] = true,
},
["SideNav"] = {
["size"] = 22159,
["path"] = "elements/SideNav.lua",
["requires"] = {
[1] = "Container",
},
["description"] = "A SideNav element that provides sidebar navigation with multiple content areas.",
["default"] = false,
},
["ProgressBar"] = {
["size"] = 3398,
["path"] = "elements/ProgressBar.lua",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "",
["default"] = true,
},
["Label"] = {
["size"] = 3088,
["path"] = "elements/Label.lua",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "A simple text display element that automatically resizes its width based on the text content.",
["default"] = true,
},
["TextBox"] = {
["size"] = 43530,
["path"] = "elements/TextBox.lua",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "A multi-line text editor component with cursor support and text manipulation features",
["default"] = false,
},
["Display"] = {
["size"] = 4493,
["path"] = "elements/Display.lua",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "The Display is a special element which uses the CC Window API which you can use.",
["default"] = false,
},
["Graph"] = {
["size"] = 6933,
["path"] = "elements/Graph.lua",
["requires"] = {
},
["description"] = "A point based graph element",
["default"] = false,
},
["BaseElement"] = {
["size"] = 13870,
["path"] = "elements/BaseElement.lua",
["requires"] = {
},
["description"] = "The base class for all UI elements in Basalt.",
["default"] = true,
},
["Accordion"] = {
["size"] = 14937,
["path"] = "elements/Accordion.lua",
["requires"] = {
[1] = "Container",
},
["description"] = "An Accordion element that provides collapsible panels with headers.",
["default"] = false,
},
["ContextMenu"] = {
["size"] = 10660,
["path"] = "elements/ContextMenu.lua",
["requires"] = {
[1] = "Container",
},
["description"] = "A ContextMenu element that displays a menu with items and submenus.",
["default"] = false,
},
["CheckBox"] = {
["size"] = 3700,
["path"] = "elements/CheckBox.lua",
["requires"] = {
},
["description"] = "This is a checkbox. It is a visual element that can be checked.",
["default"] = true,
},
["Button"] = {
["size"] = 2437,
["path"] = "elements/Button.lua",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "The Button is a standard button element with click handling and state management.",
["default"] = true,
},
["Breadcrumb"] = {
["size"] = 4381,
["path"] = "elements/Breadcrumb.lua",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "A breadcrumb navigation element that displays the current path.",
["default"] = false,
},
["Table"] = {
["size"] = 25512,
["path"] = "elements/Table.lua",
["requires"] = {
[1] = "Collection",
},
["description"] = "The Table is a sortable data grid with customizable columns, row selection, and scrolling capabilities.",
["default"] = false,
},
["ComboBox"] = {
["size"] = 14751,
["path"] = "elements/ComboBox.lua",
["requires"] = {
[1] = "DropDown",
},
["description"] = "A ComboBox that combines dropdown selection with editable text input",
["default"] = false,
},
["ScrollBar"] = {
["size"] = 9689,
["path"] = "elements/ScrollBar.lua",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "A ScrollBar element that can be attached to other elements to control their scroll properties.",
["default"] = false,
},
["BigFont"] = {
["size"] = 21551,
["path"] = "elements/BigFont.lua",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "",
["default"] = false,
},
["Menu"] = {
["size"] = 8582,
["path"] = "elements/Menu.lua",
["requires"] = {
[1] = "List",
},
["description"] = "A horizontal menu bar with selectable items.",
["default"] = true,
},
["TabControl"] = {
["size"] = 20900,
["path"] = "elements/TabControl.lua",
["requires"] = {
[1] = "Container",
},
["description"] = "A TabControl element that provides tabbed interface with multiple content areas.",
["default"] = false,
},
["ScrollFrame"] = {
["size"] = 17463,
["path"] = "elements/ScrollFrame.lua",
["requires"] = {
[1] = "Container",
},
["description"] = "A scrollable container that automatically displays scrollbars when content overflows.",
["default"] = false,
},
["Slider"] = {
["size"] = 5043,
["path"] = "elements/Slider.lua",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "A slider control element for selecting a value within a range.",
["default"] = false,
["path"] = "elements/Display.lua",
["size"] = 4549,
},
["DropDown"] = {
["size"] = 7886,
["path"] = "elements/DropDown.lua",
["description"] = "A DropDown menu that shows a list of selectable items",
["requires"] = {
[1] = "List",
},
["description"] = "A DropDown menu that shows a list of selectable items",
["default"] = false,
["path"] = "elements/DropDown.lua",
["size"] = 7988,
},
["Container"] = {
["size"] = 27475,
["path"] = "elements/Container.lua",
["Switch"] = {
["description"] = "The Switch is a standard Switch element with click handling and state management.",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "The container class. It is a visual element that can contain other elements. It is the base class for all containers",
["default"] = false,
["path"] = "elements/Switch.lua",
["size"] = 3375,
},
["BaseFrame"] = {
["description"] = "This is the base frame class. It is the root element of all elements and the only element without a parent.",
["requires"] = {
[1] = "Container",
},
["default"] = true,
["path"] = "elements/BaseFrame.lua",
["size"] = 8972,
},
["Tree"] = {
["size"] = 22168,
["path"] = "elements/Tree.lua",
["Table"] = {
["description"] = "The Table is a sortable data grid with customizable columns, row selection, and scrolling capabilities.",
["requires"] = {
[1] = "Collection",
},
["default"] = false,
["path"] = "elements/Table.lua",
["size"] = 25766,
},
["Slider"] = {
["description"] = "A slider control element for selecting a value within a range.",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "",
["default"] = false,
["path"] = "elements/Slider.lua",
["size"] = 5211,
},
["Input"] = {
["description"] = "A text input field with various features",
["requires"] = {
[1] = "VisualElement",
},
["default"] = true,
["path"] = "elements/Input.lua",
["size"] = 9456,
},
["Collection"] = {
["description"] = "A collection of items",
["requires"] = {
[1] = "VisualElement",
},
["default"] = true,
["path"] = "elements/Collection.lua",
["size"] = 7874,
},
["TextBox"] = {
["description"] = "A multi-line text editor component with cursor support and text manipulation features",
["requires"] = {
[1] = "VisualElement",
},
["default"] = false,
["path"] = "elements/TextBox.lua",
["size"] = 44462,
},
["BarChart"] = {
["description"] = "",
["requires"] = {
},
["default"] = true,
["path"] = "elements/BarChart.lua",
["size"] = 3547,
},
["Accordion"] = {
["description"] = "An Accordion element that provides collapsible panels with headers.",
["requires"] = {
[1] = "Container",
},
["default"] = false,
["path"] = "elements/Accordion.lua",
["size"] = 15169,
},
["Dialog"] = {
["size"] = 8901,
["path"] = "elements/Dialog.lua",
["description"] = "A dialog overlay system with common presets (alert, confirm, prompt).",
["requires"] = {
[1] = "Frame",
},
["description"] = "A dialog overlay system with common presets (alert, confirm, prompt).",
["default"] = false,
},
["Switch"] = {
["size"] = 3293,
["path"] = "elements/Switch.lua",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "The Switch is a standard Switch element with click handling and state management.",
["default"] = false,
},
["Frame"] = {
["size"] = 6646,
["path"] = "elements/Frame.lua",
["requires"] = {
[1] = "Container",
},
["description"] = "A frame element that serves as a grouping container for other elements.",
["default"] = true,
},
["BaseFrame"] = {
["size"] = 8972,
["path"] = "elements/BaseFrame.lua",
["requires"] = {
[1] = "Container",
},
["description"] = "This is the base frame class. It is the root element of all elements and the only element without a parent.",
["default"] = true,
["path"] = "elements/Dialog.lua",
["size"] = 9125,
},
["Toast"] = {
["size"] = 7897,
["path"] = "elements/Toast.lua",
["description"] = "A toast notification element that displays temporary messages.",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "A toast notification element that displays temporary messages.",
["default"] = false,
["path"] = "elements/Toast.lua",
["size"] = 7945,
},
["BigFont"] = {
["description"] = "",
["requires"] = {
[1] = "VisualElement",
},
["default"] = false,
["path"] = "elements/BigFont.lua",
["size"] = 21675,
},
["TabControl"] = {
["description"] = "A TabControl element that provides tabbed interface with multiple content areas.",
["requires"] = {
[1] = "Container",
},
["default"] = false,
["path"] = "elements/TabControl.lua",
["size"] = 21136,
},
["SideNav"] = {
["description"] = "A SideNav element that provides sidebar navigation with multiple content areas.",
["requires"] = {
[1] = "Container",
},
["default"] = false,
["path"] = "elements/SideNav.lua",
["size"] = 22429,
},
["CheckBox"] = {
["description"] = "This is a checkbox. It is a visual element that can be checked.",
["requires"] = {
},
["default"] = true,
["path"] = "elements/CheckBox.lua",
["size"] = 3748,
},
["Image"] = {
["description"] = "",
["requires"] = {
[1] = "VisualElement",
},
["default"] = true,
["path"] = "elements/Image.lua",
["size"] = 15372,
},
["Menu"] = {
["description"] = "A horizontal menu bar with selectable items.",
["requires"] = {
[1] = "List",
},
["default"] = true,
["path"] = "elements/Menu.lua",
["size"] = 8758,
},
["Frame"] = {
["description"] = "A frame element that serves as a grouping container for other elements.",
["requires"] = {
[1] = "Container",
},
["default"] = true,
["path"] = "elements/Frame.lua",
["size"] = 6742,
},
["ContextMenu"] = {
["description"] = "A ContextMenu element that displays a menu with items and submenus.",
["requires"] = {
[1] = "Container",
},
["default"] = false,
["path"] = "elements/ContextMenu.lua",
["size"] = 10836,
},
["List"] = {
["description"] = "A scrollable list of selectable items",
["requires"] = {
[1] = "Collection",
},
["default"] = true,
["path"] = "elements/List.lua",
["size"] = 15754,
},
["BaseElement"] = {
["description"] = "The base class for all UI elements in Basalt.",
["requires"] = {
},
["default"] = true,
["path"] = "elements/BaseElement.lua",
["size"] = 18468,
},
["Program"] = {
["description"] = "",
["requires"] = {
[1] = "VisualElement",
},
["default"] = false,
["path"] = "elements/Program.lua",
["size"] = 12833,
},
["ProgressBar"] = {
["description"] = "",
["requires"] = {
[1] = "VisualElement",
},
["default"] = true,
["path"] = "elements/ProgressBar.lua",
["size"] = 3440,
},
["Label"] = {
["description"] = "A simple text display element that automatically resizes its width based on the text content.",
["requires"] = {
[1] = "VisualElement",
},
["default"] = true,
["path"] = "elements/Label.lua",
["size"] = 3184,
},
["LineChart"] = {
["description"] = "",
["requires"] = {
},
["default"] = true,
["path"] = "elements/LineChart.lua",
["size"] = 3228,
},
["ScrollBar"] = {
["description"] = "A ScrollBar element that can be attached to other elements to control their scroll properties.",
["requires"] = {
[1] = "VisualElement",
},
["default"] = false,
["path"] = "elements/ScrollBar.lua",
["size"] = 9941,
},
["Button"] = {
["description"] = "The Button is a standard button element with click handling and state management.",
["requires"] = {
[1] = "VisualElement",
},
["default"] = true,
["path"] = "elements/Button.lua",
["size"] = 2461,
},
["Tree"] = {
["description"] = "",
["requires"] = {
[1] = "VisualElement",
},
["default"] = false,
["path"] = "elements/Tree.lua",
["size"] = 22552,
},
["Container"] = {
["description"] = "The container class. It is a visual element that can contain other elements. It is the base class for all containers",
["requires"] = {
[1] = "VisualElement",
},
["default"] = true,
["path"] = "elements/Container.lua",
["size"] = 27731,
},
["VisualElement"] = {
["size"] = 45082,
["path"] = "elements/VisualElement.lua",
["description"] = "The Visual Element class which is the base class for all visual UI elements",
["requires"] = {
[1] = "BaseElement",
},
["description"] = "The Visual Element class which is the base class for all visual UI elements",
["default"] = true,
["path"] = "elements/VisualElement.lua",
["size"] = 45338,
},
},
},
["plugins"] = {
["description"] = "Plugins",
["files"] = {
["xml"] = {
["size"] = 14068,
["path"] = "plugins/xml.lua",
["ScrollFrame"] = {
["description"] = "A scrollable container that automatically displays scrollbars when content overflows.",
["requires"] = {
[1] = "Container",
},
["description"] = "",
["default"] = false,
["path"] = "elements/ScrollFrame.lua",
["size"] = 17831,
},
["theme"] = {
["size"] = 6801,
["path"] = "plugins/theme.lua",
["Timer"] = {
["description"] = "",
["requires"] = {
[1] = "BaseElement",
},
["description"] = "",
["default"] = false,
["path"] = "elements/Timer.lua",
["size"] = 2962,
},
["animation"] = {
["size"] = 18446,
["path"] = "plugins/animation.lua",
["requires"] = {
},
["description"] = "",
["default"] = false,
},
["debug"] = {
["size"] = 6274,
["path"] = "plugins/debug.lua",
["requires"] = {
},
["description"] = "",
["default"] = false,
},
["reactive"] = {
["size"] = 11869,
["path"] = "plugins/reactive.lua",
["requires"] = {
},
["description"] = "",
["default"] = false,
},
["benchmark"] = {
["size"] = 12604,
["path"] = "plugins/benchmark.lua",
["Breadcrumb"] = {
["description"] = "A breadcrumb navigation element that displays the current path.",
["requires"] = {
[1] = "VisualElement",
},
["description"] = "",
["default"] = false,
["path"] = "elements/Breadcrumb.lua",
["size"] = 4461,
},
["canvas"] = {
["size"] = 7897,
["path"] = "plugins/canvas.lua",
["ComboBox"] = {
["description"] = "A ComboBox that combines dropdown selection with editable text input",
["requires"] = {
[1] = "DropDown",
},
["default"] = false,
["path"] = "elements/ComboBox.lua",
["size"] = 15143,
},
["Graph"] = {
["description"] = "A point based graph element",
["requires"] = {
},
["description"] = "",
["default"] = false,
["path"] = "elements/Graph.lua",
["size"] = 7045,
},
},
},
["libraries"] = {
["description"] = "Libraries",
["files"] = {
["utils"] = {
["size"] = 2661,
["path"] = "libraries/utils.lua",
["requires"] = {
},
["description"] = "",
["default"] = true,
},
["colorHex"] = {
["size"] = 132,
["path"] = "libraries/colorHex.lua",
["requires"] = {
},
["description"] = "",
["default"] = true,
},
["expect"] = {
["size"] = 846,
["path"] = "libraries/expect.lua",
["requires"] = {
},
["description"] = "",
["default"] = true,
},
["collectionentry"] = {
["size"] = 3551,
["path"] = "libraries/collectionentry.lua",
["requires"] = {
},
["description"] = "",
["default"] = true,
},
},
["description"] = "UI Elements",
},
},
["metadata"] = {
["generated"] = "Wed Nov 5 00:37:16 2025",
["version"] = "2.0",
["generated"] = "Tue Nov 4 09:01:43 2025",
},
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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 = {}

View File

@@ -225,13 +225,14 @@ 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
end
states[stateName] = priority or 0
self.set("states", states)
return self
end
@@ -299,7 +300,9 @@ end
function BaseElement:updateConditionalStates()
for stateName, stateInfo in pairs(self._registeredStates) do
if stateInfo.condition then
if stateInfo.condition(self) then
local result = stateInfo.condition(self)
if result then
self:setState(stateName, stateInfo.priority)
else
self:unsetState(stateName)
@@ -309,6 +312,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 +467,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 +483,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

View File

@@ -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

View File

@@ -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 = "... > "

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -107,7 +107,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
@@ -140,9 +140,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]
@@ -151,7 +151,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
@@ -187,26 +187,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 self.dropSymbol or self.undropSymbol),
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 self.getResolved("dropSymbol") or self.getResolved("undropSymbol")),
string.rep(tHex[self.getResolved("foreground")], width),
string.rep(tHex[self.getResolved("background")], width))
end
--- Called when the DropDown gains focus

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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])

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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

View File

@@ -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 =

View File

@@ -90,7 +90,7 @@ local function parseExpression(expr, element, propName)
if objName == "self" then
-- Check if property exists
if element._properties[propName] then
return element.get(propName)
return element.getResolved(propName)
end
if element._registeredStates and element._registeredStates[propName] then
return element:hasState(propName)
@@ -104,7 +104,7 @@ local function parseExpression(expr, element, propName)
return nil
elseif objName == "parent" then
if element.parent._properties[propName] then
return element.parent.get(propName)
return element.parent.getResolved(propName)
end
if element.parent._registeredStates and element.parent._registeredStates[propName] then
return element.parent:hasState(propName)
@@ -125,7 +125,7 @@ local function parseExpression(expr, element, propName)
end
if target._properties[propName] then
return target.get(propName)
return target.getResolved(propName)
end
if target._registeredStates and target._registeredStates[propName] then
return target:hasState(propName)

157
src/plugins/responsive.lua Normal file
View File

@@ -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
}

View File

@@ -335,12 +335,16 @@ function PropertySystem:__init()
end
self.getResolved = function(name, ...)
local currentState = self:getCurrentState()
local value
local activeStates = self:getActiveStates()
local value = nil
for _, stateInfo in ipairs(activeStates) do
if self._states and self._states[stateInfo.name] and self._states[stateInfo.name][name] ~= nil then
value = self._states[stateInfo.name][name]
break
end
end
if currentState and self._states and self._states[currentState] and self._states[currentState][name] ~= nil then
value = self._states[currentState][name]
else
if value == nil then
value = self._values[name]
end