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..708fcb57 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) 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. diff --git a/GUI/GUI.lua b/GUI/GUI.lua index c93eb57e..1f85ec99 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()) + -- 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 + 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")