|
local Element = require("elements/Element") |
|
local utils = require("gui-utils") |
|
|
|
local List = setmetatable({}, { __index = Element }) |
|
List.__index = List |
|
|
|
function List.getDefaultStyle() |
|
return { |
|
textColor = colors.white, |
|
backgroundColor = colors.gray, |
|
selectedTextColor = colors.black, |
|
selectedBackgroundColor = colors.lightGray, |
|
border = true, |
|
borderColor = colors.lightGray, |
|
fontSize = 1, |
|
padding = 2 -- Add padding property |
|
} |
|
end |
|
|
|
function List:calculateDimensions() |
|
local fontSize = self.style.fontSize or 1 |
|
local lineHeight = math.ceil(fontSize * 9) |
|
local padding = (self.style.padding or 2) * 2 -- Total padding (left + right) |
|
local borderOffset = (self.style and self.style.border) and 2 or 0 |
|
|
|
-- Calculate maximum width based on content |
|
local maxWidth = 0 |
|
for _, item in ipairs(self.items) do |
|
local textWidth = self.gui.gpu and |
|
self.gui.gpu.getTextLength(tostring(item), fontSize) or |
|
#tostring(item) |
|
maxWidth = math.max(maxWidth, textWidth) |
|
end |
|
|
|
-- Add padding to width |
|
maxWidth = maxWidth + padding |
|
|
|
-- Calculate height based on number of items |
|
local contentHeight = #self.items * lineHeight |
|
|
|
-- Update dimensions |
|
self.contentW = maxWidth |
|
self.contentH = #self.items |
|
|
|
-- Set total dimensions including borders |
|
self.w = self.contentW + borderOffset |
|
self.h = (self.contentH * lineHeight) + borderOffset |
|
|
|
return { |
|
width = self.w, |
|
height = self.h, |
|
contentWidth = self.contentW, |
|
contentHeight = self.contentH, |
|
lineHeight = lineHeight |
|
} |
|
end |
|
|
|
function List.new(config) |
|
local self = setmetatable(Element.new(config.id, config.gui), List) |
|
self.items = {} |
|
self.selectedIndex = 0 |
|
self.style = config.style |
|
self.parent = config.parent |
|
|
|
-- Set initial dimensions to minimum values |
|
self.contentW = 10 -- Minimum width |
|
self.contentH = 1 -- Minimum height |
|
self.w = self.contentW |
|
self.h = self.contentH |
|
|
|
-- Get available area dimensions |
|
local area |
|
if self.parent and self.parent.contentArea then |
|
area = { |
|
width = self.parent.contentArea.width, |
|
height = self.parent.contentArea.height |
|
} |
|
else |
|
local width, height = config.gui.getScreenDimensions() |
|
area = { |
|
width = width, |
|
height = height |
|
} |
|
end |
|
|
|
local borderOffset = (self.style and self.style.border) and 2 or 0 |
|
local fontSize = self.style.fontSize or 1 |
|
local lineHeight = math.ceil(fontSize * 9) |
|
|
|
-- Handle anchoring and positioning |
|
if config.anchor then |
|
local anchorDims = config.anchor:getDimensions() |
|
if anchorDims then |
|
if config.anchorPosition == "below" then |
|
-- Add extra spacing when anchored below a Label |
|
local extraSpacing = 2 |
|
config.x = anchorDims.x |
|
config.y = anchorDims.y + anchorDims.height + extraSpacing |
|
elseif config.anchorPosition == "right" then |
|
config.x = anchorDims.x + anchorDims.width + 2 |
|
config.y = anchorDims.y |
|
end |
|
end |
|
end |
|
|
|
-- Set positions |
|
self.x = config.x or 1 |
|
self.y = config.y or 1 |
|
|
|
-- Store the actual content dimensions |
|
self.contentW = math.min(config.w or 20, area.width - borderOffset) |
|
self.contentH = math.min(config.h or 5, math.floor((area.height - borderOffset) / lineHeight)) |
|
|
|
-- Set total dimensions including borders |
|
self.w = self.contentW + borderOffset |
|
self.h = (self.contentH * lineHeight) + borderOffset |
|
|
|
-- Additional height adjustment for parent bounds |
|
if self.parent and self.parent.contentArea then |
|
local maxHeight = self.parent.contentArea.height - (self.y - self.parent.contentArea.y) - borderOffset |
|
self.h = math.min(self.h, maxHeight) |
|
end |
|
|
|
self.visible = config.visible ~= false |
|
|
|
return self |
|
end |
|
|
|
function List:addItem(item) |
|
table.insert(self.items, item) |
|
self:calculateDimensions() -- Recalculate dimensions when adding items |
|
self:draw() |
|
return #self.items |
|
end |
|
|
|
function List:removeItem(index) |
|
if index > 0 and index <= #self.items then |
|
table.remove(self.items, index) |
|
if self.selectedIndex == index then |
|
self.selectedIndex = 0 |
|
elseif self.selectedIndex > index then |
|
self.selectedIndex = self.selectedIndex - 1 |
|
end |
|
self:draw() |
|
return true |
|
end |
|
return false |
|
end |
|
|
|
function List:editItem(index, newValue) |
|
if index > 0 and index <= #self.items then |
|
self.items[index] = newValue |
|
self:draw() |
|
return true |
|
end |
|
return false |
|
end |
|
|
|
function List:getItem(index) |
|
return self.items[index] |
|
end |
|
|
|
function List:getItemCount() |
|
return #self.items |
|
end |
|
|
|
function List:selectItem(index) |
|
if index >= 0 and index <= #self.items then |
|
local oldIndex = self.selectedIndex |
|
self.selectedIndex = index |
|
self:draw() |
|
self:emit("list_select", index, self.items[index]) |
|
return true |
|
end |
|
return false |
|
end |
|
|
|
function List:onSelect(callback) |
|
self:on("list_select", callback) |
|
end |
|
|
|
function List:clear() |
|
self.items = {} |
|
self.selectedIndex = 0 |
|
self:draw() |
|
end |
|
|
|
function List:getItemIndex() |
|
return self.selectedIndex |
|
end |
|
|
|
function List:draw() |
|
if not self.visible then return end |
|
|
|
local gpu = self.gui.gpu |
|
local bgColor = utils.mapColor(self.style.backgroundColor) |
|
local selectedBgColor = utils.mapColor(self.style.selectedBackgroundColor) |
|
|
|
if gpu then |
|
local dims = self:calculateDimensions() |
|
local fontSize = self.style.fontSize or 1 |
|
local padding = self.style.padding or 2 |
|
local borderOffset = (self.style and self.style.border) and 1 or 0 |
|
|
|
-- Calculate content area (excluding borders) |
|
local contentX = self.x + borderOffset + padding |
|
local contentY = self.y + borderOffset |
|
|
|
-- Clear entire area first |
|
gpu.filledRectangle( |
|
self.x - 1, |
|
self.y - 1, |
|
self.w + 2, |
|
self.h + 2, |
|
bgColor |
|
) |
|
|
|
-- Draw border if enabled |
|
if self.style.border then |
|
local borderColor = utils.mapColor(self.style.borderColor) |
|
gpu.filledRectangle(self.x - 1, self.y - 1, self.w + 2, 1, borderColor) -- top |
|
gpu.filledRectangle(self.x - 1, self.y + self.h, self.w + 2, 1, borderColor) -- bottom |
|
gpu.filledRectangle(self.x - 1, self.y - 1, 1, self.h + 2, borderColor) -- left |
|
gpu.filledRectangle(self.x + self.w, self.y - 1, 1, self.h + 2, borderColor) -- right |
|
end |
|
|
|
-- Draw items with proper vertical spacing |
|
for i = 1, #self.items do |
|
local item = self.items[i] |
|
if item then |
|
local itemY = contentY + ((i - 1) * dims.lineHeight) |
|
|
|
-- Draw selection background for the full width of the content area |
|
if i == self.selectedIndex then |
|
gpu.filledRectangle( |
|
self.x + borderOffset, |
|
itemY, |
|
self.w - (borderOffset * 2), |
|
dims.lineHeight, |
|
selectedBgColor |
|
) |
|
end |
|
|
|
local fg = utils.mapColor(i == self.selectedIndex and |
|
self.style.selectedTextColor or self.style.textColor) |
|
local bg = i == self.selectedIndex and selectedBgColor or bgColor |
|
|
|
gpu.drawText(contentX, itemY, tostring(item), fg, bg, fontSize) |
|
end |
|
end |
|
else |
|
-- Terminal version |
|
for i = 1, self.h do |
|
local item = self.items[i] |
|
term.setCursorPos(self.x, self.y + i - 1) |
|
|
|
if i == self.selectedIndex then |
|
term.setBackgroundColor(self.style.selectedBackgroundColor) |
|
term.setTextColor(self.style.selectedTextColor) |
|
else |
|
term.setBackgroundColor(self.style.backgroundColor) |
|
term.setTextColor(self.style.textColor) |
|
end |
|
|
|
term.write(item and utils.padString(tostring(item), self.w) or string.rep(" ", self.w)) |
|
end |
|
end |
|
end |
|
|
|
function List:handleEvent(event, ...) |
|
if not self.visible then return false end |
|
|
|
if event == "mouse_click" or event == "tm_monitor_touch" then |
|
local button, x, y |
|
if event == "mouse_click" then |
|
button, x, y = ... |
|
else |
|
_, x, y = ... |
|
button = 1 -- Treat touch as left click |
|
end |
|
|
|
if self:isPointInside(x, y) then |
|
local index |
|
if self.gui.gpu then |
|
-- GPU mode calculation |
|
local fontSize = self.style.fontSize or 1 |
|
local lineHeight = math.ceil(fontSize * 9) |
|
local borderOffset = (self.style and self.style.border) and 1 or 0 |
|
local relativeY = y - (self.y + borderOffset) |
|
index = math.floor(relativeY / lineHeight) + 1 |
|
else |
|
-- Terminal mode calculation |
|
index = y - self.y + 1 |
|
end |
|
|
|
if index > 0 and index <= #self.items then |
|
self:selectItem(index) |
|
return true |
|
end |
|
end |
|
end |
|
return false |
|
end |
|
|
|
return List |