Skip to content

Feature/player aura bars - #1199

Open
JuJuFX-dev wants to merge 46 commits into
EllesmereGaming:mainfrom
JuJuFX-dev:feature/player-aura-bars
Open

Feature/player aura bars#1199
JuJuFX-dev wants to merge 46 commits into
EllesmereGaming:mainfrom
JuJuFX-dev:feature/player-aura-bars

Conversation

@JuJuFX-dev

Copy link
Copy Markdown
Contributor

Player Aura Bars

Replaces EllesmereUIUnitFrames_PlayerAuras.lua (the old aura-style module) with Player Aura Bars (PAB): a filter-driven buff/debuff bar system built on EllesmereUI.AuraKit, with a redesigned options UI and an embedded live preview.

Features

  • Filter engine: buffs/debuffs resolve through a class vocabulary (All Buffs, Has Duration, boss/role/priority aura, stealable, dispel-typed, Extra Spells, etc.) shared with EUI_UnitFrames_AuraContainers.lua's TOKEN_CLASSES/CANDIDATE_CLASSES instead of hardcoded spell lists. Multiple filters combine per bar.
  • Three built-in bars: Buffs, Debuffs, and External Defensives (migrated into PAB as a third built-in bar, previously its own thing).
  • Unlimited custom bars: each independently configurable — filters, Sort Method/Direction, Growth Direction (horizontal Left/Right and now vertical Up/Down), Icon Effects Per-Filter, Row Spacing, Icons Per Row.
  • Icon Effects Per-Filter: per-filter icon styling (glow/border) for both buffs and debuffs, applying correctly on the default Debuffs bar as well as custom bars.
  • Live preview: embedded preview in the options pane (EUI_PlayerAuraBars_ManagerPages.lua) simulates Filters, Sort, Extra Spells and Icon Effects against a placeholder/real-spell-icon pool without requiring live auras in-game.
  • Sort Method/Direction: e.g. Important-first, with the preview's simulation matching real in-game resolution order (not just re-shuffling which icons are picked).
  • Unlock Mode support for custom bars: drag-to-position, Grow Left/Right/Up/Down, wired through EUI_UnlockMode.lua's generic mover (see below).
  • EditMode/legacy seeding: on first PAB use, default Buffs/Debuffs bars are seeded from the user's existing Blizzard EditMode aura frame settings and the old aura-style module's saved config, so upgrading users keep their prior setup instead of resetting to PAB defaults.
  • Bar tile subtitles live-refresh based on selection mode (Filters vs Extra Spells) instead of a static resolved-count snapshot.

Bug fixes bundled in this branch

  • Stale duration = 0 on pre-existing buffs after login/reload (2s post-login aura-container poke, with an earlier poke variant reverted as ineffective).
  • Cooldown swipe overlay reappearing on aura updates / after zone changes (swipe alpha forced to 0 as a fallback); right-click-to-cancel added for buffs.
  • Custom/default Debuff bars showing every debuff regardless of configured filters.
  • Icon Effects Per-Filter not applying on the default Debuffs bar.
  • Default-value mismatches between the options UI and actual rendering (default + custom bars).
  • Grow Left/Right no-op for custom bars in Unlock Mode.
  • Unbounded custom debuff bar table growth on delete; cog-tooltip fallback not resolving through L().
  • Reload lag and a forbidden-object crash in debuff Icon Effects glow layering (see AuraKit change below).

Shared/global file changes — and why

