Skip to content

Commit 351e837

Browse files
author
mergetest
committed
Merge main: v4.8.0 click-cast cold-start and pinned-frame prune fixes
# Conflicts: # DandersFrames.toc
2 parents 6eb5c7a + 517239c commit 351e837

6 files changed

Lines changed: 241 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@
22

33
## [5.0.0]
44

5+
## [4.8.0]
6+
7+
### Bug Fixes
8+
9+
* (Click Casting) Fixed all binds being dead in your first arena or dungeon of a session until a reload. (by Krathe)
10+
* (Pinned Frames) Fixed an empty "drag to move" box getting stuck on screen after a profile switch, surviving reloads. (by Krathe)
11+
512
### Improvements
613

7-
* (Click Casting) Enabling a targeting fallback (Global, Target, Self or Always Cast) on a key that already does something in WoW now asks for confirmation and names exactly what the key will stop doing — these options make the key active everywhere, not just over the frames. The fallback tooltips explain this too.
8-
* (Click Casting) The "Clear Blizzard Bindings" button now warns that clearing is permanent, and it no longer silently re-clears Blizzard's click-casting profile every time click casting is enabled — clearing only happens when you press the button.
14+
* (Click Casting) Enabling a targeting fallback on a key that already does something now asks for confirmation and names what the key will stop doing.
15+
* (Click Casting) "Clear Blizzard Bindings" now warns that clearing is permanent, and only clears when you press it.
916

1017
### WoW 12.1 (Midnight) Rework
1118

@@ -162,8 +169,7 @@ DandersFrames has been rebuilt for WoW 12.1 (Midnight), which fundamentally chan
162169

163170
### Bug Fixes
164171

165-
* (Click Casting) **Fixed the 4.7.4 regression where every key bound in DF stopped working on the action bars ("like my keyboard is unplugged") after hovering a party/raid frame, until a reload.** 4.7.4's hover-bind rework applied binds under one owner but released them under another — so leaving a frame looked clean but never actually freed the keys. Bind ownership is now genuinely held by one permanent frame (applied and released in the same secure context), every release point additionally clears both possible owners as insurance, and the combat safety-check regression from 4.7.4 is reverted — keys release the moment the cursor leaves a frame. (by Krathe)
166-
* (Click Casting) Added extra recovery layers on top: stuck hover binds detected after leaving a frame are released immediately out of combat (or at combat end / next frame hover in combat), and the loading-screen self-repair now releases lingering hover binds unconditionally instead of trusting a state flag. (by Krathe)
172+
* (Click Casting) **Fixed keybinds going dead after hovering a party or raid frame (a 4.7.4 regression)** — keys now release the moment the cursor leaves a frame, with extra self-recovery layers if binds ever stick again. (by Krathe)
167173

168174
## [4.7.4]
169175

ClickCasting/Bindings.lua

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2386,13 +2386,55 @@ end
23862386

