-- 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 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