Files
Adrestie ca2b78e217 Remove every debug command
/fui keeps edit mode, reset and list. The thirty diagnostic and tuning
functions behind the other subcommands (bar, bags, character, tabs, close,
model, minimap, mouse, map, questlog, chat, chatlines, tabard...) go with
them, with Panes.Report, the bags read-back and the 205 texts they alone used.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 13:39:35 +02:00

933 lines
30 KiB
Lua

-- ForeverUI: the Reputation tab, after camelot ReputationFrame.xml and ReputationFrame.lua.
-- Rows are our own, filled from GetFactionInfo: ReputationFrame_Update re-applies its art to
-- the client rows on every pass, so they cannot be reskinned. The client frame is only a parent.
-- 3.3.5 has no AccountWideIcon, Paragon or friendship; the scroll bar comes from ScrollBar.lua.
local ForeverUI = ForeverUI or {}
_G.ForeverUI = ForeverUI
local L = ForeverUI.L
-- List inset in CharacterFrameLeftPaneHost (camelot ReputationFrame.xml).
local LIST_X, LIST_Y = 10, -40
local LIST_X2, LIST_Y2 = -25, 15
-- Three templates, three indents (camelot ReputationFrame.lua).
-- SetElementFactory: entry 30 high, header 28, sub-header (header and child) 22.
-- SetElementIndentCalculator: top-level header 0, child that is not a header 46, others 2.
-- SetPadding: 10 on each edge, 3 between rows.
local ENTRY_H = 30 -- ReputationEntryTemplate
local SUBHEADER_H = 22 -- ReputationSubHeaderTemplate
local HEADER_H = 28 -- ReputationHeaderTemplate
local MARGIN = 10 -- SetPadding
local GAP = 3 -- elementSpacing
local HEADER_INDENT = 0
local CHILD_INDENT = 46
local OTHER_INDENT = 2
local BAR_W, BAR_H = 160, 29
local BAR_X = -3 -- from the row's RIGHT
local FILL_H = 15
local NAME_H = 15
-- Entry name x: AccountWideIcon right edge (25), minus the 10 that ReputationEntryMixin:Initialize
-- removes when the icon is hidden. It is always hidden: 3.3.5 has no account reputation.
local NAME_X = 15
-- Sub-header button: at the icon's right edge + 3, so x = 28; its name follows 4 px after it.
local CHEVRON_X = 28
-- Collapse button width, the same in both states (plus is 13 x 13, minus 13 x 4).
local CHEVRON_W = 13
local SUBNAME_GAP = 4
local NAME_GAP = -10 -- from the bar's LEFT
local HEADER_NAME_X = 10
local ARROW_X, ARROW_Y = -8, -1
local ARROW_SPACE = 16 -- room the name leaves for the arrow
local SIDE = 6 -- width of the hover side slices
-- The header plate (64 x 29) is nine-sliced: stretched over a 300+ px row, its rounded corners
-- become ellipses. The rounding spans about 10 px; 12 covers it and stays under half the height.
local PLATE_CORNER = 12
-- The gauge background (68 x 30) is nine-sliced too: its rounded ends span 10 px of the art.
-- The fill is not sliced: it is a gauge, cropped to the fraction like camelot's SetFillPercent.
local GAUGE_CORNER = 10
local ATLAS_BAR_BACKGROUND = "common-stat-bar-bg"
-- Tint of an at-war row (camelot RefreshBackgroundHighlightColor). FACTION_AT_WAR_COLOR does not
-- exist in 3.3.5; this is its value in the modern client's GlobalColor.db2: 0xFF690300.
local AT_WAR_COLOR = { r = 105 / 255, g = 3 / 255, b = 0 / 255 }
-- camelot shapes the fill with common-stat-bar-Mask, a MaskTexture 3.3.5 lacks.
-- tools/bake_masks.py bakes the mask into this file, sliced like the background, for a 160 bar.
local FILL_PATH = "Interface\\ForeverUI\\Bars\\statbarfill"
local ATLAS_HEADER = "common-button-list-collapseexpand"
local ATLAS_PLUS = "common-button-list-plus"
local ATLAS_MINUS = "common-button-list-minus"
local ATLAS_LINE = "ui-character-info-scrollline-long"
local ATLAS_HOVER_SIDE = "charactercreate-customize-dropdown-linemouseover-side"
local ATLAS_HOVER_MIDDLE = "charactercreate-customize-dropdown-linemouseover-middle"
-- Fixed pane size: a tab can be built before the window is laid out, when GetHeight is 0.
local PANE_W, PANE_H = 398, 464
local rows = {}
local panel, offset = nil, 0
local visibleCount = 0 -- rows that actually fit
local selectedItem
-- ---------- Rows
local function createRow(index, width)
local row = CreateFrame("Button", "ForeverUIReputationRow" .. index, panel)
row:SetWidth(width)
row:SetHeight(ENTRY_H)
-- Header plate, nine-sliced: only the middle slices stretch.
row.plate = ForeverUI.CreateNineSlice(row, ATLAS_HEADER, PLATE_CORNER,
{ 0, 0, 0, 0 }, "BACKGROUND") or {}
for _, slice in ipairs(row.plate) do
slice:Hide()
end
-- Entry hover highlight: three slices, sides 6 wide.
local hover = CreateFrame("Frame", nil, row)
hover:SetAllPoints(row)
hover:SetAlpha(0)
row.hover = hover
local left = hover:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(left, ATLAS_HOVER_SIDE, true)
left:SetWidth(SIDE)
left:SetPoint("TOPLEFT", hover, "TOPLEFT", 0, 0)
left:SetPoint("BOTTOMLEFT", hover, "BOTTOMLEFT", 0, 0)
local right = hover:CreateTexture(nil, "BACKGROUND")
if ForeverUI.SetAtlas(right, ATLAS_HOVER_SIDE, true) then
-- The source mirrors it: TexCoords left = 1, right = 0.
local e = ForeverUI.AtlasEntry(ATLAS_HOVER_SIDE)
right:SetTexCoord(e[3], e[2], e[4], e[5])
end
right:SetWidth(SIDE)
right:SetPoint("TOPRIGHT", hover, "TOPRIGHT", 0, 0)
right:SetPoint("BOTTOMRIGHT", hover, "BOTTOMRIGHT", 0, 0)
local middle = hover:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(middle, ATLAS_HOVER_MIDDLE, true)
middle:SetPoint("TOPLEFT", left, "TOPRIGHT", 0, 0)
middle:SetPoint("BOTTOMRIGHT", right, "BOTTOMLEFT", 0, 0)
row.hoverPieces = { left, right, middle }
-- Reputation bar: background, fill and text.
local bar = CreateFrame("Frame", nil, row)
bar:SetWidth(BAR_W)
bar:SetHeight(BAR_H)
bar:SetPoint("RIGHT", row, "RIGHT", BAR_X, 0)
row.bar = bar
ForeverUI.CreateNineSlice(bar, ATLAS_BAR_BACKGROUND, GAUGE_CORNER,
{ 0, 0, 0, 0 }, "BACKGROUND")
local fill = bar:CreateTexture(nil, "BORDER")
fill:SetPoint("LEFT", bar, "LEFT", 0, 0)
bar.fill = fill
local text = bar:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
text:SetPoint("LEFT", bar, "LEFT", 0, 0)
text:SetPoint("RIGHT", bar, "RIGHT", 0, 0)
text:SetJustifyH("CENTER")
bar.text = text
-- Name anchors depend on the template, so they are set on each fill.
local name = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
name:SetHeight(NAME_H)
name:SetJustifyH("LEFT")
row.name = name
-- Collapse arrow of a top-level header, on the right.
local arrow = row:CreateTexture(nil, "OVERLAY")
arrow:SetPoint("RIGHT", row, "RIGHT", ARROW_X, ARROW_Y)
arrow:Hide()
row.arrow = arrow
-- Collapse button of a sub-header, on the left. camelot uses campaign_headericon_closed and
-- _open from questmaplogatlas.blp; the header's plus and minus are used instead.
local chevron = row:CreateTexture(nil, "OVERLAY")
chevron:SetPoint("LEFT", row, "LEFT", CHEVRON_X, 0)
chevron:Hide()
row.chevron = chevron
row:RegisterForClicks("LeftButtonUp")
return row
end
-- ---------- Bar fill
-- Fills a reputation bar. data: readFaction result; width: bar width.
local function placeBarIn(bar, data, width)
local fraction = 0
if data.maximum and data.maximum > 0 then
fraction = data.value / data.maximum
end
if fraction < 0 then
fraction = 0
elseif fraction > 1 then
fraction = 1
end
-- As SetFillPercent: width = fraction x bar width, texture cropped to match.
-- The client rejects a zero width.
if fraction * width < 1 then
bar.fill:Hide()
else
bar.fill:SetTexture(FILL_PATH)
bar.fill:SetTexCoord(0, fraction, 0, 1)
bar.fill:SetWidth(width * fraction)
bar.fill:SetHeight(FILL_H)
bar.fill:Show()
end
local color = FACTION_BAR_COLORS and FACTION_BAR_COLORS[data.standingId]
if color then
bar.fill:SetVertexColor(color.r, color.g, color.b)
end
bar.text:SetText(data.label or "")
end
-- List row: its bar is 160 wide.
local function placeBar(row, data)
placeBarIn(row.bar, data, BAR_W)
end
-- Hover opacity, as in RefreshBackgroundHighlightOpacity.
local function placeHover(row)
if row.header then
row.hover:SetAlpha(0)
return
end
local hovered = row:IsMouseOver()
local alpha
if row.atWar then
alpha = (row.selectedItem and 0.85) or (hovered and 0.65) or 0.50
else
alpha = (row.selectedItem and 0.20) or (hovered and 0.10) or 0
end
row.hover:SetAlpha(alpha)
local tint = row.atWar
and (FACTION_AT_WAR_COLOR or AT_WAR_COLOR)
for _, piece in ipairs(row.hoverPieces) do
if tint then
piece:SetVertexColor(tint.r, tint.g, tint.b)
else
piece:SetVertexColor(1, 1, 1)
end
end
end
-- Fills a row from readFaction data and applies its template.
-- SetFontObject resets the justification to the font object's own (GameFontHighlight centers),
-- so SetJustifyH is applied after it, as camelot does with justifyH="LEFT".
local function populateRow(row, data)
row.factionIndex = data.index
row.factionName = data.name
row.header = data.header
row.child = data.child
row.collapsed = data.collapsed
row.atWar = data.atWar
row.progression = data.progression
row.label = data.label
row.lastHovered = nil
row.name:SetText(data.name or "")
row.name:ClearAllPoints()
local plate = data.header and not data.child
local subHeader = data.header and data.child
for _, slice in ipairs(row.plate) do
if plate then slice:Show() else slice:Hide() end
end
if plate then
-- Top-level header: plate, gold name, arrow on the right.
row:SetHeight(HEADER_H)
row.bar:Hide()
row.chevron:Hide()
row.name:SetFontObject(GameFontNormalLeft or GameFontNormal)
row.name:SetJustifyH("LEFT")
row.name:SetPoint("LEFT", row, "LEFT", HEADER_NAME_X, 0)
row.name:SetPoint("RIGHT", row, "RIGHT", ARROW_X - ARROW_SPACE, 0)
ForeverUI.SetAtlas(row.arrow, data.collapsed and ATLAS_PLUS or ATLAS_MINUS)
row.arrow:Show()
elseif subHeader then
-- Sub-header: button on the left, name after it, bar only if it has reputation.
row:SetHeight(SUBHEADER_H)
row.arrow:Hide()
ForeverUI.SetAtlas(row.chevron, data.collapsed and ATLAS_PLUS or ATLAS_MINUS)
row.chevron:Show()
row.name:SetFontObject(GameFontHighlight or GameFontNormal)
row.name:SetJustifyH("LEFT")
-- The name anchors to the row, not to the chevron: a region anchored to another region of
-- the same frame does not always follow when that frame is re-anchored in the same pass.
-- The chevron is 13 wide in both states, so the offset stays the same.
local chevronWidth = row.chevron:GetWidth() or 0
if chevronWidth <= 0 then
chevronWidth = CHEVRON_W
end
row.name:SetPoint("LEFT", row, "LEFT",
CHEVRON_X + chevronWidth + SUBNAME_GAP, 0)
row.name:SetPoint("RIGHT", row.bar, "LEFT", NAME_GAP, 0)
if data.hasRep then
row.bar:Show()
placeBar(row, data)
else
row.bar:Hide()
end
else
-- Entry.
row:SetHeight(ENTRY_H)
row.bar:Show()
row.arrow:Hide()
row.chevron:Hide()
row.name:SetFontObject(GameFontHighlight or GameFontNormal)
row.name:SetJustifyH("LEFT")
row.name:SetPoint("LEFT", row, "LEFT", NAME_X, 0)
row.name:SetPoint("RIGHT", row.bar, "LEFT", NAME_GAP, 0)
placeBar(row, data)
end
-- Selection is kept by name: collapsing renumbers factions, so an index would move.
row.selectedItem = (selectedItem ~= nil and selectedItem == data.name)
placeHover(row)
row:Show()
end
-- ---------- Data
-- Reads one faction. GetFactionInfo returns: name, description, standing, bottom and top
-- thresholds, value, atWar, canToggleAtWar, isHeader, isCollapsed, hasRep, isWatched, isChild.
local function readFaction(rank)
local name, _, standingId, threshold, following, value, atWar, _, header,
collapsed, hasRep, _, child = GetFactionInfo(rank)
if not name then
return nil
end
local maximum, current = 1, 1
local full = (standingId == (MAX_REPUTATION_REACTION or 8))
if not full then
maximum = (following or 0) - (threshold or 0)
current = (value or 0) - (threshold or 0)
end
local label = ""
if GetText then
label = GetText("FACTION_STANDING_LABEL" .. tostring(standingId),
UnitSex and UnitSex("player")) or ""
end
return {
index = rank,
name = name,
standingId = standingId,
value = current,
maximum = maximum,
header = header,
child = child,
hasRep = hasRep,
collapsed = collapsed,
atWar = atWar,
label = label,
progression = (not full)
and (tostring(current) .. " / " .. tostring(maximum)) or nil,
}
end
-- Indent, as in SetElementIndentCalculator.
local function indentOf(data)
if data.header and not data.child then
return HEADER_INDENT
end
if not data.header and data.child then
return CHILD_INDENT
end
return OTHER_INDENT
end
local function heightOf(data)
if data.header then
return data.child and SUBHEADER_H or HEADER_H
end
return ENTRY_H
end
-- Hides every region and child frame of the client ReputationFrame, on each pass:
-- ReputationFrame_Update shows its rows and art again every time. GetRegions only returns
-- regions and GetChildren only frames, so both are needed. The detail frame lives elsewhere.
local function suppressClientScreen()
local frame = _G["ReputationFrame"]
if not frame then
return
end
for _, region in ipairs({ frame:GetRegions() }) do
if region.Hide then
region:Hide()
end
end
if frame.GetChildren then
for _, childFrame in ipairs({ frame:GetChildren() }) do
if childFrame ~= panel and childFrame.Hide then
childFrame:Hide()
end
end
end
end
-- One pass: stacks rows from the offset down to the bottom margin; returns how many were placed.
-- Stops at GetNumFactions: GetFactionInfo still answers past it (the Inactive header).
local function layout()
local total = (GetNumFactions and GetNumFactions()) or 0
local usableHeight = (panel:GetHeight() or 0)
if usableHeight < 50 then
usableHeight = PANE_H + LIST_Y - LIST_Y2
end
local usableWidth = (panel:GetWidth() or 0)
if usableWidth < 50 then
usableWidth = PANE_W + LIST_X2 - LIST_X
end
-- Row heights vary by template, so rows are stacked, not divided.
local y = MARGIN
local placedCount = 0
local full = false
for rank, row in ipairs(rows) do
local index = offset + rank
local data = (not full and index <= total) and readFaction(index) or nil
local height = data and heightOf(data) or 0
if data and y + height <= usableHeight - MARGIN then
local indent = indentOf(data)
row:SetWidth(usableWidth - 2 * MARGIN - indent)
populateRow(row, data)
row:ClearAllPoints()
row:SetPoint("TOPLEFT", panel, "TOPLEFT", MARGIN + indent, -y)
y = y + height + GAP
placedCount = placedCount + 1
else
-- Past the first row that does not fit nothing is placed: a shorter row further down
-- (a header after an entry) would otherwise take its slot, out of order
full = full or data ~= nil
row:Hide()
end
end
return placedCount
end
-- Collapsing removes children from the client's numbering, so the list shrinks.
-- If the offset is then too large, it is clamped and the list laid out again.
local function layoutList()
if not panel then
return
end
suppressClientScreen()
local total = (GetNumFactions and GetNumFactions()) or 0
local placedCount = layout()
if offset > 0 and offset + placedCount > total then
offset = math.max(0, total - placedCount)
placedCount = layout()
end
visibleCount = placedCount
if panel.bar then
panel.bar:Configure(total, placedCount, offset)
end
-- The detail pane follows the selection. It is defined below, so it is reached through
-- ForeverUI; a plain name would resolve to a global.
if ForeverUI.ReputationDetail then
ForeverUI.ReputationDetail()
end
end
ForeverUI.ReputationLayout = layoutList
-- ---------- Build
-- Every frame: hover highlight and bar text of each shown row.
local function trackHover()
for _, row in ipairs(rows) do
if row:IsShown() and not row.header then
placeHover(row)
-- On hover the bar shows progress, otherwise the standing (TryShowBarProgressText).
local hovered = row:IsMouseOver()
if hovered ~= row.lastHovered then
row.lastHovered = hovered
if hovered and row.progression then
row.bar.text:SetText(row.progression)
else
row.bar.text:SetText(row.label or "")
end
end
end
end
end
-- Builds the list over the client ReputationFrame. host: left pane frame.
local function build(host)
local frame = _G["ReputationFrame"]
if not frame or not host then
return nil, {}
end
frame:ClearAllPoints()
frame:SetPoint("TOPLEFT", host, "TOPLEFT", 0, 0)
frame:SetPoint("BOTTOMRIGHT", host, "BOTTOMRIGHT", 0, 0)
if panel then
return nil, { frame }
end
-- The client's art and rows are hidden by suppressClientScreen on every pass, the first
-- included. Only the frame itself (as parent) and its read functions are used.
if frame.SetBackdrop then
frame:SetBackdrop(nil)
end
local height = host:GetHeight() or 0
if height < 100 then
height = PANE_H
end
local width = host:GetWidth() or 0
if width < 100 then
width = PANE_W
end
width = width + LIST_X2 - LIST_X
panel = CreateFrame("Frame", "ForeverUIReputationList", frame)
panel:SetPoint("TOPLEFT", host, "TOPLEFT", LIST_X, LIST_Y)
panel:SetPoint("BOTTOMRIGHT", host, "BOTTOMRIGHT", LIST_X2, LIST_Y2)
panel:SetWidth(width)
-- Enough rows for the densest case (only sub-headers, the shortest); layout stops at the
-- bottom margin.
local position = math.floor((height + LIST_Y - LIST_Y2) / (SUBHEADER_H + GAP))
if position < 1 then
position = 1
end
for index = 1, position do
local row = createRow(index, width)
row:SetScript("OnClick", function(self)
if not self.factionIndex then
return
end
-- Headers and sub-headers both collapse.
if self.header then
if self.collapsed then
ExpandFactionHeader(self.factionIndex)
else
CollapseFactionHeader(self.factionIndex)
end
else
-- Selection goes through the client: ReputationFrame_Update fills the description and the
-- three checkboxes, only while its detail frame is shown.
local frame = _G["ReputationDetailFrame"]
if frame then
frame:Show()
end
ForeverUI.ReputationSelect(self.factionName)
end
end)
rows[index] = row
end
-- Both lines live on our panel: every region of the client frame is hidden on each pass.
local top = panel:CreateTexture(nil, "ARTWORK")
ForeverUI.SetAtlas(top, ATLAS_LINE)
top:SetPoint("CENTER", panel, "TOP", 0, 0)
local down = panel:CreateTexture(nil, "ARTWORK")
ForeverUI.SetAtlas(down, ATLAS_LINE)
down:SetPoint("CENTER", panel, "BOTTOM", 0, 0)
-- camelot scroll bar, right of the list. It only takes three numbers and returns the new
-- offset.
panel.bar = ForeverUI.CreateScrollBar("ForeverUIReputationScrollBar",
frame, panel)
panel.bar.onScroll = function(new)
offset = new
layoutList()
end
-- Without a scroll bar the list takes its space: right edge from -25 to -10 (left margin).
panel.bar.onVisibility = function(hasBar)
local x2 = hasBar and LIST_X2 or -LIST_X
panel:SetPoint("BOTTOMRIGHT", host, "BOTTOMRIGHT", x2, LIST_Y2)
panel:SetWidth(width + x2 - LIST_X2)
layout()
end
panel:SetScript("OnUpdate", trackHover)
panel:EnableMouseWheel(true)
panel:SetScript("OnMouseWheel", function(self, direction)
local total = (GetNumFactions and GetNumFactions()) or 0
offset = math.max(0, math.min(offset - direction, total - visibleCount))
layoutList()
end)
layoutList()
return nil, { frame }
end
-- ---------- Right pane: detail of the selected faction
-- Layout after camelot CharacterFrameSidePaneTemplate and ReputationDetailFrame.
-- The client's ReputationDetailFrame is kept and moved here: ReputationFrame_Update fills its
-- name, description and three checkboxes for the selected faction, only while it is shown.
-- camelot's description scrolls (ScrollingFontTemplate, missing in 3.3.5); ours is bounded.
local RIGHT_PANE_X, RIGHT_PANE_Y = 16, -14
local RIGHT_PANE_X2, RIGHT_PANE_Y2 = -12, 14
local TITLE_W = 195
local SUBTITLE_Y = -3
local SEPARATOR_Y = -4
local DETAIL_GAUGE_W, DETAIL_GAUGE_H = 180, 29
local DETAIL_GAUGE_Y = -6
local DESCRIPTION_Y = -6
local DESCRIPTION_X2 = -14
local DESCRIPTION_Y2 = 6 -- gap above the checkboxes
local CHECKBOX = 26
local CHECKBOX_X, CHECKBOX_GAP = -4, -2
local CHECKBOX_LABEL_L = 158
local CHECKBOX_LABEL_X = 2
local ATLAS_CHECKBOX = "checkbox-minimal"
local ATLAS_CHECKMARK = "checkmark-minimal"
local ATLAS_SEPARATOR = "ui-character-info-scrollline"
local CHECKMARK = "Interface\\Buttons\\UI-CheckBox-SwordCheck"
local CHECK_SIDE = 32
local CHECKMARK_X, CHECKMARK_Y = 3, -5
local detail
-- The client's three checkboxes, moved and reskinned; their logic stays the client's.
-- All use checkbox-minimal; At War checks with crossed swords (UI-CheckBox-SwordCheck),
-- the others with checkmark-minimal (camelot ReputationFrame.xml).
local CHECKBOXES = {
{ name = "ReputationDetailAtWarCheckBox", red = true, swords = true },
{ name = "ReputationDetailInactiveCheckBox" },
{ name = "ReputationDetailMainScreenCheckBox" },
}
-- Reskins a client checkbox once. red: red label; swords: sword check mark.
local function skinCell(checkbox, red, swords)
if not checkbox or checkbox.foreverSkinDone then
return
end
checkbox:SetWidth(CHECKBOX)
checkbox:SetHeight(CHECKBOX)
for _, method in ipairs({ "GetNormalTexture", "GetPushedTexture",
"GetHighlightTexture", "GetCheckedTexture", "GetDisabledCheckedTexture" }) do
local texture = checkbox[method] and checkbox[method](checkbox)
if texture then
texture:SetAlpha(0)
end
end
local background = checkbox:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(background, ATLAS_CHECKBOX)
background:SetPoint("CENTER", checkbox, "CENTER", 0, 0)
-- Check mark: At War uses the client file UI-CheckBox-SwordCheck, 32 at (3, -5);
-- the others the checkmark-minimal atlas, centered.
local checkMark = checkbox:CreateTexture(nil, "OVERLAY")
if swords then
checkMark:SetTexture(CHECKMARK)
checkMark:SetWidth(CHECK_SIDE)
checkMark:SetHeight(CHECK_SIDE)
checkMark:SetPoint("TOPLEFT", checkbox, "TOPLEFT", CHECKMARK_X, CHECKMARK_Y)
else
ForeverUI.SetAtlas(checkMark, ATLAS_CHECKMARK)
checkMark:SetPoint("CENTER", checkbox, "CENTER", 0, 0)
end
checkbox.foreverCheck = checkMark
local label = _G[checkbox:GetName() .. "Text"]
if label then
label:ClearAllPoints()
label:SetPoint("LEFT", checkbox, "RIGHT", CHECKBOX_LABEL_X, 0)
label:SetWidth(CHECKBOX_LABEL_L)
label:SetJustifyH("LEFT")
if GameFontNormal then
label:SetFontObject(GameFontNormal)
end
if red and RED_FONT_COLOR then
label:SetTextColor(RED_FONT_COLOR.r, RED_FONT_COLOR.g, RED_FONT_COLOR.b)
end
end
checkbox.foreverSkinDone = true
end
-- Check marks follow the state the client just set.
local function syncCheckboxes()
for _, desc in ipairs(CHECKBOXES) do
local checkbox = _G[desc.name]
if checkbox and checkbox.foreverCheck then
if checkbox:GetChecked() then
checkbox.foreverCheck:Show()
else
checkbox.foreverCheck:Hide()
end
end
end
end
-- Shows or hides the three checkboxes.
local function showCells(state)
for _, desc in ipairs(CHECKBOXES) do
local checkbox = _G[desc.name]
if checkbox then
if state then checkbox:Show() else checkbox:Hide() end
end
end
end
-- Current index of a faction, by name. Move to Inactive renumbers the list, so the detail
-- keeps the name, looks up the index on each pass and gives it back to the client.
local function indexOf(name)
if not name then
return nil
end
for rank = 1, (GetNumFactions and GetNumFactions()) or 0 do
if GetFactionInfo(rank) == name then
return rank
end
end
return nil
end
local function clearDetail()
detail.title:SetText("")
detail.subtitle:SetText("")
detail.description:SetText("")
detail.separator:Hide()
detail.gauge:Hide()
showCells(false)
end
local lastData
local function updateDetail()
if not detail then
return
end
-- Nothing selected: empty pane.
if not selectedItem then
lastData = nil
clearDetail()
return
end
local index = indexOf(selectedItem)
local data
if index then
if SetSelectedFaction and GetSelectedFaction
and GetSelectedFaction() ~= index then
SetSelectedFaction(index)
end
data = readFaction(index)
lastData = data
else
-- The faction left the list (made inactive or its group collapsed): keep the last data.
data = lastData
end
if not data or data.header then
clearDetail()
return
end
detail.title:SetText(data.name or "")
detail.subtitle:SetText(data.label or "")
detail.separator:Show()
detail.gauge:Show()
placeBarIn(detail.gauge, data, DETAIL_GAUGE_W)
detail.gauge.text:SetText(data.progression or data.label or "")
-- ReputationFrame_Update fills the client's description and check boxes only when the
-- selection is one of the 15 rows of its own list, hidden here and never scrolled: from the
-- 16th faction on they kept the previous faction's, and a click acted on the new one with
-- the wrong intent (at war, watched bar). They are read from the faction itself, as there.
if index then
local _, description, _, _, _, _, atWar, canToggleAtWar, _, _, _, isWatched = GetFactionInfo(index)
detail.description:SetText(description or "")
local atWarBox = _G["ReputationDetailAtWarCheckBox"]
if atWarBox then
atWarBox:SetChecked(atWar and 1 or nil)
if canToggleAtWar then atWarBox:Enable() else atWarBox:Disable() end
end
local inactiveBox = _G["ReputationDetailInactiveCheckBox"]
if inactiveBox then
inactiveBox:Enable()
inactiveBox:SetChecked(IsFactionInactive(index) and 1 or nil)
end
local watchBox = _G["ReputationDetailMainScreenCheckBox"]
if watchBox then
watchBox:SetChecked(isWatched and 1 or nil)
end
else
local source = _G["ReputationDetailFactionDescription"]
detail.description:SetText((source and source:GetText()) or "")
end
showCells(true)
syncCheckboxes()
end
ForeverUI.ReputationDetail = updateDetail
-- Selects a faction by name; nil empties the right pane.
local function choose(name)
selectedItem = name
local index = indexOf(name)
if index and SetSelectedFaction then
SetSelectedFaction(index)
end
if ReputationFrame_Update then
ReputationFrame_Update()
else
layoutList()
end
end
ForeverUI.ReputationSelect = choose
-- Moves the client's ReputationDetailFrame into the right pane. host: right pane frame.
local function buildDetail(host)
if detail then
return detail, {}
end
local frame = _G["ReputationDetailFrame"]
if not frame or not host then
return nil, {}
end
-- The client frame becomes our pane: no window art, spread over camelot's pane area.
frame:SetParent(host)
if frame.SetToplevel then
frame:SetToplevel(false)
end
frame:ClearAllPoints()
frame:SetPoint("TOPLEFT", host, "TOPLEFT", RIGHT_PANE_X, RIGHT_PANE_Y)
frame:SetPoint("BOTTOMRIGHT", host, "BOTTOMRIGHT", RIGHT_PANE_X2, RIGHT_PANE_Y2)
-- Its <Backdrop> is not a region, so GetRegions misses it: SetBackdrop(nil) removes it.
if frame.SetBackdrop then
frame:SetBackdrop(nil)
end
for _, region in ipairs({ frame:GetRegions() }) do
if region.GetObjectType and region:GetObjectType() == "Texture" then
region:SetAlpha(0)
end
end
local close = _G["ReputationDetailCloseButton"]
if close then
close:Hide()
end
detail = frame
-- camelot uses GameFontNormalMed3, missing in 3.3.5; GameFontNormalLarge is the closest.
detail.title = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
detail.title:SetWidth(TITLE_W)
detail.title:SetJustifyH("CENTER")
detail.title:SetPoint("TOP", frame, "TOP", 0, 0)
detail.subtitle = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
detail.subtitle:SetWidth(TITLE_W)
detail.subtitle:SetJustifyH("CENTER")
detail.subtitle:SetPoint("TOP", detail.title, "BOTTOM", 0, SUBTITLE_Y)
detail.separator = frame:CreateTexture(nil, "BORDER")
ForeverUI.SetAtlas(detail.separator, ATLAS_SEPARATOR)
detail.separator:SetPoint("TOP", detail.subtitle, "BOTTOM", 0, SEPARATOR_Y)
-- Pane gauge: same template as the list bars, 180 wide.
local gauge = CreateFrame("Frame", "ForeverUIReputationStanding", frame)
gauge:SetWidth(DETAIL_GAUGE_W)
gauge:SetHeight(DETAIL_GAUGE_H)
gauge:SetPoint("TOP", detail.separator, "BOTTOM", 0, DETAIL_GAUGE_Y)
ForeverUI.CreateNineSlice(gauge, ATLAS_BAR_BACKGROUND, GAUGE_CORNER,
{ 0, 0, 0, 0 }, "BACKGROUND")
gauge.fill = gauge:CreateTexture(nil, "BORDER")
gauge.fill:SetPoint("LEFT", gauge, "LEFT", 0, 0)
gauge.text = gauge:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
gauge.text:SetPoint("LEFT", gauge, "LEFT", 0, 0)
gauge.text:SetPoint("RIGHT", gauge, "RIGHT", 0, 0)
gauge.text:SetJustifyH("CENTER")
detail.gauge = gauge
-- Footer height: three checkboxes and two gaps. The description ends just above.
local footer = 3 * CHECKBOX + 2 * (-CHECKBOX_GAP)
-- The client description is only a source: hidden, and copied into ours.
for _, name in ipairs({ "ReputationDetailFactionDescription",
"ReputationDetailFactionName" }) do
local piece = _G[name]
if piece then
piece:Hide()
end
end
-- GameFontHighlight: camelot's GameFontNormal is gold in 3.3.5, the reference text is white.
-- Anchored on all four sides so long text wraps inside the box.
detail.description = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
detail.description:SetPoint("TOPLEFT", gauge, "BOTTOMLEFT", 0, DESCRIPTION_Y)
detail.description:SetPoint("TOPRIGHT", gauge, "BOTTOMRIGHT", 0, DESCRIPTION_Y)
detail.description:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, footer + DESCRIPTION_Y2)
detail.description:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT",
DESCRIPTION_X2, footer + DESCRIPTION_Y2)
detail.description:SetJustifyH("LEFT")
detail.description:SetJustifyV("TOP")
if detail.description.SetWordWrap then
detail.description:SetWordWrap(true)
end
-- Footer: the three checkboxes, stacked from the bottom of the pane.
local prev
for _, desc in ipairs(CHECKBOXES) do
local checkbox = _G[desc.name]
if checkbox then
skinCell(checkbox, desc.red, desc.swords)
checkbox:SetParent(frame)
checkbox:ClearAllPoints()
if prev then
checkbox:SetPoint("TOPLEFT", prev, "BOTTOMLEFT", 0, CHECKBOX_GAP)
else
checkbox:SetPoint("TOPLEFT", frame, "BOTTOMLEFT", CHECKBOX_X, footer)
end
prev = checkbox
end
end
frame:Show()
updateDetail()
return frame, {}
end
ForeverUI.ReputationTab = { Build = build, BuildRight = buildDetail, Rows = rows }
-- The client handles UPDATE_FACTION in ReputationFrame_Update; hook it to redraw the list.
if hooksecurefunc and type(_G["ReputationFrame_Update"]) == "function" then
hooksecurefunc("ReputationFrame_Update", layoutList)
end