23872387
-- Process all bindings and build unified macro map
23882388
-- Returns: { [keyString] = { macroText = "...", templateBinding = binding } }
2389+
-- Re-resolve click-casting state once cold-start data becomes available. Two
2390+
-- things can be built before GetSpecialization() resolves at the first login
2391+
-- of a session, and both used to stay wrong all day until a /reload:
2392+
-- * loadoutCheckUnresolved (CheckLoadoutProfileSwitch): the spec→profile
2393+
-- auto-switch could not run (or, before the guard, ran with the `or 1`
2394+
-- spec fallback and switched to the WRONG spec's profile) — the field
2395+
-- report: "none of my binds work in my first arena of the day"
2396+
-- * macroMapUnresolved (BuildUnifiedMacroMap): loadSpec-scoped bindings
2397+
-- were dropped from the map (legacy/import-only config; belt)
2398+
-- Debounced; each resolver clears its own flag on success and self-defers in
2399+
-- combat (pendingLoadoutCheck / needsBindingRefresh). No-op in steady state,
2400+
-- so the extra triggers cost nothing.
2401+
function CC:ResolveProvisionalMap(reason)
2402+
if not (self.macroMapUnresolved or self.loadoutCheckUnresolved) then return end
2403+
if not (self.db and self.db.enabled) then return end
2404+
if self.provisionalResolveTimer then self.provisionalResolveTimer:Cancel() end
2405+
self.provisionalResolveTimer = C_Timer.NewTimer(0.5, function()
2406+
CC.provisionalResolveTimer = nil
2407+
if CC.loadoutCheckUnresolved then
2408+
DF:Debug("CLICK", "Re-running deferred loadout profile check (%s)", tostring(reason))
2409+
CC:CheckLoadoutProfileSwitch()
2410+
end
2411+
if CC.macroMapUnresolved then
2412+
DF:Debug("CLICK", "Rebuilding provisional binding map (%s)", tostring(reason))
2413+
CC:ApplyBindings()
2414+
end
2415+
end)
2416+
end
2417+
23892418
function CC:BuildUnifiedMacroMap()
23902419
local macroMap = {}
2391-
2420+
2421+
-- Cold-start guard: ShouldBindingLoad drops every loadSpec-scoped binding
2422+
-- while GetSpecialization() is still nil (first login of a session, before
2423+
-- spec data resolves). The map is built once and cached, so a map built in
2424+
-- that window silently loses those bindings — and an all-spec-scoped setup
2425+
-- comes up EMPTY, which downstream disables clicks on our frames entirely
2426+
-- ("none of my binds work in my first arena of the day until I reload").
2427+
-- Record the condition on the module; the resolve watchers (SPELLS_CHANGED /
2428+
-- PLAYER_SPECIALIZATION_CHANGED / arena prep) rebuild when data arrives, and
2429+
-- ApplyBindingsToFrameUnified refuses to wipe a frame off a suspect map.
2430+
local specKnown = GetSpecialization() ~= nil
2431+
local anySpecScoped = false
2432+
23922433
-- Group all bindings by their key string
23932434
local keyGroups = {}
23942435
for i, binding in ipairs(self.db.bindings) do
23952436
if binding.enabled ~= false then
2437+
if binding.loadSpec then anySpecScoped = true end
23962438
local keyString = self:GetBindingKeyString(binding)
23972439
if keyString then
23982440
if not keyGroups[keyString] then
@@ -2402,6 +2444,11 @@ function CC:BuildUnifiedMacroMap()
24022444
end
24032445
end
24042446
end
2447+
2448+
self.macroMapUnresolved = (anySpecScoped and not specKnown) or nil
2449+
if self.macroMapUnresolved then
2450+
DF:DebugWarn("CLICK", "BuildUnifiedMacroMap: spec unknown with spec-scoped bindings — map is provisional")
2451+
end
24052452

24062453
-- Build macro for each key group
24072454
for keyString, group in pairs(keyGroups) do
@@ -2507,17 +2554,16 @@ function CC:ApplyBindingsToFrameUnified(frame, skipKeyboardUpdate)
25072554
frameName, debugstack(2, 1, 0) or "unknown")
25082555
end
25092556

2510-
-- Clear existing bindings first
2511-
self:ClearBindingsFromFrame(frame)
2512-
25132557
-- Build unified macro map if not already built
25142558
if not self.unifiedMacroMap then
25152559
self.unifiedMacroMap = self:BuildUnifiedMacroMap()
25162560
-- Refresh keyboard bindings on all frames since map was just built
25172561
self:RefreshKeyboardBindings()
25182562
end
2519-
2520-
-- Check if this frame has ANY bindings that apply to it
2563+
2564+
-- Check if this frame has ANY bindings that apply to it — BEFORE the
2565+
-- destructive clear below, so a provisional (cold-start) map can bail out
2566+
-- without touching the frame's existing state.
25212567
local hasAnyBindings = false
25222568
local isDandersFrame = frame.dfIsDandersFrame == true
25232569
local isBlizzardFrame = frame.dfIsBlizzardFrame == true
@@ -2543,6 +2589,16 @@ function CC:ApplyBindingsToFrameUnified(frame, skipKeyboardUpdate)
25432589

