mirror of
				https://github.com/ErikasKontenis/SabrehavenServer.git
				synced 2025-10-31 03:56:22 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			433 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			433 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| -- @docclass
 | |
| --[[
 | |
|   TODO:
 | |
|     * Make table headers more robust.
 | |
|     * Get dynamic row heights working with text wrapping.
 | |
| ]]
 | |
| 
 | |
| TABLE_SORTING_ASC = 0
 | |
| TABLE_SORTING_DESC = 1
 | |
| 
 | |
| UITable = extends(UIWidget, "UITable")
 | |
| 
 | |
| -- Initialize default values
 | |
| function UITable.create()
 | |
|   local table = UITable.internalCreate()
 | |
|   table.headerRow = nil
 | |
|   table.headerColumns = {}
 | |
|   table.dataSpace = nil
 | |
|   table.rows = {}
 | |
|   table.rowBaseStyle = nil
 | |
|   table.columns = {}
 | |
|   table.columnWidth = {}
 | |
|   table.columBaseStyle = nil
 | |
|   table.headerRowBaseStyle = nil
 | |
|   table.headerColumnBaseStyle = nil
 | |
|   table.selectedRow = nil
 | |
|   table.defaultColumnWidth = 80
 | |
|   table.sortColumn = -1
 | |
|   table.sortType = TABLE_SORTING_ASC
 | |
|   table.autoSort = false
 | |
| 
 | |
|   return table
 | |
| end
 | |
| 
 | |
| -- Clear table values
 | |
| function UITable:onDestroy()
 | |
|   for _,row in pairs(self.rows) do
 | |
|     row.onClick = nil
 | |
|   end
 | |
|   self.rows = {}
 | |
|   self.columns = {}
 | |
|   self.headerRow = nil
 | |
|   self.headerColumns = {}
 | |
|   self.columnWidth = {}
 | |
|   self.selectedRow = nil
 | |
| 
 | |
|   if self.dataSpace then
 | |
|     self.dataSpace:destroyChildren()
 | |
|     self.dataSpace = nil
 | |
|   end
 | |
| end
 | |
| 
 | |
| -- Detect if a header is already defined
 | |
| function UITable:onSetup()
 | |
|   local header = self:getChildById('header')
 | |
|   if header then
 | |
|     self:setHeader(header)
 | |
|   end
 | |
| end
 | |
| 
 | |
| -- Parse table related styles
 | |
| function UITable:onStyleApply(styleName, styleNode)
 | |
|   for name, value in pairs(styleNode) do
 | |
|     if value ~= false then
 | |
|       if name == 'table-data' then
 | |
|         addEvent(function()
 | |
|           self:setTableData(self:getParent():getChildById(value))
 | |
|         end)
 | |
|       elseif name == 'column-style' then
 | |
|         addEvent(function()
 | |
|           self:setColumnStyle(value)
 | |
|         end)
 | |
|       elseif name == 'row-style' then
 | |
|         addEvent(function()
 | |
|           self:setRowStyle(value)
 | |
|         end)
 | |
|       elseif name == 'header-column-style' then
 | |
|         addEvent(function()
 | |
|           self:setHeaderColumnStyle(value)
 | |
|         end)
 | |
|       elseif name == 'header-row-style' then
 | |
|         addEvent(function()
 | |
|           self:setHeaderRowStyle(value)
 | |
|         end)
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| function UITable:setColumnWidth(width)
 | |
|   if self:hasHeader() then return end
 | |
|   self.columnWidth = width
 | |
| end
 | |
| 
 | |
| function UITable:setDefaultColumnWidth(width)
 | |
|   self.defaultColumnWidth = width
 | |
| end
 | |
| 
 | |
| -- Check if the table has a header
 | |
| function UITable:hasHeader()
 | |
|   return self.headerRow ~= nil
 | |
| end
 | |
| 
 | |
| -- Clear all rows
 | |
| function UITable:clearData()
 | |
|   if not self.dataSpace then
 | |
|     return
 | |
|   end
 | |
|   self.dataSpace:destroyChildren()
 | |
