Skip to content

Fix(unitframes): pab pixel drift - #2068

Open
JuJuFX-dev wants to merge 4 commits into
EllesmereGaming:mainfrom
JuJuFX-dev:fix/pab-pixel-drift
Open

Fix(unitframes): pab pixel drift#2068
JuJuFX-dev wants to merge 4 commits into
EllesmereGaming:mainfrom
JuJuFX-dev:fix/pab-pixel-drift

Conversation

@JuJuFX-dev

Copy link
Copy Markdown
Contributor

What does this PR do?

Player Aura Bars laid out its icons in a way that depended on the screen resolution. Icon size, padding and row spacing went through PP.Scale, which truncates to whole physical pixels, so at a UI scale that is not pixel-perfect for the current resolution every icon and every gap lost up to a pixel. The loss adds up along a row, and because the bars are anchored by their center, the missing width was spread over both edges. The result: a bar carefully placed against another element at one resolution no longer sat there at another. In the reported case the buff bar sat flush to the left of the minimap at 1440p and, after switching to 1080p at the same UI scale and aspect ratio, the gap was gone and the bar sat a pixel above the map.

Every PAB grid number is now rounded to the nearest pixel instead, the same rounding Action Bars and Cooldown Manager already use (their comments call the PP.Scale truncation a bug outright). On top of that, a bar's growth origin, meaning the parent corner its aura container is pinned to and the center for the centered growth modes, is now derived from a resolution-independent design size, so it stays put instead of drifting with the rounding. Bars whose position was saved after this change line up within a pixel between 1080p and 1440p, and profiles shared between players on different resolutions land in the same place.

Existing bars do not move. A stored position only switches to the new resolution-independent meaning once the bar is actually moved in unlock mode; until then it keeps the exact placement it had before, with its growth origin pixel-identical to what the old truncating layout produced at the current resolution. The trade-off is that a bar that is never moved stays resolution-dependent, as it is today, so the reported setup needs the bar re-placed once to benefit. Icons can also come out a pixel larger than before, since rounding up is now possible where the old code always rounded down.

Three smaller defects in the same area are fixed along the way. Changing a bar's grow or wrap direction now re-places it immediately, where it previously kept the old placement until the next reload. The in-session compensation that keeps a bar's edge fixed while its grid grows or shrinks now holds the bar's actual growth origin; it always pinned the top-right corner before, which is only the right corner for leftward growth with rows wrapping down. And a bar switching to a centered growth mode no longer risks a one-pixel jump on the next login.

One shared file is touched: spec-override unlock layers harvested element positions through loadPosition and wrote them back through savePosition, which for these bars are the on-screen positions and therefore resolution-dependent. Layers can now bank an element's stored position table verbatim through an opt-in loadRawPosition/saveRawPosition pair, so a layer harvested at one resolution stays correct when it is applied at another. Elements that do not expose the pair keep the existing path, including the anchor-owned skip and the settle, and behave exactly as before.

How was it tested?

Tested in game on the live client.

Checklist

  • New settings default OFF (no behavior change without opt-in) -- N/A, no new settings; the fix is opt-in per bar in the sense that an existing bar keeps its placement until it is moved
  • Zero cost while disabled: no events registered, no polling, no hooks doing work, no frames built
  • Cheap while enabled: event-driven (no polling, no timer-based logic, no per-frame allocations)
  • No writes onto Blizzard-owned frames (weak-table pattern used); HookScript/hooksecurefunc only, never SetScript on Blizzard frames
  • Tested in-game on live; no version gates or pre-Midnight APIs added

PAB snapped icon size, padding and row gap with PP.Scale, which
truncates to whole physical pixels. The per-icon loss added up along a
row, so a bar measured a different number of UI units at 1080p and at
1440p, and the CENTER anchor spread that difference over both edges. A
bar placed flush against another element at one resolution opened a
gap or overlapped it at the other.

Round every PAB grid number to the nearest pixel through a local
PabSnap, the same rounding Action Bars and Cooldown Manager use.
ComputeGrid now also returns the unrounded design size. Stored
positions refer to that design frame, and OriginShift offsets the
SetPoint so the growth origin (the parent corner the aura container is
pinned to, or the center for centered growth) no longer depends on the
resolution. The unlock-mode movers of the default and custom bars
convert between the visual and the stored position in both directions,
so a save and load round trip is exact.

The in-session resize compensation in ApplyLiveConfig now moves a
stored center toward the actual growth origin. It always pinned the
top-right corner before, which was only right for leftward growth with
rows wrapping down.
Rounding to the nearest pixel changes the snapped bar size. With the
design-frame positions of the previous commit, every existing bar would
have moved once after the update, by up to half the accumulated
rounding.

A stored position now refers to the design frame only after an
unlock-mode move has saved it, which marks it with design = true. Every
other position, the defaults included, keeps its pre-fix placement:
ComputeGrid also returns the extent the old PP.Scale truncation
produced, and BarAnchorOffset puts the growth origin exactly where that
truncated frame, snapped as before, had it at the current resolution.
Such a position stays resolution-dependent, as before, until the bar is
moved.

The movers' savePos goes through MoverStorePos, which keeps the stored
table untouched when the incoming position equals what loadPos
returned, so a discard or an untouched commit does not convert it. The
in-session resize compensation moves each position by the delta of its
own reference frame.
Review follow-ups to the two commits before this one.

ApplyLiveConfig only re-placed a bar when its grid size changed. A grow
or wrap direction change moves the growth origin without changing that
size, so the bar kept the previous rounding shift until the next
reload, and loadPos disagreed with where the frame actually was.
lastSize now carries the direction pair and the re-place runs on a
change of it too, in combat through the regen queue.

The re-place is skipped for a bar anchored in unlock mode: the anchor
owns its placement and re-applies itself on a size change, so re-seating
such a bar to its stored position pulled it off the anchor.

RebaseBarPositionToCenter marks its result as a design position.
Centered growth has no rounding shift at CENTER, so the measured center
already is the design center; without the flag the next apply snapped
it twice and could jump a pixel.

PabSnap rounds ties up with the same 0.001 guard PP.SnapForES uses, so
float dust at an exact half pixel cannot flip a size between sessions.
The layers harvest element geometry through loadPosition and write it
back through savePosition. For Player Aura Bars those are the VISUAL
positions: they depend on the resolution and carry no design flag, so a
layer harvested at one resolution and flushed at another moved the bar
and converted its stored position along the way.

Add an opt-in pair, loadRawPosition and saveRawPosition. When an element
exposes both, the harvest banks its stored table verbatim (marked with
rawPos for the flush) and the flush hands that table straight back,
guarded by an exact equality that counts the element's own position
flags rather than coordinates alone. Elements without the pair keep the
existing loadPosition/savePosition path unchanged, including the
anchor-owned skip and the settle.

PlayerAuraBars' default and custom bar elements expose the pair, so
their stored positions survive a layer round trip at any resolution.
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