Fixed a Bug with Children Elements inside Containers (negative location invisible bug)
Improved Table Element
This commit is contained in:
@@ -17,7 +17,12 @@ Table.defineProperty(Table, "columns", {default = {}, type = "table", canTrigger
|
|||||||
if type(col) == "string" then
|
if type(col) == "string" then
|
||||||
t[i] = {name = col, width = #col+1}
|
t[i] = {name = col, width = #col+1}
|
||||||
elseif type(col) == "table" then
|
elseif type(col) == "table" then
|
||||||
t[i] = {name = col.name or "", width = col.width or #col.name+1}
|
t[i] = {
|
||||||
|
name = col.name or "",
|
||||||
|
width = col.width, -- Can be number, "auto", or percentage like "30%"
|
||||||
|
minWidth = col.minWidth or 3,
|
||||||
|
maxWidth = col.maxWidth or nil
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return t
|
return t
|
||||||
@@ -44,6 +49,8 @@ Table.defineProperty(Table, "sortColumn", {default = nil, type = "number", canTr
|
|||||||
Table.defineProperty(Table, "sortDirection", {default = "asc", type = "string", canTriggerRender = true})
|
Table.defineProperty(Table, "sortDirection", {default = "asc", type = "string", canTriggerRender = true})
|
||||||
---@property scrollOffset number 0 Current scroll position
|
---@property scrollOffset number 0 Current scroll position
|
||||||
Table.defineProperty(Table, "scrollOffset", {default = 0, type = "number", canTriggerRender = true})
|
Table.defineProperty(Table, "scrollOffset", {default = 0, type = "number", canTriggerRender = true})
|
||||||
|
---@property customSortFunction table {} Custom sort functions for columns
|
||||||
|
Table.defineProperty(Table, "customSortFunction", {default = {}, type = "table"})
|
||||||
|
|
||||||
Table.defineEvent(Table, "mouse_click")
|
Table.defineEvent(Table, "mouse_click")
|
||||||
Table.defineEvent(Table, "mouse_scroll")
|
Table.defineEvent(Table, "mouse_scroll")
|
||||||
@@ -95,6 +102,140 @@ function Table:addData(...)
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Sets a custom sort function for a specific column
|
||||||
|
--- @shortDescription Sets a custom sort function for a column
|
||||||
|
--- @param columnIndex number The index of the column
|
||||||
|
--- @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")
|
||||||
|
customSorts[columnIndex] = sortFn
|
||||||
|
self.set("customSortFunction", customSorts)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Adds data with both display and sort values
|
||||||
|
--- @shortDescription Adds formatted data with raw sort values
|
||||||
|
--- @param displayData table The formatted data for display
|
||||||
|
--- @param sortData table The raw data for sorting (optional)
|
||||||
|
--- @return Table self The Table instance
|
||||||
|
function Table:setFormattedData(displayData, sortData)
|
||||||
|
local enrichedData = {}
|
||||||
|
|
||||||
|
for i, row in ipairs(displayData) do
|
||||||
|
local enrichedRow = {}
|
||||||
|
for j, cell in ipairs(row) do
|
||||||
|
enrichedRow[j] = cell
|
||||||
|
end
|
||||||
|
|
||||||
|
if sortData and sortData[i] then
|
||||||
|
enrichedRow._sortValues = sortData[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(enrichedData, enrichedRow)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.set("data", enrichedData)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Set data with automatic formatting
|
||||||
|
--- @shortDescription Sets table data with optional column formatters
|
||||||
|
--- @param rawData table The raw data array
|
||||||
|
--- @param formatters table Optional formatter functions for columns {[2] = function(value) return value end}
|
||||||
|
--- @return Table self The Table instance
|
||||||
|
function Table:setData(rawData, formatters)
|
||||||
|
if not formatters then
|
||||||
|
self.set("data", rawData)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
local formattedData = {}
|
||||||
|
for i, row in ipairs(rawData) do
|
||||||
|
local formattedRow = {}
|
||||||
|
for j, cell in ipairs(row) do
|
||||||
|
if formatters[j] then
|
||||||
|
formattedRow[j] = formatters[j](cell)
|
||||||
|
else
|
||||||
|
formattedRow[j] = cell
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(formattedData, formattedRow)
|
||||||
|
end
|
||||||
|
|
||||||
|
return self:setFormattedData(formattedData, rawData)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @shortDescription Calculates column widths for rendering
|
||||||
|
--- @param columns table The column definitions
|
||||||
|
--- @param totalWidth number The total available width
|
||||||
|
--- @return table The columns with calculated visibleWidth
|
||||||
|
--- @private
|
||||||
|
function Table:calculateColumnWidths(columns, totalWidth)
|
||||||
|
local calculatedColumns = {}
|
||||||
|
local remainingWidth = totalWidth
|
||||||
|
local autoColumns = {}
|
||||||
|
local fixedWidth = 0
|
||||||
|
|
||||||
|
for i, col in ipairs(columns) do
|
||||||
|
calculatedColumns[i] = {
|
||||||
|
name = col.name,
|
||||||
|
width = col.width,
|
||||||
|
minWidth = col.minWidth or 3,
|
||||||
|
maxWidth = col.maxWidth
|
||||||
|
}
|
||||||
|
if type(col.width) == "number" then
|
||||||
|
calculatedColumns[i].visibleWidth = math.max(col.width, calculatedColumns[i].minWidth)
|
||||||
|
if calculatedColumns[i].maxWidth then
|
||||||
|
calculatedColumns[i].visibleWidth = math.min(calculatedColumns[i].visibleWidth, calculatedColumns[i].maxWidth)
|
||||||
|
end
|
||||||
|
remainingWidth = remainingWidth - calculatedColumns[i].visibleWidth
|
||||||
|
fixedWidth = fixedWidth + calculatedColumns[i].visibleWidth
|
||||||
|
elseif type(col.width) == "string" and col.width:match("%%$") then
|
||||||
|
local percent = tonumber(col.width:match("(%d+)%%"))
|
||||||
|
if percent then
|
||||||
|
calculatedColumns[i].visibleWidth = math.floor(totalWidth * percent / 100)
|
||||||
|
calculatedColumns[i].visibleWidth = math.max(calculatedColumns[i].visibleWidth, calculatedColumns[i].minWidth)
|
||||||
|
if calculatedColumns[i].maxWidth then
|
||||||
|
calculatedColumns[i].visibleWidth = math.min(calculatedColumns[i].visibleWidth, calculatedColumns[i].maxWidth)
|
||||||
|
end
|
||||||
|
remainingWidth = remainingWidth - calculatedColumns[i].visibleWidth
|
||||||
|
fixedWidth = fixedWidth + calculatedColumns[i].visibleWidth
|
||||||
|
else
|
||||||
|
table.insert(autoColumns, i)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
table.insert(autoColumns, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #autoColumns > 0 and remainingWidth > 0 then
|
||||||
|
local autoWidth = math.floor(remainingWidth / #autoColumns)
|
||||||
|
for _, colIndex in ipairs(autoColumns) do
|
||||||
|
calculatedColumns[colIndex].visibleWidth = math.max(autoWidth, calculatedColumns[colIndex].minWidth)
|
||||||
|
if calculatedColumns[colIndex].maxWidth then
|
||||||
|
calculatedColumns[colIndex].visibleWidth = math.min(calculatedColumns[colIndex].visibleWidth, calculatedColumns[colIndex].maxWidth)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local totalCalculated = 0
|
||||||
|
for i, col in ipairs(calculatedColumns) do
|
||||||
|
totalCalculated = totalCalculated + (col.visibleWidth or 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
if totalCalculated > totalWidth then
|
||||||
|
local scale = totalWidth / totalCalculated
|
||||||
|
for i, col in ipairs(calculatedColumns) do
|
||||||
|
if col.visibleWidth then
|
||||||
|
col.visibleWidth = math.max(1, math.floor(col.visibleWidth * scale))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return calculatedColumns
|
||||||
|
end
|
||||||
|
|
||||||
--- Sorts the table data by column
|
--- Sorts the table data by column
|
||||||
--- @shortDescription Sorts the table data by the specified column
|
--- @shortDescription Sorts the table data by the specified column
|
||||||
--- @param columnIndex number The index of the column to sort by
|
--- @param columnIndex number The index of the column to sort by
|
||||||
@@ -103,17 +244,47 @@ end
|
|||||||
function Table:sortData(columnIndex, fn)
|
function Table:sortData(columnIndex, fn)
|
||||||
local data = self.get("data")
|
local data = self.get("data")
|
||||||
local direction = self.get("sortDirection")
|
local direction = self.get("sortDirection")
|
||||||
if not fn then
|
local customSorts = self.get("customSortFunction")
|
||||||
|
|
||||||
|
local sortFn = fn or customSorts[columnIndex]
|
||||||
|
|
||||||
|
if sortFn then
|
||||||
table.sort(data, function(a, b)
|
table.sort(data, function(a, b)
|
||||||
if direction == "asc" then
|
return sortFn(a, b, direction)
|
||||||
return a[columnIndex] < b[columnIndex]
|
|
||||||
else
|
|
||||||
return a[columnIndex] > b[columnIndex]
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
table.sort(data, function(a, b)
|
table.sort(data, function(a, b)
|
||||||
return fn(a[columnIndex], b[columnIndex])
|
if not a or not b then return false end
|
||||||
|
|
||||||
|
local valueA, valueB
|
||||||
|
|
||||||
|
if a._sortValues and a._sortValues[columnIndex] then
|
||||||
|
valueA = a._sortValues[columnIndex]
|
||||||
|
else
|
||||||
|
valueA = a[columnIndex]
|
||||||
|
end
|
||||||
|
|
||||||
|
if b._sortValues and b._sortValues[columnIndex] then
|
||||||
|
valueB = b._sortValues[columnIndex]
|
||||||
|
else
|
||||||
|
valueB = b[columnIndex]
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(valueA) == "number" and type(valueB) == "number" then
|
||||||
|
if direction == "asc" then
|
||||||
|
return valueA < valueB
|
||||||
|
else
|
||||||
|
return valueA > valueB
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local strA = tostring(valueA or "")
|
||||||
|
local strB = tostring(valueB or "")
|
||||||
|
if direction == "asc" then
|
||||||
|
return strA < strB
|
||||||
|
else
|
||||||
|
return strA > strB
|
||||||
|
end
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
return self
|
return self
|
||||||
@@ -131,9 +302,14 @@ function Table:mouse_click(button, x, y)
|
|||||||
local relX, relY = self:getRelativePosition(x, y)
|
local relX, relY = self:getRelativePosition(x, y)
|
||||||
|
|
||||||
if relY == 1 then
|
if relY == 1 then
|
||||||
|
local columns = self.get("columns")
|
||||||
|
local width = self.get("width")
|
||||||
|
local calculatedColumns = self:calculateColumnWidths(columns, width)
|
||||||
|
|
||||||
local currentX = 1
|
local currentX = 1
|
||||||
for i, col in ipairs(self.get("columns")) do
|
for i, col in ipairs(calculatedColumns) do
|
||||||
if relX >= currentX and relX < currentX + col.width then
|
local colWidth = col.visibleWidth or col.width or 10
|
||||||
|
if relX >= currentX and relX < currentX + colWidth then
|
||||||
if self.get("sortColumn") == i then
|
if self.get("sortColumn") == i then
|
||||||
self.set("sortDirection", self.get("sortDirection") == "asc" and "desc" or "asc")
|
self.set("sortDirection", self.get("sortDirection") == "asc" and "desc" or "asc")
|
||||||
else
|
else
|
||||||
@@ -143,7 +319,7 @@ function Table:mouse_click(button, x, y)
|
|||||||
self:sortData(i)
|
self:sortData(i)
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
currentX = currentX + col.width
|
currentX = currentX + colWidth
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -189,24 +365,20 @@ function Table:render()
|
|||||||
local height = self.get("height")
|
local height = self.get("height")
|
||||||
local width = self.get("width")
|
local width = self.get("width")
|
||||||
|
|
||||||
|
local calculatedColumns = self:calculateColumnWidths(columns, width)
|
||||||
|
|
||||||
local totalWidth = 0
|
local totalWidth = 0
|
||||||
local lastVisibleColumn = #columns
|
local lastVisibleColumn = #calculatedColumns
|
||||||
for i, col in ipairs(columns) do
|
for i, col in ipairs(calculatedColumns) do
|
||||||
if totalWidth + col.width > width then
|
if totalWidth + col.visibleWidth > width then
|
||||||
if i == 1 then
|
lastVisibleColumn = i - 1
|
||||||
col.visibleWidth = width
|
|
||||||
else
|
|
||||||
col.visibleWidth = width - totalWidth
|
|
||||||
lastVisibleColumn = i
|
|
||||||
end
|
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
col.visibleWidth = col.width
|
totalWidth = totalWidth + col.visibleWidth
|
||||||
totalWidth = totalWidth + col.width
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local currentX = 1
|
local currentX = 1
|
||||||
for i, col in ipairs(columns) do
|
for i, col in ipairs(calculatedColumns) do
|
||||||
if i > lastVisibleColumn then break end
|
if i > lastVisibleColumn then break end
|
||||||
local text = col.name
|
local text = col.name
|
||||||
if i == sortCol then
|
if i == sortCol then
|
||||||
@@ -224,7 +396,7 @@ function Table:render()
|
|||||||
currentX = 1
|
currentX = 1
|
||||||
local bg = (rowIndex + 1) == selected and self.get("selectedColor") or self.get("background")
|
local bg = (rowIndex + 1) == selected and self.get("selectedColor") or self.get("background")
|
||||||
|
|
||||||
for i, col in ipairs(columns) do
|
for i, col in ipairs(calculatedColumns) do
|
||||||
if i > lastVisibleColumn then break end
|
if i > lastVisibleColumn then break end
|
||||||
local cellText = tostring(rowData[i] or "")
|
local cellText = tostring(rowData[i] or "")
|
||||||
local paddedText = cellText .. string.rep(" ", col.visibleWidth - #cellText)
|
local paddedText = cellText .. string.rep(" ", col.visibleWidth - #cellText)
|
||||||
|
|||||||
@@ -125,6 +125,31 @@ end
|
|||||||
function VisualElement:init(props, basalt)
|
function VisualElement:init(props, basalt)
|
||||||
BaseElement.init(self, props, basalt)
|
BaseElement.init(self, props, basalt)
|
||||||
self.set("type", "VisualElement")
|
self.set("type", "VisualElement")
|
||||||
|
self:observe("x", function()
|
||||||
|
if self.parent then
|
||||||
|
self.parent.set("childrenSorted", false)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
self:observe("y", function()
|
||||||
|
if self.parent then
|
||||||
|
self.parent.set("childrenSorted", false)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
self:observe("width", function()
|
||||||
|
if self.parent then
|
||||||
|
self.parent.set("childrenSorted", false)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
self:observe("height", function()
|
||||||
|
if self.parent then
|
||||||
|
self.parent.set("childrenSorted", false)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
self:observe("visible", function()
|
||||||
|
if self.parent then
|
||||||
|
self.parent.set("childrenSorted", false)
|
||||||
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @shortDescription Multi-character drawing with colors
|
--- @shortDescription Multi-character drawing with colors
|
||||||
@@ -210,9 +235,6 @@ end
|
|||||||
--- @param y number The y position to check
|
--- @param y number The y position to check
|
||||||
--- @return boolean isInBounds Whether the coordinates are within the bounds of the element
|
--- @return boolean isInBounds Whether the coordinates are within the bounds of the element
|
||||||
function VisualElement:isInBounds(x, y)
|
function VisualElement:isInBounds(x, y)
|
||||||
if x == nil or y == nil then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
local xPos, yPos = self.get("x"), self.get("y")
|
local xPos, yPos = self.get("x"), self.get("y")
|
||||||
local width, height = self.get("width"), self.get("height")
|
local width, height = self.get("width"), self.get("height")
|
||||||
if(self.get("ignoreOffset"))then
|
if(self.get("ignoreOffset"))then
|
||||||
|
|||||||
Reference in New Issue
Block a user