|   self.selectedRow = nil
 | |
|   self.columns = {}
 | |
|   self.rows = {}
 | |
| end
 | |
| 
 | |
| -- Set existing child as header
 | |
| function UITable:setHeader(headerWidget)
 | |
|   self:removeHeader()
 | |
| 
 | |
|   if self.dataSpace then
 | |
|     local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop()
 | |
|     self.dataSpace:applyStyle({ height = newHeight })
 | |
|   end
 | |
| 
 | |
|   self.headerColumns = {}
 | |
|   self.columnWidth = {}
 | |
|   for colId, column in pairs(headerWidget:getChildren()) do
 | |
|     column.colId = colId
 | |
|     column.table = self
 | |
|     table.insert(self.columnWidth, column:getWidth())
 | |
|     table.insert(self.headerColumns, column)
 | |
|   end
 | |
| 
 | |
|   self.headerRow = headerWidget
 | |
| end
 | |
| 
 | |
| -- Create and add header from table data
 | |
| function UITable:addHeader(data)
 | |
|   if not data or type(data) ~= 'table' then
 | |
|     g_logger.error('UITable:addHeaderRow - table columns must be provided in a table')
 | |
|     return
 | |
|   end
 | |
| 
 | |
|   self:removeHeader()
 | |
| 
 | |
|   -- build header columns
 | |
|   local columns = {}
 | |
|   for colId, column in pairs(data) do
 | |
|     local col = g_ui.createWidget(self.headerColumnBaseStyle)
 | |
|     col.colId = colId
 | |
|     col.table = self
 | |
|     for type, value in pairs(column) do
 | |
|       if type == 'width' then
 | |
|         col:setWidth(value)
 | |
|       elseif type == 'height' then
 | |
|         col:setHeight(value)
 | |
|       elseif type == 'text' then
 | |
|         col:setText(value)
 | |
|       elseif type == 'onClick' then
 | |
|         col.onClick = value
 | |
|       end
 | |
|     end
 | |
|     table.insert(columns, col)
 | |
|   end
 | |
| 
 | |
|   -- create a new header
 | |
|   local headerRow = g_ui.createWidget(self.headerRowBaseStyle, self)
 | |
|   local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop()
 | |
|   self.dataSpace:applyStyle({ height = newHeight })
 | |
| 
 | |
|   headerRow:setId('header')
 | |
|   self.headerColumns = {}
 | |
|   self.columnWidth = {}
 | |
|   for _, column in pairs(columns) do
 | |
|     headerRow:addChild(column)
 | |
|     table.insert(self.columnWidth, column:getWidth())
 | |
|     table.insert(self.headerColumns, column)
 | |
|   end
 | |
| 
 | |
|   self.headerRow = headerRow
 | |
|   return headerRow
 | |
| end
 | |
| 
 | |
| -- Remove header
 | |
| function UITable:removeHeader()
 | |
|   if self:hasHeader() then
 | |
|     if self.dataSpace then
 | |
|       local newHeight = self.dataSpace:getHeight()+self.headerRow:getHeight()+self.dataSpace:getMarginTop()
 | |
|       self.dataSpace:applyStyle({ height = newHeight })
 | |
|     end
 | |
|     self.headerColumns = {}
 | |
|     self.columnWidth = {}
 | |
|     self.headerRow:destroy()
 | |
|     self.headerRow = nil
 | |
|   end
 | |
| end
 | |
| 
 | |
| function UITable:addRow(data, height)
 | |
|   if not self.dataSpace then
 | |
|     g_logger.error('UITable:addRow - table data space has not been set, cannot add rows.')
 | |
|     return
 | |
|   end
 | |
|   if not data or type(data) ~= 'table' then
 | |
|     g_logger.error('UITable:addRow - table columns must be provided in a table.')
 | |
|     return
 | |
|   end
 | |
| 
 | |
|   local row = g_ui.createWidget(self.rowBaseStyle)
 | |
|   row.table = self
 | |
|   if height then row:setHeight(height) end
 | |
| 
 | |
|   local rowId = #self.rows + 1
 | |