25442590
-- If no bindings apply to this frame
25452591
if not hasAnyBindings then
2592+
-- Provisional map (spec not yet resolved at build time): the emptiness
2593+
-- is almost certainly the cold-start drop, not the user's config. Do
2594+
-- NOT clear/disable anything — leave the frame exactly as it is; the
2595+
-- resolve watchers rebuild and re-apply once spec data arrives.
2596+
if self.macroMapUnresolved then
2597+
DF:Debug("CLICK", "ApplyBindings %s deferred — provisional map (spec unresolved)", frameName)
2598+
return
2599+
end
2600+
-- Genuinely no bindings for this frame: clean it up.
2601+
self:ClearBindingsFromFrame(frame)
25462602
if isDandersFrame then
25472603
-- For DandersFrames, completely disable clicks when no bindings apply
25482604
-- Our own frames get type1/type2 set in InitializeHeaderChild as a safety net
@@ -2557,7 +2613,11 @@ function CC:ApplyBindingsToFrameUnified(frame, skipKeyboardUpdate)
25572613
end
25582614
return
25592615
end
2560-
2616+
2617+
-- Clear existing bindings first (moved below the applicability check so a
2618+
-- provisional-map bailout above never strips a frame's working state)
2619+
self:ClearBindingsFromFrame(frame)
2620+
25612621
-- Register for clicks based on castOnDown option
25622622
if frame.RegisterForClicks then
25632623
local castOnDown = self.profile and self.profile.options and self.profile.options.castOnDown

ClickCasting/Events.lua

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ function CC:RegisterEvents()
2929
-- Nameplate events for click-casting on nameplates
3030
eventFrame:RegisterEvent("NAME_PLATE_UNIT_ADDED")
3131
eventFrame:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
32+
33+
-- Spell data arrival — resolves a provisional (cold-start) binding map
34+
eventFrame:RegisterEvent("SPELLS_CHANGED")
3235

3336
eventFrame:SetScript("OnEvent", function(_, event, ...)
3437
if event == "PLAYER_REGEN_ENABLED" then
@@ -39,6 +42,12 @@ function CC:RegisterEvents()
3942
elseif event == "PLAYER_SPECIALIZATION_CHANGED" or event == "ACTIVE_PLAYER_SPECIALIZATION_CHANGED" then
4043
-- Spec changed - check for profile switch
4144
CC:OnSpecChanged()
45+
-- Cold-start resolve: a map built before GetSpecialization()
46+
-- resolved dropped every spec-scoped binding — rebuild it now
47+
CC:ResolveProvisionalMap("spec-resolved")
48+
elseif event == "SPELLS_CHANGED" then
49+
-- Spell data arrived/changed — no-op unless the map is provisional
50+
CC:ResolveProvisionalMap("spells-changed")
4251
elseif event == "TRAIT_CONFIG_UPDATED" or event == "TRAIT_CONFIG_CREATED" or event == "ACTIVE_COMBAT_CONFIG_CHANGED" then
4352
-- Loadout/talent changed - check for profile switch and reapply bindings (with debounce)
4453
if not InCombatLockdown() then
@@ -93,15 +102,26 @@ function CC:RegisterEvents()
93102
-- so a broken hover-bind state never survives a zone change
94103
CC:RunBindingRepair("zone-in", true)
95104

96-
-- Check for loadout-based profile on initial load
105+
-- Cold-start resolve: if the login build ran before spec data
106+
-- was available, the map is provisional — rebuild it on the
107+
-- first loading screen so it is correct BEFORE the first
108+
-- arena/dungeon of the session, not only after a /reload
109+
CC:ResolveProvisionalMap("zone-in")
110+
111+
-- Check for loadout-based profile on initial load. No combat
112+
-- guard here: CheckLoadoutProfileSwitch defers itself via
113+
-- pendingLoadoutCheck in lockdown — the old call-site guard
114+
-- silently DROPPED the check when zone-in+1s landed in combat
115+
-- (the arena-load race), leaving the previous spec's profile
116+
-- active for the whole match.
97117
C_Timer.After(1, function()
98-
if not InCombatLockdown() then
99-
CC:CheckLoadoutProfileSwitch()
100-
end
118+
CC:CheckLoadoutProfileSwitch()
101119
end)
102120
end)
103121
elseif event == "ARENA_PREP_OPPONENT_SPECIALIZATIONS" then
104122
-- Arena frames should now exist
123+
-- Belt: never enter an arena on a provisional (cold-start) map
124+
CC:ResolveProvisionalMap("arena-prep")
105125
CC:OnArenaPrep()
106126
elseif event == "INSTANCE_ENCOUNTER_ENGAGE_UNIT" then
107127
-- Boss frames should now exist

ClickCasting/Profiles.lua

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,10 +492,27 @@ end
492492
-- Check and auto-switch profile based on current loadout
493493
function CC:CheckLoadoutProfileSwitch()
494494
if InCombatLockdown() then
495-
-- Will be called again when combat ends
495+
-- Defer, don't drop: entering an arena/dungeon starts combat quickly,
496+
-- and silently losing the check leaves the previous spec's profile
497+
-- active for the whole match. OnCombatEnd drains pendingLoadoutCheck.
498+
self.pendingLoadoutCheck = true
496499
return
497500
end
498-
501+
502+
-- Cold-start guard: at the first login of a session this can run BEFORE
503+
-- GetSpecialization() resolves. GetCurrentSpec()'s `or 1` fallback would
504+
-- then MASK the missing data and switch to spec 1's profile — the wrong
505+
-- profile for anyone whose actual spec is 2+ ("none of my binds work in
506+
-- my first arena of the day until I reload"). Record the unresolved state
507+
-- and let the resolve watchers (spec/spell events, loading screens, arena
508+
-- prep) re-run this check once real data arrives.
509+
if not GetSpecialization() then
510+
self.loadoutCheckUnresolved = true
511+
DF:Debug("CLICK", "CheckLoadoutProfileSwitch: spec data not ready — deferred to resolve watchers")
512+
return
513+
end
514+
self.loadoutCheckUnresolved = nil
515+
499516
local specIndex = GetCurrentSpec()
500517
local loadoutID = GetCurrentLoadoutConfigID()
501518
local assignedProfile, isSpecific = self:GetProfileForLoadout(specIndex, loadoutID)

Core.lua

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5755,6 +5755,16 @@ function DF:FullProfileRefresh()
57555755

57565756
-- === REFRESH PINNED FRAMES IF ACTIVE ===
57575757
if DF.PinnedFrames and DF.PinnedFrames.initialized then
5758+
-- Reap sets the NEW profile no longer defines. Profile switching does
5759+
-- NOT rebuild pinned frames (this function refreshes headers in place),
5760+
-- so switching from a profile with more sets to one with fewer leaves
5761+
-- the extra containers/movers live but untracked, and every hide path
5762+
-- gates on the current profile's GetSetDB — so nothing removes them
5763+
-- (the "stuck pinned box that survives everything" reports). Prune them
5764+
-- before refreshing the survivors.
5765+
if DF.PinnedFrames.PruneOrphanedSets then
5766+
DF.PinnedFrames:PruneOrphanedSets()
5767+
end
57585768
-- Sync each set's visibility to the NEW profile FIRST — hide sets it
57595769
-- disables, show/create sets it enables — so a set shown under the
57605770
-- previous profile doesn't linger in a stale state after the switch.

0 commit comments

Comments
 (0)