uitable.lua

main 433 lines · 10.3 KB Raw
Alex Alex Commit Client Upload Part 2 19/10/2026 00:06
1-- @docclass
2--[[
3 TODO:
4 * Make table headers more robust.
5 * Get dynamic row heights working with text wrapping.
6]]
7
8TABLE_SORTING_ASC = 0
9TABLE_SORTING_DESC = 1
10
11UITable = extends(UIWidget, "UITable")
12
13-- Initialize default values
14function UITable.create()
15 local table = UITable.internalCreate()
16 table.headerRow = nil
17 table.headerColumns = {}
18 table.dataSpace = nil
19 table.rows = {}
20 table.rowBaseStyle = nil
21 table.columns = {}
22 table.columnWidth = {}
23 table.columBaseStyle = nil
24 table.headerRowBaseStyle = nil
25 table.headerColumnBaseStyle = nil
26 table.selectedRow = nil
27 table.defaultColumnWidth = 80
28 table.sortColumn = -1
29 table.sortType = TABLE_SORTING_ASC
30 table.autoSort = false
31
32 return table
33end
34
35-- Clear table values
36function UITable:onDestroy()
37 for _,row in pairs(self.rows) do
38 row.onClick = nil
39 end
40 self.rows = {}
41 self.columns = {}
42 self.headerRow = nil
43 self.headerColumns = {}
44 self.columnWidth = {}
45 self.selectedRow = nil
46
47 if self.dataSpace then
48 self.dataSpace:destroyChildren()
49 self.dataSpace = nil
50 end
51end
52
53-- Detect if a header is already defined
54function UITable:onSetup()
55 local header = self:getChildById('header')
56 if header then
57 self:setHeader(header)
58 end
59end
60
61-- Parse table related styles
62function UITable:onStyleApply(styleName, styleNode)
63 for name, value in pairs(styleNode) do
64 if value ~= false then
65 if name == 'table-data' then
66 addEvent(function()
67 self:setTableData(self:getParent():getChildById(value))
68 end)
69 elseif name == 'column-style' then
70 addEvent(function()
71 self:setColumnStyle(value)
72 end)
73 elseif name == 'row-style' then
74 addEvent(function()
75 self:setRowStyle(value)
76 end)
77 elseif name == 'header-column-style' then
78 addEvent(function()
79 self:setHeaderColumnStyle(value)
80 end)
81 elseif name == 'header-row-style' then
82 addEvent(function()
83 self:setHeaderRowStyle(value)
84 end)
85 end
86 end
87 end
88end
89
90function UITable:setColumnWidth(width)
91 if self:hasHeader() then return end
92 self.columnWidth = width
93end
94
95function UITable:setDefaultColumnWidth(width)
96 self.defaultColumnWidth = width
97end
98
99-- Check if the table has a header
100function UITable:hasHeader()
101 return self.headerRow ~= nil
102end
103
104-- Clear all rows
105function UITable:clearData()
106 if not self.dataSpace then
107 return
108 end
109 self.dataSpace:destroyChildren()
110 self.selectedRow = nil
111 self.columns = {}
112 self.rows = {}
113end
114
115-- Set existing child as header
116function UITable:setHeader(headerWidget)
117 self:removeHeader()
118
119 if self.dataSpace then
120 local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop()
121 self.dataSpace:applyStyle({ height = newHeight })
122 end
123
124 self.headerColumns = {}
125 self.columnWidth = {}
126 for colId, column in pairs(headerWidget:getChildren()) do
127 column.colId = colId
128 column.table = self
129 table.insert(self.columnWidth, column:getWidth())
130 table.insert(self.headerColumns, column)
131 end
132
133 self.headerRow = headerWidget
134end
135
136-- Create and add header from table data
137function UITable:addHeader(data)
138 if not data or type(data) ~= 'table' then
139 g_logger.error('UITable:addHeaderRow - table columns must be provided in a table')
140 return
141 end
142
143 self:removeHeader()
144
145 -- build header columns
146 local columns = {}
147 for colId, column in pairs(data) do
148 local col = g_ui.createWidget(self.headerColumnBaseStyle)
149 col.colId = colId
150 col.table = self
151 for type, value in pairs(column) do
152 if type == 'width' then
153 col:setWidth(value)
154 elseif type == 'height' then
155 col:setHeight(value)
156 elseif type == 'text' then
157 col:setText(value)
158 elseif type == 'onClick' then
159 col.onClick = value
160 end
161 end
162 table.insert(columns, col)
163 end
164
165 -- create a new header
166 local headerRow = g_ui.createWidget(self.headerRowBaseStyle, self)
167 local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop()
168 self.dataSpace:applyStyle({ height = newHeight })
169
170 headerRow:setId('header')
171 self.headerColumns = {}
172 self.columnWidth = {}
173 for _, column in pairs(columns) do
174 headerRow:addChild(column)
175 table.insert(self.columnWidth, column:getWidth())
176 table.insert(self.headerColumns, column)
177 end
178
179 self.headerRow = headerRow
180 return headerRow
181end
182
183-- Remove header
184function UITable:removeHeader()
185 if self:hasHeader() then
186 if self.dataSpace then
187 local newHeight = self.dataSpace:getHeight()+self.headerRow:getHeight()+self.dataSpace:getMarginTop()
188 self.dataSpace:applyStyle({ height = newHeight })
189 end
190 self.headerColumns = {}
191 self.columnWidth = {}
192 self.headerRow:destroy()
193 self.headerRow = nil
194 end
195end
196
197function UITable:addRow(data, height)
198 if not self.dataSpace then
199 g_logger.error('UITable:addRow - table data space has not been set, cannot add rows.')
200 return
201 end
202 if not data or type(data) ~= 'table' then
203 g_logger.error('UITable:addRow - table columns must be provided in a table.')
204 return
205 end
206
207 local row = g_ui.createWidget(self.rowBaseStyle)
208 row.table = self
209 if height then row:setHeight(height) end
210
211 local rowId = #self.rows + 1
212 row.rowId = rowId
213 row:setId('row'..rowId)
214 row:updateBackgroundColor()
215
216 self.columns[rowId] = {}
217 for colId, column in pairs(data) do
218 local col = g_ui.createWidget(self.columBaseStyle, row)
219 if column.width then
220 col:setWidth(column.width)
221 else
222 col:setWidth(self.columnWidth[colId] or self.defaultColumnWidth)
223 end
224 if column.height then
225 col:setHeight(column.height)
226 end
227 if column.text then
228 col:setText(column.text)
229 end
230 if column.sortvalue then
231 col.sortvalue = column.sortvalue
232 else
233 col.sortvalue = column.text or 0
234 end
235 table.insert(self.columns[rowId], col)
236 end
237
238 self.dataSpace:addChild(row)
239 table.insert(self.rows, row)
240
241 if self.autoSort then
242 self:sort()
243 end
244
245 return row
246end
247
248-- Update row indices and background color
249function UITable:updateRows()
250 for rowId = 1, #self.rows do
251 local row = self.rows[rowId]
252 row.rowId = rowId
253 row:setId('row'..rowId)
254 row:updateBackgroundColor()
255 end
256end
257
258-- Removes the given row widget from the table
259function UITable:removeRow(row)
260 if self.selectedRow == row then
261 self:selectRow(nil)
262 end
263 row.onClick = nil
264 row.table = nil
265 table.remove(self.columns, row.rowId)
266 table.remove(self.rows, row.rowId)
267 self.dataSpace:removeChild(row)
268 self:updateRows()
269end
270
271function UITable:toggleSorting(enabled)
272 self.autoSort = enabled
273end
274
275function UITable:setSorting(colId, sortType)
276 self.headerColumns[colId]:focus()
277
278 if sortType then
279 self.sortType = sortType
280 elseif self.sortColumn == colId then
281 if self.sortType == TABLE_SORTING_ASC then
282 self.sortType = TABLE_SORTING_DESC
283 else
284 self.sortType = TABLE_SORTING_ASC
285 end
286 else
287 self.sortType = TABLE_SORTING_ASC
288 end
289 self.sortColumn = colId
290end
291
292function UITable:sort()
293 if self.sortColumn <= 0 then
294 return
295 end
296
297 if self.sortType == TABLE_SORTING_ASC then
298 table.sort(self.rows, function(rowA, b)
299 return rowA:getChildByIndex(self.sortColumn).sortvalue < b:getChildByIndex(self.sortColumn).sortvalue
300 end)
301 else
302 table.sort(self.rows, function(rowA, b)
303 return rowA:getChildByIndex(self.sortColumn).sortvalue > b:getChildByIndex(self.sortColumn).sortvalue
304 end)
305 end
306
307 if self.dataSpace then
308 for _, child in pairs(self.dataSpace:getChildren()) do
309 self.dataSpace:removeChild(child)
310 end
311 end
312
313 self:updateRows()
314 self.columns = {}
315 for _, row in pairs(self.rows) do
316 if self.dataSpace then
317 self.dataSpace:addChild(row)
318 end
319
320 self.columns[row.rowId] = {}
321 for _, column in pairs(row:getChildren()) do
322 table.insert(self.columns[row.rowId], column)
323 end
324 end
325end
326
327function UITable:selectRow(selectedRow)
328 if selectedRow == self.selectedRow then return end
329
330 local previousSelectedRow = self.selectedRow
331 self.selectedRow = selectedRow
332
333 if previousSelectedRow then
334 previousSelectedRow:setChecked(false)
335 end
336
337 if selectedRow then
338 selectedRow:setChecked(true)
339 end
340
341 signalcall(self.onSelectionChange, self, selectedRow, previousSelectedRow)
342end
343
344function UITable:setTableData(tableData)
345 local headerHeight = 0
346 if self.headerRow then
347 headerHeight = self.headerRow:getHeight()
348 end
349
350 self.dataSpace = tableData
351 self.dataSpace:applyStyle({ height = self:getHeight()-headerHeight-self:getMarginTop() })
352end
353
354function UITable:setRowStyle(style, dontUpdate)
355 self.rowBaseStyle = style
356
357 if not dontUpdate then
358 for _, row in pairs(self.rows) do
359 row:setStyle(style)
360 end
361 end
362end
363
364function UITable:setColumnStyle(style, dontUpdate)
365 self.columBaseStyle = style
366
367 if not dontUpdate then
368 for _, columns in pairs(self.columns) do
369 for _, col in pairs(columns) do
370 col:setStyle(style)
371 end
372 end
373 end
374end
375
376function UITable:setHeaderRowStyle(style)
377 self.headerRowBaseStyle = style
378 if self.headerRow then
379 self.headerRow:setStyle(style)
380 end
381end
382
383function UITable:setHeaderColumnStyle(style)
384 self.headerColumnBaseStyle = style
385 for _, col in pairs(self.headerColumns) do
386 col:setStyle(style)
387 end
388end
389
390
391UITableRow = extends(UIWidget, "UITableRow")
392
393function UITableRow:onFocusChange(focused)
394 if focused then
395 if self.table then self.table:selectRow(self) end
396 end
397end
398
399function UITableRow:onStyleApply(styleName, styleNode)
400 for name,value in pairs(styleNode) do
401 if name == 'even-background-color' then
402 self.evenBackgroundColor = value
403 elseif name == 'odd-background-color' then
404 self.oddBackgroundColor = value
405 end
406 end
407end
408
409function UITableRow:updateBackgroundColor()
410 self.backgroundColor = nil
411
412 local isEven = (self.rowId % 2 == 0)
413 if isEven and self.evenBackgroundColor then
414 self.backgroundColor = self.evenBackgroundColor
415 elseif not isEven and self.oddBackgroundColor then
416 self.backgroundColor = self.oddBackgroundColor
417 end
418
419 if self.backgroundColor then
420 self:mergeStyle({ ['background-color'] = self.backgroundColor })
421 end
422end
423
424
425UITableHeaderColumn = extends(UIButton, "UITableHeaderColumn")
426
427function UITableHeaderColumn:onClick()
428 if self.table then
429 self.table:setSorting(self.colId)
430 self.table:sort()
431 end
432end
433
Top