-- ------------------------------------------------------------------------------ -- -- TradeSkillMaster -- -- https://tradeskillmaster.com -- -- All Rights Reserved - Detailed license information included with addon. -- -- ------------------------------------------------------------------------------ -- local _, TSM = ... local L = TSM.Include("Locale").GetTable() local TableRow = TSM.Include("LibTSMClass").DefineClass("TableRow") local Table = TSM.Include("Util.Table") local Math = TSM.Include("Util.Math") local Wow = TSM.Include("Util.Wow") local Theme = TSM.Include("Util.Theme") local ScriptWrapper = TSM.Include("Util.ScriptWrapper") local ItemInfo = TSM.Include("Service.ItemInfo") local Tooltip = TSM.Include("UI.Tooltip") TSM.UI.Util.TableRow = TableRow local private = { rowFrameLookup = {}, layoutTemp = {}, colIdsTemp = {}, } local FLAG_WIDTH = 6 local FLAG_SPACING = 2 local ICON_SPACING = 4 local MIN_TEXT_WIDTH = 12 local RESIZER_WIDTH = 4 local INDENT_WIDTH = 8 local HEADER_LINE_WIDTH = 2 local MORE_COL_WIDTH = 8 local FULL_TEXT_TOOLTIP_DELAY_S = 1 -- ============================================================================ -- TableRow - Public Class Methods -- ============================================================================ function TableRow.__init(self) self._scrollingTable = nil self._tableInfo = nil self._rowData = nil self._texts = {} self._icons = {} self._rotatingIcons = {} self._buttons = {} self._recycled = { buttons = {}, texts = {}, icons = {}, rotatingIcons = {}, } self._sortId = nil self._sortAscending = true self._state = nil local frame = CreateFrame("Button", nil, nil, nil) frame:RegisterForClicks("LeftButtonUp", "RightButtonUp") self._frame = frame private.rowFrameLookup[frame] = self frame.background = frame:CreateTexture(nil, "BACKGROUND") frame.background:SetAllPoints() frame.highlight = frame:CreateTexture(nil, "ARTWORK", -1) frame.highlight:SetAllPoints() frame.highlight:Hide() frame.sortBackground = frame:CreateTexture(nil, "ARTWORK", -2) frame.sortBackground:Hide() frame.sortFlag = frame:CreateTexture(nil, "ARTWORK", -1) frame.sortFlag:SetHeight(3) frame.sortFlag:Hide() end function TableRow.Acquire(self, scrollingTable, isHeader) self._scrollingTable = scrollingTable self._tableInfo = self._scrollingTable._tableInfo self._frame:SetParent(isHeader and self._scrollingTable._hContent or self._scrollingTable._content) self._frame:SetHitRectInsets(0, 0, 0, 0) self._frame:Show() self._frame.highlight:Hide() self._frame.sortBackground:Hide() self._frame.sortFlag:Hide() if isHeader then self:_CreateHeaderRowCols() self._frame:SetPoint("TOPLEFT", 0, -HEADER_LINE_WIDTH) self._frame:SetPoint("TOPRIGHT", 0, -HEADER_LINE_WIDTH) self:_LayoutHeaderRow() else self:_CreateDataRowCols() ScriptWrapper.Set(self._frame, "OnEnter", private.RowOnEnter, self) ScriptWrapper.Set(self._frame, "OnLeave", private.RowOnLeave, self) ScriptWrapper.Set(self._frame, "OnClick", private.RowOnClick, self) if scrollingTable._rightClickToggle then ScriptWrapper.Set(self._frame, "OnMouseDown", private.RowOnMouseDown, self) else ScriptWrapper.Clear(self._frame, "OnMouseDown") end self:_LayoutDataRow() end end function TableRow.Release(self) self._frame:Hide() for _, text in pairs(self._texts) do text:Hide() text:ClearAllPoints() text:SetWidth(0) text:SetHeight(0) text:SetTextColor(1, 1, 1, 1) tinsert(self._recycled.texts, text) end wipe(self._texts) for _, icon in pairs(self._icons) do icon:Hide() icon:SetDrawLayer("ARTWORK", 0) icon:SetTexture(nil) icon:SetTexCoord(0, 0, 0, 1, 1, 0, 1, 1) icon:SetColorTexture(0, 0, 0, 0) icon:SetVertexColor(1, 1, 1, 1) icon:ClearAllPoints() icon:SetWidth(0) icon:SetHeight(0) tinsert(self._recycled.icons, icon) end wipe(self._icons) for _, icon in pairs(self._rotatingIcons) do icon.ag:Stop() icon:Hide() icon:SetDrawLayer("ARTWORK", 0) icon:SetTexture(nil) icon:SetTexCoord(0, 0, 0, 1, 1, 0, 1, 1) icon:SetColorTexture(0, 0, 0, 0) icon:SetVertexColor(1, 1, 1, 1) icon:ClearAllPoints() icon:SetWidth(0) icon:SetHeight(0) tinsert(self._recycled.rotatingIcons, icon) end wipe(self._rotatingIcons) for _, button in pairs(self._buttons) do if button.isShowingTooltip then Tooltip.Hide() button.isShowingTooltip = nil end button:Hide() button:SetMouseClickEnabled(true) button:RegisterForDrag(nil) button:SetResizable(false) button:SetMovable(false) ScriptWrapper.Clear(button, "OnEnter") ScriptWrapper.Clear(button, "OnLeave") ScriptWrapper.Clear(button, "OnClick") ScriptWrapper.Clear(button, "OnMouseDown") ScriptWrapper.Clear(button, "OnMouseUp") ScriptWrapper.Clear(button, "OnUpdate") button:SetParent(nil) button:ClearAllPoints() button:SetWidth(0) button:SetHeight(0) tinsert(self._recycled.buttons, button) end wipe(self._buttons) self._scrollingTable = nil self._tableInfo = nil self._rowData = nil self._state = nil self._frame:SetParent(nil) self._frame:ClearAllPoints() ScriptWrapper.Clear(self._frame, "OnEnter") ScriptWrapper.Clear(self._frame, "OnLeave") ScriptWrapper.Clear(self._frame, "OnClick") end function TableRow.SetData(self, data) for _, col in self._tableInfo:_ColIterator() do local id = col:_GetId() self._texts[id]:SetText(col:_GetText(data)) if col:_GetIconSize() then local button = self._buttons["_icon_"..id] local texture = col:_GetIcon(data, button and button:IsMouseOver()) if type(texture) == "string" and TSM.UI.TexturePacks.IsValid(texture) then TSM.UI.TexturePacks.SetTexture(self._icons[id], texture) else self._icons[id]:SetTexture(texture) end end end self._rowData = data self:_LayoutDataRow() end function TableRow.SetHeaderData(self) if self._scrollingTable._headerHidden then return end for _, col in self._tableInfo:_ColIterator() do if not col:_GetTitleIcon() then self._texts[col:_GetId()]:SetText(col:_GetTitle()) end end end function TableRow.GetData(self) return self._rowData end function TableRow.ClearData(self) self._rowData = nil end function TableRow.SetHeight(self, height) for _, text in pairs(self._texts) do text:SetHeight(height) end for _, btn in pairs(self._buttons) do btn:SetHeight(height) end for _, col in ipairs(self._tableInfo:_GetCols()) do local id = col:_GetId() local flag = self._icons["_flag_"..id] if flag then flag:SetHeight(height - FLAG_SPACING * 2) end end self._frame:SetHeight(height) end function TableRow.SetBackgroundColor(self, color) self._frame.background:SetColorTexture(color:GetFractionalRGBA()) end function TableRow.SetTextColor(self, color) for _, col in ipairs(self._tableInfo:_GetCols()) do local id = col:_GetId() local text = self._texts[id] if text then text:SetTextColor(color:GetFractionalRGBA()) end end end function TableRow.SetVisible(self, visible) if visible == self._frame:IsVisible() then return end if visible then self._frame:Show() self._frame.highlight:Hide() else self._frame:Hide() end end function TableRow.IsVisible(self) return self._frame:IsVisible() end function TableRow.SetHighlightState(self, state, noLayout) self._state = state local highlightTint, shouldLayoutActionIcons = nil, false if state == "selectedHover" then highlightTint = "+SELECTED_HOVER" elseif state == "selected" then highlightTint = "+SELECTED" elseif state == "hover" then highlightTint = self._scrollingTable._rowHoverEnabled and "+HOVER" or nil shouldLayoutActionIcons = true elseif state == nil then shouldLayoutActionIcons = true else error("Invalid state: "..state) end if highlightTint then local backgroundColor = self._scrollingTable._backgroundColor self._frame.highlight:SetColorTexture(Theme.GetColor(backgroundColor):GetTint(highlightTint):GetFractionalRGBA()) self._frame.highlight:Show() else self._frame.highlight:Hide() end if not noLayout then local shouldLayout = false for _, col in self._tableInfo:_VisibleColIterator() do if col:_HasFlag() or (shouldLayoutActionIcons and select(3, col:_GetActionIconInfo())) then shouldLayout = true break end end if shouldLayout then self:_LayoutDataRow() end end end function TableRow.IsMouseOver(self) return self._frame:IsMouseOver() end function TableRow.SetHitRectInsets(self, left, right, top, bottom) for _, tooltipFrame in pairs(self._buttons) do tooltipFrame:SetHitRectInsets(left, right, top, bottom) end self._frame:SetHitRectInsets(left, right, top, bottom) end function TableRow.SetSort(self, sortId, sortAscending) if sortId == self._sortId and sortAscending == self._sortAscending then return end self._sortId = sortId self._sortAscending = sortAscending self:_LayoutHeaderRow() end -- ============================================================================ -- TableRow - Private Class Methods -- ============================================================================ function TableRow._GetFontString(self) local fontString = tremove(self._recycled.texts) if not fontString then fontString = self._frame:CreateFontString() fontString:SetShadowColor(0, 0, 0, 0) fontString:SetWordWrap(false) end fontString:Show() return fontString end function TableRow._GetTexture(self) local texture = tremove(self._recycled.icons) if not texture then texture = self._frame:CreateTexture() end texture:Show() return texture end function TableRow._GetRotatingTexture(self) local texture = tremove(self._recycled.rotatingIcons) if not texture then texture = self._frame:CreateTexture() texture.ag = texture:CreateAnimationGroup() local spin = texture.ag:CreateAnimation("Rotation") spin:SetDuration(2) spin:SetDegrees(360) texture.ag:SetLooping("REPEAT") end texture:Show() return texture end function TableRow._GetButton(self) local button = tremove(self._recycled.buttons) if not button then button = CreateFrame("Button", nil, self._frame, nil) end button:SetParent(self._frame) button:SetHitRectInsets(0, 0, 0, 0) button:RegisterForClicks("LeftButtonUp", "RightButtonUp") button:Show() return button end function TableRow._GetSepTexture(self) local sepTexture = self:_GetTexture() sepTexture:SetDrawLayer("BORDER") sepTexture:SetWidth(HEADER_LINE_WIDTH) return sepTexture end function TableRow._CreateHeaderRowCols(self) if self._scrollingTable._headerHidden then return end if self._scrollingTable:_CanResizeCols() then -- craete the "more" column local button = self:_GetButton() ScriptWrapper.Set(button, "OnClick", private.MoreColOnClick, self) self._buttons._more = button local moreIcon = self:_GetTexture() moreIcon:SetDrawLayer("ARTWORK") TSM.UI.TexturePacks.SetTextureAndSize(moreIcon, "iconPack.12x12/More/Vertical") self._icons._more = moreIcon self._icons._sep__more = self:_GetSepTexture() end for _, col in self._tableInfo:_ColIterator() do local id = col:_GetId() local button = self:_GetButton() ScriptWrapper.Set(button, "OnClick", private.HeaderColOnClick) ScriptWrapper.Set(button, "OnEnter", private.HeaderColOnEnter) ScriptWrapper.Set(button, "OnLeave", private.HeaderColOnLeave) button:SetResizable(true) self._buttons[id] = button if self._scrollingTable:_CanResizeCols() then local resizerButton = self:_GetButton() resizerButton:SetMovable(true) ScriptWrapper.Set(resizerButton, "OnEnter", private.ResizerOnEnter) ScriptWrapper.Set(resizerButton, "OnLeave", private.ResizerOnLeave) ScriptWrapper.Set(resizerButton, "OnMouseDown", private.ResizerOnMouseDown) ScriptWrapper.Set(resizerButton, "OnMouseUp", private.ResizerOnMouseUp) ScriptWrapper.Set(resizerButton, "OnClick", private.ResizerOnClick) self._buttons["_resizer_"..id] = resizerButton local resizerHighlight = self:_GetTexture() resizerHighlight:SetDrawLayer("ARTWORK") resizerHighlight:SetPoint("TOPLEFT", resizerButton) resizerHighlight:SetPoint("BOTTOMRIGHT", resizerButton) resizerHighlight:SetColorTexture(Theme.GetColor("ACTIVE_BG+HOVER"):GetFractionalRGBA()) resizerHighlight:Hide() self._icons["_resizer_"..id] = resizerHighlight end self._icons["_sep_"..id] = self:_GetSepTexture() local iconTexture = col:_GetTitleIcon() if iconTexture then local icon = self:_GetTexture() icon:SetDrawLayer("ARTWORK", 1) TSM.UI.TexturePacks.SetTextureAndSize(icon, iconTexture) self._icons[id] = icon else local text = self:_GetFontString() text:SetFont(Theme.GetFont("BODY_BODY3_MEDIUM"):GetWowFont()) text:SetJustifyH("LEFT") text:SetText(col:_GetTitle()) self._texts[id] = text end end end function TableRow._CreateDataRowCols(self) for _, col in self._tableInfo:_ColIterator() do local id = col:_GetId() local iconSize = col:_GetIconSize() if iconSize then local icon = self:_GetTexture() icon:SetDrawLayer("ARTWORK", 1) icon:SetWidth(iconSize) icon:SetHeight(iconSize) self._icons[id] = icon if col:_IsIconHoverEnabled() then local iconBtn = self:_GetButton() iconBtn:SetAllPoints(icon) ScriptWrapper.Set(iconBtn, "OnEnter", private.IconButtonOnEnter) ScriptWrapper.Set(iconBtn, "OnLeave", private.IconButtonOnLeave) ScriptWrapper.Set(iconBtn, "OnClick", private.IconButtonOnClick) self._buttons["_icon_"..id] = iconBtn end end -- need to create a text element even if there's no text, as it's used for the layout local text = self:_GetFontString() if col:_HasText() then text:SetFont(col:_GetWowFont()) text:SetJustifyH(col:_GetJustifyH()) else -- use an arbitrary default font since the text will be an empty string text:SetFontObject(GameFontNormal) end self._texts[id] = text if col:_HasTooltip() then local tooltipFrame = self:_GetButton() ScriptWrapper.Set(tooltipFrame, "OnEnter", private.TooltipFrameOnEnter) ScriptWrapper.Set(tooltipFrame, "OnLeave", private.TooltipFrameOnLeave) ScriptWrapper.Set(tooltipFrame, "OnClick", private.TooltipFrameOnClick) if self._scrollingTable._rightClickToggle then ScriptWrapper.Set(tooltipFrame, "OnMouseDown", private.RowOnMouseDown, self) end self._buttons[id] = tooltipFrame end if col:_HasFlag() then -- add the flag texture local flag = self:_GetTexture() flag:SetDrawLayer("ARTWORK", 1) flag:SetWidth(FLAG_WIDTH) self._icons["_flag_"..id] = flag end if col:_HasExpander() then -- add the expander texture local expander = self:_GetTexture() expander:SetDrawLayer("ARTWORK", 1) self._icons["_expander_"..id] = expander local expanderBtn = self:_GetButton() expanderBtn:SetAllPoints(expander) ScriptWrapper.SetPropagate(expanderBtn, "OnEnter") ScriptWrapper.Set(expanderBtn, "OnLeave", private.ExpanderOnLeave, self) ScriptWrapper.Set(expanderBtn, "OnClick", private.ExpanderOnClick, self) self._buttons["_expander_"..id] = expanderBtn end if col:_HasCheck() then -- add the check texture local expander = self:_GetTexture() expander:SetDrawLayer("ARTWORK", 1) TSM.UI.TexturePacks.SetTextureAndSize(expander, "iconPack.14x14/Checkmark/Default") self._icons["_check_"..id] = expander end if col:_HasBadge() then local badgeText = self:_GetFontString() badgeText:SetFont(Theme.GetFont("TABLE_TABLE1"):GetWowFont()) badgeText:SetTextColor(Theme.GetColor("INDICATOR"):GetFractionalRGBA()) badgeText:SetJustifyH("RIGHT") badgeText:SetJustifyV("MIDDLE") self._texts["_badge_"..id] = badgeText end -- add the action icons local numActionIcons, actionIconSize = col:_GetActionIconInfo() for i = 1, numActionIcons do local icon = self:_GetRotatingTexture() icon:SetDrawLayer("ARTWORK", 1) icon:SetWidth(actionIconSize) icon:SetHeight(actionIconSize) self._rotatingIcons["_action"..i.."_"..id] = icon local iconBtn = self:_GetButton() iconBtn:SetAllPoints(icon) ScriptWrapper.Set(iconBtn, "OnEnter", private.ActionIconButtonOnEnter) ScriptWrapper.Set(iconBtn, "OnLeave", private.ActionIconButtonOnLeave) ScriptWrapper.Set(iconBtn, "OnClick", private.ActionIconButtonOnClick) self._buttons["_action"..i.."_"..id] = iconBtn end end end function TableRow._LayoutHeaderRow(self) if self._scrollingTable._headerHidden then return end -- hide any hidden cols for _, col in self._tableInfo:_HiddenColIterator() do local id = col:_GetId() local button = self._buttons[id] button:Hide() local sepIcon = self._icons["_sep_"..id] sepIcon:Hide() if self._scrollingTable:_CanResizeCols() then local resizerButton = self._buttons["_resizer_"..id] resizerButton:Hide() end local iconTexture = col:_GetTitleIcon() if iconTexture then self._icons[id]:Hide() else self._texts[id]:Hide() end end local cols = self._tableInfo:_GetVisibleCols() if self._scrollingTable:_CanResizeCols() then -- layout the "more" column local button = self._buttons._more button:SetPoint("LEFT", Theme.GetColSpacing() / 2, 0) button:SetWidth(MORE_COL_WIDTH) local icon = self._icons._more icon:SetPoint("CENTER", button) local sepIcon = self._icons._sep__more sepIcon:SetPoint("TOP", button, "TOPRIGHT", Theme.GetColSpacing() / 2, 0) sepIcon:SetPoint("BOTTOM", button, "BOTTOMRIGHT", Theme.GetColSpacing() / 2, 0) sepIcon:SetColorTexture(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA()) end -- build buttons from the left until we get to the col without a width, then switch to building from the right for i, col, isAscending in Table.InwardIterator(cols) do local id = col:_GetId() local button = self._buttons[id] local leftButton = i > 1 and self._buttons[cols[i - 1]:_GetId()] or (self._scrollingTable:_CanResizeCols() and self._buttons._more) or nil local rightButton = i < #cols and self._buttons[cols[i + 1]:_GetId()] or nil button:Show() button:ClearAllPoints() if self._scrollingTable:_CanResizeCols() then local resizerBtn = self._buttons["_resizer_"..id] if self._scrollingTable:_IsColWidthLocked() then resizerBtn:Disable() resizerBtn:SetMovable(false) resizerBtn:SetMouseClickEnabled(false) else resizerBtn:Enable() resizerBtn:SetMovable(true) resizerBtn:SetMouseClickEnabled(true) end end local sepIcon = self._icons["_sep_"..id] sepIcon:Show() sepIcon:SetColorTexture(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA()) sepIcon:SetPoint("TOP", button, "TOPRIGHT", Theme.GetColSpacing() / 2, 0) sepIcon:SetPoint("BOTTOM", button, "BOTTOMRIGHT", Theme.GetColSpacing() / 2, 0) -- layout the button if isAscending then if leftButton then button:SetPoint("LEFT", leftButton, "RIGHT", Theme.GetColSpacing(), 0) else button:SetPoint("LEFT", Theme.GetColSpacing() / 2, 0) end else if rightButton then button:SetPoint("RIGHT", rightButton, "LEFT", -Theme.GetColSpacing(), 0) else button:SetPoint("RIGHT", -Theme.GetColSpacing() / 2, 0) end end if self._scrollingTable:_CanResizeCols() then local iconSize = col:_GetIconSize() local iconTexture = col:_GetTitleIcon() button:SetWidth(self._scrollingTable:_GetColWidth(id)) -- the minimum width of the content is our minimum text width plus the size of the content icon local minContentWidth = 0 if col:_HasText() then minContentWidth = minContentWidth + MIN_TEXT_WIDTH end if iconSize then minContentWidth = minContentWidth + iconSize + ICON_SPACING end if col:_HasFlag() then minContentWidth = minContentWidth + FLAG_WIDTH end if col:_HasExpander() then minContentWidth = minContentWidth + TSM.UI.TexturePacks.GetWidth("iconPack.12x12/Caret/Right") + ICON_SPACING end if col:_HasCheck() then minContentWidth = minContentWidth + TSM.UI.TexturePacks.GetWidth("iconPack.14x14/Checkmark/Default") + ICON_SPACING end if col:_HasBadge() then minContentWidth = minContentWidth + TSM.UI.TexturePacks.GetWidth("uiFrames.AuctionCounterTexture") + ICON_SPACING end -- the minimum header width is either our header icon width or the minimum text width local minHeaderWidth = iconTexture and TSM.UI.TexturePacks.GetWidth(iconTexture) or MIN_TEXT_WIDTH button:SetMinResize(max(minContentWidth, minHeaderWidth), 0) -- layout the resizer button local resizerButton = self._buttons["_resizer_"..id] resizerButton:Show() resizerButton:SetPoint("LEFT", button, "RIGHT", (Theme.GetColSpacing() - RESIZER_WIDTH) / 2, 0) resizerButton:SetWidth(RESIZER_WIDTH) resizerButton:SetHitRectInsets(-RESIZER_WIDTH / 2, -RESIZER_WIDTH / 2, 0, 0) else local width = col:_GetWidth() if width then -- don't currently support auto-width with a header assert(width ~= true) button:SetWidth(width) else -- we found the button which will expand to fill the extra width, so reverse the iterator direction (only once) assert(isAscending) Table.InwardIteratorReverse(cols) if rightButton then -- anchor the right of this button to the left of the next one button:SetPoint("RIGHT", rightButton, "LEFT", -Theme.GetColSpacing(), 0) else -- this button is the last one, so anchor it to the right of our frame button:SetPoint("RIGHT", -Theme.GetColSpacing() / 2, 0) end end end end -- update the text, icons, and sort icons local sortBackground = self._frame.sortBackground local sortFlag = self._frame.sortFlag sortBackground:Hide() sortBackground:ClearAllPoints() sortFlag:Hide() sortFlag:ClearAllPoints() for _, col in ipairs(cols) do local id = col:_GetId() local button = self._buttons[id] if self._sortId == id then sortBackground:SetPoint("TOPLEFT", button, -Theme.GetColSpacing() / 2, 0) sortBackground:SetPoint("BOTTOMRIGHT", button, Theme.GetColSpacing() / 2, 0) sortBackground:SetColorTexture(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA()) sortBackground:Show() sortFlag:SetPoint("TOPLEFT", button, -Theme.GetColSpacing() / 2, 2) sortFlag:SetPoint("TOPRIGHT", button, Theme.GetColSpacing() / 2, 2) sortFlag:SetColorTexture(Theme.GetColor(self._sortAscending and "INDICATOR" or "INDICATOR_ALT"):GetFractionalRGBA()) sortFlag:Show() end local iconTexture = col:_GetTitleIcon() if iconTexture then local icon = self._icons[id] icon:Show() icon:ClearAllPoints() icon:SetPoint(col:_GetJustifyH(), button) else local text = self._texts[id] text:Show() text:ClearAllPoints() text:SetAllPoints(button) end end end function TableRow._LayoutDataRow(self) -- hide any hidden cols for _, col in self._tableInfo:_HiddenColIterator() do local id = col:_GetId() local icon = self._icons[id] local flag = self._icons["_flag_"..id] if flag then flag:Hide() end local expander = self._icons["_expander_"..id] if expander then expander:Hide() local expanderButton = self._buttons["_expander_"..id] expanderButton:Hide() end local check = self._icons["_check_"..id] if check then check:Hide() end if icon then icon:Hide() end if col:_HasText() then local text = self._texts[id] text:Hide() end local badge = self._texts["_badge_"..id] if badge then badge:Hide() end local numActionIcons = col:_GetActionIconInfo() for i = 1, numActionIcons do local actionIconKey = "_action"..i.."_"..id local actionIcon = self._rotatingIcons[actionIconKey] local actionIconButton = self._buttons[actionIconKey] actionIcon:Hide() actionIconButton:Hide() end if col:_HasTooltip() then local tooltipFrame = self._buttons[id] tooltipFrame:Hide() end end local cols = self._tableInfo:_GetVisibleCols() local canResize = self._scrollingTable:_CanResizeCols() local data = self:GetData() -- cache a list of the col ids wipe(private.colIdsTemp) for i, col in ipairs(cols) do private.colIdsTemp[i] = col:_GetId() end -- build from the left until we get to the col without a width, then switch to building from the right for i, col, isAscending in Table.InwardIterator(cols) do local id = private.colIdsTemp[i] local text = self._texts[id] local flag = self._icons["_flag_"..id] local expander = self._icons["_expander_"..id] local check = self._icons["_check_"..id] local badge = self._texts["_badge_"..id] local icon = self._icons[id] local width = canResize and self._scrollingTable:_GetColWidth(id) or col:_GetWidth() local hasText = col:_HasText() local textIndex = nil local numActionIcons, actionIconSize, actionIconShowOnHover = col:_GetActionIconInfo() local leftText = self:_GetDataLayoutRelativeFrame(i > 1 and private.colIdsTemp[i - 1], i > 1 and cols[i - 1], data, "LEFT") local rightText = self:_GetDataLayoutRelativeFrame(i < #cols and private.colIdsTemp[i + 1], i < #cols and cols[i + 1], data, "RIGHT") wipe(private.layoutTemp) local extraWidth = 0 if not leftText and canResize then extraWidth = MORE_COL_WIDTH + Theme.GetColSpacing() private.LayoutTempInsertSpacing(MORE_COL_WIDTH + Theme.GetColSpacing()) end if flag then local visible, color = false, nil if data then visible, color = col:_GetFlagState(data, self:IsMouseOver()) end if visible then flag:Show() flag:SetHeight(self._frame:GetHeight() - FLAG_SPACING * 2) flag:SetColorTexture(color:GetFractionalRGBA()) private.LayoutTempInsertSpacing(-FLAG_SPACING) private.LayoutTempInsertElementWithSpacing(flag, FLAG_SPACING) else flag:Hide() end end if expander then expander:Show() expander:ClearAllPoints() local visible, expanded, indentLevel, indentWidth, expanderSpacing, largeCaretIcons = false, false, 0, nil, nil, false if data then visible, expanded, indentLevel, indentWidth, expanderSpacing, largeCaretIcons = col:_GetExpanderState(data) end expanderSpacing = expanderSpacing or ICON_SPACING local expanderButton = self._buttons["_expander_"..id] if indentWidth or indentLevel < 0 then -- indent the expander itself private.LayoutTempInsertSpacing(indentWidth or (abs(indentLevel) * INDENT_WIDTH)) end local texture = expanded and (largeCaretIcons and "iconPack.14x14/Caret/Down" or "iconPack.12x12/Caret/Down") or (largeCaretIcons and "iconPack.14x14/Caret/Right" or "iconPack.12x12/Caret/Right") local expanderWidth = TSM.UI.TexturePacks.GetWidth(texture) -- check if there is only spacing before the expander and the expander can fit within the spacing local firstLayoutElement = #private.layoutTemp == 1 and not leftText and private.layoutTemp[1] or nil if type(firstLayoutElement) == "number" and Math.Round(expanderWidth + expanderSpacing) <= Math.Round(firstLayoutElement) then private.layoutTemp[1] = private.layoutTemp[1] - expanderWidth - expanderSpacing end if visible then TSM.UI.TexturePacks.SetTextureAndSize(expander, texture) expander:Show() expanderButton:Show() private.LayoutTempInsertElementWithSpacing(expander, expanderSpacing) else expander:Hide() ScriptWrapper.Clear(expanderButton, "OnLeave") expanderButton:Hide() ScriptWrapper.Set(expanderButton, "OnLeave", private.ExpanderOnLeave, self) private.LayoutTempInsertSpacing(expanderWidth + expanderSpacing) end if not indentWidth and indentLevel > 0 then -- indent the other elements after the expander private.LayoutTempInsertSpacing(indentLevel * INDENT_WIDTH) end end if check then check:Show() check:ClearAllPoints() local visible = false if data then visible = col:_GetCheckState(data) end if visible then local checkWidth = check:GetWidth() -- check if there is only spacing before the check and the check can fit within the spacing local firstLayoutElement = #private.layoutTemp == 1 and not leftText and private.layoutTemp[1] or nil if type(firstLayoutElement) == "number" and Math.Round(checkWidth + ICON_SPACING) <= Math.Round(firstLayoutElement) then private.layoutTemp[1] = private.layoutTemp[1] - checkWidth - ICON_SPACING end check:Show() private.LayoutTempInsertElementWithSpacing(check, ICON_SPACING) else check:Hide() end end local iconTexture = icon and icon:GetTexture() if iconTexture then icon:Show() icon:ClearAllPoints() textIndex = #private.layoutTemp + 1 private.LayoutTempInsertElementWithSpacing(icon, ICON_SPACING) end if hasText then text:Show() text:ClearAllPoints() textIndex = textIndex or #private.layoutTemp + 1 private.LayoutTempInsertElementWithSpacing(text, ICON_SPACING) end if badge then badge:ClearAllPoints() private.LayoutTempInsertElementWithSpacing(badge, ICON_SPACING) local visible, value = false, nil if data then visible, value = col:_GetBadgeState(data) end if visible then badge:SetText(value) badge:Show() else badge:Hide() end end local hadVisibleRightActionIcon = false for j = 1, numActionIcons do local actionIconKey = "_action"..j.."_"..id local actionIcon = self._rotatingIcons[actionIconKey] local actionIconButton = self._buttons[actionIconKey] actionIcon:ClearAllPoints() actionIconButton:Show() local visible, texture, isLeft = false, nil, false if data and (not actionIconShowOnHover or self._state) then local _, shouldRotate = nil, nil visible, texture, isLeft, _, shouldRotate = col:_GetActionIcon(data, j, actionIconButton:IsMouseOver()) if shouldRotate then actionIcon.ag:Play() else actionIcon.ag:Stop() end end if visible then hadVisibleRightActionIcon = hadVisibleRightActionIcon or not isLeft if isLeft and textIndex then -- check if there is only padding before the text, and if this icon can fit within that padding local firstLayoutElement = textIndex == 2 and not leftText and private.layoutTemp[1] or nil if textIndex == 2 and type(firstLayoutElement) == "number" and Math.Round(actionIconSize + ICON_SPACING) <= Math.Round(firstLayoutElement) then private.layoutTemp[1] = private.layoutTemp[1] - actionIconSize - ICON_SPACING end private.LayoutTempInsertElementWithSpacing(actionIcon, ICON_SPACING, textIndex) textIndex = textIndex + 2 else private.LayoutTempInsertElementWithSpacing(actionIcon, ICON_SPACING) end actionIcon:Show() actionIconButton:SetMouseClickEnabled(true) if type(texture) == "string" and TSM.UI.TexturePacks.IsValid(texture) then TSM.UI.TexturePacks.SetTexture(actionIcon, texture) else actionIcon:SetTexture(texture) end else actionIcon:Hide() actionIconButton:SetMouseClickEnabled(false) end end local usedWidth = 0 local lastElement = text if hasText then local didText = false local prevElement = nil local currentSpacing = 0 local isInverted = false local primaryText = (isAscending and leftText) or (not isAscending and rightText) or nil local primarySide = isAscending and "LEFT" or "RIGHT" local secondarySide = isAscending and "RIGHT" or "LEFT" local spacingModifier = isAscending and 1 or -1 for j = (isAscending and 1 or #private.layoutTemp), (isAscending and #private.layoutTemp or 1), (isAscending and 1 or -1) do local element = private.layoutTemp[j] if type(element) == "number" then currentSpacing = currentSpacing + element else if prevElement then isInverted = isInverted or (prevElement == text and not width and numActionIcons > 0) if isInverted then assert(isAscending) -- this is an icon to the right of the text and the text will need to expand, so invert the anchoring prevElement:SetPoint("RIGHT", element, "LEFT", -currentSpacing, 0) else element:SetPoint(primarySide, prevElement, secondarySide, currentSpacing * spacingModifier, 0) end elseif primaryText then element:SetPoint(primarySide, primaryText, secondarySide, (currentSpacing + Theme.GetColSpacing()) * spacingModifier, 0) else if element == text and not canResize then currentSpacing = currentSpacing + Theme.GetColSpacing() / 2 end element:SetPoint(primarySide, (currentSpacing + Theme.GetColSpacing() / 2) * spacingModifier, 0) end -- don't use up width for elements to the right of the text which aren't visible if isAscending and (not didText or element:IsVisible()) then -- the text will resize to fill any remaining width, so don't count it in the used width if element ~= text then usedWidth = usedWidth + element:GetWidth() end usedWidth = usedWidth + currentSpacing end prevElement = element currentSpacing = 0 didText = didText or element == text end end lastElement = prevElement else -- center the icon in the middle -- TODO: support other combinations assert(isAscending and icon and not badge and not expander) if leftText then text:SetPoint("LEFT", leftText, "RIGHT", Theme.GetColSpacing(), 0) else text:SetPoint("LEFT", Theme.GetColSpacing() / 2, 0) end icon:SetPoint("CENTER", text, extraWidth / 2, 0) end if col:_HasTooltip() then local tooltipFrame = self._buttons[id] tooltipFrame:Show() tooltipFrame:SetPoint("LEFT", iconTexture and icon or text) tooltipFrame:SetPoint("RIGHT", text) end if width then if hasText then if width == true then -- auto-width text:SetWidth(10000) width = text:GetStringWidth() elseif not rightText then -- leave an extra COL_SPACING / 2 of margin on the right of the last col extraWidth = extraWidth - Theme.GetColSpacing() / 2 end text:SetWidth(width - usedWidth + extraWidth) else assert(width ~= true) -- auto-width requires the cell to have text text:SetWidth(width + extraWidth) end else -- we found the text which will expand to fill the extra width, so reverse the iterator direction (only once) assert(isAscending and not canResize) Table.InwardIteratorReverse(cols) if rightText then -- anchor the right of this the last element for this col to the left of the next one lastElement:SetPoint("RIGHT", rightText, "LEFT", -Theme.GetColSpacing(), 0) else -- this col is the last one, so anchor it to the right of our frame local spacing = Theme.GetColSpacing() if hadVisibleRightActionIcon and not rightText then -- a bit of extra spacing to the right of the last action icon spacing = spacing + ICON_SPACING end lastElement:SetPoint("RIGHT", -spacing, 0) end end end end function TableRow._GetDataLayoutRelativeFrame(self, id, col, data, side) if not id then return nil end assert(side == "LEFT" or side == "RIGHT") local numActionIcons, _, actionIconShowOnHover = col:_GetActionIconInfo() if data and (not actionIconShowOnHover or self._state) then for i = (side == "RIGHT" and 1 or numActionIcons), (side == "RIGHT" and numActionIcons or 1), (side == "RIGHT" and 1 or -1) do local actionIconKey = "_action"..i.."_"..id local actionIcon = self._rotatingIcons[actionIconKey] local actionIconButton = self._buttons[actionIconKey] local visible, _, isLeft = col:_GetActionIcon(data, i, actionIconButton:IsMouseOver()) if visible and ((isLeft and side == "RIGHT") or (not isLeft and side == "LEFT")) then return actionIcon end end end local badge = self._texts["_badge_"..id] if side == "LEFT" and badge and badge:IsVisible() then return badge else return self._texts[id] end end function TableRow._LookupIconByButton(self, button) local key = Table.GetDistinctKey(self._buttons, button) local id = strmatch(key, "^_icon_(.+)$") local col = self._tableInfo:GetColById(id) return col, self._icons[id] end function TableRow._LookupActionIconByButton(self, button) local key = Table.GetDistinctKey(self._buttons, button) local iconIndex, id = strmatch(key, "^_action(%d+)_(.+)$") iconIndex = tonumber(iconIndex) assert(iconIndex and id) local col = self._tableInfo:GetColById(id) return col, iconIndex, self._rotatingIcons[key] end function TableRow._LookupTooltipByFrame(self, frame) local col = self._tableInfo:GetColById(Table.GetDistinctKey(self._buttons, frame)) if not col:_HasTooltip() then return nil end local tooltip, noWrap, anchorFrame = col:_GetTooltip(self:GetData()) local linkingDisabled = col:_GetTooltipLinkingDisabled() return tooltip, noWrap, anchorFrame, linkingDisabled end -- ============================================================================ -- TableRow - Local Script Handlers -- ============================================================================ function private.MenuDialogIterator(self, prevIndex) if prevIndex == nil then return "HIDE", L["Hide Columns"], private.MenuDialogHideColIterator elseif prevIndex == "HIDE" then return "LOCK", self._scrollingTable:_IsColWidthLocked() and L["Unlock Column Width"] or L["Lock Column Width"] elseif prevIndex == "LOCK" then return "RESET", L["Reset Table"] else return self._tableInfo:_MenuDialogIterator(prevIndex) end end function private.GetMoreDialogColRowContent(col) local color = Theme.GetColor(col:_IsHidden() and "TEXT+DISABLED" or "TEXT") local titleText = col:_GetTitle() if titleText then return color:ColorText(titleText) end local titleIcon = col:_GetTitleIcon() if titleIcon then local textureKey = TSM.UI.TexturePacks.GetColoredKey(titleIcon, color) return TSM.UI.TexturePacks.GetTextureLink(textureKey) end error("Unknown title") end function private.MenuDialogHideColIterator(self, prevIndex) local foundPrevIndex = prevIndex == nil for _, col in ipairs(self._tableInfo:_GetCols()) do if foundPrevIndex and col:_CanHide() then return col:_GetId(), private.GetMoreDialogColRowContent(col) elseif col:_GetId() == prevIndex then foundPrevIndex = true end end end function private.MoreMenuDialogButtonOnClick(button, self, index1, index2, extra) assert(not extra and index1) if index1 == "HIDE" then assert(index2) local col = self._tableInfo:GetColById(index2) assert(col:_CanHide()) self._scrollingTable:_ToggleColHide(index2) -- update the button text button:SetText(private.GetMoreDialogColRowContent(col)) :Draw() elseif index1 == "LOCK" then assert(not index2) self._scrollingTable:GetBaseElement():HideDialog() self._scrollingTable:_ToogleColWidthLocked() elseif index1 == "RESET" then assert(not index2) self._scrollingTable:GetBaseElement():HideDialog() self._scrollingTable:_ResetContext() else self._tableInfo:_HandleMenuButtonClick(index1, index2) end end function private.MoreColOnClick(self, mouseButton) self._scrollingTable:GetBaseElement():ShowMenuDialog(self._frame, private.MenuDialogIterator, self, private.MoreMenuDialogButtonOnClick) end function private.HeaderColOnClick(button, mouseButton) local self = private.rowFrameLookup[button:GetParent()] if mouseButton == "LeftButton" then self._scrollingTable:_ToggleSort(Table.GetDistinctKey(self._buttons, button)) end end function private.HeaderColOnEnter(button) local self = private.rowFrameLookup[button:GetParent()] local col = self._tableInfo:GetColById(Table.GetDistinctKey(self._buttons, button)) local tooltip = col:_GetHeaderTooltip() if tooltip then Tooltip.Show(button, tooltip) end end function private.HeaderColOnLeave(button) local self = private.rowFrameLookup[button:GetParent()] local col = self._tableInfo:GetColById(Table.GetDistinctKey(self._buttons, button)) if col:_GetHeaderTooltip() then Tooltip.Hide() end end function private.RowOnMouseDown(self, button) if button ~= "RightButton" then return end private.RowOnClick(self, "LeftButton") end function private.RowOnClick(self, mouseButton) if mouseButton == "LeftButton" and not self._scrollingTable._selectionDisabled then self._scrollingTable:SetSelection(self:GetData()) end if self._scrollingTable:_IsSelected(self:GetData()) then self:SetHighlightState(self._scrollingTable._selectionDisabled and "selected" or "selectedHover") else self:SetHighlightState("hover") end self._scrollingTable:_HandleRowClick(self:GetData(), mouseButton) end function private.RowOnEnter(self) if self._scrollingTable:_IsSelected(self:GetData()) then self:SetHighlightState(self._scrollingTable._selectionDisabled and "selected" or "selectedHover") else self:SetHighlightState("hover") end if self._scrollingTable._rightClickToggle and IsMouseButtonDown("RightButton") then private.RowOnClick(self, "LeftButton") end local cursor = self._tableInfo:_GetCursor() if cursor then SetCursor(cursor) end ScriptWrapper.Set(self._frame, "OnUpdate", private.RowOnUpdate, self) end function private.RowOnLeave(self) for _, text in pairs(self._texts) do if text._hoverStart then Tooltip.Hide() text._hoverStart = nil end end ScriptWrapper.Clear(self._frame, "OnUpdate") if self:IsVisible() then if self._scrollingTable:_IsSelected(self:GetData()) then self:SetHighlightState("selected") else self:SetHighlightState(nil) end end if self._tableInfo:_GetCursor() then ResetCursor() end end function private.RowOnUpdate(self) if not self._frame:IsMouseOver() then private.RowOnLeave(self) return end local tooltipText = nil for _, tooltipFrame in pairs(self._buttons) do if tooltipFrame:IsVisible() and tooltipFrame:IsMouseOver() then return end end for _, text in pairs(self._texts) do if text:IsMouseOver() and text:GetWidth() + 0.5 < text:GetUnboundedStringWidth() then text._hoverStart = text._hoverStart or GetTime() tooltipText = text else text._hoverStart = nil end end if not tooltipText or tooltipText:GetText() == "" or GetTime() - tooltipText._hoverStart < FULL_TEXT_TOOLTIP_DELAY_S then Tooltip.Hide() return end Tooltip.Show(tooltipText, tooltipText:GetText(), true) end function private.IconButtonOnEnter(button) local self = private.rowFrameLookup[button:GetParent()] self._frame:GetScript("OnEnter")(self._frame) local col, icon = self:_LookupIconByButton(button) local texture, tooltip = col:_GetIcon(self:GetData(), true) if type(texture) == "string" and TSM.UI.TexturePacks.IsValid(texture) then TSM.UI.TexturePacks.SetTexture(icon, texture) else icon:SetTexture(texture) end if tooltip then Tooltip.Show(button, tooltip) end end function private.IconButtonOnLeave(button) local self = private.rowFrameLookup[button:GetParent()] self._frame:GetScript("OnLeave")(self._frame) local col, icon = self:_LookupIconByButton(button) local texture, tooltip = col:_GetIcon(self:GetData(), false) if type(texture) == "string" and TSM.UI.TexturePacks.IsValid(texture) then TSM.UI.TexturePacks.SetTexture(icon, texture) else icon:SetTexture(texture) end if tooltip then Tooltip.Hide() end end function private.IconButtonOnClick(button, mouseButton) local self = private.rowFrameLookup[button:GetParent()] local col = self:_LookupIconByButton(button) col:_OnIconClick(self:GetData(), mouseButton) end function private.ActionIconButtonOnEnter(button) local self = private.rowFrameLookup[button:GetParent()] self._frame:GetScript("OnEnter")(self._frame) local col, iconIndex, icon = self:_LookupActionIconByButton(button) local visible, texture, _, tooltip = col:_GetActionIcon(self:GetData(), iconIndex, true) if not visible then return end if tooltip then Tooltip.Show(button, tooltip) end if type(texture) == "string" and TSM.UI.TexturePacks.IsValid(texture) then TSM.UI.TexturePacks.SetTexture(icon, texture) else icon:SetTexture(texture) end end function private.ActionIconButtonOnLeave(button) if not button:IsVisible() then -- ignore OnLeave if we just hid this button Tooltip.Hide() return end local self = private.rowFrameLookup[button:GetParent()] self._frame:GetScript("OnLeave")(self._frame) local col, iconIndex, icon, tooltip = self:_LookupActionIconByButton(button) local visible, texture = col:_GetActionIcon(self:GetData(), iconIndex, false) if tooltip then Tooltip.Hide() end if not visible then return end if type(texture) == "string" and TSM.UI.TexturePacks.IsValid(texture) then TSM.UI.TexturePacks.SetTexture(icon, texture) else icon:SetTexture(texture) end end function private.ActionIconButtonOnClick(button, mouseButton) local self = private.rowFrameLookup[button:GetParent()] local col, iconIndex = self:_LookupActionIconByButton(button) col:_OnActionButtonClick(self:GetData(), iconIndex, mouseButton) end function private.TooltipFrameOnEnter(frame) frame.isShowingTooltip = true local self = private.rowFrameLookup[frame:GetParent()] self._frame:GetScript("OnEnter")(self._frame) local tooltip, noWrap, anchorFrame = self:_LookupTooltipByFrame(frame) Tooltip.Show(anchorFrame and self._frame or frame, tooltip, noWrap) end function private.TooltipFrameOnLeave(frame) frame.isShowingTooltip = nil local self = private.rowFrameLookup[frame:GetParent()] self._frame:GetScript("OnLeave")(self._frame) Tooltip.Hide() end function private.TooltipFrameOnClick(frame, ...) local self = private.rowFrameLookup[frame:GetParent()] local tooltip, _, _, linkingDisabled = self:_LookupTooltipByFrame(frame) if not linkingDisabled and (IsShiftKeyDown() or IsControlKeyDown()) then local link = tooltip and ItemInfo.GetLink(tooltip) if link then if IsShiftKeyDown() then Wow.SafeItemRef(link) elseif IsControlKeyDown() then DressUpItemLink(link) end return end end self._frame:GetScript("OnClick")(self._frame, ...) end function private.ResizerOnEnter(button) local self = private.rowFrameLookup[button:GetParent()] local id = Table.GetDistinctKey(self._buttons, button) self._icons[id]:Show() end function private.ResizerOnLeave(button) local self = private.rowFrameLookup[button:GetParent()] local id = Table.GetDistinctKey(self._buttons, button) self._icons[id]:Hide() end function private.ResizerOnMouseDown(button, mouseButton) if mouseButton ~= "LeftButton" then return end ScriptWrapper.Set(button, "OnUpdate", private.ResizerOnUpdate) local self = private.rowFrameLookup[button:GetParent()] local id = Table.GetDistinctKey(self._buttons, button) local parentId = strmatch(id, "^_resizer_(.+)$") self._buttons[parentId]:StartSizing("RIGHT") self._icons[id]:SetPoint("TOPLEFT", self._buttons[parentId], "TOPLEFT", -Theme.GetColSpacing() / 2, 0) end function private.ResizerOnMouseUp(button, mouseButton) if mouseButton ~= "LeftButton" then return end ScriptWrapper.Clear(button, "OnUpdate") local self = private.rowFrameLookup[button:GetParent()] local id = Table.GetDistinctKey(self._buttons, button) local parentId = strmatch(id, "^_resizer_(.+)$") local parentButton = self._buttons[parentId] parentButton:StopMovingOrSizing() self:_LayoutHeaderRow() self._scrollingTable:_SetColWidth(parentId, Math.Round(parentButton:GetWidth()), true) self._icons[id]:SetPoint("TOPLEFT", button) end function private.ResizerOnClick(button, mouseButton) if mouseButton ~= "RightButton" then return end local self = private.rowFrameLookup[button:GetParent()] local id = Table.GetDistinctKey(self._buttons, button) local parentId = strmatch(id, "^_resizer_(.+)$") self._scrollingTable:_ResetColWidth(parentId) end function private.ResizerOnUpdate(button) local self = private.rowFrameLookup[button:GetParent()] local id = Table.GetDistinctKey(self._buttons, button) local parentId = strmatch(id, "^_resizer_(.+)$") local parentButton = self._buttons[parentId] self._scrollingTable:_SetColWidth(parentId, Math.Round(parentButton:GetWidth())) end function private.ExpanderOnLeave(self) self._frame:GetScript("OnEnter")(self._frame) end function private.ExpanderOnClick(self, mouseButton, ...) if mouseButton ~= "LeftButton" then return end self._frame:GetScript("OnDoubleClick")(self._frame, mouseButton, ...) end -- ============================================================================ -- Private Helper Functions -- ============================================================================ function private.LayoutTempInsertSpacing(spacing) assert(type(spacing) == "number") local length = #private.layoutTemp if type(private.layoutTemp[length]) == "number" then -- add to the previous spacing private.layoutTemp[length] = private.layoutTemp[length] + spacing else -- insert a new entry private.layoutTemp[length + 1] = spacing end end function private.LayoutTempInsertElementWithSpacing(element, spacing, index) assert(type(element) == "table" and type(spacing) == "number") if index then tinsert(private.layoutTemp, index, element) tinsert(private.layoutTemp, index + 1, spacing) else local length = #private.layoutTemp private.layoutTemp[length + 1] = element private.layoutTemp[length + 2] = spacing end end