|   row.rowId = rowId
 | |
|   row:setId('row'..rowId)
 | |
|   row:updateBackgroundColor()
 | |
| 
 | |
|   self.columns[rowId] = {}
 | |
|   for colId, column in pairs(data) do
 | |
|     local col = g_ui.createWidget(self.columBaseStyle, row)
 | |
|     if column.width then
 | |
|       col:setWidth(column.width)
 | |
|     else
 | |
|       col:setWidth(self.columnWidth[colId] or self.defaultColumnWidth)
 | |
|     end
 | |
|     if column.height then
 | |
|       col:setHeight(column.height)
 | |
|     end
 | |
|     if column.text then
 | |
|       col:setText(column.text)
 | |
|     end
 | |
|     if column.sortvalue then
 | |
|       col.sortvalue = column.sortvalue
 | |
|     else
 | |
|       col.sortvalue = column.text or 0
 | |
|     end
 | |
|     table.insert(self.columns[rowId], col)
 | |
|   end
 | |
| 
 | |
|   self.dataSpace:addChild(row)
 | |
|   table.insert(self.rows, row)
 | |
| 
 | |
|   if self.autoSort then
 | |
|     self:sort()
 | |
|   end
 | |
| 
 | |
|   return row
 | |
| end
 | |
| 
 | |
| -- Update row indices and background color
 | |
| function UITable:updateRows()
 | |
|   for rowId = 1, #self.rows do
 | |
|     local row = self.rows[rowId]
 | |
|     row.rowId = rowId
 | |
|     row:setId('row'..rowId)
 | |
|     row:updateBackgroundColor()
 | |
|   end
 | |
| end
 | |
| 
 | |
| -- Removes the given row widget from the table
 | |
| function UITable:removeRow(row)
 | |
|   if self.selectedRow == row then
 | |
|     self:selectRow(nil)
 | |
|   end
 | |
|   row.onClick = nil
 | |
|   row.table = nil
 | |
|   table.remove(self.columns, row.rowId)
 | |
|   table.remove(self.rows, row.rowId)
 | |
|   self.dataSpace:removeChild(row)
 | |
|   self:updateRows()
 | |
| end
 | |
| 
 | |
| function UITable:toggleSorting(enabled)
 | |
|   self.autoSort = enabled
 | |
| end
 | |
| 
 | |
| function UITable:setSorting(colId, sortType)
 | |
|   self.headerColumns[colId]:focus()
 | |
| 
 | |
|   if sortType then
 | |
|     self.sortType = sortType
 | |
|   elseif self.sortColumn == colId then
 | |
|     if self.sortType == TABLE_SORTING_ASC then
 | |
|       self.sortType = TABLE_SORTING_DESC
 | |
|     else
 | |
|       self.sortType = TABLE_SORTING_ASC
 | |
|     end
 | |
|   else
 | |
|     self.sortType = TABLE_SORTING_ASC
 | |
|   end
 | |
|   self.sortColumn = colId
 | |
| end
 | |
| 
 | |
| function UITable:sort()
 | |
|   if self.sortColumn <= 0 then
 | |
|     return
 | |
|   end
 | |
| 
 | |
|   if self.sortType == TABLE_SORTING_ASC then
 | |
|     table.sort(self.rows, function(rowA, b)
 | |
|       return rowA:getChildByIndex(self.sortColumn).sortvalue < b:getChildByIndex(self.sortColumn).sortvalue
 | |
|     end)
 | |
|   else
 | |
|     table.sort(self.rows, function(rowA, b)
 | |
|       return rowA:getChildByIndex(self.sortColumn).sortvalue > b:getChildByIndex(self.sortColumn).sortvalue
 | |
|     end)
 | |
|   end
 | |
| 
 | |
|   if self.dataSpace then
 | |
|     for _, child in pairs(self.dataSpace:getChildren()) do
 | |
|       self.dataSpace:removeChild(child)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   self:updateRows()
 | |
|   self.columns = {}
 | |
|   for _, row in pairs(self.rows) do
 | |
|     if self.dataSpace then
 | |
|       self.dataSpace:addChild(row)
 | |
