742 lines
26 KiB
Lua
742 lines
26 KiB
Lua
|
-- ------------------------------------------------------------------------------ --
|
||
|
-- TradeSkillMaster --
|
||
|
-- https://tradeskillmaster.com --
|
||
|
-- All Rights Reserved - Detailed license information included with addon. --
|
||
|
-- ------------------------------------------------------------------------------ --
|
||
|
|
||
|
--- Scrolling Table UI Element Class.
|
||
|
-- A scrolling table contains a scrollable list of rows with a fixed set of columns. It is a subclass of the @{Element}
|
||
|
-- class.
|
||
|
-- @classmod ScrollingTable
|
||
|
|
||
|
local _, TSM = ...
|
||
|
local ObjectPool = TSM.Include("Util.ObjectPool")
|
||
|
local Table = TSM.Include("Util.Table")
|
||
|
local Math = TSM.Include("Util.Math")
|
||
|
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||
|
local Color = TSM.Include("Util.Color")
|
||
|
local Theme = TSM.Include("Util.Theme")
|
||
|
local ScrollingTable = TSM.Include("LibTSMClass").DefineClass("ScrollingTable", TSM.UI.Element, "ABSTRACT")
|
||
|
local UIElements = TSM.Include("UI.UIElements")
|
||
|
UIElements.Register(ScrollingTable)
|
||
|
TSM.UI.ScrollingTable = ScrollingTable
|
||
|
local private = {
|
||
|
rowPool = ObjectPool.New("TABLE_ROWS", TSM.UI.Util.TableRow, 1),
|
||
|
}
|
||
|
local HEADER_HEIGHT = 22
|
||
|
local HEADER_LINE_HEIGHT = 2
|
||
|
local MORE_COL_WIDTH = 8
|
||
|
local FORCE_DATA_UPDATE = newproxy()
|
||
|
local IGNORE_DATA_UPDATE = newproxy()
|
||
|
local SCROLL_TO_DATA_TOTAL_TIME_S = 0.1
|
||
|
|
||
|
|
||
|
|
||
|
-- ============================================================================
|
||
|
-- Meta Class Methods
|
||
|
-- ============================================================================
|
||
|
|
||
|
function ScrollingTable.__init(self)
|
||
|
local frame = UIElements.CreateFrame(self, "Frame", nil, nil, TSM.IsShadowlands() and "BackdropTemplate" or nil)
|
||
|
self.__super:__init(frame)
|
||
|
|
||
|
frame:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" })
|
||
|
|
||
|
self._lineTop = frame:CreateTexture(nil, "ARTWORK")
|
||
|
self._lineTop:SetPoint("TOPLEFT")
|
||
|
self._lineTop:SetPoint("TOPRIGHT")
|
||
|
self._lineTop:SetHeight(HEADER_LINE_HEIGHT)
|
||
|
self._lineTop:SetColorTexture(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA())
|
||
|
|
||
|
self._lineBottom = frame:CreateTexture(nil, "ARTWORK")
|
||
|
self._lineBottom:SetPoint("TOPLEFT", 0, -HEADER_HEIGHT - HEADER_LINE_HEIGHT)
|
||
|
self._lineBottom:SetPoint("TOPRIGHT", 0, -HEADER_HEIGHT - HEADER_LINE_HEIGHT)
|
||
|
self._lineBottom:SetHeight(HEADER_LINE_HEIGHT)
|
||
|
self._lineBottom:SetColorTexture(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA())
|
||
|
|
||
|
self._hScrollFrame = CreateFrame("ScrollFrame", nil, frame)
|
||
|
self._hScrollFrame:SetPoint("TOPLEFT")
|
||
|
self._hScrollFrame:SetPoint("BOTTOMRIGHT")
|
||
|
self._hScrollFrame:EnableMouseWheel(true)
|
||
|
self._hScrollFrame:SetClipsChildren(true)
|
||
|
ScriptWrapper.Set(self._hScrollFrame, "OnUpdate", private.HScrollFrameOnUpdate, self)
|
||
|
ScriptWrapper.Set(self._hScrollFrame, "OnMouseWheel", private.FrameOnMouseWheel, self)
|
||
|
|
||
|
self._hContent = CreateFrame("Frame", nil, self._hScrollFrame)
|
||
|
self._hContent:SetPoint("TOPLEFT")
|
||
|
self._hScrollFrame:SetScrollChild(self._hContent)
|
||
|
|
||
|
self._vScrollFrame = CreateFrame("ScrollFrame", nil, self._hContent)
|
||
|
self._vScrollFrame:SetPoint("TOPLEFT")
|
||
|
self._vScrollFrame:SetPoint("BOTTOMRIGHT")
|
||
|
self._vScrollFrame:EnableMouseWheel(true)
|
||
|
self._vScrollFrame:SetClipsChildren(true)
|
||
|
ScriptWrapper.Set(self._vScrollFrame, "OnUpdate", private.VScrollFrameOnUpdate, self)
|
||
|
ScriptWrapper.Set(self._vScrollFrame, "OnMouseWheel", private.FrameOnMouseWheel, self)
|
||
|
|
||
|
self._content = CreateFrame("Frame", nil, self._vScrollFrame)
|
||
|
self._content:SetPoint("TOPLEFT")
|
||
|
self._vScrollFrame:SetScrollChild(self._content)
|
||
|
|
||
|
self._hScrollbar = TSM.UI.Scrollbar.Create(frame, true)
|
||
|
self._vScrollbar = TSM.UI.Scrollbar.Create(frame)
|
||
|
|
||
|
self._rowHeight = 20
|
||
|
self._backgroundColor = "PRIMARY_BG"
|
||
|
self._rows = {}
|
||
|
self._data = {}
|
||
|
self._hScrollValue = 0
|
||
|
self._vScrollValue = 0
|
||
|
self._onSelectionChangedHandler = nil
|
||
|
self._onRowClickHandler = nil
|
||
|
self._selection = nil
|
||
|
self._selectionDisabled = nil
|
||
|
self._selectionValidator = nil
|
||
|
self._tableInfo = self:_CreateScrollingTableInfo()
|
||
|
self._header = nil
|
||
|
self._dataTranslationFunc = nil
|
||
|
self._contextTable = nil
|
||
|
self._defaultContextTable = nil
|
||
|
self._prevDataOffset = nil
|
||
|
self._lastDataUpdate = nil
|
||
|
self._rowHoverEnabled = true
|
||
|
self._headerHidden = false
|
||
|
self._targetScrollValue = nil
|
||
|
self._totalScrollDistance = nil
|
||
|
self._rightClickToggle = nil
|
||
|
|
||
|
Theme.RegisterChangeCallback(function()
|
||
|
if self:IsVisible() and self._header then
|
||
|
self._header:_LayoutHeaderRow()
|
||
|
end
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
function ScrollingTable.Acquire(self)
|
||
|
self.__super:Acquire()
|
||
|
self._tableInfo:_Acquire(self)
|
||
|
self._hScrollFrame:SetHorizontalScroll(0)
|
||
|
self._hScrollValue = 0
|
||
|
self._vScrollValue = 0
|
||
|
|
||
|
ScriptWrapper.Set(self._vScrollbar, "OnValueChanged", private.OnVScrollbarValueChangedNoDraw, self)
|
||
|
-- don't want to cause this element to be drawn for this initial scrollbar change
|
||
|
self._vScrollbar:SetValue(0)
|
||
|
ScriptWrapper.Set(self._vScrollbar, "OnValueChanged", private.OnVScrollbarValueChanged, self)
|
||
|
|
||
|
ScriptWrapper.Set(self._hScrollbar, "OnValueChanged", private.OnHScrollbarValueChangedNoDraw, self)
|
||
|
-- don't want to cause this element to be drawn for this initial scrollbar change
|
||
|
self._hScrollbar:SetValue(0)
|
||
|
ScriptWrapper.Set(self._hScrollbar, "OnValueChanged", private.OnHScrollbarValueChanged, self)
|
||
|
end
|
||
|
|
||
|
function ScrollingTable.Release(self)
|
||
|
self._rowHeight = 20
|
||
|
self._backgroundColor = "PRIMARY_BG"
|
||
|
self._onSelectionChangedHandler = nil
|
||
|
self._onRowClickHandler = nil
|
||
|
self._selection = nil
|
||
|
self._selectionDisabled = nil
|
||
|
self._selectionValidator = nil
|
||
|
self._dataTranslationFunc = nil
|
||
|
self._contextTable = nil
|
||
|
self._defaultContextTable = nil
|
||
|
self._prevDataOffset = nil
|
||
|
self._lastDataUpdate = nil
|
||
|
if self._header then
|
||
|
self._header:Release()
|
||
|
private.rowPool:Recycle(self._header)
|
||
|
self._header = nil
|
||
|
end
|
||
|
for _, row in ipairs(self._rows) do
|
||
|
row:Release()
|
||
|
private.rowPool:Recycle(row)
|
||
|
end
|
||
|
wipe(self._rows)
|
||
|
self._tableInfo:_Release()
|
||
|
wipe(self._data)
|
||
|
self._headerHidden = false
|
||
|
self._targetScrollValue = nil
|
||
|
self._totalScrollDistance = nil
|
||
|
self.__super:Release()
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
-- ============================================================================
|
||
|
-- Public Class Methods
|
||
|
-- ============================================================================
|
||
|
|
||
|
--- Sets the background of the scrolling table.
|
||
|
-- @tparam ScrollingTable self The scrolling table object
|
||
|
-- @tparam number rowHeight The row height
|
||
|
-- @treturn ScrollingTable The scrolling table object
|
||
|
function ScrollingTable.SetRowHeight(self, rowHeight)
|
||
|
self._rowHeight = rowHeight
|
||
|
return self
|
||
|
end
|
||
|
|
||
|
--- Sets the background of the scrolling table.
|
||
|
-- @tparam ScrollingTable self The scrolling table object
|
||
|
-- @tparam boolean hidden Whether or not the header should be hidden
|
||
|
-- @treturn ScrollingTable The scrolling table object
|
||
|
function ScrollingTable.SetHeaderHidden(self, hidden)
|
||
|
self._headerHidden = hidden
|
||
|
return self
|
||
|
end
|
||
|
|
||
|
--- Sets the background of the scrolling table.
|
||
|
-- @tparam ScrollingTable self The scrolling table object
|
||
|
-- @tparam string color The background color as a theme color key
|
||
|
-- @treturn ScrollingTable The scrolling table object
|
||
|
function ScrollingTable.SetBackgroundColor(self, color)
|
||
|
assert(Theme.GetColor(color))
|
||
|
self._backgroundColor = color
|
||
|
return self
|
||
|
end
|
||
|
|
||
|
--- Sets the context table.
|
||
|
-- This table can be used to preserve the table configuration across lifecycles of the scrolling table and even WoW
|
||
|
-- sessions if it's within the settings DB.
|
||
|
-- @tparam ScrollingTable self The scrolling table object
|
||
|
-- @tparam table tbl The context table
|
||
|
-- @tparam table defaultTbl The default table (required fields: `colWidth`, `colHidden`)
|
||
|
-- @treturn ScrollingTable The scrolling table object
|
||
|
function ScrollingTable.SetContextTable(self, tbl, defaultTbl)
|
||
|
assert(type(defaultTbl.colWidth) == "table" and type(defaultTbl.colHidden) == "table")
|
||
|
tbl.colWidth = tbl.colWidth or CopyTable(defaultTbl.colWidth)
|
||
|
tbl.colHidden = tbl.colHidden or CopyTable(defaultTbl.colHidden)
|
||
|
self._contextTable = tbl
|
||
|
self._defaultContextTable = defaultTbl
|
||
|
self:_UpdateColsHidden()
|
||
|
return self
|
||
|
end
|
||
|
|
||
|
--- Sets the context table from a settings object.
|
||
|
-- @tparam ScrollingTable self The scrolling table object
|
||
|
-- @tparam Settings settings The settings object
|
||
|
-- @tparam string key The setting key
|
||
|
-- @treturn ScrollingTable The scrolling table object
|
||
|
function ScrollingTable.SetSettingsContext(self, settings, key)
|
||
|
return self:SetContextTable(settings[key], settings:GetDefaultReadOnly(key))
|
||
|
end
|
||
|
|
||
|
--- Forces an update of the data shown within the table.
|
||
|
-- @tparam ScrollingTable self The scrolling table object
|
||
|
-- @tparam[opt=false] bool redraw Whether or not to redraw the scrolling table
|
||
|
-- @treturn ScrollingTable The scrolling table object
|
||
|
function ScrollingTable.UpdateData(self, redraw)
|
||
|
self:_ForceLastDataUpdate()
|
||
|
self:_UpdateData()
|
||
|
if redraw then
|
||
|
self:Draw()
|
||
|
end
|
||
|
return self
|
||
|
end
|
||
|
|
||
|
--- Gets the ScrollingTableInfo object.
|
||
|
-- @tparam ScrollingTable self The scrolling table object
|
||
|
-- @treturn ScrollingTableInfo The scrolling table info object
|
||
|
function ScrollingTable.GetScrollingTableInfo(self)
|
||
|
return self._tableInfo
|
||
|
end
|
||
|
|
||
|
--- Commits the scrolling table info.
|
||
|
-- This should be called once the scrolling table info is completely set (retrieved via @{ScrollingTable.GetScrollingTableInfo}).
|
||
|
-- @tparam ScrollingTable self The scrolling table object
|
||
|
-- @treturn ScrollingTable The scrolling table object
|
||
|
function ScrollingTable.CommitTableInfo(self)
|
||
|
self:_UpdateColsHidden()
|
||
|
if self._header then
|
||
|
self._header:Release()
|
||
|
private.rowPool:Recycle(self._header)
|
||
|
self._header = nil
|
||
|
end
|
||
|
return self
|
||
|
end
|
||
|
|
||
|
--- Registers a script handler.
|
||
|
-- @tparam ScrollingTable self The scrolling table object
|
||
|
-- @tparam string script The script to register for (supported scripts: `OnSelectionChanged`, `OnRowClick`)
|
||
|
-- @tparam function handler The script handler which will be called with the scrolling table object followed by any
|
||
|
-- arguments to the script
|
||
|
-- @treturn ScrollingTable The scrolling table object
|
||
|
function ScrollingTable.SetScript(self, script, handler)
|
||
|
if script == "OnSelectionChanged" then
|
||
|
self._onSelectionChangedHandler = handler
|
||
|
elseif script == "OnRowClick" then
|
||
|
self._onRowClickHandler = handler
|
||
|
else
|
||
|
error("Unknown ScrollingTable script: "..tostring(script))
|
||
|
end
|
||
|
return self
|
||
|
end
|
||
|
|
||
|
--- Sets the selected row.
|
||
|
-- @tparam ScrollingTable self The scrolling table object
|
||
|
-- @param selection The selected row or nil to clear the selection
|
||
|
-- @tparam[opt=false] boolean noDraw Don't redraw the rows
|
||
|
-- @treturn ScrollingTable The scrolling table object
|
||
|
function ScrollingTable.SetSelection(self, selection, noDraw)
|
||
|
if selection == self._selection then
|
||
|
self:_JumpToData(selection)
|
||
|
return self
|
||
|
elseif selection and self._selectionValidator and not self:_selectionValidator(selection) then
|
||
|
return self
|
||
|
end
|
||
|
self:_IgnoreLastDataUpdate()
|
||
|
self._selection = selection
|
||
|
self:_JumpToData(selection)
|
||
|
if not noDraw then
|
||
|
for _, row in ipairs(self._rows) do
|
||
|
if not row:IsMouseOver() and row:IsVisible() and not self:_IsSelected(row:GetData()) then
|
||
|
row:SetHighlightState(nil)
|
||
|
elseif row:IsMouseOver() and row:IsVisible() and not self:_IsSelected(row:GetData()) then
|
||
|
row:SetHighlightState("hover")
|
||
|
elseif row:IsMouseOver() and row:IsVisible() and self:_IsSelected(row:GetData()) then
|
||
|
row:SetHighlightState(self._selectionDisabled and "hover" or "selectedHover")
|
||
|
elseif row:IsVisible() and self:_IsSelected(row:GetData()) then
|
||
|
row:SetHighlightState("selected")
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
if self._onSelectionChangedHandler then
|
||
|
self:_onSelectionChangedHandler()
|
||
|
end
|
||
|
return self
|
||
|
end
|
||
|
|
||
|
--- Gets the currently selected row.
|
||
|
-- @tparam ScrollingTable self The scrolling table object
|
||
|
-- @return The selected row or nil if there's nothing selected
|
||
|
function ScrollingTable.GetSelection(self)
|
||
|
return self._selection
|
||
|
end
|
||
|
|
||
|
--- Sets a selection validator function.
|
||
|
-- @tparam ScrollingTable self The scrolling table object
|
||
|
-- @tparam function validator A function which gets called with the scrolling table object and a row to validate
|
||
|
-- whether or not it's selectable (returns true if it is, false otherwise)
|
||
|
-- @treturn ScrollingTable The scrolling table object
|
||
|
function ScrollingTable.SetSelectionValidator(self, validator)
|
||
|
self._selectionValidator = validator
|
||
|
return self
|
||
|
end
|
||
|
|
||
|
--- Sets whether or not selection is disabled.
|
||
|
-- @tparam ScrollingTable self The scrolling table object
|
||
|
-- @tparam boolean disabled Whether or not to disable selection
|
||
|
-- @treturn ScrollingTable The scrolling table object
|
||
|
function ScrollingTable.SetSelectionDisabled(self, disabled)
|
||
|
self._selectionDisabled = disabled
|
||
|
return self
|
||
|
end
|
||
|
|
||
|
function ScrollingTable.Draw(self)
|
||
|
self.__super:Draw()
|
||
|
local frame = self:_GetBaseFrame()
|
||
|
local background = Theme.GetColor(self._backgroundColor)
|
||
|
frame:SetBackdropColor(background:GetFractionalRGBA())
|
||
|
if self:_CanResizeCols() then
|
||
|
self:_UpdateColsHidden()
|
||
|
end
|
||
|
|
||
|
if not self._header then
|
||
|
self._header = self:_GetTableRow(true)
|
||
|
self._header:SetBackgroundColor(Theme.GetColor("FRAME_BG"))
|
||
|
self._header:SetHeight(HEADER_HEIGHT)
|
||
|
end
|
||
|
|
||
|
-- update the scrollbar layout
|
||
|
if self._headerHidden then
|
||
|
self._vScrollbar:SetPoint("TOPRIGHT", -Theme.GetScrollbarMargin(), -Theme.GetScrollbarMargin())
|
||
|
else
|
||
|
self._vScrollbar:SetPoint("TOPRIGHT", -Theme.GetScrollbarMargin(), -Theme.GetScrollbarMargin() - HEADER_HEIGHT - HEADER_LINE_HEIGHT * 2)
|
||
|
end
|
||
|
|
||
|
local totalWidth = 0
|
||
|
if self:_CanResizeCols() then
|
||
|
-- add the "more" column
|
||
|
totalWidth = totalWidth + MORE_COL_WIDTH + Theme.GetColSpacing()
|
||
|
for colId, colWidth in pairs(self._contextTable.colWidth) do
|
||
|
if not self._contextTable.colHidden[colId] then
|
||
|
totalWidth = totalWidth + colWidth + Theme.GetColSpacing()
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
totalWidth = max(totalWidth, self:_GetDimension("WIDTH"))
|
||
|
self._hContent:SetHeight(self._hScrollFrame:GetHeight())
|
||
|
self._hContent:SetWidth(totalWidth)
|
||
|
self._content:SetWidth(self._hContent:GetWidth())
|
||
|
|
||
|
local rowHeight = self._rowHeight
|
||
|
local totalHeight = #self._data * rowHeight
|
||
|
local visibleHeight = self._vScrollFrame:GetHeight()
|
||
|
local visibleWidth = self._hScrollFrame:GetWidth()
|
||
|
local numVisibleRows = min(ceil(visibleHeight / rowHeight), #self._data)
|
||
|
local maxScroll = self:_GetMaxScroll()
|
||
|
local vScrollOffset = min(self._vScrollValue, maxScroll)
|
||
|
local hScrollOffset = min(self._hScrollValue, self:_GetMaxHScroll())
|
||
|
local dataOffset = floor(vScrollOffset / rowHeight)
|
||
|
|
||
|
self._vScrollbar.thumb:SetHeight(TSM.UI.Scrollbar.GetLength(totalHeight, visibleHeight))
|
||
|
self._vScrollbar:SetMinMaxValues(0, maxScroll)
|
||
|
self._vScrollbar:SetValue(vScrollOffset)
|
||
|
self._hScrollbar.thumb:SetWidth(TSM.UI.Scrollbar.GetLength(self._hContent:GetWidth(), visibleWidth))
|
||
|
self._hScrollbar:SetMinMaxValues(0, self:_GetMaxHScroll())
|
||
|
self._hScrollbar:SetValue(hScrollOffset)
|
||
|
self._content:SetHeight(numVisibleRows * rowHeight)
|
||
|
|
||
|
if self._headerHidden then
|
||
|
self._lineTop:Hide()
|
||
|
self._lineBottom:Hide()
|
||
|
self._header:SetHeight(0)
|
||
|
self._header:SetBackgroundColor(Color.GetTransparent())
|
||
|
self._vScrollFrame:SetPoint("TOPLEFT", 0, 0)
|
||
|
else
|
||
|
self._lineTop:SetColorTexture(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA())
|
||
|
self._lineBottom:SetColorTexture(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA())
|
||
|
self._lineTop:Show()
|
||
|
self._lineBottom:Show()
|
||
|
self._vScrollFrame:SetPoint("TOPLEFT", 0, -HEADER_HEIGHT - HEADER_LINE_HEIGHT * 2)
|
||
|
self._header:SetBackgroundColor(Theme.GetColor("FRAME_BG"))
|
||
|
self._header:SetHeight(HEADER_HEIGHT)
|
||
|
end
|
||
|
|
||
|
if Math.Round(vScrollOffset + visibleHeight) == totalHeight then
|
||
|
-- we are at the bottom
|
||
|
self._vScrollFrame:SetVerticalScroll(numVisibleRows * rowHeight - visibleHeight)
|
||
|
else
|
||
|
self._vScrollFrame:SetVerticalScroll(0)
|
||
|
end
|
||
|
self._hScrollFrame:SetHorizontalScroll(hScrollOffset)
|
||
|
|
||
|
while #self._rows < numVisibleRows do
|
||
|
local row = self:_GetTableRow(false)
|
||
|
row._frame:SetPoint("TOPLEFT", 0, -rowHeight * #self._rows)
|
||
|
row._frame:SetPoint("TOPRIGHT", 0, -rowHeight * #self._rows)
|
||
|
tinsert(self._rows, row)
|
||
|
end
|
||
|
|
||
|
local scrollDiff = dataOffset - (self._prevDataOffset or dataOffset)
|
||
|
self._prevDataOffset = dataOffset
|
||
|
if scrollDiff ~= 0 then
|
||
|
-- Shuffle the rows around to accomplish the scrolling so that the data only changes
|
||
|
-- for the minimal number of rows, which allows for better optimization
|
||
|
for _ = 1, abs(scrollDiff) do
|
||
|
if scrollDiff > 0 then
|
||
|
tinsert(self._rows, tremove(self._rows, 1))
|
||
|
else
|
||
|
tinsert(self._rows, 1, tremove(self._rows))
|
||
|
end
|
||
|
end
|
||
|
-- fix the points of all the rows
|
||
|
for i, row in ipairs(self._rows) do
|
||
|
row._frame:SetPoint("TOPLEFT", 0, -rowHeight * (i - 1))
|
||
|
row._frame:SetPoint("TOPRIGHT", 0, -rowHeight * (i - 1))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
for i, row in ipairs(self._rows) do
|
||
|
local dataIndex = i + dataOffset
|
||
|
local data = self._data[dataIndex]
|
||
|
if i > numVisibleRows or not data then
|
||
|
row:SetVisible(false)
|
||
|
row:ClearData()
|
||
|
else
|
||
|
row:SetVisible(true)
|
||
|
self:_SetRowData(row, data)
|
||
|
row:SetBackgroundColor(background)
|
||
|
row:SetHeight(rowHeight)
|
||
|
end
|
||
|
end
|
||
|
self._lastDataUpdate = nil
|
||
|
|
||
|
self._header:SetHeaderData()
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
-- ============================================================================
|
||
|
-- ScrollingTable - Private Class Methods
|
||
|
-- ============================================================================
|
||
|
|
||
|
function ScrollingTable._CreateScrollingTableInfo(self)
|
||
|
return TSM.UI.Util.ScrollingTableInfo()
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._GetTableRow(self, isHeader)
|
||
|
local row = private.rowPool:Get()
|
||
|
row:Acquire(self, isHeader)
|
||
|
return row
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._SetRowData(self, row, data)
|
||
|
-- updating the row data is expensive, so only do it if necessary
|
||
|
local dataUpdated = row:GetData() ~= data or not self._lastDataUpdate or self._lastDataUpdate == FORCE_DATA_UPDATE or self._lastDataUpdate == data
|
||
|
local isMouseOver = row:IsMouseOver()
|
||
|
local isSelected = self:_IsSelected(data)
|
||
|
if not isMouseOver and isSelected then
|
||
|
row:SetHighlightState("selected", dataUpdated)
|
||
|
elseif isMouseOver and isSelected then
|
||
|
row:SetHighlightState(self._selectionDisabled and "hover" or "selectedHover", dataUpdated)
|
||
|
elseif isMouseOver and not isSelected then
|
||
|
row:SetHighlightState("hover", dataUpdated)
|
||
|
else
|
||
|
row:SetHighlightState(nil, dataUpdated)
|
||
|
end
|
||
|
if dataUpdated then
|
||
|
row:SetData(data)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._OnScrollValueChanged(self, value, noDraw)
|
||
|
self._vScrollValue = value
|
||
|
if not noDraw then
|
||
|
self:Draw()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._OnHScrollValueChanged(self, value, noDraw)
|
||
|
self._hScrollValue = value
|
||
|
if not noDraw then
|
||
|
self:Draw()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._GetMaxScroll(self)
|
||
|
return max(#self._data * self._rowHeight - self._vScrollFrame:GetHeight(), 0)
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._GetMaxHScroll(self)
|
||
|
return max(self._hContent:GetWidth() - self._hScrollFrame:GetWidth(), 0)
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._UpdateData(self)
|
||
|
error("Must be implemented by the child class")
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._ToggleSort(self, id)
|
||
|
error("Must be implemented by the child class")
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._IsSelected(self, data)
|
||
|
return data == self._selection
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._HandleRowClick(self, data, mouseButton)
|
||
|
if self._onRowClickHandler then
|
||
|
self:_onRowClickHandler(data, mouseButton)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._GetColWidth(self, id)
|
||
|
return self._contextTable.colWidth[id]
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._ResetColWidth(self, id)
|
||
|
local defaultWidth = self._defaultContextTable.colWidth[id]
|
||
|
local currentWidth = self._contextTable.colWidth[id]
|
||
|
assert(currentWidth and defaultWidth)
|
||
|
self._contextTable.colWidth[id] = defaultWidth
|
||
|
self._header:_LayoutHeaderRow()
|
||
|
for _, row in ipairs(self._rows) do
|
||
|
row:_LayoutDataRow()
|
||
|
end
|
||
|
self:Draw()
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._SetColWidth(self, id, width, redraw)
|
||
|
assert(not self._contextTable.colWidthLocked)
|
||
|
local prevWidth = self._contextTable.colWidth[id]
|
||
|
assert(prevWidth)
|
||
|
if width == prevWidth and not redraw then
|
||
|
return
|
||
|
end
|
||
|
self._contextTable.colWidth[id] = width
|
||
|
for _, row in ipairs(self._rows) do
|
||
|
row:_LayoutDataRow()
|
||
|
end
|
||
|
if redraw then
|
||
|
self:Draw()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._IsColWidthLocked(self)
|
||
|
return self._contextTable.colWidthLocked
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._ToogleColWidthLocked(self)
|
||
|
self._contextTable.colWidthLocked = not self._contextTable.colWidthLocked or nil
|
||
|
self._header:_LayoutHeaderRow()
|
||
|
self:Draw()
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._CanResizeCols(self)
|
||
|
return self._contextTable and true or false
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._ToggleColHide(self, id)
|
||
|
if not self._contextTable then
|
||
|
return
|
||
|
end
|
||
|
self._contextTable.colHidden[id] = not self._contextTable.colHidden[id] or nil
|
||
|
self:_UpdateColsHidden()
|
||
|
self._header:_LayoutHeaderRow()
|
||
|
for _, row in ipairs(self._rows) do
|
||
|
row:_LayoutDataRow()
|
||
|
end
|
||
|
self:Draw()
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._ResetContext(self)
|
||
|
assert(self._contextTable)
|
||
|
if self._defaultContextTable.colWidth then
|
||
|
wipe(self._contextTable.colWidth)
|
||
|
for col, width in pairs(self._defaultContextTable.colWidth) do
|
||
|
self._contextTable.colWidth[col] = width
|
||
|
end
|
||
|
end
|
||
|
if self._defaultContextTable.colHidden then
|
||
|
wipe(self._contextTable.colHidden)
|
||
|
for col, hidden in pairs(self._defaultContextTable.colHidden) do
|
||
|
self._contextTable.colHidden[col] = hidden
|
||
|
end
|
||
|
self:_UpdateColsHidden()
|
||
|
end
|
||
|
self._header:_LayoutHeaderRow()
|
||
|
for _, row in ipairs(self._rows) do
|
||
|
row:_LayoutDataRow()
|
||
|
end
|
||
|
self:Draw()
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._UpdateColsHidden(self)
|
||
|
for _, col in self:GetScrollingTableInfo():_ColIterator() do
|
||
|
local colId = col:_GetId()
|
||
|
if col:_CanHide() then
|
||
|
col:_SetHidden(self._contextTable and self._contextTable.colHidden[colId] and true or false)
|
||
|
elseif self._contextTable then
|
||
|
self._contextTable.colHidden[colId] = nil
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._SetLastDataUpdate(self, value)
|
||
|
self._lastDataUpdate = value
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._IgnoreLastDataUpdate(self)
|
||
|
self._lastDataUpdate = IGNORE_DATA_UPDATE
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._ForceLastDataUpdate(self)
|
||
|
self._lastDataUpdate = FORCE_DATA_UPDATE
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._ScrollToData(self, data)
|
||
|
local rowHeight = self._rowHeight
|
||
|
local visibleHeight = self._vScrollFrame:GetHeight()
|
||
|
local currentOffset = self._vScrollbar:GetValue()
|
||
|
local dataIndex = Table.KeyByValue(self._data, data)
|
||
|
-- if we are going to scroll up/down, we want to scroll such that the top of the passed row is in the visible area
|
||
|
-- by at least 1 row height
|
||
|
local scrollUpOffset = max(rowHeight * (dataIndex - 1) - rowHeight, 0)
|
||
|
local scrollDownOffset = min(rowHeight * dataIndex + rowHeight - visibleHeight, self:_GetMaxScroll())
|
||
|
if scrollUpOffset < currentOffset and scrollDownOffset > currentOffset then
|
||
|
-- it's impossible to scroll to the right place, so do nothing
|
||
|
elseif scrollUpOffset < currentOffset then
|
||
|
-- we need to scroll up
|
||
|
self._targetScrollValue = scrollUpOffset
|
||
|
self._totalScrollDistance = currentOffset - scrollUpOffset
|
||
|
elseif scrollDownOffset > currentOffset then
|
||
|
-- we need to scroll down
|
||
|
self._targetScrollValue = scrollDownOffset
|
||
|
self._totalScrollDistance = scrollDownOffset - currentOffset
|
||
|
else
|
||
|
-- the data is already in the visible area, so do nothing
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function ScrollingTable._JumpToData(self, data)
|
||
|
if not data then
|
||
|
return
|
||
|
end
|
||
|
local index = Table.KeyByValue(self._data, data)
|
||
|
assert(index)
|
||
|
-- set the scroll so that the selection is visible if necessary
|
||
|
local rowHeight = self._rowHeight
|
||
|
local firstVisibleIndex = ceil(self._vScrollValue / rowHeight) + 1
|
||
|
local lastVisibleIndex = floor((self._vScrollValue + self:_GetDimension("HEIGHT")) / rowHeight)
|
||
|
if lastVisibleIndex > firstVisibleIndex and (index < firstVisibleIndex or index > lastVisibleIndex) then
|
||
|
self:_OnScrollValueChanged(min((index - 1) * rowHeight, self:_GetMaxScroll()))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
-- ============================================================================
|
||
|
-- ScrollingTable - Local Script Handlers
|
||
|
-- ============================================================================
|
||
|
|
||
|
function private.OnHScrollbarValueChanged(self, value)
|
||
|
value = max(min(value, self:_GetMaxHScroll()), 0)
|
||
|
self:_OnHScrollValueChanged(value)
|
||
|
end
|
||
|
|
||
|
function private.OnVScrollbarValueChanged(self, value)
|
||
|
value = max(min(value, self:_GetMaxScroll()), 0)
|
||
|
self:_OnScrollValueChanged(value)
|
||
|
end
|
||
|
|
||
|
function private.OnHScrollbarValueChangedNoDraw(self, value)
|
||
|
value = max(min(value, self:_GetMaxHScroll()), 0)
|
||
|
self:_OnHScrollValueChanged(value, true)
|
||
|
end
|
||
|
|
||
|
function private.OnVScrollbarValueChangedNoDraw(self, value)
|
||
|
value = max(min(value, self:_GetMaxScroll()), 0)
|
||
|
self:_OnScrollValueChanged(value, true)
|
||
|
end
|
||
|
|
||
|
function private.HScrollFrameOnUpdate(self)
|
||
|
if (self._hScrollFrame:IsMouseOver() and self:_GetMaxHScroll() > 1) or self._hScrollbar.dragging then
|
||
|
self._hScrollbar:Show()
|
||
|
else
|
||
|
self._hScrollbar:Hide()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function private.VScrollFrameOnUpdate(self, elapsed)
|
||
|
elapsed = min(elapsed, 0.01)
|
||
|
|
||
|
if self._targetScrollValue then
|
||
|
local scrollValue = self._vScrollbar:GetValue()
|
||
|
local direction = scrollValue < self._targetScrollValue and 1 or -1
|
||
|
local newScrollValue = scrollValue + direction * self._totalScrollDistance * elapsed / SCROLL_TO_DATA_TOTAL_TIME_S
|
||
|
self._vScrollbar:SetValue(newScrollValue)
|
||
|
if direction * newScrollValue >= direction * self._targetScrollValue or newScrollValue <= 0 or newScrollValue >= self:_GetMaxScroll() then
|
||
|
-- we are done scrolling
|
||
|
self._targetScrollValue = nil
|
||
|
self._totalScrollDistance = nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local rOffset = max(self._hContent:GetWidth() - self._hScrollFrame:GetWidth() - self._hScrollbar:GetValue(), 0)
|
||
|
if (self._vScrollFrame:IsMouseOver(0, 0, 0, -rOffset) and self:_GetMaxScroll() > 1) or self._vScrollbar.dragging then
|
||
|
self._vScrollbar:Show()
|
||
|
else
|
||
|
self._vScrollbar:Hide()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function private.FrameOnMouseWheel(self, direction)
|
||
|
local scrollAmount = -direction * Theme.GetMouseWheelScrollAmount()
|
||
|
if IsShiftKeyDown() and self._hScrollbar:IsVisible() then
|
||
|
-- scroll horizontally
|
||
|
self._hScrollbar:SetValue(self._hScrollbar:GetValue() + scrollAmount)
|
||
|
else
|
||
|
self._vScrollbar:SetValue(self._vScrollbar:GetValue() + scrollAmount)
|
||
|
end
|
||
|
end
|