Skip to content

Commit f3216d3

Browse files
committed
raid container: compensate CENTER anchor for populated-row drift (#867)
Introduce DF:ComputeRaidContainerCompensation() which mirrors the secure positioning snippet's populated-row math and returns an inverse offset to shift the container by. UpdateRaidContainerPosition applies the offset before SetPoint, so visible content's screen position stays put across roster transitions when raidGroupAnchor == "CENTER". Reference state is popRows=1, so existing user positions don't shift on first load. Returns (0,0) for non-CENTER anchor, flat mode, or single-row layouts — most users see no behaviour change.
1 parent af9d463 commit f3216d3

3 files changed

Lines changed: 135 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# DandersFrames Changelog
22

3+
## [4.3.7] - 2026-05-02
4+
5+
### Bug Fixes
6+
7+
* (Raid Frames) Fix raid frames jumping upward when group composition changes with Groups Grow From set to Center. Previously the visible frames could shoot off the top of the screen each time a player joined a new group, requiring a Groups Grow From toggle to recover.
8+
39
## [4.3.6] - 2026-04-30
410

511
### Improvements

Frames/Headers.lua

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,6 +2191,111 @@ end
21912191
-- Lua only sets attributes, secure code does all positioning
21922192
-- ============================================================
21932193

2194+
-- Compensation offset for the raid container's anchor when raidGroupAnchor == "CENTER".
2195+
--
2196+
-- The secure positioning snippet (below) centers the *visible* groups inside a
2197+
-- fixed full-grid-sized container by offsetting each group by
2198+
-- yStart = (totalHeight - populatedHeight) / 2
2199+
-- Where totalHeight is the full 8-group grid capacity and populatedHeight grows
2200+
-- with the number of populated rows. As popRows changes (player joins/leaves
2201+
-- shifts a group across the groupsPerRow threshold), every visible group snaps
2202+
-- by (deltaPopulatedDim / 2) inside the container. Container itself does not
2203+
-- move — only the visible content. Users perceive this as the frames "jumping
2204+
-- upward off the screen" (bug #867).
2205+
--
2206+
-- This helper returns the inverse offset so callers can shift the container by
2207+
-- the same amount, keeping the visible content's screen position stable across
2208+
-- roster transitions. The reference state is popRows=1 (one row populated) so
2209+
-- existing user positions don't shift on first load: with one populated row,
2210+
-- compensation is (0, 0) and the container sits exactly at db.raidAnchorX/Y
2211+
-- — identical to current behaviour. As popRows grows beyond 1, compensation
2212+
-- shifts the container away from the anchor by enough to cancel the snippet's
2213+
-- upward visual snap.
2214+
--
2215+
-- Returns (dx, dy) to add to the container's anchor coordinates BEFORE the
2216+
-- frameScale division. (0, 0) for any case the bug doesn't apply to:
2217+
-- * raidGroupAnchor != "CENTER"
2218+
-- * raidUseGroups == false (flat raid mode has its own positioning path)
2219+
-- * No populated groups (nothing visible yet)
2220+
-- * Single populated row (no drift)
2221+
-- * Required handler / headers not yet created
2222+
function DF:ComputeRaidContainerCompensation()
2223+
if not DF.raidPositionHandler then return 0, 0 end
2224+
if not DF.raidSeparatedHeaders then return 0, 0 end
2225+
2226+
local db = DF:GetRaidDB()
2227+
if not db then return 0, 0 end
2228+
if not db.raidUseGroups then return 0, 0 end
2229+
if (db.raidGroupAnchor or "START") ~= "CENTER" then return 0, 0 end
2230+
2231+
-- Mirror the snippet: a group is "populated" when its child count > 0.
2232+
-- Read counts from the same handler attributes the snippet reads, so the
2233+
-- compensation always agrees with the snippet's view of the world.
2234+
local handler = DF.raidPositionHandler
2235+
local numPopulated = 0
2236+
for i = 1, 8 do
2237+
local count = handler:GetAttribute("group" .. i .. "count") or 0
2238+
if count > 0 then numPopulated = numPopulated + 1 end
2239+
end
2240+
if numPopulated == 0 then return 0, 0 end
2241+
2242+
-- Mirror the snippet's totalHeight / populatedHeight calculation.
2243+
local groupsPerRow = db.raidGroupsPerRow or 8
2244+
if groupsPerRow < 1 then groupsPerRow = 1 end
2245+
if groupsPerRow > 8 then groupsPerRow = 8 end
2246+
2247+
local growDirection = db.growDirection or "HORIZONTAL"
2248+
local isHorizontal = (growDirection == "HORIZONTAL")
2249+
local frameWidth = db.frameWidth or 80
2250+
local frameHeight = db.frameHeight or 40
2251+
local spacing = db.frameSpacing or 2
2252+
local rowColSpacing = db.raidRowColSpacing or 30
2253+
2254+
local groupHeight, groupWidth
2255+
if isHorizontal then
2256+
groupWidth = frameWidth
2257+
groupHeight = 5 * frameHeight + 4 * spacing
2258+
else
2259+
groupWidth = 5 * frameWidth + 4 * spacing
2260+
groupHeight = frameHeight
2261+
end
2262+
2263+
-- popRows = ceil(numPopulated / groupsPerRow)
2264+
local popRem = numPopulated % groupsPerRow
2265+
local popRows = (numPopulated - popRem) / groupsPerRow
2266+
if popRem > 0 then popRows = popRows + 1 end
2267+
2268+
-- popRows == 1 is the reference state — compensation is zero there. Skip the
2269+
-- rest to avoid pointless arithmetic and to match existing behaviour exactly
2270+
-- when the bug doesn't apply.
2271+
if popRows <= 1 then return 0, 0 end
2272+
2273+
-- The drift dimension depends on growDirection: HORIZONTAL = vertical drift,
2274+
-- VERTICAL = horizontal drift. (Per the snippet's totalHeight/populatedHeight
2275+
-- vs totalWidth/populatedWidth selection at lines 2492-2498.)
2276+
local groupDim, populatedDim
2277+
if isHorizontal then
2278+
groupDim = groupHeight
2279+
populatedDim = popRows * groupHeight + (popRows - 1) * rowColSpacing
2280+
else
2281+
groupDim = groupWidth
2282+
populatedDim = popRows * groupWidth + (popRows - 1) * rowColSpacing
2283+
end
2284+
2285+
-- Compensation = (groupDim - populatedDim) / 2. This is yStart_now minus
2286+
-- yStart_at_popRows1, so popRows=1 yields zero (existing behaviour preserved)
2287+
-- and popRows>1 yields a negative offset that shifts the container in the
2288+
-- direction opposite to the snippet's content drift. Net visual: visible
2289+
-- content's TOP edge stays where popRows=1 layout placed it for every
2290+
-- larger populated count.
2291+
local compensation = (groupDim - populatedDim) / 2
2292+
if isHorizontal then
2293+
return 0, compensation
2294+
else
2295+
return compensation, 0
2296+
end
2297+
end
2298+
21942299
function DF:CreateRaidPositionHandler()
21952300
if DF.raidPositionHandler then return end
21962301
if not DF.raidContainer then return end

Frames/Position.lua

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2145,24 +2145,42 @@ function DF:UpdateRaidContainerPosition()
21452145
local x, y = db.raidAnchorX or 0, db.raidAnchorY or 0
21462146
local scale = db.frameScale or 1.0
21472147

2148+
-- CENTER-anchor compensation: when raidGroupAnchor == "CENTER", the secure
2149+
-- positioning snippet shifts visible groups inside the container by
2150+
-- (totalHeight - populatedHeight) / 2 to centre the visible content. As
2151+
-- populated rows grow with the roster, that offset shrinks and visible
2152+
-- frames snap upward inside the container. We undo the visual snap by
2153+
-- shifting the container itself by the same magnitude. The user's saved
2154+
-- anchor (x, y) is preserved as the centroid of the visible content. (#867)
2155+
local dx, dy = 0, 0
2156+
if DF.ComputeRaidContainerCompensation then
2157+
dx, dy = DF:ComputeRaidContainerCompensation()
2158+
end
2159+
local cx, cy = x + dx, y + dy
2160+
21482161
if DF.Debug then
2149-
DF:Debug("RAIDPOS", "UpdateRaidContainerPosition @ %s : applying (%.1f,%.1f) scale=%.3f combat=%s",
2150-
ShortCaller(3), x, y, scale, tostring(InCombatLockdown()))
2162+
if dx ~= 0 or dy ~= 0 then
2163+
DF:Debug("RAIDPOS", "UpdateRaidContainerPosition @ %s : applying (%.1f,%.1f) +comp(%.1f,%.1f) -> (%.1f,%.1f) scale=%.3f combat=%s",
2164+
ShortCaller(3), x, y, dx, dy, cx, cy, scale, tostring(InCombatLockdown()))
2165+
else
2166+
DF:Debug("RAIDPOS", "UpdateRaidContainerPosition @ %s : applying (%.1f,%.1f) scale=%.3f combat=%s",
2167+
ShortCaller(3), x, y, scale, tostring(InCombatLockdown()))
2168+
end
21512169
end
21522170

21532171
DF.raidContainer:SetScale(scale)
21542172
DF.raidContainer:ClearAllPoints()
2155-
DF.raidContainer:SetPoint("CENTER", UIParent, "CENTER", x / scale, y / scale)
2173+
DF.raidContainer:SetPoint("CENTER", UIParent, "CENTER", cx / scale, cy / scale)
21562174

2157-
-- Also update mover if visible
2158-
-- Raid mover is parented to UIParent, needs explicit scale + compensation
2175+
-- Mover/test container do NOT get the compensation: the mover represents
2176+
-- the user's saved position (centroid), and the test container has no
2177+
-- secure snippet running so populated-row drift never happens there.
21592178
if DF.raidMoverFrame and DF.raidMoverFrame:IsShown() then
21602179
DF.raidMoverFrame:SetScale(scale)
21612180
DF.raidMoverFrame:ClearAllPoints()
21622181
DF.raidMoverFrame:SetPoint("CENTER", UIParent, "CENTER", x / scale, y / scale)
21632182
end
21642183

2165-
-- Also update test container if visible
21662184
if DF.testRaidContainer then
21672185
DF.testRaidContainer:SetScale(scale)
21682186
DF.testRaidContainer:ClearAllPoints()

0 commit comments

Comments
 (0)