From a1aedcfe8d417562e47bd10266b1a60649af24e4 Mon Sep 17 00:00:00 2001 From: Krathe Date: Fri, 8 May 2026 23:20:25 +0100 Subject: [PATCH 1/3] Add reusable CreateInfoBanner helper; fix Aura Filters banner links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces GUI:CreateInfoBanner — a self-resizing banner with tone presets (info/warning/caution/danger/success) for consistent styling across the addon. Uses a Button flow layout so clickable links dispatch OnClick reliably and text word-wraps at any window width. Replaces ad-hoc inline banners in Aura Filters, Aura Blacklist, Aura Designer, and Boss Debuffs pages with the new helper. Also fixes the Aura Filters info banner links which were not navigating when clicked. --- AuraBlacklist/Options.lua | 26 +-- AuraDesigner/Options.lua | 24 +-- CHANGELOG.md | 2 + GUI/GUI.lua | 382 ++++++++++++++++++++++++++++++++++++++ Options/Options.lua | 293 +++++++---------------------- 5 files changed, 467 insertions(+), 260 deletions(-) diff --git a/AuraBlacklist/Options.lua b/AuraBlacklist/Options.lua index 1722442c..8f2e1e10 100644 --- a/AuraBlacklist/Options.lua +++ b/AuraBlacklist/Options.lua @@ -389,26 +389,12 @@ function DF.BuildAuraBlacklistPage(guiRef, pageRef, dbRef) end -- ========== CURATED LIST NOTICE ========== - local noticeBanner = CreateFrame("Frame", nil, parent, "BackdropTemplate") + local noticeBanner = GUI:CreateInfoBanner(parent, { + tone = "warning", + text = L["This is a curated list selected by Blizzard. Additional spells cannot be added as these are the only spells Blizzard has allowed. If more are permitted in the future, they will be added to this list."], + }) noticeBanner:SetPoint("TOPLEFT", 10, -10) noticeBanner:SetPoint("RIGHT", -135, 0) - noticeBanner:SetHeight(44) - if not noticeBanner.SetBackdrop then Mixin(noticeBanner, BackdropTemplateMixin) end - noticeBanner:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Buttons\\WHITE8x8", edgeSize = 1 }) - noticeBanner:SetBackdropColor(0.25, 0.22, 0.10, 1) - noticeBanner:SetBackdropBorderColor(0.6, 0.55, 0.2, 0.6) - - local noticeIcon = noticeBanner:CreateTexture(nil, "OVERLAY") - noticeIcon:SetPoint("LEFT", 10, 0) - noticeIcon:SetSize(20, 20) - noticeIcon:SetTexture("Interface\\AddOns\\DandersFrames\\Media\\Icons\\warning") - - local noticeText = noticeBanner:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") - noticeText:SetPoint("LEFT", noticeIcon, "RIGHT", 8, 0) - noticeText:SetPoint("RIGHT", noticeBanner, "RIGHT", -10, 0) - noticeText:SetJustifyH("LEFT") - noticeText:SetText(L["This is a curated list selected by Blizzard. Additional spells cannot be added as these are the only spells Blizzard has allowed. If more are permitted in the future, they will be added to this list."]) - noticeText:SetTextColor(1, 0.82, 0) -- ========== DESCRIPTION ========== local desc = parent:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") @@ -419,9 +405,11 @@ function DF.BuildAuraBlacklistPage(guiRef, pageRef, dbRef) desc:SetTextColor(0.6, 0.6, 0.6) -- ========== CLASS DROPDOWN ========== + -- Anchored to desc's BOTTOMLEFT (which itself trails noticeBanner) so this + -- block shifts down when the banner wraps to multiple lines. local dropdownContainer = CreateFrame("Frame", nil, parent) dropdownContainer:SetSize(280, 55) - dropdownContainer:SetPoint("TOPLEFT", 10, -80) + dropdownContainer:SetPoint("TOPLEFT", desc, "BOTTOMLEFT", 0, -10) local classLabel = dropdownContainer:CreateFontString(nil, "OVERLAY", "DFFontNormal") classLabel:SetPoint("TOPLEFT", 0, 0) diff --git a/AuraDesigner/Options.lua b/AuraDesigner/Options.lua index 62bb4976..3e2ff314 100644 --- a/AuraDesigner/Options.lua +++ b/AuraDesigner/Options.lua @@ -2903,26 +2903,12 @@ local function BuildTypeContent(parent, typeKey, auraName, width, optProxy, yOff topSpacer:SetHeight(4) g:AddWidget(topSpacer, 4) - local banner = CreateFrame("Frame", nil, parent, "BackdropTemplate") - banner:SetHeight(36) + local banner = GUI:CreateInfoBanner(parent, { + tone = "warning", + text = L["Sound alerts only work when you are in a group."], + }) banner:SetWidth(contentWidth - 10) - banner:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Buttons\\WHITE8x8", edgeSize = 1 }) - banner:SetBackdropColor(0.25, 0.22, 0.10, 1) - banner:SetBackdropBorderColor(0.6, 0.55, 0.2, 0.6) - - local icon = banner:CreateTexture(nil, "OVERLAY") - icon:SetPoint("LEFT", 8, 0) - icon:SetSize(18, 18) - icon:SetTexture("Interface\\AddOns\\DandersFrames\\Media\\Icons\\warning") - - local text = banner:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") - text:SetPoint("LEFT", icon, "RIGHT", 7, 0) - text:SetPoint("RIGHT", banner, "RIGHT", -8, 0) - text:SetJustifyH("LEFT") - text:SetText(L["Sound alerts only work when you are in a group."]) - text:SetTextColor(1, 0.82, 0) - - g:AddWidget(banner, 36) + g:AddWidget(banner, banner.layoutHeight) local spacer = CreateFrame("Frame", nil, parent) spacer:SetHeight(6) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f306d80..a47ad28a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Improvements * Reset, Copy, and Sync confirmation popups now use the addon's themed popup style. +* Info and warning banners across all settings pages now have consistent styling with tone-coloured icons (info, warning, caution). (PR by Krathe) * (Aura Designer) Reworked sound indicator: Missing Trigger and Expire Alert can now be toggled independently, with separate loop intervals and a new Play Once option. (PR #54 by Krathe) ### Bug Fixes @@ -21,6 +22,7 @@ * (Aura Designer) Fix indicators not firing on the first aura application after joining a group or entering a new zone. (PR #53 by Krathe) * (Aura Designer) Fix sound expire alert not re-triggering after a buff is refreshed and decays again. (PR #54 by Krathe) * (Targeted List) Fix self-target colour overlay covering the text, sticking on the wrong bar, and snapping off instead of fading. (PR #55 by Krathe) +* (Aura Filters) Fix info banner links not navigating to the linked page when clicked. (PR by Krathe) * (Aura Designer) Fix Global Defaults changes not applying to live frames without a /reload. * (Aura Designer) Fix the Reset All Aura Configs button not clearing indicators from live frames until /reload. * (Aura Designer) Fix Color Duration by Time not transitioning live as a buff ticks down on icon, square, and bar indicators. diff --git a/GUI/GUI.lua b/GUI/GUI.lua index c93eb57e..3f9b5e6a 100644 --- a/GUI/GUI.lua +++ b/GUI/GUI.lua @@ -899,6 +899,388 @@ function GUI:CreateWarningBox(parent, text, width, height) return frame end +-- ============================================================ +-- CreateInfoBanner +-- ------------------------------------------------------------ +-- A self-resizing banner with an icon, body text, and a "tone" +-- (info / warning / caution / danger / success) that controls +-- background, border, default text colour, and default icon. +-- +-- Usage: +-- local banner = GUI:CreateInfoBanner(parent, { tone = "warning", text = "..." }) +-- Add(banner, banner.layoutHeight, "both") +-- +-- Methods on the returned frame: +-- :SetTone(name) apply a preset (see TONES below) +-- :SetText(text, optColor) plain text mode, auto-wraps + auto-resizes +-- :SetHTML(html, onLinkClick) flow-layout body with clickable link buttons +-- :SetIcon(texture, r, g, b) icon texture + optional vertex colour +-- :SetIconTexture(path) icon texture only +-- :SetIconColor(r, g, b) icon vertex colour only +-- +-- The body word-wraps automatically; banner height is recomputed via +-- OnSizeChanged so resizing the GUI (or calling SetText/SetHTML) grows +-- or shrinks the banner to fit. The host page is re-laid out so widgets +-- below the banner reposition. +-- ============================================================ +local INFO_BANNER_TONES = { + info = { + bg = {0.15, 0.18, 0.28, 1}, + useThemeBorder = true, borderAlpha = 0.5, + icon = "info", + textColor = {0.85, 0.85, 0.85}, + }, + warning = { + bg = {0.25, 0.22, 0.10, 1}, + border = {0.6, 0.55, 0.2, 0.6}, + icon = "warning", + textColor = {1, 0.82, 0}, + }, + caution = { + bg = {0.5, 0.45, 0.1, 0.9}, + border = {0.7, 0.6, 0.1, 1}, + icon = "warning", iconColor = {1, 0.9, 0.3}, + textColor = {1, 0.95, 0.7}, + }, + danger = { + bg = {0.6, 0.3, 0.1, 0.9}, + border = {0.8, 0.4, 0.1, 1}, + icon = "warning", iconColor = {1, 0.6, 0.2}, + textColor = {1, 0.85, 0.7}, + }, + success = { + bg = {0.1, 0.4, 0.2, 0.9}, + border = {0.2, 0.6, 0.3, 1}, + icon = "check", iconColor = {0.3, 1, 0.5}, + textColor = {0.7, 1, 0.8}, + }, +} +local INFO_BANNER_ICON_PATH = "Interface\\AddOns\\DandersFrames\\Media\\Icons\\" + +function GUI:CreateInfoBanner(parent, opts) + opts = opts or {} + + local banner = CreateFrame("Frame", nil, parent, "BackdropTemplate") + if not banner.SetBackdrop then Mixin(banner, BackdropTemplateMixin) end + banner:SetBackdrop({ + bgFile = "Interface\\Buttons\\WHITE8x8", + edgeFile = "Interface\\Buttons\\WHITE8x8", + edgeSize = 1, + }) + -- Give the banner a defined initial height so child frames have valid positions + -- from the very first frame (before DoRecomputeHeight has run). + banner:SetHeight(opts.minHeight or 34) + + -- Icon: top-left anchored so it stays put when content wraps to multiple lines. + local icon = banner:CreateTexture(nil, "OVERLAY") + icon:SetPoint("TOPLEFT", 12, -10) + icon:SetSize(18, 18) + banner.icon = icon + + -- Plain-text body. Anchored top + right (no bottom) so the FontString + -- auto-grows to its natural wrapped height; the banner then resizes + -- to fit it via RecomputeHeight. SetWordWrap is on so long text wraps + -- at the width defined by the LEFT/RIGHT anchors. + local fontTemplate = opts.fontTemplate or "DFFontHighlightSmall" + local body = banner:CreateFontString(nil, "OVERLAY", fontTemplate) + -- Y offset of -3 nudges the first line of text down so its visual centre + -- aligns with the icon's vertical centre (DFFont line height is ~12 px, + -- icon is 18 px; the 3 px offset closes most of the gap). + body:SetPoint("TOPLEFT", icon, "TOPRIGHT", 8, -3) + body:SetPoint("RIGHT", banner, "RIGHT", -12, 0) + body:SetJustifyH("LEFT") + body:SetJustifyV("TOP") + body:SetWordWrap(true) + body:SetNonSpaceWrap(true) + if body.SetMaxLines then body:SetMaxLines(0) end + banner.body = body + + banner.layoutHeight = (opts.minHeight or 28) + 6 + + local cachedH, recomputing = nil, false + + local function TriggerHostRelayout() + -- If this banner was added to a SettingsGroup, sync its stored + -- height entry and re-lay out the group. + if banner.settingsGroup and banner.settingsGroup.LayoutChildren then + local g = banner.settingsGroup + for _, entry in ipairs(g.groupChildren or {}) do + if entry.widget == banner then + entry.height = banner.layoutHeight + break + end + end + g:LayoutChildren() + return + end + -- Otherwise, walk up to find a host page. + local p = banner:GetParent() + while p do + if type(p.RefreshStates) == "function" and p.children then + p:RefreshStates() + return + end + p = p:GetParent() + end + end + + local function MeasureContent() + if banner._isHTML then + -- For HTML mode the flow layout positions all widgets and returns + -- the total pixel height of all lines. Re-running it here keeps + -- positions fresh and gives us an accurate height in one step. + return math.max(18, banner._DoFlowLayout and banner._DoFlowLayout() or 18) + end + return math.max(18, body:GetStringHeight()) + end + + local pending = false + local function DoRecomputeHeight() + pending = false + if recomputing then return end + local h = math.ceil(MeasureContent()) + -- Padding: 10 top (icon offset) + 6 bottom = 16 px chrome. + local newH = math.max(opts.minHeight or 28, h + 16) + if cachedH ~= newH then + cachedH = newH + recomputing = true + banner:SetHeight(newH) + banner.layoutHeight = newH + 6 + TriggerHostRelayout() + recomputing = false + end + -- Schedule one more measurement next frame: GetStringHeight can + -- return a stale single-line value the first time it's read after + -- a width change, before the FontString has finished re-rendering. + -- A second pass converges to the true wrapped height. + if not banner._secondPassDone then + banner._secondPassDone = true + if C_Timer and C_Timer.After then + C_Timer.After(0, DoRecomputeHeight) + end + end + end + + -- Defer measurement to next frame so FontString has rendered with its + -- current width — GetStringHeight can return a stale single-line value + -- if called immediately after a width change. Coalesce multiple calls + -- per frame via the `pending` flag. + local function RecomputeHeight() + banner._secondPassDone = false -- allow follow-up pass on every fresh trigger + if pending then return end + pending = true + if C_Timer and C_Timer.After then + C_Timer.After(0, DoRecomputeHeight) + else + DoRecomputeHeight() + end + end + + banner:SetScript("OnSizeChanged", function() + RecomputeHeight() + end) + banner._RecomputeHeight = RecomputeHeight + + function banner:SetIconTexture(path) + self.icon:SetTexture(path) + end + + function banner:SetIconColor(r, g, b) + self.icon:SetVertexColor(r or 1, g or 1, b or 1) + end + + function banner:SetIcon(path, r, g, b) + self:SetIconTexture(path) + if r then self:SetIconColor(r, g, b) end + end + + function banner:SetTone(toneName) + local tone = INFO_BANNER_TONES[toneName] + if not tone then return end + self._tone = toneName + if tone.bg then self:SetBackdropColor(tone.bg[1], tone.bg[2], tone.bg[3], tone.bg[4] or 1) end + if tone.useThemeBorder then + local tc = (GUI.GetThemeColor and GUI.GetThemeColor()) or {r = 1, g = 1, b = 1} + self:SetBackdropBorderColor(tc.r, tc.g, tc.b, tone.borderAlpha or 1) + elseif tone.border then + self:SetBackdropBorderColor(tone.border[1], tone.border[2], tone.border[3], tone.border[4] or 1) + end + if tone.icon then + self:SetIconTexture(INFO_BANNER_ICON_PATH .. tone.icon) + end + if tone.iconColor then + self:SetIconColor(tone.iconColor[1], tone.iconColor[2], tone.iconColor[3]) + else + self:SetIconColor(1, 1, 1) + end + if tone.textColor then + self.body:SetTextColor(tone.textColor[1], tone.textColor[2], tone.textColor[3]) + end + end + + function banner:SetText(text, color) + -- Hide any flow widgets from a previous SetHTML call. + if self._flowWidgets then + for _, w in ipairs(self._flowWidgets) do w:Hide() end + end + self._isHTML = false + self.body:Show() + self.body:SetText(text or "") + if color then + local r = color[1] or color.r + local g = color[2] or color.g + local b = color[3] or color.b + if r then self.body:SetTextColor(r, g, b) end + end + cachedH = nil + banner._secondPassDone = false + RecomputeHeight() + end + + -- SetHTML renders text + clickable links using real Button widgets in a + -- flow layout. This mirrors the original per-link-button approach that + -- reliably dispatches OnClick in WoW, unlike SimpleHTML whose + -- OnHyperlinkClick failed to fire consistently. + -- + -- Input text uses WoW hyperlink markup: |cCOLOR|HlinkData|hText|h|r + -- and \n for explicit line breaks. Plain text is word-split so wrapping + -- occurs at word boundaries when the banner is narrow. + + -- Parse text into a flat list of typed tokens. + local function ParseHTMLSegments(s) + local segs = {} + local function addWords(chunk) + local pos = 1 + while pos <= #chunk do + local nl = chunk:find("\n", pos, true) + local line = nl and chunk:sub(pos, nl - 1) or chunk:sub(pos) + for _, w in ipairs({strsplit(" ", line)}) do + if #w > 0 then segs[#segs + 1] = {type = "word", text = w} end + end + if nl then + segs[#segs + 1] = {type = "newline"} + pos = nl + 1 + else + break + end + end + end + local rem = s + while #rem > 0 do + local pre, color, data, lt, rest = + rem:match("^(.-)|c(%x%x%x%x%x%x%x%x)|H([^|]*)|h([^|]*)|h|r(.*)") + if pre ~= nil then + addWords(pre) + segs[#segs + 1] = {type = "link", text = lt, data = data, color = color} + rem = rest or "" + else + addWords(rem) + break + end + end + return segs + end + + -- Position all flow widgets left-to-right with wrapping; returns total + -- content height. Punctuation tokens attach to the preceding element + -- with no leading gap so "Foo," renders without extra space before the comma. + local FLOW_LINE_H = 14 + local function DoFlowLayout() + if not banner._flowSegs then return 0 end + local availW = banner:GetWidth() - (12 + 18 + 8) - 12 + if availW < 20 then return FLOW_LINE_H end + local x, lineY = 0, -3 + for _, seg in ipairs(banner._flowSegs) do + if seg.type == "newline" then + x = 0; lineY = lineY - FLOW_LINE_H - 2 + elseif seg._widget then + local w = seg._w + local isPunct = seg.type == "word" and seg.text:match("^[%p]") and true or false + local gap = (x > 0 and not isPunct) and 3 or 0 + if x > 0 and (x + gap + w) > availW then + x = 0; lineY = lineY - FLOW_LINE_H - 2; gap = 0 + end + seg._widget:ClearAllPoints() + seg._widget:SetPoint("TOPLEFT", icon, "TOPRIGHT", 8 + x + gap, lineY) + x = x + gap + w + end + end + return math.abs(lineY - (-3)) + FLOW_LINE_H + end + banner._DoFlowLayout = DoFlowLayout + + function banner:SetHTML(text, onLinkClick) + self._htmlText = text or "" + self._onLinkClick = onLinkClick + self._isHTML = true + self.body:Hide() + + -- Tear down widgets from any previous call. + if self._flowWidgets then + for _, w in ipairs(self._flowWidgets) do w:Hide() end + end + self._flowWidgets = {} + + local tc = GUI.GetThemeColor and GUI.GetThemeColor() or {r = 1, g = 0.82, b = 0} + local segs = ParseHTMLSegments(self._htmlText) + self._flowSegs = segs + + for _, seg in ipairs(segs) do + if seg.type == "word" then + local fs = self:CreateFontString(nil, "OVERLAY", fontTemplate) + fs:SetText(seg.text) + fs:SetTextColor(0.85, 0.85, 0.85) + seg._w = fs:GetStringWidth() + -- Give an explicit size matching the button height so TOPLEFT + -- anchors place both text words and link buttons on the same baseline. + fs:SetSize(seg._w, FLOW_LINE_H) + seg._widget = fs + self._flowWidgets[#self._flowWidgets + 1] = fs + elseif seg.type == "link" then + local btn = CreateFrame("Button", nil, self) + local fs = btn:CreateFontString(nil, "OVERLAY", fontTemplate) + fs:SetAllPoints() + fs:SetText(seg.text) + fs:SetTextColor(tc.r, tc.g, tc.b) + local w = fs:GetStringWidth() + 2 + btn:SetSize(w, FLOW_LINE_H) + btn:SetScript("OnEnter", function() fs:SetTextColor(1, 1, 1) end) + btn:SetScript("OnLeave", function() + local c = GUI.GetThemeColor and GUI.GetThemeColor() or tc + fs:SetTextColor(c.r, c.g, c.b) + end) + local segData = seg.data + btn:SetScript("OnClick", function() + if self._onLinkClick then + local _, pageId = strsplit(":", segData) + self._onLinkClick(pageId or segData) + end + end) + seg._widget = btn + seg._w = w + self._flowWidgets[#self._flowWidgets + 1] = btn + end + end + + DoFlowLayout() + cachedH = nil + banner._secondPassDone = false + RecomputeHeight() + end + + -- Apply opts at creation + if opts.tone then banner:SetTone(opts.tone) end + if opts.iconTexture then banner:SetIconTexture(opts.iconTexture) end + if opts.iconColor then banner:SetIconColor(opts.iconColor[1], opts.iconColor[2], opts.iconColor[3]) end + if opts.html then + banner:SetHTML(opts.text, opts.onLinkClick) + elseif opts.text then + banner:SetText(opts.text, opts.textColor) + end + + return banner +end + function GUI:CreateButton(parent, text, width, height, func) local btn = CreateFrame("Button", nil, parent, "BackdropTemplate") btn:SetSize(width or 120, height or 22) diff --git a/Options/Options.lua b/Options/Options.lua index 014e52df..f524e196 100644 --- a/Options/Options.lua +++ b/Options/Options.lua @@ -1227,28 +1227,11 @@ function DF:SetupGUIPages(GUI, CreateCategory, CreateSubTab, BuildPage) -- ===== INFO BANNER (global settings notice) ===== do - local banner = CreateFrame("Frame", nil, self.child, "BackdropTemplate") - banner:SetSize(560, 40) - if not banner.SetBackdrop then Mixin(banner, BackdropTemplateMixin) end - banner:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Buttons\\WHITE8x8", edgeSize = 1 }) - banner:SetBackdropColor(0.15, 0.18, 0.28, 1) - local tc = GUI.GetThemeColor and GUI.GetThemeColor() or {r = 0.45, g = 0.45, b = 0.95} - banner:SetBackdropBorderColor(tc.r, tc.g, tc.b, 0.5) - - local icon = banner:CreateTexture(nil, "OVERLAY") - icon:SetPoint("LEFT", 10, 0) - icon:SetSize(16, 16) - icon:SetTexture("Interface\\AddOns\\DandersFrames\\Media\\Icons\\info") - - local txt = banner:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") - txt:SetPoint("LEFT", icon, "RIGHT", 8, 0) - txt:SetPoint("RIGHT", banner, "RIGHT", -10, 0) - txt:SetJustifyH("LEFT") - txt:SetWordWrap(true) - txt:SetText(L["Settings on this page apply globally — changes persist across both the Party and Raid sections."]) - txt:SetTextColor(0.85, 0.85, 0.85) - - Add(banner, 44, "both") + local banner = GUI:CreateInfoBanner(self.child, { + tone = "info", + text = L["Settings on this page apply globally — changes persist across both the Party and Raid sections."], + }) + Add(banner, banner.layoutHeight, "both") end -- ===== FRAME MODES GROUP (Column 1, Top) ===== @@ -2971,66 +2954,36 @@ function DF:SetupGUIPages(GUI, CreateCategory, CreateSubTab, BuildPage) local roleOrderWidget = nil -- ===== COMBAT STATUS BANNER (full width) ===== - local combatBanner = CreateFrame("Frame", nil, self.child, "BackdropTemplate") - combatBanner:SetSize(560, 45) - combatBanner:SetBackdrop({ - bgFile = "Interface\\Buttons\\WHITE8x8", - edgeFile = "Interface\\Buttons\\WHITE8x8", - edgeSize = 1, - }) - - local combatBannerIcon = combatBanner:CreateTexture(nil, "OVERLAY") - combatBannerIcon:SetSize(20, 20) - combatBannerIcon:SetPoint("LEFT", 12, 0) - - local combatBannerText = combatBanner:CreateFontString(nil, "OVERLAY", "DFFontNormal") - combatBannerText:SetPoint("LEFT", combatBannerIcon, "RIGHT", 8, 0) - combatBannerText:SetPoint("RIGHT", -12, 0) - combatBannerText:SetJustifyH("LEFT") - combatBannerText:SetWordWrap(true) - + local combatBanner = GUI:CreateInfoBanner(self.child, { fontTemplate = "DFFontNormal" }) + local function UpdateCombatBanner() if not db.sortEnabled then combatBanner:Hide() return end - combatBanner:Show() - + local selfPos = db.sortSelfPosition or "SORTED" local hasAdvancedOptions = db.sortSeparateMeleeRanged or db.sortByClass or db.sortAlphabetical - + if hasAdvancedOptions then - -- All groups limited - combatBanner:SetBackdropColor(0.6, 0.3, 0.1, 0.9) - combatBanner:SetBackdropBorderColor(0.8, 0.4, 0.1, 1) - combatBannerIcon:SetTexture("Interface\\AddOns\\DandersFrames\\Media\\Icons\\warning") - combatBannerIcon:SetVertexColor(1, 0.6, 0.2) - combatBannerText:SetText(L["Combat Limitation: All groups will not update with new players that join mid-combat."]) - combatBannerText:SetTextColor(1, 0.85, 0.7) + combatBanner:SetTone("danger") + combatBanner:SetText(L["Combat Limitation: All groups will not update with new players that join mid-combat."]) elseif selfPos == "FIRST" or selfPos == "LAST" then - -- Player's group limited - combatBanner:SetBackdropColor(0.5, 0.45, 0.1, 0.9) - combatBanner:SetBackdropBorderColor(0.7, 0.6, 0.1, 1) - combatBannerIcon:SetTexture("Interface\\AddOns\\DandersFrames\\Media\\Icons\\info") - combatBannerIcon:SetVertexColor(1, 0.9, 0.3) - combatBannerText:SetText(L["Combat Limitation: Your group will not update with new players that join mid-combat."]) - combatBannerText:SetTextColor(1, 0.95, 0.7) + combatBanner:SetTone("caution") + -- Override default warning icon with info icon for this softer state. + combatBanner:SetIconTexture("Interface\\AddOns\\DandersFrames\\Media\\Icons\\info") + combatBanner:SetText(L["Combat Limitation: Your group will not update with new players that join mid-combat."]) else - -- Fully combat safe - combatBanner:SetBackdropColor(0.1, 0.4, 0.2, 0.9) - combatBanner:SetBackdropBorderColor(0.2, 0.6, 0.3, 1) - combatBannerIcon:SetTexture("Interface\\AddOns\\DandersFrames\\Media\\Icons\\check") - combatBannerIcon:SetVertexColor(0.3, 1, 0.5) - combatBannerText:SetText(L["Fully Combat Safe: Frames will update normally during combat."]) - combatBannerText:SetTextColor(0.7, 1, 0.8) + combatBanner:SetTone("success") + combatBanner:SetText(L["Fully Combat Safe: Frames will update normally during combat."]) end end - + combatBanner.hideOn = HideSortOptions combatBanner.UpdateBanner = UpdateCombatBanner - Add(combatBanner, 50, "both") - + Add(combatBanner, combatBanner.layoutHeight, "both") + -- Initial update UpdateCombatBanner() @@ -4357,100 +4310,37 @@ function DF:SetupGUIPages(GUI, CreateCategory, CreateSubTab, BuildPage) -- Explains that Aura Filters only affect buff/debuff bars, with inline -- links to related pages so users can find the independent systems. do - local infoBanner = CreateFrame("Frame", nil, self.child, "BackdropTemplate") - infoBanner:SetSize(560, 56) - if not infoBanner.SetBackdrop then Mixin(infoBanner, BackdropTemplateMixin) end - infoBanner:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Buttons\\WHITE8x8", edgeSize = 1 }) - infoBanner:SetBackdropColor(0.15, 0.18, 0.28, 1) local tc = GUI.GetThemeColor() - infoBanner:SetBackdropBorderColor(tc.r, tc.g, tc.b, 0.5) - - local infoIcon = infoBanner:CreateTexture(nil, "OVERLAY") - infoIcon:SetPoint("TOPLEFT", 12, -10) - infoIcon:SetSize(18, 18) - infoIcon:SetTexture("Interface\\AddOns\\DandersFrames\\Media\\Icons\\info") - - -- Helper to create an inline clickable link - local function CreateInlineLink(parent, text, pageId) - local btn = CreateFrame("Button", nil, parent) - local fs = btn:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") - fs:SetAllPoints() - fs:SetText(text) - local c = GUI.GetThemeColor() - fs:SetTextColor(c.r, c.g, c.b) - btn:SetScript("OnEnter", function() fs:SetTextColor(1, 1, 1) end) - btn:SetScript("OnLeave", function() - local c2 = GUI.GetThemeColor() - fs:SetTextColor(c2.r, c2.g, c2.b) - end) - btn:SetScript("OnClick", function() + local linkColor = string.format("|cFF%02X%02X%02X", + math.floor((tc.r or 1) * 255), + math.floor((tc.g or 1) * 255), + math.floor((tc.b or 1) * 255)) + local function L_link(text, pageId) + return linkColor .. "|HdfPage:" .. pageId .. "|h" .. text .. "|h|r" + end + local bodyText = L["Aura Filters only affect the"] .. " " + .. L_link(L["Buff Bar"], "auras_buffs") .. " " + .. L["and"] .. " " + .. L_link(L["Debuff Bar"], "auras_debuffs") .. "." + .. "\n" + .. L["Auras displayed in the"] .. " " + .. L_link(L["Dispel Overlay"], "auras_dispel") .. ", " + .. L_link(L["Defensive Icon"], "auras_defensiveicon") .. ", " + .. L_link(L["Aura Designer"], "auras_auradesigner") .. ", " + .. L["and"] .. " " + .. L_link(L["Boss Debuffs"], "auras_bossdebuffs") .. " " + .. L["are independent of Aura Filters."] + + local infoBanner = GUI:CreateInfoBanner(self.child, { + tone = "info", + html = true, + text = bodyText, + onLinkClick = function(pageId) if GUI.SelectTab then GUI.SelectTab(pageId) end - end) - btn:SetSize(fs:GetStringWidth() + 2, 14) - return btn - end - - -- Line 1: "Aura Filters only affect the [Buff Bar] and [Debuff Bar]." - local t1 = infoBanner:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") - t1:SetPoint("TOPLEFT", infoIcon, "TOPRIGHT", 8, 2) - t1:SetText(L["Aura Filters only affect the"]) - t1:SetTextColor(0.85, 0.85, 0.85) - - local linkBuff = CreateInlineLink(infoBanner, L["Buff Bar"], "auras_buffs") - linkBuff:SetPoint("LEFT", t1, "RIGHT", 3, 0) - - local t2 = infoBanner:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") - t2:SetPoint("LEFT", linkBuff, "RIGHT", 3, 0) - t2:SetText(L["and"]) - t2:SetTextColor(0.85, 0.85, 0.85) - - local linkDebuff = CreateInlineLink(infoBanner, L["Debuff Bar"], "auras_debuffs") - linkDebuff:SetPoint("LEFT", t2, "RIGHT", 3, 0) - - local t2b = infoBanner:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") - t2b:SetPoint("LEFT", linkDebuff, "RIGHT", 0, 0) - t2b:SetText(".") - t2b:SetTextColor(0.85, 0.85, 0.85) - - -- Line 2: "Auras displayed in the [Dispel Overlay], [Defensive Icon], and [Aura Designer] are independent." - local t3 = infoBanner:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") - t3:SetPoint("TOPLEFT", t1, "BOTTOMLEFT", 0, -4) - t3:SetText(L["Auras displayed in the"]) - t3:SetTextColor(0.85, 0.85, 0.85) - - local linkDispel = CreateInlineLink(infoBanner, L["Dispel Overlay"], "auras_dispel") - linkDispel:SetPoint("LEFT", t3, "RIGHT", 3, 0) - - local t4 = infoBanner:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") - t4:SetPoint("LEFT", linkDispel, "RIGHT", 0, 0) - t4:SetText(",") - t4:SetTextColor(0.85, 0.85, 0.85) - - local linkDef = CreateInlineLink(infoBanner, L["Defensive Icon"], "auras_defensiveicon") - linkDef:SetPoint("LEFT", t4, "RIGHT", 3, 0) - - local t5 = infoBanner:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") - t5:SetPoint("LEFT", linkDef, "RIGHT", 0, 0) - t5:SetText(",") - t5:SetTextColor(0.85, 0.85, 0.85) - - local linkAD = CreateInlineLink(infoBanner, L["Aura Designer"], "auras_auradesigner") - linkAD:SetPoint("LEFT", t5, "RIGHT", 3, 0) - - local t5b = infoBanner:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") - t5b:SetPoint("LEFT", linkAD, "RIGHT", 0, 0) - t5b:SetText(", " .. L["and"]) - t5b:SetTextColor(0.85, 0.85, 0.85) - - local linkBoss = CreateInlineLink(infoBanner, L["Boss Debuffs"], "auras_bossdebuffs") - linkBoss:SetPoint("LEFT", t5b, "RIGHT", 3, 0) - - local t6 = infoBanner:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") - t6:SetPoint("LEFT", linkBoss, "RIGHT", 3, 0) - t6:SetText(L["are independent of Aura Filters."]) - t6:SetTextColor(0.85, 0.85, 0.85) - - Add(infoBanner, 62, "both") + end, + minHeight = 56, + }) + Add(infoBanner, infoBanner.layoutHeight, "both") AddSpace(4, "both") end @@ -4579,34 +4469,15 @@ function DF:SetupGUIPages(GUI, CreateCategory, CreateSubTab, BuildPage) dfAll.tooltip = L["Show every debuff with no filtering."] -- ===== WARNING BANNER: All Debuffs disabled ===== - local debuffWarningBanner = CreateFrame("Frame", nil, self.child, "BackdropTemplate") - debuffWarningBanner:SetSize(560, 45) - debuffWarningBanner:SetBackdrop({ - bgFile = "Interface\\Buttons\\WHITE8x8", - edgeFile = "Interface\\Buttons\\WHITE8x8", - edgeSize = 1, + local debuffWarningBanner = GUI:CreateInfoBanner(self.child, { + tone = "caution", + fontTemplate = "DFFontNormal", + text = L["Recommended: enable 'All Debuffs' to see all relevant debuffs, especially for healers."], }) - debuffWarningBanner:SetBackdropColor(0.5, 0.45, 0.1, 0.9) - debuffWarningBanner:SetBackdropBorderColor(0.7, 0.6, 0.1, 1) - - local debuffWarningIcon = debuffWarningBanner:CreateTexture(nil, "OVERLAY") - debuffWarningIcon:SetSize(20, 20) - debuffWarningIcon:SetPoint("LEFT", 12, 0) - debuffWarningIcon:SetTexture("Interface\\AddOns\\DandersFrames\\Media\\Icons\\warning") - debuffWarningIcon:SetVertexColor(1, 0.9, 0.3) - - local debuffWarningText = debuffWarningBanner:CreateFontString(nil, "OVERLAY", "DFFontNormal") - debuffWarningText:SetPoint("LEFT", debuffWarningIcon, "RIGHT", 8, 0) - debuffWarningText:SetPoint("RIGHT", -12, 0) - debuffWarningText:SetJustifyH("LEFT") - debuffWarningText:SetWordWrap(true) - debuffWarningText:SetText(L["Recommended: enable 'All Debuffs' to see all relevant debuffs, especially for healers."]) - debuffWarningText:SetTextColor(1, 0.95, 0.7) - debuffWarningBanner.hideOn = function(d) return d.auraSourceMode ~= "DIRECT" or d.directDebuffShowAll end - debuffGroup:AddWidget(debuffWarningBanner, 50) + debuffGroup:AddWidget(debuffWarningBanner, debuffWarningBanner.layoutHeight) local debuffSubInfo = debuffGroup:AddWidget(GUI:CreateLabel(self.child, "|cff888888Enabled filters are combined \226\128\148 debuffs matching any selected filter will be shown.|r", 250), 35) debuffSubInfo.hideOn = HideDebuffSubFilters @@ -5180,46 +5051,24 @@ function DF:SetupGUIPages(GUI, CreateCategory, CreateSubTab, BuildPage) -- ===== INFO BANNER ===== do - local bdBanner = CreateFrame("Frame", nil, self.child, "BackdropTemplate") - bdBanner:SetSize(560, 38) - if not bdBanner.SetBackdrop then Mixin(bdBanner, BackdropTemplateMixin) end - bdBanner:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Buttons\\WHITE8x8", edgeSize = 1 }) - bdBanner:SetBackdropColor(0.15, 0.18, 0.28, 1) local tc = GUI.GetThemeColor() - bdBanner:SetBackdropBorderColor(tc.r, tc.g, tc.b, 0.5) - - local bdIcon = bdBanner:CreateTexture(nil, "OVERLAY") - bdIcon:SetPoint("LEFT", 12, 0) - bdIcon:SetSize(18, 18) - bdIcon:SetTexture("Interface\\AddOns\\DandersFrames\\Media\\Icons\\info") - - local bdText = bdBanner:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") - bdText:SetPoint("LEFT", bdIcon, "RIGHT", 8, 0) - bdText:SetText(L["Boss Debuffs only trigger"]) - bdText:SetTextColor(0.85, 0.85, 0.85) - - local bdLink = CreateFrame("Button", nil, bdBanner) - local bdLinkText = bdLink:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") - bdLinkText:SetAllPoints() - bdLinkText:SetText(L["Dispel Overlays"]) - bdLinkText:SetTextColor(tc.r, tc.g, tc.b) - bdLink:SetSize(bdLinkText:GetStringWidth() + 2, 14) - bdLink:SetPoint("LEFT", bdText, "RIGHT", 3, 0) - bdLink:SetScript("OnEnter", function() bdLinkText:SetTextColor(1, 1, 1) end) - bdLink:SetScript("OnLeave", function() - local c = GUI.GetThemeColor() - bdLinkText:SetTextColor(c.r, c.g, c.b) - end) - bdLink:SetScript("OnClick", function() - if GUI.SelectTab then GUI.SelectTab("auras_dispel") end - end) - - local bdSuffix = bdBanner:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") - bdSuffix:SetPoint("LEFT", bdLink, "RIGHT", 3, 0) - bdSuffix:SetText(L["in Hybrid or Blizzard mode."]) - bdSuffix:SetTextColor(0.85, 0.85, 0.85) - - Add(bdBanner, 44, "both") + local linkColor = string.format("|cFF%02X%02X%02X", + math.floor((tc.r or 1) * 255), + math.floor((tc.g or 1) * 255), + math.floor((tc.b or 1) * 255)) + local bodyText = L["Boss Debuffs only trigger"] .. " " + .. linkColor .. "|HdfPage:auras_dispel|h" .. L["Dispel Overlays"] .. "|h|r " + .. L["in Hybrid or Blizzard mode."] + + local bdBanner = GUI:CreateInfoBanner(self.child, { + tone = "info", + html = true, + text = bodyText, + onLinkClick = function(pageId) + if GUI.SelectTab then GUI.SelectTab(pageId) end + end, + }) + Add(bdBanner, bdBanner.layoutHeight, "both") end AddSpace(10, "both") From c4f3217021d3a1f3667859ade9d494efd894622f Mon Sep 17 00:00:00 2001 From: Krathe Date: Fri, 8 May 2026 23:37:01 +0100 Subject: [PATCH 2/3] Fix changelog entry for banner rework PR --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a47ad28a..708fcb57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ * (Aura Designer) Fix indicators not firing on the first aura application after joining a group or entering a new zone. (PR #53 by Krathe) * (Aura Designer) Fix sound expire alert not re-triggering after a buff is refreshed and decays again. (PR #54 by Krathe) * (Targeted List) Fix self-target colour overlay covering the text, sticking on the wrong bar, and snapping off instead of fading. (PR #55 by Krathe) -* (Aura Filters) Fix info banner links not navigating to the linked page when clicked. (PR by Krathe) +* (Aura Filters) Info banner text now wraps correctly at narrow window widths instead of overflowing the banner. (PR by Krathe) * (Aura Designer) Fix Global Defaults changes not applying to live frames without a /reload. * (Aura Designer) Fix the Reset All Aura Configs button not clearing indicators from live frames until /reload. * (Aura Designer) Fix Color Duration by Time not transitioning live as a buff ticks down on icon, square, and bar indicators. From 93bf6a99624f18b9e78d3719f54b347968c1fea4 Mon Sep 17 00:00:00 2001 From: Krathe Date: Fri, 8 May 2026 23:43:38 +0100 Subject: [PATCH 3/3] CreateInfoBanner: increase bottom padding on multi-line banners --- GUI/GUI.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GUI/GUI.lua b/GUI/GUI.lua index 3f9b5e6a..1f85ec99 100644 --- a/GUI/GUI.lua +++ b/GUI/GUI.lua @@ -1039,8 +1039,8 @@ function GUI:CreateInfoBanner(parent, opts) pending = false if recomputing then return end local h = math.ceil(MeasureContent()) - -- Padding: 10 top (icon offset) + 6 bottom = 16 px chrome. - local newH = math.max(opts.minHeight or 28, h + 16) + -- Chrome: 13 px top (icon at -10, text nudged -3) + 9 px bottom = 22 px. + local newH = math.max(opts.minHeight or 28, h + 22) if cachedH ~= newH then cachedH = newH recomputing = true