|     end
 | |
| 
 | |
|     self.columns[row.rowId] = {}
 | |
|     for _, column in pairs(row:getChildren()) do
 | |
|       table.insert(self.columns[row.rowId], column)
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| function UITable:selectRow(selectedRow)
 | |
|   if selectedRow == self.selectedRow then return end
 | |
| 
 | |
|   local previousSelectedRow = self.selectedRow
 | |
|   self.selectedRow = selectedRow
 | |
| 
 | |
|   if previousSelectedRow then
 | |
|     previousSelectedRow:setChecked(false)
 | |
|   end
 | |
| 
 | |
|   if selectedRow then
 | |
|     selectedRow:setChecked(true)
 | |
|   end
 | |
| 
 | |
|   signalcall(self.onSelectionChange, self, selectedRow, previousSelectedRow)
 | |
| end
 | |
| 
 | |
| function UITable:setTableData(tableData)
 | |
|   local headerHeight = 0
 | |
|   if self.headerRow then
 | |
|     headerHeight = self.headerRow:getHeight()
 | |
|   end
 | |
| 
 | |
|   self.dataSpace = tableData
 | |
|   self.dataSpace:applyStyle({ height = self:getHeight()-headerHeight-self:getMarginTop() })
 | |
| end
 | |
| 
 | |
| function UITable:setRowStyle(style, dontUpdate)
 | |
|   self.rowBaseStyle = style
 | |
| 
 | |
|   if not dontUpdate then
 | |
|     for _, row in pairs(self.rows) do
 | |
|       row:setStyle(style)
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| function UITable:setColumnStyle(style, dontUpdate)
 | |
|   self.columBaseStyle = style
 | |
| 
 | |
|   if not dontUpdate then
 | |
|     for _, columns in pairs(self.columns) do
 | |
|       for _, col in pairs(columns) do
 | |
|         col:setStyle(style)
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| function UITable:setHeaderRowStyle(style)
 | |
|   self.headerRowBaseStyle = style
 | |
|   if self.headerRow then
 | |
|     self.headerRow:setStyle(style)
 | |
|   end
 | |
| end
 | |
| 
 | |
| function UITable:setHeaderColumnStyle(style)
 | |
|   self.headerColumnBaseStyle = style
 | |
|   for _, col in pairs(self.headerColumns) do
 | |
|     col:setStyle(style)
 | |
|   end
 | |
| end
 | |
| 
 | |
| 
 | |
| UITableRow = extends(UIWidget, "UITableRow")
 | |
| 
 | |
| function UITableRow:onFocusChange(focused)
 | |
|   if focused then
 | |
|     if self.table then self.table:selectRow(self) end
 | |
|   end
 | |
| end
 | |
| 
 | |
| function UITableRow:onStyleApply(styleName, styleNode)
 | |
|   for name,value in pairs(styleNode) do
 | |
|     if name == 'even-background-color' then
 | |
|       self.evenBackgroundColor = value
 | |
|     elseif name == 'odd-background-color' then
 | |
|       self.oddBackgroundColor = value
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| function UITableRow:updateBackgroundColor()
 | |
|   self.backgroundColor = nil
 | |
| 
 | |
|   local isEven = (self.rowId % 2 == 0)
 | |
|   if isEven and self.evenBackgroundColor then
 | |
|     self.backgroundColor = self.evenBackgroundColor
 | |
|   elseif not isEven and self.oddBackgroundColor then
 | |
|     self.backgroundColor = self.oddBackgroundColor
 | |
|   end
 | |
| 
 | |
|   if self.backgroundColor then
 | |
|     self:mergeStyle({ ['background-color'] = self.backgroundColor })
 | |
|   end
 | |
| end
 | |
| 
 | |
| 
 | |
| UITableHeaderColumn = extends(UIButton, "UITableHeaderColumn")
 | |
| 
 | |
| function UITableHeaderColumn:onClick()
 | |
|   if self.table then
 | |
|     self.table:setSorting(self.colId)
 | |
|     self.table:sort()
 | |
|   end
 | |
| end
 | 