This branch touches four files other modules depend on. None of them change existing behavior for non-PAB consumers except where noted:

  • EllesmereUI_AuraKit.lua

    • Fixed ApplyStyleToRegions's stack-count text color to read c.r/c.g/c.b/c.a instead of c[1..4]stackColor is stored as an {r,g,b,a} table everywhere it's built, so the old indexed access returned nil and crashed. This is a real AuraKit bug affecting any consumer with a custom stackColor, not PAB-specific; PAB's Icon Effects work just surfaced it.
    • Shifted the button's internal frame-level ladder (dispel ring +3 → +4, stack/duration text carrier +4 → +5) to open a level for the DM per-filter glow (ApplyDmFx/PAB_ApplyDmFx), which now sits between the per-filter border override (+2) and the dispel ring. Without this, PAB's per-filter glow could render on top of the dispel ring/text, or (the forbidden-object crash) collide with a level Blizzard's engine already owns. EUI_RaidFrames_AuraContainers.lua's ApplyDmFx frame levels are updated in lockstep (+2 → +3, carrier +4 → +5) to match this shared ladder.
  • EllesmereUIUnitFrames/EUI_UnitFrames_AuraContainers.lua

    • Added a dispeltyped candidate class (TYPED_DEBUFF_TYPES) and threaded candValue through BuildChain/DeclareElementGroup/ApplyGroupConfig so a candidate class can carry a value set, not just a boolean — needed for PAB's own filter resolution to reuse the exact same class definitions.
    • Exposed TOKEN_CLASSES/CANDIDATE_CLASSES as ns.UF_TokenClasses/ns.UF_CandidateClasses so PAB shares one source of truth for the class vocabulary instead of maintaining a second copy that could silently drift.
    • Removed the debuffAnchorBuffs ("Anchor Buffs with Debuffs") merge path in AnchorContainer/ApplyGroupConfig. This was the old aura-style module's mechanism for stacking buffs onto the debuff container; PAB owns bar composition itself now (separate bars, separate anchoring), so the merge became dead weight and a second, conflicting anchoring path for buffs. Confirmed intentional, not incidental scope creep.
    • Added SetTextColor change-guarding via a CK() color fingerprint (d.ufDurColor/d.ufStackColor) and cached style.fontPath once per style rebuild instead of resolving it on every ApplyUFText call — this runs on every registered aura button on display updates, so redundant SetTextColor/GetFontPath calls are real, avoidable per-frame cost.
  • EUI_UnlockMode.lua

    • CreateMover now recognizes the PAB_ bar-key prefix: canGrow includes it, and the Grow-direction popup reads/writes direction via EllesmereUIUnitFrames:GetGrowDirectionForBar/SetGrowDirectionForBar instead of the ActionBars/CDM-specific paths those functions already used for other bar families.
    • The popup omits "Grow Centered" for PAB_ bars: AuraKit's SetFlowLayoutGrowthDirection (AnchorUtil.FlowDirection) is a strict Left/Right/Up/Down axis with no centered concept, unlike the CDM/ActionBars bar renderer this popup was originally built for. Offering it would show a selectable option PAB's layout can't actually honor.

Files touched

  • EllesmereUIUnitFrames/EllesmereUIUnitFrames_PlayerAuraBars.lua (new, ~4.3k lines) — core PAB module
  • EllesmereUIUnitFrames/EUI_PlayerAuraBars_ManagerPages.lua (new, ~2.5k lines) — options UI / manager pages
  • EllesmereUIUnitFrames/EllesmereUIUnitFrames_PlayerAuras.lua — removed (superseded by PAB)
  • EllesmereUIUnitFrames/EUI_UnitFrames_Options.lua, EllesmereUIUnitFrames.lua, EllesmereUIUnitFrames.toc — wiring/registration
  • EUI_UnlockMode.lua, EllesmereUIUnitFrames/EUI_UnitFrames_AuraContainers.lua, EllesmereUIRaidFrames/EUI_RaidFrames_AuraContainers.lua, EllesmereUI_AuraKit.lua — shared-file changes, see above

Testing

  • Manually tested in-game: default Buffs/Debuffs/External Defensives bars, custom bar creation/deletion, filter combinations, sort methods, icon effects per-filter, Unlock Mode dragging/growth direction (horizontal and vertical), and EditMode/legacy seeding on both a fresh profile and an existing one.
  • All Lua files checked with luac5.1 -p.

Replaces the retired BuffFrame/DebuffFrame reskin with an AuraKit-based
Player Aura Bars module. Default Buffs bar and every custom buff bar now
share a BM2-modeled filters/extra-spells system with a dedicated Filter
Editor; debuff bars keep the existing class-token model. Custom bar
creation goes through a Name popup instead of an instant unnamed bar, with
sidebar rename support afterward.

