Skip to content

Commit 1b53b62

Browse files
committed
Fixed a Bug with Children Elements inside Containers (negative location invisible bug)
Improved Table Element
1 parent f0d8d7d commit 1b53b62

File tree

2 files changed

+221
-27
lines changed

2 files changed

+221
-27
lines changed

src/elements/Table.lua

Lines changed: 196 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ Table.defineProperty(Table, "columns", {default = {}, type = "table", canTrigger
1717
if type(col) == "string" then
1818
t[i] = {name = col, width = #col+1}
1919
elseif type(col) == "table" then
20-
t[i] = {name = col.name or "", width = col.width or #col.name+1}
20+
t[i] = {
21+
name = col.name or "",
22+
width = col.width, -- Can be number, "auto", or percentage like "30%"
23+
minWidth = col.minWidth or 3,
24+
maxWidth = col.maxWidth or nil
25+
}
2126
end
2227
end
2328
return t
@@ -44,6 +49,8 @@ Table.defineProperty(Table, "sortColumn", {default = nil, type = "number", canTr
4449
Table.defineProperty(Table, "sortDirection", {default = "asc", type = "string", canTriggerRender = true})
4550
---@property scrollOffset number 0 Current scroll position
4651
Table.defineProperty(Table, "scrollOffset", {default = 0, type = "number", canTriggerRender = true})
52+
---@property customSortFunction table {} Custom sort functions for columns
53+
Table.defineProperty(Table, "customSortFunction", {default = {}, type = "table"})
4754

4855
Table.defineEvent(Table, "mouse_click")
4956
Table.defineEvent(Table, "mouse_scroll")
@@ -95,6 +102,140 @@ function Table:addData(...)
95102
return self
96103
end
97104

105+
--- Sets a custom sort function for a specific column
106+
--- @shortDescription Sets a custom sort function for a column
107+
--- @param columnIndex number The index of the column
108+
--- @param sortFn function Function that takes (rowA, rowB) and returns comparison result
109+
--- @return Table self The Table instance
110+
function Table:setColumnSortFunction(columnIndex, sortFn)
111+
local customSorts = self.get("customSortFunction")
112+
customSorts[columnIndex] = sortFn
113+
self.set("customSortFunction", customSorts)
114+
return self
115+
end
116+
117+
--- Adds data with both display and sort values
118+
--- @shortDescription Adds formatted data with raw sort values
119+
--- @param displayData table The formatted data for display
120+
--- @param sortData table The raw data for sorting (optional)
121+
--- @return Table self The Table instance
122+
function Table:setFormattedData(displayData, sortData)
123+
local enrichedData = {}
124+
125+
for i, row in ipairs(displayData) do
126+
local enrichedRow = {}
127+
for j, cell in ipairs(row) do
128+
enrichedRow[j] = cell
129+
end
130+
131+
if sortData and sortData[i] then
132+
enrichedRow._sortValues = sortData[i]
133+
end
134+
135+
table.insert(enrichedData, enrichedRow)
136+
end
137+
138+
self.set("data", enrichedData)
139+
return self
140+
end
141+
142+
--- Set data with automatic formatting
143+
--- @shortDescription Sets table data with optional column formatters
144+
--- @param rawData table The raw data array
145+
--- @param formatters table Optional formatter functions for columns {[2] = function(value) return value end}
146+
--- @return Table self The Table instance
147+
function Table:setData(rawData, formatters)
148+
if not formatters then
149+
self.set("data", rawData)
150+
return self
151+
end
152+
153+
local formattedData = {}
154+
for i, row in ipairs(rawData) do
155+
local formattedRow = {}
156+
for j, cell in ipairs(row) do
157+
if formatters[j] then
158+
formattedRow[j] = formatters[j](cell)
159+
else
160+
formattedRow[j] = cell
161+
end
162+
end
163+
table.insert(formattedData, formattedRow)
164+
end
165+
166+
return self:setFormattedData(formattedData, rawData)
167+
end
168+
169+
--- @shortDescription Calculates column widths for rendering
170+
--- @param columns table The column definitions
171+
--- @param totalWidth number The total available width
172+
--- @return table The columns with calculated visibleWidth
173+
--- @private
174+
function Table:calculateColumnWidths(columns, totalWidth)
175+
local calculatedColumns = {}
176+
local remainingWidth = totalWidth
177+
local autoColumns = {}
178+
local fixedWidth = 0
179+
180+
for i, col in ipairs(columns) do
181+
calculatedColumns[i] = {
182+
name = col.name,
183+
width = col.width,
184+
minWidth = col.minWidth or 3,
185+
maxWidth = col.maxWidth
186+
}
187+
if type(col.width) == "number" then
188+
calculatedColumns[i].visibleWidth = math.max(col.width, calculatedColumns[i].minWidth)
189+
if calculatedColumns[i].maxWidth then
190+
calculatedColumns[i].visibleWidth = math.min(calculatedColumns[i].visibleWidth, calculatedColumns[i].maxWidth)
191+
end
192+
remainingWidth = remainingWidth - calculatedColumns[i].visibleWidth
193+
fixedWidth = fixedWidth + calculatedColumns[i].visibleWidth
194+
elseif type(col.width) == "string" and col.width:match("%%$") then
195+
local percent = tonumber(col.width:match("(%d+)%%"))
196+
if percent then
197+
calculatedColumns[i].visibleWidth = math.floor(totalWidth * percent / 100)
198+
calculatedColumns[i].visibleWidth = math.max(calculatedColumns[i].visibleWidth, calculatedColumns[i].minWidth)
199+
if calculatedColumns[i].maxWidth then
200+
calculatedColumns[i].visibleWidth = math.min(calculatedColumns[i].visibleWidth, calculatedColumns[i].maxWidth)
201+
end
202+
remainingWidth = remainingWidth - calculatedColumns[i].visibleWidth
203+
fixedWidth = fixedWidth + calculatedColumns[i].visibleWidth
204+
else
205+
table.insert(autoColumns, i)
206+
end
207+
else
208+
table.insert(autoColumns, i)
209+
end
210+
end
211+
212+
if #autoColumns > 0 and remainingWidth > 0 then
213+
local autoWidth = math.floor(remainingWidth / #autoColumns)
214+
for _, colIndex in ipairs(autoColumns) do
215+
calculatedColumns[colIndex].visibleWidth = math.max(autoWidth, calculatedColumns[colIndex].minWidth)
216+
if calculatedColumns[colIndex].maxWidth then
217+
calculatedColumns[colIndex].visibleWidth = math.min(calculatedColumns[colIndex].visibleWidth, calculatedColumns[colIndex].maxWidth)
218+
end
219+
end
220+
end
221+
222+
local totalCalculated = 0
223+
for i, col in ipairs(calculatedColumns) do
224+
totalCalculated = totalCalculated + (col.visibleWidth or 0)
225+
end
226+
227+
if totalCalculated > totalWidth then
228+
local scale = totalWidth / totalCalculated
229+
for i, col in ipairs(calculatedColumns) do
230+
if col.visibleWidth then
231+
col.visibleWidth = math.max(1, math.floor(col.visibleWidth * scale))
232+
end
233+
end
234+
end
235+
236+
return calculatedColumns
237+
end
238+
98239
--- Sorts the table data by column
99240
--- @shortDescription Sorts the table data by the specified column
100241
--- @param columnIndex number The index of the column to sort by
@@ -103,17 +244,47 @@ end
103244
function Table:sortData(columnIndex, fn)
104245
local data = self.get("data")
105246
local direction = self.get("sortDirection")
106-
if not fn then
247+
local customSorts = self.get("customSortFunction")
248+
249+
local sortFn = fn or customSorts[columnIndex]
250+
251+
if sortFn then
107252
table.sort(data, function(a, b)
108-
if direction == "asc" then
109-
return a[columnIndex] < b[columnIndex]
110-
else
111-
return a[columnIndex] > b[columnIndex]
112-
end
253+
return sortFn(a, b, direction)
113254
end)
114255
else
115256
table.sort(data, function(a, b)
116-
return fn(a[columnIndex], b[columnIndex])
257+
if not a or not b then return false end
258+
259+
local valueA, valueB
260+
261+
if a._sortValues and a._sortValues[columnIndex] then
262+
valueA = a._sortValues[columnIndex]
263+
else
264+
valueA = a[columnIndex]
265+
end
266+
267+
if b._sortValues and b._sortValues[columnIndex] then
268+
valueB = b._sortValues[columnIndex]
269+
else
270+
valueB = b[columnIndex]
271+
end
272+
273+
if type(valueA) == "number" and type(valueB) == "number" then
274+
if direction == "asc" then
275+
return valueA < valueB
276+
else
277+
return valueA > valueB
278+
end
279+
else
280+
local strA = tostring(valueA or "")
281+
local strB = tostring(valueB or "")
282+
if direction == "asc" then
283+
return strA < strB
284+
else
285+
return strA > strB
286+
end
287+
end
117288
end)
118289
end
119290
return self
@@ -131,9 +302,14 @@ function Table:mouse_click(button, x, y)
131302
local relX, relY = self:getRelativePosition(x, y)
132303

133304
if relY == 1 then
305+
local columns = self.get("columns")
306+
local width = self.get("width")
307+
local calculatedColumns = self:calculateColumnWidths(columns, width)
308+
134309
local currentX = 1
135-
for i, col in ipairs(self.get("columns")) do
136-
if relX >= currentX and relX < currentX + col.width then
310+
for i, col in ipairs(calculatedColumns) do
311+
local colWidth = col.visibleWidth or col.width or 10
312+
if relX >= currentX and relX < currentX + colWidth then
137313
if self.get("sortColumn") == i then
138314
self.set("sortDirection", self.get("sortDirection") == "asc" and "desc" or "asc")
139315
else
@@ -143,7 +319,7 @@ function Table:mouse_click(button, x, y)
143319
self:sortData(i)
144320
break
145321
end
146-
currentX = currentX + col.width
322+
currentX = currentX + colWidth
147323
end
148324
end
149325

@@ -189,24 +365,20 @@ function Table:render()
189365
local height = self.get("height")
190366
local width = self.get("width")
191367

368+
local calculatedColumns = self:calculateColumnWidths(columns, width)
369+
192370
local totalWidth = 0
193-
local lastVisibleColumn = #columns
194-
for i, col in ipairs(columns) do
195-
if totalWidth + col.width > width then
196-
if i == 1 then
197-
col.visibleWidth = width
198-
else
199-
col.visibleWidth = width - totalWidth
200-
lastVisibleColumn = i
201-
end
371+
local lastVisibleColumn = #calculatedColumns
372+
for i, col in ipairs(calculatedColumns) do
373+
if totalWidth + col.visibleWidth > width then
374+
lastVisibleColumn = i - 1
202375
break
203376
end
204-
col.visibleWidth = col.width
205-
totalWidth = totalWidth + col.width
377+
totalWidth = totalWidth + col.visibleWidth
206378
end
207379

208380
local currentX = 1
209-
for i, col in ipairs(columns) do
381+
for i, col in ipairs(calculatedColumns) do
210382
if i > lastVisibleColumn then break end
211383
local text = col.name
212384
if i == sortCol then
@@ -224,7 +396,7 @@ function Table:render()
224396
currentX = 1
225397
local bg = (rowIndex + 1) == selected and self.get("selectedColor") or self.get("background")
226398

227-
for i, col in ipairs(columns) do
399+
for i, col in ipairs(calculatedColumns) do
228400
if i > lastVisibleColumn then break end
229401
local cellText = tostring(rowData[i] or "")
230402
local paddedText = cellText .. string.rep(" ", col.visibleWidth - #cellText)

src/elements/VisualElement.lua

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,31 @@ end
125125
function VisualElement:init(props, basalt)
126126
BaseElement.init(self, props, basalt)
127127
self.set("type", "VisualElement")
128+
self:observe("x", function()
129+
if self.parent then
130+
self.parent.set("childrenSorted", false)
131+
end
132+
end)
133+
self:observe("y", function()
134+
if self.parent then
135+
self.parent.set("childrenSorted", false)
136+
end
137+
end)
138+
self:observe("width", function()
139+
if self.parent then
140+
self.parent.set("childrenSorted", false)
141+
end
142+
end)
143+
self:observe("height", function()
144+
if self.parent then
145+
self.parent.set("childrenSorted", false)
146+
end
147+
end)
148+
self:observe("visible", function()
149+
if self.parent then
150+
self.parent.set("childrenSorted", false)
151+
end
152+
end)
128153
end
129154

130155
--- @shortDescription Multi-character drawing with colors
@@ -210,9 +235,6 @@ end
210235
--- @param y number The y position to check
211236
--- @return boolean isInBounds Whether the coordinates are within the bounds of the element
212237
function VisualElement:isInBounds(x, y)
213-
if x == nil or y == nil then
214-
return false
215-
end
216238
local xPos, yPos = self.get("x"), self.get("y")
217239
local width, height = self.get("width"), self.get("height")
218240
if(self.get("ignoreOffset"))then

0 commit comments

Comments
 (0)