Files
Basalt2/src/elements/List.lua
Robert Jelic 373bd7b485 Added basalt.getElementClass(name)
Added Graph
Added List:getSelectedItem()
Fixed Image:addFrame()
2025-03-03 17:38:30 +01:00

249 lines
8.4 KiB
Lua

local VisualElement = require("elements/VisualElement")
---@configDescription A scrollable list of selectable items
--- This is the list class. It provides a scrollable list of selectable items with support for
--- custom item rendering, separators, and selection handling.
---@class List : VisualElement
local List = setmetatable({}, VisualElement)
List.__index = List
---@property items table {} List of items to display. Items can be tables with properties including selected state
List.defineProperty(List, "items", {default = {}, type = "table", canTriggerRender = true})
---@property selectable boolean true Whether items in the list can be selected
List.defineProperty(List, "selectable", {default = true, type = "boolean"})
---@property multiSelection boolean false Whether multiple items can be selected at once
List.defineProperty(List, "multiSelection", {default = false, type = "boolean"})
---@property offset number 0 Current scroll offset for viewing long lists
List.defineProperty(List, "offset", {default = 0, type = "number", canTriggerRender = true})
---@property selectedBackground color blue Background color for selected items
List.defineProperty(List, "selectedBackground", {default = colors.blue, type = "color"})
---@property selectedForeground color white Text color for selected items
List.defineProperty(List, "selectedForeground", {default = colors.white, type = "color"})
---@event onSelect {index number, item any} Fired when an item is selected
List.defineEvent(List, "mouse_click")
List.defineEvent(List, "mouse_scroll")
--- Creates a new List instance
--- @shortDescription Creates a new List instance
--- @return List self The newly created List instance
--- @private
function List.new()
local self = setmetatable({}, List):__init()
self.set("width", 16)
self.set("height", 8)
self.set("z", 5)
self.set("background", colors.gray)
return self
end
--- @shortDescription Initializes the List instance
--- @param props table The properties to initialize the element with
--- @param basalt table The basalt instance
--- @return List self The initialized instance
--- @protected
function List:init(props, basalt)
VisualElement.init(self, props, basalt)
self.set("type", "List")
return self
end
--- Adds an item to the list
--- @shortDescription Adds an item to the list
--- @param text string|table The item to add (string or item table)
--- @return List self The List instance
--- @usage list:addItem("New Item")
--- @usage list:addItem({text="Item", callback=function() end})
function List:addItem(text)
local items = self.get("items")
table.insert(items, text)
self:updateRender()
return self
end
--- Removes an item from the list
--- @shortDescription Removes an item from the list
--- @param index number The index of the item to remove
--- @return List self The List instance
--- @usage list:removeItem(1)
function List:removeItem(index)
local items = self.get("items")
table.remove(items, index)
self:updateRender()
return self
end
--- Clears all items from the list
--- @shortDescription Clears all items from the list
--- @return List self The List instance
--- @usage list:clear()
function List:clear()
self.set("items", {})
self:updateRender()
return self
end
-- Gets the currently selected items
--- @shortDescription Gets the currently selected items
--- @return table selected List of selected items
--- @usage local selected = list:getSelectedItems()
function List:getSelectedItems()
local selected = {}
for i, item in ipairs(self.get("items")) do
if type(item) == "table" and item.selected then
local selectedItem = item
selectedItem.index = i
table.insert(selected, selectedItem)
end
end
return selected
end
--- Gets first selected item
--- @shortDescription Gets first selected item
--- @return table? selected The first item
function List:getSelectedItem()
local items = self.get("items")
for i, item in ipairs(items) do
if type(item) == "table" and item.selected then
return item
end
end
return nil
end
--- @shortDescription Handles mouse click events
--- @param button number The mouse button that was clicked
--- @param x number The x-coordinate of the click
--- @param y number The y-coordinate of the click
--- @return boolean Whether the event was handled
--- @protected
function List:mouse_click(button, x, y)
if button == 1 and self:isInBounds(x, y) and self.get("selectable") then
local _, index = self:getRelativePosition(x, y)
local adjustedIndex = index + self.get("offset")
local items = self.get("items")
if adjustedIndex <= #items then
local item = items[adjustedIndex]
if type(item) == "string" then
item = {text = item}
items[adjustedIndex] = item
end
if not self.get("multiSelection") then
for _, otherItem in ipairs(items) do
if type(otherItem) == "table" then
otherItem.selected = false
end
end
end
item.selected = not item.selected
if item.callback then
item.callback(self)
end
self:fireEvent("select", adjustedIndex, item)
self:updateRender()
return true
end
end
return false
end
--- @shortDescription Handles mouse scroll events
--- @param direction number The direction of the scroll (1 for down, -1 for up)
--- @param x number The x-coordinate of the scroll
--- @param y number The y-coordinate of the scroll
--- @return boolean Whether the event was handled
--- @protected
function List:mouse_scroll(direction, x, y)
if self:isInBounds(x, y) then
local offset = self.get("offset")
local maxOffset = math.max(0, #self.get("items") - self.get("height"))
offset = math.min(maxOffset, math.max(0, offset + direction))
self.set("offset", offset)
return true
end
return false
end
--- Registers a callback for the select event
--- @shortDescription Registers a callback for the select event
--- @param callback function The callback function to register
--- @return List self The List instance
--- @usage list:onSelect(function(index, item) print("Selected item:", index, item) end)
function List:onSelect(callback)
self:registerCallback("select", callback)
return self
end
--- Scrolls the list to the bottom
--- @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"))
self.set("offset", maxOffset)
return self
end
--- Scrolls the list to the top
--- @shortDescription Scrolls the list to the top
--- @return List self The List instance
function List:scrollToTop()
self.set("offset", 0)
return self
end
--- @shortDescription Renders the list
--- @protected
function List:render()
VisualElement.render(self)
local items = self.get("items")
local height = self.get("height")
local offset = self.get("offset")
local width = self.get("width")
for i = 1, height do
local itemIndex = i + offset
local item = items[itemIndex]
if item then
if type(item) == "string" then
item = {text = item}
items[itemIndex] = item
end
if item.separator then
local separatorChar = (item.text or "-"):sub(1,1)
local separatorText = string.rep(separatorChar, width)
local fg = item.foreground or self.get("foreground")
local bg = item.background or self.get("background")
self:textBg(1, i, string.rep(" ", width), bg)
self:textFg(1, i, separatorText:sub(1, width), fg)
else
local text = item.text
local isSelected = item.selected
local bg = isSelected and
(item.selectedBackground or self.get("selectedBackground")) or
(item.background or self.get("background"))
local fg = isSelected and
(item.selectedForeground or self.get("selectedForeground")) or
(item.foreground or self.get("foreground"))
self:textBg(1, i, string.rep(" ", width), bg)
self:textFg(1, i, text:sub(1, width), fg)
end
end
end
end
return List