Fixes discovered during testing: buff spell selection was built on
AK.AddAuraSlot, whose buttons never receive a flow-layout anchor and never
render -- switched to AK.AddAuraGroup with a candidateFilters.includeSpellIDs
restriction (the same mechanism the working catch-all/debuff groups already
use), and wired Show All Buffs into custom buff bars to match Show All
Debuffs' existing parity. Deduped same-named preset/alt spell entries out of
the Extra Spells, Search Spells, and Filter Editor spell-checkbox lists.
Registers a mover for every custom buff/debuff bar (dynamic list, keyed
by the bar's permanent id) alongside the existing default Buffs/Debuffs
movers, and cleans up unlock registration/anchor links when a custom bar
is deleted. Also trims unlock mover labels: default bars now read just
"Buffs"/"Debuffs", custom bars read "PAB: <name>".
The unlock overlay's Grow dropdown dispatches to PAB_Get/SetGrowDirection
for any barKey prefixed "PAB_", but the bridge only recognized the two
literal default-bar keys. Custom-bar keys (PAB_CustomBuff_<id> /
PAB_CustomDebuff_<id>) fell through silently: the dropdown always showed
LEFT and selecting a direction did nothing. Both functions now resolve
the custom bar by id, read/write its growDirection, and reload it.
Custom buff bar tiles in the sidebar showed the flat resolved spell
count ("0 Spells", "1 Spells", ...), which didn't reflect whether the
bar was actually in Show All Buffs mode, filter mode, or just had a few
direct spells. BuildBuffBarSubtitle now shows "Show All Buffs" (+ "N
spells" if Extra Spells also has entries), or up to 3 selected filter
names (3rd truncated to 3 chars + "..." once 3+ filters are assigned),
falling back to the old resolved-count phrasing when no filters are set.
The Filters and Extra Spells checkbox dropdowns only called apply()
(engine reload), never RefreshPage -- a full RefreshPage(true) would
have torn down the page and closed the open checkbox dropdown mid
multi-select, so it was deliberately omitted. That left the sidebar
tile subtitle frozen until some other action forced a full rebuild.

BuildTile now accepts subtitleFn (in addition to a static subtitle) and
registers it with RegisterWidgetRefresh so it re-reads on every
lightweight RefreshPage() pass. Both checkbox callbacks now call
RefreshPage() (non-force) after apply(), which runs just the registered
refresh callbacks in place -- same fast path already used elsewhere in
this codebase -- without closing the dropdown.
Same gap as the buff-side fix: the Base Filters checkbox dropdown only
called apply(), never RefreshPage, so the sidebar tile subtitle froze
until an unrelated full rebuild. The subtitle itself also never checked
showAllDebuffs, so it always showed "N classes" even in Show All mode.

BuildDebuffBarSubtitle now returns "Show All Debuffs" when that toggle
is on, else the existing "N classes" count. The tile switches to
subtitleFn (live-refreshing via RegisterWidgetRefresh, same as buffs),
and the Base Filters checkbox callback calls RefreshPage() (non-force)
after apply() so it re-reads without closing the open dropdown.
Style/grid fallback defaults (used whenever a bar doesn't set the field
itself):
  iconZoom 0.07 -> 0.055, padding 4 -> 5, stackPosition BOTTOMRIGHT -> TOP,
  stackTextSize 12 -> 11, stackOffsetX/Y 2/-2 -> 0/0, durationOffsetY
  -2 -> 0. iconsPerRow/maxRows/maxTotal are now isBuff-conditional instead
  of one shared fallback: 11x3=32 for buffs, 8x2=16 for debuffs (only
  reached by the two default bars -- every custom bar sets these three
  explicitly at creation now, see below).

showAllDebuffs now defaults to true (nil == on), mirroring showAllBuffs'
existing "nil == on" convention -- previously defaulted to off. Updated
in ClassEnabled, the Show All Debuffs toggle, its blocking-overlay state,
and the debuff tile subtitle.

New custom bars (buff + debuff) now start with growDirection LEFT
(was RIGHT) and an explicit compact starting grid (iconsPerRow=8,
maxRows=1, maxTotal=8) instead of inheriting the default bars' larger
grid via fallback.

New custom bars' default spawn position is now a single fixed spot
(screen center, y=80) instead of staggered by barId -- since it's only
ever read as a fallback for a bar with no saved position, an untouched
bar (1st, 2nd, 3rd, ...) always starts there regardless of how many
other bars exist or have been moved, while a moved bar keeps its own
saved position.
hideSwipe = true unconditionally for both buffs and debuffs -- removes
the darkening radial overlay CooldownFrameTemplate draws over the icon
as remaining time shrinks (duration NUMBER text is a separate style
field, unaffected). Live via ApplyStyleToRegions/RestyleSoon, no reload
needed.

cancelButtons = "RightButtonUp" for buffs only (nil for debuffs), same
condition EUI_UnitFrames_AuraContainers.lua already uses for the player
unit ("unit == player and isBuff"), minus the always-true unit check
since PAB is always the player. Routes straight through AK's own
AuraButtonMixin:SetCancelAuraButtons -- same secure right-click-cancel
Blizzard's own BuffFrame/Edit Mode uses, not a hand-rolled macro/
attribute setup. Only applies to buttons created after this change
(AK wires it in MakeInitializer, not the live-restyle path) -- a
/reload picks it up for any buttons already on screen.
ReloadCustomBuffBar/DebuffBarImpl wrote AK.styles[styleKey] = BuildStyle(...)
but never called AK.RestyleSoon(styleKey), unlike RestyleBars() for the
two default bars. AK's MakeInitializer only runs once per created
button, so a style-only change (icon zoom, swipe, stack/duration
position, ...) on a custom bar whose buttons already exist (the "spell
list unchanged" cheap path) silently kept rendering the OLD style
until the container was released and rebuilt for some unrelated reason
-- e.g. the swipe-hide fix appearing to not take effect on an
already-open custom bar. RestyleSoon re-runs ApplyStyleToRegions
against every already-live button under that style key.
… bar

Retires the standalone EllesmereUIUnitFrames_ExternalDefensives.lua
module (and its own Options tab) in favor of a new defaultExternalDefensives
bar inside PAB, using AK's engine-driven EXTERNAL_DEFENSIVE classification
via a single static group (filter={"HELPFUL","EXTERNAL_DEFENSIVE"}) --
no spell/class selection needed since the content is a fixed engine
classification, not user-configurable. Only toggle is enable/disable;
detail pane has no "Assigned" section, just Core/Display.

One-time migration reads any existing db.profile.externalDefensives
(enabled, iconSize, growDirection, showText->durationShow,
textSize->stackTextSize, border color/size, position) on first access.
Border texture/offset/shift/behind and duration-format variants are not
carried over -- PAB doesn't expose those fields at all yet (tracked as a
follow-up in memory, not just for this bar).

Own Unlock Mode element (including Grow Direction dropdown support) and
sidebar tile in the Options UI, alongside the existing Buffs/Debuffs
defaults. Old module file deleted, its .toc load line and Options tab
(PAGE_AURAS) removed entirely, and the old externalDefensives SavedVariables
defaults block dropped (migration reads existing profile data directly,
independent of the defaults table).

Also: ComputeGrid's width/height now use "(N-1) gaps between N icons"
instead of "N cells", removing one extra trailing pad's worth of edge
margin the previous formula baked into every PAB bar's rendered box size
(surfaced by comparing the migrated External Defensives bar's box against
the old standalone module's tighter sizing formula).
hideSwipe only re-applies through AK's ApplyStyleToRegions, which fires
at button creation and on explicit Restyle passes -- not on ordinary
aura content churn. Blizzard's own engine calls the equivalent of
Cooldown:SetCooldown() on d.cooldown internally on every aura update,
and that native API implicitly re-Shows the frame, silently undoing our
SetShown(false) after the first update. Same "Blizzard keeps re-showing
this" pattern already used here for BuffFrame/DebuffFrame: hook
d.cooldown's Show and re-Hide it immediately, installed once per button.
Covers every PAB bar (default + custom) since PAB_ApplyExtraText runs
for all of them.

Also lands the ComputeGrid box-sizing tweak from the External Defensives
migration: width/height now use "(N-1) gaps between N icons" instead of
"N cells", removing one extra trailing pad's worth of edge margin from
every PAB bar's rendered box size. Confirmed in-game.
BM2 preset filters (Defensives, Offensive CDs, ...) were only imported
when the options page opened, so a buff bar referencing a preset that
hadn't been imported yet this session resolved and cached an
incomplete spell set at login, never re-resolving until something
else forced a signature change. Import now also runs at CreateBars().
BuildChain() unconditionally appended a catch-all group ("everything
not claimed by an enabled class") after the per-class groups, so a
Debuff bar with Show All Debuffs off and specific classes selected
still rendered every other debuff through that catch-all. BuildChain
now takes an includeCatchAll flag, and all three debuff call sites
(default Debuffs bar, ApplyLiveConfig, custom Debuff Bars) gate it on
showAllDebuffs so an explicit class selection is actually exclusive.
…ebuff bar table growth on delete

Tooltip fallback text was being passed into L() as if it were a locale
key instead of already-resolved text. Custom debuff bar deletion left
stale customDebuffContainers/customDebuffDeclared entries behind since
bar IDs are never reused.
…me containers

GetFontPath was being re-resolved on every ApplyUFText call (the engine's
own display-update cadence); it's now cached once per style rebuild since
the result only changes when font settings change, which already forces
a fresh style table. Duration/stack text color now uses the same
change-guard fingerprint pattern already used for font and anchor, instead
of calling SetTextColor unconditionally on every update.
… duration=0 on pre-existing buffs

Pre-existing buffs (e.g. Battle Shout applied before this client logged
in) can occasionally bind to a container with a stale/zero
expirationTime if SetUnit lands inside AK's accelerated login build
window before the server's own aura-timing sync settles, showing a
"0" duration until the aura naturally refreshes. A single delayed
UpdateAllAuras() poke per already-built unit re-pulls a fresh snapshot.

NOTE: root cause is a suspected client-side timing race, not something
reliably reproducible on demand -- this is a best-effort mitigation
based on available evidence, not a confirmed fix. The underlying bug
may still occur; needs real-world testing/confirmation.
Each bar's detail page now shows a fixed, non-scrolling preview box with
fake buffs/debuffs styled from the bar's real BuildStyle/ComputeGrid
output, so icon size, growth direction, spacing, border, dispel-type
colors, and duration/stack formatting can be tuned live without needing to
actually be buffed/debuffed. Buff previews draw from a per-class spell
pool; debuffs cycle through a fixed set including one entry per dispel
type. The settings area below the preview now scrolls independently via a
real ScrollFrame (WrapCompensatedBody previously had no scroll mechanism
at all, just silent clipping).
…arance

The existing Show-hook fix only catches the swipe reappearing via
Cooldown:Show(); after an instance change it was reappearing through some
other Blizzard-internal path that hook doesn't cover. SetAlpha(0) is
independent of Show/Hide state, so the swipe stays invisible no matter
which path re-arms visibility. Confirmed fixed in-game after zone change.
Reverts 8cdfb93d. Tested in-game -- the 2s post-login UpdateAllAuras()
poke did not resolve the reported stale duration=0 issue on
pre-existing buffs, so the underlying assumption (stale expirationTime
snapshot during the accelerated login build window) is not confirmed
as the actual root cause. Root cause is still open.
…cing option

Duration and Stacks now use EllesmereUI.RESIZE_ICON for their inline popup
button, matching the icon RaidFrames' Buff Manager 2 uses for the same
Duration Text/Stacks Text popups (Icon Size's own cog stays COGS_ICON).

Also adds an optional per-bar Row Spacing field (Icons Per Row's popup),
overriding only the row-to-row gap (lineSpacing/groupLineSpacing) while
icon-to-icon spacing within a row stays tied to Spacing/padding as before.
Defaults to the current padding value until explicitly changed. Threaded
through ComputeGrid (new grid.rowGap), ApplyGroupConfig, every default/
custom/external-defensives bar apply path, and the live options preview.
…x wider than intended

Preview box (icon grid, header band, divider) previously only recomputed
its footprint on a structural tab/bar rebuild, not on live slider/toggle
changes -- growing the icon grid left icons overflow the box, shrinking it
left a stale gap above the settings fields. PAB_MaybeRefreshPreview now
re-derives the same grid every live-apply and resizes box/header/divider,
with a new resize hook so the settings scroll area repositions too.

Separately, WrapCompensatedBody's padDiff compensation shifted the
oversized scroll CHILD instead of the scroll frame itself -- mathematically
equivalent to RaidFrames' own settingsScroll (both landed DualRow content
at the same relative offset per a full frame-tree dump), but measured
in-game as a real ~30px wider left inset than RaidFrames' Debuff Manager
(50px vs 20px, confirmed by hand more than once). Restructured to shift
`scroll` itself instead, matching RaidFrames' approach line-for-line;
AttachEditorScroll gained an optional rightInset param so the scrollbar
still lands near the true visible edge instead of the now-wider scroll
frame's own edge. PAB_BuildPreviewBox's "PREVIEW" header band had the same
shift-the-child pattern and got the same fix.
Threads native AuraContainerSortMethod/SortDirection through every PAB
bar (Default Buffs/Debuffs, External Defensives, custom bars) via a new
per-bar cfg.sortMethod/sortDirection, applied both at group declaration
and live via SetAuraGroupSortMethod. UI exposes a curated 4-value dropdown
(Default/Expiration/Name/Important), with Important hidden on buff bars
where it has no effect. The embedded options preview simulates the same
sorting for its fake icons so slider/dropdown edits show live.
Session handoff/briefing doc, same category as the existing MIDNIGHT_*
working documents -- dev-only, never meant to ship with the addon.
Verified against Blizzard's actual PTR source (Gethe/wow-ui-source,
AuraUtil.ImportantOnlyAuraCompare): ImportantOnly sorts by the native
C_Spell.IsSpellImportant per-spell flag, not by dispel type. It's not
debuff-specific, so it's no longer hidden from buff bars' Sort Method
dropdown -- the previous hiding was based on a wrong assumption.
Replaces the standalone "Show All Buffs" toggle + blocking-overlay with
a single unified Filters checkbox dropdown: "All Buffs" (same cfg field,
never locked) and the new "Has Duration" pseudo-filter sit above a divider,
followed by the real editable PAB_Filters entries -- all locked while
All Buffs is on, using BuildVisOptsCBDropdown's existing per-row lockedFn/
lockedTooltip mechanism (no shared-widget changes needed).

Has Duration uses the native candidateFilters.maxDuration (verified against
Blizzard's PTR source), which implicitly excludes permanent (duration=0)
buffs. Applied to every active buff group via a new extraCand parameter on
ApplyGroupConfig, re-applied live through SetAuraGroupCandidateFilters.
Buffs-only; no native stacks-based equivalent exists.

Also sorts every editable-filters list (Filters dropdown, Filter Editor
sidebar) alphabetically via a shared SortFiltersByName helper, always on a
fresh copy since ns.PAB_Filters() returns the live persisted list.
Has Duration was wrongly locked alongside real filters while All Buffs
was on, making it permanently unselectable -- it narrows the All Buffs
catch-all group too (BuffCandidateExtras applies to every active buff
group), so unlike real filters it's never redundant and must stay usable.

Extra Spells now shares ffRow with Filters instead of its own row below.

Turning All Buffs on also clears cfg.filters (real filter selections
would be locked/redundant anyway) -- Extra Spells and Has Duration are
left untouched since both stay meaningful alongside All Buffs.
… Spells

The embedded options preview always rendered fake example icons regardless
of actual config, misleadingly implying content even when the real bar
would show nothing (e.g. All Buffs off with no filters selected).

New HasFillerSource(isBuff, cfg) mirrors the exact condition the engine
uses to decide whether a catch-all/class-token chain would render anything
(buffs: showAllBuffs or any filter; debuffs: showAllDebuffs or any class --
BuildChain's includeCatchAll fully respects both, correcting a stale
assumption that debuffs could never show nothing).

BuildPreviewSlots assigns each icon slot a kind: buffs' Extra Spells always
occupy the leading slots with their real icon (not the fake pool), the
remaining slots are fake example icons when HasFillerSource is true or a
placeholder (grey box + red X, reusing eui-close.png) otherwise. Border
now draws for every slot kind, including placeholders.
Fake filler icons now only appear when All Buffs is on -- it has no
finite spell list, so padding with examples is a fair approximation.
Real Filters/Extra Spells resolve to a concrete, finite set
(ns.PAB_ResolveSpells), so they now render as their own real icons in the
leading slots with placeholders for any remaining capacity, instead of
fake icons pretending more content exists (e.g. 4 resolved spells with
room for 8 icons and All Buffs off now shows 4 real icons + 4 placeholders).

Also dedupes the resolved list by icon texture, not just spell ID: a
filter's alt/rank spell IDs for the same buff were rendering as duplicate
icons since ns.PAB_ResolveSpells only dedupes by spell ID.
PREVIEW_DEBUFF_SPELLS grows from 6 to 66 (Magic/Curse/Disease/Poison/Bleed
x10 + 9 non-dispellable) -- list supplied directly by Joel, since unlike
CLASS_PREVIEW_BUFFS there was no existing verified debuff catalog anywhere
in this repo to draw from.

New BuildDebuffPreviewPool() shuffles it once per preview box build, same
treatment BuildBuffPreviewPool already gets. The pool threading
(RenderPreviewIcons, ns.PAB_BuildPreviewBox, PAB_MaybeRefreshPreview,
activePreview) is renamed from buffPool to the generic pool, since both
polarities now get a shuffled pool instead of debuffs using the static
list directly.
Documents the settled behavior: the combined 134-entry class-buff+extras
pool is shuffled with no per-class priority, since it's already far larger
than any configured grid (maxTotal defaults to 32) -- "notfalls" fallback
padding essentially never triggers. A brief own-class-first variant was
tried and reverted within the same session before landing here.
Was W:SectionHeader, a shared widget fixed at 40px tall with its label
anchored only 8px from the block's bottom -- built for spacing consistency
among stacked option rows, not this floating title/desc/box context, and
left ~20px of pure blank padding above the "PREVIEW" text.

Replaced with a lightweight, purpose-built label + separator at 18px,
matching the same look via EllesmereUI.TEXT_SECTION/BORDER_COLOR (already
exposed on the shared table) without touching the shared widget file.
Row Spacing is icon-to-icon gap between rows, same family as Spacing
(padding), not a grid-size concern like Icons Per Row/Max Rows/Max Total.
ns.PAB_ResolveSpells unions every selected Filter's + Extra Spells' ids
into one set sorted purely by raw spell id. Truncating that to fewer
available icon slots than resolved spells meant whichever Filter happened
to contain the lowest-numbered spells won every visible slot, and every
other selected Filter (plus Extra Spells) never appeared at all.

New BuildMixedRealSpells(cfg) interleaves round-robin across sources (each
selected Filter is its own source, Extra Spells is one more) instead of a
flat numeric sort, so truncation always samples a bit of everything.
Deterministic on purpose -- no reshuffle on every live-apply refresh.

Also applies SortPreviewList to these real extra icons, not just the fake
filler pool -- with All Buffs off, content is mostly/only these real
slots, so Sort Method/Direction had no visible effect there before.
…t order

Both the real "extra" icons (Filters/Extra Spells) and the fake "All
Buffs" filler had the same bug: sorting the full candidate list BEFORE
truncating to the available icon-slot count meant a sort change could
pull a DIFFERENT subset into the surviving slots, not just reorder the
ones already visible.

Fixed by selecting first, sorting second, in both places: the real extra
icons truncate BuildMixedRealSpells' deduped/mixed list to `count` before
sorting; the fake filler now selects its fixed slice from the stable
per-box-build pool (no longer pre-sorted in RenderPreviewIcons) before
sorting that slice. Sort Method/Direction now only reorders whatever's
already showing.
Ports Raid Frames' DebuffManager fx system (per-category Icon Glow/
Border/Size overrides) to PAB's debuff bars (Default Debuffs + every
Custom Debuff Bar). PAB's debuff classes are a mutual-exclusion chain
(unlike Raid Frames' overlapping records), so matching a button's
category to an fx block is a single dictionary lookup via a d.dmCat
stamp set in ApplyGroupConfig's extraInit.

Size overrides needed more than expected: PAB's icon size is entirely
style-driven (button:SetSize from style.width/height), not group-layout-
driven, so a per-category size needed its own sized-style-variant system
(EnsurePabSizedStyle) mirroring Raid Frames' EnsureBaseSizeStyle, plus a
`|sz`-suffixed group-key variant so a size toggle swaps groups cleanly.

Curated the debuff filter vocabulary (Base Filters + Icon Effects
Filters) to match Raid Frames exactly: Important, Crowd Control, Boss
Debuffs, Role Debuffs, Raid, Raid In Combat, Dispellable By You, Dispels.
Raid/Raid In Combat are now real, functional categories (previously
excluded as "roster-context, doesn't apply to a standalone display" --
reversed since they're per-aura flags, not roster-size-dependent). Added
the missing "Dispels" (any dispel type) filter as a new value-bearing
candidate class (includeDispelTypes) in the shared
EUI_UnitFrames_AuraContainers.lua class tables, threading a new
candValue field through both that file's and PAB's own independent
BuildChain/ApplyGroupConfig implementations.

Preview panel changes:
- Every active fx block claims 1-2 fake icons directly, independent of
  Base Filters/Show All Debuffs state -- requiring the same category in
  both dropdowns made the preview show nothing in the normal case.
- Real per-icon flow packing (slotSize/colOffset/rowYOffset) instead of
  a uniform worst-case cell, so an oversized icon doesn't overlap its
  neighbor without inflating the gap around every other icon.
- ComputeGrid's box/parent-frame footprint (MaxIconSizeFor) accounts for
  the largest configured icon size -- a real bug on the live bar too,
  not just the preview, since the parent frame never grew to fit an
  oversized category before this.
style.stackColor is always a keyed {r,g,b} table across every caller in
the addon, but ApplyStyleToRegions indexed it as an array, so
SetTextColor received nils and errored. Latent since AK.lua's
introduction; only surfaced now because Player Aura Bars is the first
module to skip noDefaultFonts and hit this default path.
Options UI dropdown/slider fallbacks (stackPosition, padding, rowSpacing,
iconsPerRow, maxRows, maxTotal, iconZoom, stackTextSize) disagreed with
the actual render-time defaults, making fresh profiles show wrong values
in the UI even though rendering was correct. Also changes Row Spacing's
default to a fixed 12px, decoupled from Padding/Spacing.
Extends PAB's Growth Direction beyond Left/Right with Up/Down, wiring
AK's existing column-flow axis (AK.SetContainerAxis) through every
live-apply/create/reload path, ComputeGrid's width/height swap, and a
new column-major packing branch in the options preview's hand-rolled
icon layout. Adds a cog-only Icon Wrap (Left/Right) setting for
multi-column vertical bars, and updates the unlock-mode drag popup so
its Grow menu offers Up/Down for PAB bars too.

Also caps the options preview area's height (proportional shrink,
matches the existing panel-zoom scaling) so Up/Down bars with many
icons per column don't blow up the options page height.
d.dmCat (a button's category stamp for Icon Effects matching) was always
"all" on the default Debuffs bar, since its BuildChain always routes
everything through the catch-all group unless Show All Debuffs is off --
and Icon Effects deliberately never match the catch-all key. Per-aura
category can't be read back from an AK-engine button after the fact
(confirmed via the dispel-ring mechanism: that data is engine-secret in
12.1), so the fix instead forces a real per-category group into existence
whenever an active Icon Effects block targets that category, same as if
the user had manually enabled the matching Base Filter.

Also fixes a duplicate-icon side effect this exposed: candidate classes
(e.g. "Dispels") had no token to negate forward into the catch-all, so
forcing one active while the catch-all was still included rendered the
same debuff twice. BuildChain now propagates excludeDispelTypes forward
once the dispel-typed candidate class is active. The three boolean
candidate classes (Boss/Role/Priority Debuffs) have no confirmed Blizzard
exclude counterpart, so those are deliberately left requiring Show All
Debuffs off + the matching Base Filter, same as before this fix.
…le module on first PAB use

On a profile's very first PAB run, position/icon size/icons-per-row/grow
direction now seed from the live BuffFrame/DebuffFrame EditMode state
instead of PAB's hardcoded defaults, so a first-time user doesn't lose an
already-customized Blizzard layout. If the old retired "Styled Player
Auras" module was previously enabled, its border/duration-show/text-size
settings are layered on top too. Guarded by a one-time flag so existing
profiles are never touched.
# Conflicts:
#	EllesmereUIUnitFrames/EUI_UnitFrames_Options.lua
…object crash

Icon Effects Per-Filter's glow could render behind the per-filter border
override: both landed on the same AuraKit frame level (borderHost+2).
AuraKit's border/dispel-ring/text ladder is shifted one level higher
(dispel ring +3->+4, text +4->+5) to give the glow its own level
(borderHost+3), and PAB/RaidFrames' ApplyDmFx now use that level -- final
order is border < fx border < glow < dispel ring < text, with no ties.

PAB's glow/border overlay frames are now pre-created unconditionally at
button creation (mirroring AuraKit's own dispelHolder), instead of lazily
on first use. Turning on a previously-inactive filter's glow/border no
longer needs a /reload: the frame already exists, so it's just Show/Hide
+ StartGlow/StopGlow, which is legal outside the button's one-time
creation window (CreateFrame-parenting a new frame to the secure engine
button is not).

Attempted to stop unconditionally remapping driver-ticked glow styles
(Pixel Glow, Action Button Glow, Auto-Cast Shine, Shape Glow) to their
FlipBook-safe equivalents on live debuff icons, so the selected style
would render as picked instead of always downgrading. Reverted after a
field crash: parenting one of these to a real AuraButton and letting it
register with the central glow driver hard-errors ("Attempt to access
forbidden object from code tainted by an AddOn") the moment the driver
calls wrapper:IsVisible() on it. Confirmed in Blizzard's own PTR 12.1
source that AuraButton is unconditionally forbidden (useForbiddenObjectTable
+ ForbiddenAspects baked into the base template), so PAB_ApplyDmFx keeps
the unconditional RestrictionSafeStyle remap. Those four styles are now
hidden from the Icon Effects Per-Filter glow dropdown entirely, since
picking one never actually renders as selected on a live icon.

Also: renaming a custom Buff/Debuff bar now re-registers its Unlock Mode
label immediately (Apply() was missing from the rename flow, unlike
toggle/delete), instead of showing the old name until the next /reload.
…code

File-untracking from 9b25c16 stays in effect; only the .gitignore
addition is reverted so it doesn't show as an unrelated change in the
upcoming PR against upstream.
EUI_QoL_Options.lua registers its page under the key "QoL"
(PAGE_QOL), not "Quality of Life" -- these four
_ELEMENT_SETTINGS_MAP entries silently broke Unlock Mode's jump-to
for FPS/Secondary Stats/BattleRes/Bloodlust since 04bf744. Unrelated
to Player Aura Bars, reverting to keep the PR scoped.
…ns UI

Also drops several stale entries the committed file carried that
.tools/extract-locale-keys.sh cannot statically produce (tip/offTip
table fields in EUI__General_Options.lua, not literal L() calls) --
pre-existing drift on upstream/main itself, confirmed by running the
script against a clean upstream/main checkout (658 keys there vs the
666 the committed file claims). Unrelated to PAB; this commit only
brings our branch's file back in sync with what the script actually
produces.
Only reproducible with a bar's declared row capacity >=2 (usedRows) at a
UIParent scale other than PP.PixelBestSize() -- e.g. 0.7111 at 1440p
instead of the native 0.53333. Root cause was raw, never-PP.Scale'd
numbers reaching two different places:

BuildStyle/MaxIconSizeFor fed cfg.iconSize straight into both the
button's SetSize and ComputeGrid's cross-axis extent math. ApplyGroupConfig
fed cfg.padding/cfg.rowSpacing straight into the native AuraContainer
engine's SetAuraGroupLayout (elementSpacing/lineSpacing). An exhaustive
1-20 sweep of Row Spacing (everything else held fixed) showed only 2 of
20 values reproduced the border defect, with no clean divisibility
pattern -- consistent with the native engine's own internal pixel
rounding landing on the wrong side of a boundary for specific raw inputs
it was never guaranteed to receive on a pixel boundary in the first
place. All three now run through PP.Scale before use.

Separately, PAB's four bar-position SetPoint call sites (ApplyBarPosition,
ApplyExtDefPosition, ApplyCustomBarPosition, and the CENTER-anchor
recompensation blocks in ApplyLiveConfig/ApplyExtDefLiveConfig) applied
saved x/y coordinates raw, unlike EllesmereUIUnitFrames.lua's own
ApplyFramePosition which snaps via PP.SnapCenterForDim/PP.SnapForES
first. Added a matching SnapBarPos helper and wired it into all four
sites. This alone did not resolve the reported symptom, but is a real,
independently-justified fix for the same class of bug and is kept.

Ruled out during investigation (confirmed via a temporary secret-safe
debug dump, since removed): d.borderHost:GetEffectiveScale() reads are
fully reliable on aura-button-parented frames, including during live
bug reproduction -- not the cause, contrary to earlier suspicion.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant