diff --git a/data/styles/20-tables.otui b/data/styles/20-tables.otui index a6d80cbb..51e53e5c 100644 --- a/data/styles/20-tables.otui +++ b/data/styles/20-tables.otui @@ -1,26 +1,62 @@ Table < UITable layout: verticalBox - header-column-style: HeaderTableColumn - header-row-style: HeaderTableRow + header-column-style: TableHeaderColumn + header-row-style: TableHeaderRow column-style: TableColumn row-style: TableRow TableData < UIScrollArea layout: verticalBox -TableRow < Label +TableRow < UITableRow layout: horizontalBox height: 10 text-wrap: true + focusable: true + even-background-color: alpha + odd-background-color: #00000022 + + $focus: + background-color: #294f6d + color: #ffffff TableColumn < Label width: 30 text-wrap: true + focusable: false TableHeaderRow < Label layout: horizontalBox + focusable: false height: 10 text-wrap: true -TableHeaderColumn < Button - width: 30 \ No newline at end of file +TableHeaderColumn < UITableHeaderColumn + font: verdana-11px-antialised + background-color: alpha + color: #dfdfdfff + height: 23 + focusable: true + text-offset: 0 0 + image-source: /images/ui/button + image-color: #dfdfdf + image-clip: 0 0 22 23 + image-border: 3 + padding: 5 10 5 10 + enabled: false + focusable: false + + $hover !disabled: + image-clip: 0 23 22 23 + + $pressed: + image-clip: 0 46 22 23 + text-offset: 1 1 + + $disabled: + color: #dfdfdf88 + opacity: 0.8 + +SortableTableHeaderColumn < TableHeaderColumn + enabled: true + focusable: true \ No newline at end of file diff --git a/modules/corelib/ui/uitable.lua b/modules/corelib/ui/uitable.lua index af70a01b..6529d8fc 100644 --- a/modules/corelib/ui/uitable.lua +++ b/modules/corelib/ui/uitable.lua @@ -3,33 +3,45 @@ TODO: * Make table headers more robust. * Get dynamic row heights working with text wrapping. - * Every second row different background color applied. ]] -UITable = extends(UIWidget, "UITable") -local HEADER_ID = 'row0' +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 k,row in pairs(self.rows) do + for _,row in pairs(self.rows) do row.onClick = nil end self.rows = {} self.columns = {} - self.headerRow = {} + self.headerRow = nil + self.headerColumns = {} + self.columnWidth = {} self.selectedRow = nil if self.dataSpace then @@ -38,36 +50,58 @@ function UITable:onDestroy() 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 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) + 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 @@ -78,23 +112,49 @@ function UITable:clearData() self.rows = {} end -function UITable:addHeaderRow(data) +-- 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 _, column in pairs(data) do + 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) + col:setText(tr(value)) elseif type == 'onClick' then col.onClick = value end @@ -104,23 +164,34 @@ function UITable:addHeaderRow(data) -- create a new header local headerRow = g_ui.createWidget(self.headerRowBaseStyle, self) - local newHeight = (self.dataSpace:getHeight()-headerRow:getHeight())-self.dataSpace:getMarginTop() + local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop() self.dataSpace:applyStyle({ height = newHeight }) - headerRow:setId(HEADER_ID) + headerRow:setId('header') + self.headerColumns = {} + self.columnWidth = {} for _, column in pairs(columns) do headerRow:addChild(column) - self.columns[HEADER_ID] = column + table.insert(self.columnWidth, column:getWidth()) + table.insert(self.headerColumns, column) end - headerRow.onClick = function(headerRow) self:selectRow(headerRow) end self.headerRow = headerRow return headerRow end -function UITable:removeHeaderRow() - self.headerRow:destroy() - self.headerRow = nil +-- 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, ref, height) @@ -134,41 +205,124 @@ function UITable:addRow(data, ref, height) end local row = g_ui.createWidget(self.rowBaseStyle) + row.table = self if ref then row.ref = ref end if height then row:setHeight(height) end - local rowId = #self.rows - row:setId('row'..(rowId < 1 and 1 or rowId)) + local rowId = #self.rows + 1 + row.rowId = rowId + row:setId('row'..rowId) + row:updateBackgroundColor() - for _, column in pairs(data) do + self.columns[rowId] = {} + for colId, column in pairs(data) do local col = g_ui.createWidget(self.columBaseStyle, row) - 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) - end + 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 - self.columns[rowId] = col + 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 - row.onFocusChange = function(row, focused) - if focused then self:selectRow(row) end - 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 - table.removevalue(self.rows, row) + 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) @@ -189,8 +343,13 @@ function UITable:selectRow(selectedRow) 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() }) + self.dataSpace:applyStyle({ height = self:getHeight()-headerHeight-self:getMarginTop() }) end function UITable:setRowStyle(style) @@ -202,8 +361,10 @@ end function UITable:setColumnStyle(style) self.columBaseStyle = style - for _, col in pairs(self.columns) do - col:setStyle(style) + for _, columns in pairs(self.columns) do + for _, col in pairs(columns) do + col:setStyle(style) + end end end @@ -216,7 +377,51 @@ end function UITable:setHeaderColumnStyle(style) self.headerColumnBaseStyle = style - if table.haskey(HEADER_ID) then - self.columns[HEADER_ID]:setStyle(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 diff --git a/modules/game_market/market.lua b/modules/game_market/market.lua index 49d326e4..7cebeb24 100644 --- a/modules/game_market/market.lua +++ b/modules/game_market/market.lua @@ -13,9 +13,6 @@ * Clean up the interface building - Add a new market interface file to handle building? - * Add offer table column ordering. - - Player Name, Amount, Total Price, Peice Price and Ends At - * Extend information features - Hover over offers for purchase information (balance after transaction, etc) - Display out of trend market offers based on their previous statistics (like cipsoft does) @@ -78,14 +75,6 @@ fee = 0 loaded = false -local offerTableHeader = { - {['text'] = 'Player Name', ['width'] = 100}, - {['text'] = 'Amount', ['width'] = 60}, - {['text'] = 'Total Price', ['width'] = 90}, - {['text'] = 'Piece Price', ['width'] = 80}, - {['text'] = 'Ends at', ['width'] = 120} -} - local function isItemValid(item, category, searchFilter) if not item or not item.marketData then return false @@ -169,7 +158,7 @@ local function refreshTypeList() end end -local function addOffer(offer, type) +local function addOffer(offer, offerType) if not offer then return false end @@ -179,26 +168,35 @@ local function addOffer(offer, type) local price = offer:getPrice() local timestamp = offer:getTimeStamp() + buyOfferTable:toggleSorting(false) + sellOfferTable:toggleSorting(false) + if amount < 1 then return false end - if type == MarketAction.Buy then + if offerType == MarketAction.Buy then local data = { - {['text'] = player, ['width'] = 100}, - {['text'] = amount, ['width'] = 60}, - {['text'] = price*amount, ['width'] = 90}, - {['text'] = price, ['width'] = 80}, - {['text'] = string.gsub(os.date('%c', timestamp), " ", " "), ['width'] = 120} + {text = player}, + {text = amount}, + {text = price*amount}, + {text = price}, + {text = string.gsub(os.date('%c', timestamp), " ", " ")} } buyOfferTable:addRow(data, id) else local data = { - {['text'] = player, ['width'] = 100}, - {['text'] = amount, ['width'] = 60}, - {['text'] = price*amount, ['width'] = 90}, - {['text'] = price, ['width'] = 80}, - {['text'] = string.gsub(os.date('%c', timestamp), " ", " "), ['width'] = 120} + {text = player}, + {text = amount}, + {text = price*amount}, + {text = price}, + {text = string.gsub(os.date('%c', timestamp), " ", " "), sortvalue = timestamp} } sellOfferTable:addRow(data, id) end + + buyOfferTable:toggleSorting(false) + sellOfferTable:toggleSorting(false) + buyOfferTable:sort() + sellOfferTable:sort() + return true end @@ -207,11 +205,11 @@ local function mergeOffer(offer) return false end local id = offer:getId() - local type = offer:getType() + local offerType = offer:getType() local amount = offer:getAmount() local replaced = false - if type == MarketAction.Buy then + if offerType == MarketAction.Buy then for i = 1, #marketOffers[MarketAction.Buy] do local o = marketOffers[MarketAction.Buy][i] -- replace existing offer @@ -250,7 +248,9 @@ local function updateOffers(offers) -- clear existing offer data buyOfferTable:clearData() + buyOfferTable:setSorting(4, TABLE_SORTING_DESC) sellOfferTable:clearData() + sellOfferTable:setSorting(4, TABLE_SORTING_ASC) sellButton:setEnabled(false) buyButton:setEnabled(false) @@ -274,8 +274,8 @@ local function updateDetails(itemId, descriptions, purchaseStats, saleStats) detailsTable:clearData() for k, desc in pairs(descriptions) do local columns = { - {['text'] = getMarketDescriptionName(desc[1])..':'}, - {['text'] = desc[2], ['width'] = 330} + {text = getMarketDescriptionName(desc[1])..':'}, + {text = desc[2]} } detailsTable:addRow(columns) end @@ -283,7 +283,7 @@ local function updateDetails(itemId, descriptions, purchaseStats, saleStats) -- update sale item statistics sellStatsTable:clearData() if table.empty(saleStats) then - sellStatsTable:addRow({{['text'] = 'No information'}}) + sellStatsTable:addRow({{text = 'No information'}}) else local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0 for _, stat in pairs(saleStats) do @@ -301,28 +301,24 @@ local function updateDetails(itemId, descriptions, purchaseStats, saleStats) end end end - sellStatsTable:addRow({{['text'] = 'Total Transations:'}, - {['text'] = transactions, ['width'] = 270}}) - sellStatsTable:addRow({{['text'] = 'Highest Price:'}, - {['text'] = highestPrice, ['width'] = 270}}) + sellStatsTable:addRow({{text = 'Total Transations:'}, {text = transactions}}) + sellStatsTable:addRow({{text = 'Highest Price:'}, {text = highestPrice}}) if totalPrice > 0 and transactions > 0 then - sellStatsTable:addRow({{['text'] = 'Average Price:'}, - {['text'] = math.floor(totalPrice/transactions), ['width'] = 270}}) + sellStatsTable:addRow({{text = 'Average Price:'}, + {text = math.floor(totalPrice/transactions)}}) else - sellStatsTable:addRow({{['text'] = 'Average Price:'}, - {['text'] = 0, ['width'] = 270}}) + sellStatsTable:addRow({{text = 'Average Price:'}, {text = 0}}) end - sellStatsTable:addRow({{['text'] = 'Lowest Price:'}, - {['text'] = lowestPrice, ['width'] = 270}}) + sellStatsTable:addRow({{text = 'Lowest Price:'}, {text = lowestPrice}}) end -- update buy item statistics buyStatsTable:clearData() if table.empty(purchaseStats) then - buyStatsTable:addRow({{['text'] = 'No information'}}) + buyStatsTable:addRow({{text = 'No information'}}) else local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0 for _, stat in pairs(purchaseStats) do @@ -340,22 +336,18 @@ local function updateDetails(itemId, descriptions, purchaseStats, saleStats) end end end - buyStatsTable:addRow({{['text'] = 'Total Transations:'}, - {['text'] = transactions, ['width'] = 270}}) - buyStatsTable:addRow({{['text'] = 'Highest Price:'}, - {['text'] = highestPrice, ['width'] = 270}}) + buyStatsTable:addRow({{text = 'Total Transations:'},{text = transactions}}) + buyStatsTable:addRow({{text = 'Highest Price:'}, {text = highestPrice}}) if totalPrice > 0 and transactions > 0 then - buyStatsTable:addRow({{['text'] = 'Average Price:'}, - {['text'] = math.floor(totalPrice/transactions), ['width'] = 270}}) + buyStatsTable:addRow({{text = 'Average Price:'}, + {text = math.floor(totalPrice/transactions)}}) else - buyStatsTable:addRow({{['text'] = 'Average Price:'}, - {['text'] = 0, ['width'] = 270}}) + buyStatsTable:addRow({{text = 'Average Price:'}, {text = 0}}) end - buyStatsTable:addRow({{['text'] = 'Lowest Price:'}, - {['text'] = lowestPrice, ['width'] = 270}}) + buyStatsTable:addRow({{text = 'Lowest Price:'}, {text = lowestPrice}}) end end @@ -737,6 +729,13 @@ local function initInterface() sellStatsTable = itemStatsPanel:recursiveGetChildById('sellStatsTable') buyOfferTable.onSelectionChange = onSelectBuyOffer sellOfferTable.onSelectionChange = onSelectSellOffer + + buyStatsTable:setColumnWidth({120, 270}) + sellStatsTable:setColumnWidth({120, 270}) + detailsTable:setColumnWidth({80, 330}) + + buyOfferTable:setSorting(4, TABLE_SORTING_DESC) + sellOfferTable:setSorting(4, TABLE_SORTING_ASC) end function init() @@ -1125,14 +1124,6 @@ function Market.onMarketEnter(depotItems, offers, balance, vocation) Market.loadMarketItems(MarketCategory.First) end - -- build offer table header - if buyOfferTable and not buyOfferTable:hasHeader() then - buyOfferTable:addHeaderRow(offerTableHeader) - end - if sellOfferTable and not sellOfferTable:hasHeader() then - sellOfferTable:addHeaderRow(offerTableHeader) - end - if g_game.isOnline() then marketWindow:lock() marketWindow:show() diff --git a/modules/game_market/ui/marketoffers/itemdetails.otui b/modules/game_market/ui/marketoffers/itemdetails.otui index 4b2fc55f..310f858b 100644 --- a/modules/game_market/ui/marketoffers/itemdetails.otui +++ b/modules/game_market/ui/marketoffers/itemdetails.otui @@ -1,11 +1,12 @@ DetailsTableRow < TableRow font: verdana-11px-monochrome - background-color: alpha focusable: true color: #cccccc height: 45 focusable: false padding: 2 + even-background-color: alpha + odd-background-color: alpha DetailsTableColumn < TableColumn font: verdana-11px-monochrome diff --git a/modules/game_market/ui/marketoffers/itemoffers.otui b/modules/game_market/ui/marketoffers/itemoffers.otui index 275d1bb1..0556450b 100644 --- a/modules/game_market/ui/marketoffers/itemoffers.otui +++ b/modules/game_market/ui/marketoffers/itemoffers.otui @@ -1,35 +1,24 @@ OfferTableRow < TableRow font: verdana-11px-monochrome - background-color: alpha - focusable: true color: #cccccc height: 15 - $focus: - background-color: #294f6d - color: #ffffff - OfferTableColumn < TableColumn font: verdana-11px-monochrome background-color: alpha text-offset: 5 0 color: #cccccc width: 80 - focusable: false OfferTableHeaderRow < TableHeaderRow font: verdana-11px-monochrome - focusable: false color: #cccccc height: 20 -OfferTableHeaderColumn < TableHeaderColumn +OfferTableHeaderColumn < SortableTableHeaderColumn font: verdana-11px-monochrome - background-color: alpha text-offset: 2 0 color: #cccccc - width: 80 - focusable: true $focus: background-color: #294f6d @@ -74,8 +63,26 @@ Panel table-data: sellingTableData row-style: OfferTableRow column-style: OfferTableColumn - header-row-style: OfferTableHeaderRow - header-column-style: OfferTableHeaderColumn + header-column-style: false + header-row-style: false + + OfferTableHeaderRow + id: header + OfferTableHeaderColumn + !text: tr('Buyer Name') + width: 100 + OfferTableHeaderColumn + !text: tr('Amount') + width: 60 + OfferTableHeaderColumn + !text: tr('Total Price') + width: 90 + OfferTableHeaderColumn + !text: tr('Piece Price') + width: 80 + OfferTableHeaderColumn + !text: tr('Auction End') + width: 120 TableData id: sellingTableData @@ -129,8 +136,26 @@ Panel table-data: buyingTableData row-style: OfferTableRow column-style: OfferTableColumn - header-row-style: OfferTableHeaderRow - header-column-style: OfferTableHeaderColumn + header-column-style: false + header-row-style: false + + OfferTableHeaderRow + id: header + OfferTableHeaderColumn + !text: tr('Seller Name') + width: 100 + OfferTableHeaderColumn + !text: tr('Amount') + width: 60 + OfferTableHeaderColumn + !text: tr('Total Price') + width: 90 + OfferTableHeaderColumn + !text: tr('Piece Price') + width: 80 + OfferTableHeaderColumn + !text: tr('Auction End') + width: 120 TableData id: buyingTableData diff --git a/modules/game_market/ui/marketoffers/itemstats.otui b/modules/game_market/ui/marketoffers/itemstats.otui index 709831b4..61afa97b 100644 --- a/modules/game_market/ui/marketoffers/itemstats.otui +++ b/modules/game_market/ui/marketoffers/itemstats.otui @@ -1,6 +1,5 @@ StatsTableRow < TableRow font: verdana-11px-monochrome - background-color: alpha focusable: true color: #cccccc height: 20 @@ -9,7 +8,7 @@ StatsTableRow < TableRow StatsTableColumn < TableColumn font: verdana-11px-monochrome background-color: alpha - text-offset: 5 0 + text-offset: 5 3 color: #cccccc width: 110 focusable: false