-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDragReorder.lua
More file actions
358 lines (303 loc) · 13.2 KB
/
Copy pathDragReorder.lua
File metadata and controls
358 lines (303 loc) · 13.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
-- DragReorder.lua - Drag-to-reorder with ghost bar and drop indicator.
-- Author: Serv
-- Source: https://github.com/powerfulqa/BarWarden
-- License: see LICENSE; attribution preservation is required.
local addonName, ns = ...
-- ============================================================================
-- DragReorder.lua - Drag-to-reorder bars within a frame group
-- ============================================================================
local DRAG_THRESHOLD = 5 -- pixels before drag starts
local HIGHLIGHT_ALPHA = 0.4 -- drop indicator opacity
local GHOST_ALPHA = 0.5 -- ghost bar opacity during drag
-- Shared state for active drag operation
local dragState = {
active = false,
bar = nil,
frameIndex = nil,
startY = 0,
ghost = nil,
indicator = nil,
dropIndex = nil,
}
-- ----------------------------------------------------------------------------
-- CreateIndicator: Lazy-create the drop-target highlight line
-- ----------------------------------------------------------------------------
local function GetIndicator()
if dragState.indicator then return dragState.indicator end
local ind = CreateFrame("Frame", "BarWardenDropIndicator", UIParent)
ind:SetHeight(3)
ind:SetFrameStrata("TOOLTIP")
local tex = ind:CreateTexture(nil, "OVERLAY")
tex:SetAllPoints()
tex:SetTexture("Interface\\Buttons\\WHITE8x8")
tex:SetVertexColor(0.2, 0.8, 1.0, HIGHLIGHT_ALPHA)
ind.texture = tex
ind:Hide()
dragState.indicator = ind
return ind
end
-- ----------------------------------------------------------------------------
-- CreateGhost: Lazy-create the ghost bar shown while dragging
-- ----------------------------------------------------------------------------
local function GetGhost()
if dragState.ghost then return dragState.ghost end
local ghost = CreateFrame("Frame", "BarWardenDragGhost", UIParent)
ghost:SetFrameStrata("TOOLTIP")
ghost:SetFrameLevel(100)
local tex = ghost:CreateTexture(nil, "BACKGROUND")
tex:SetAllPoints()
tex:SetTexture("Interface\\Buttons\\WHITE8x8")
tex:SetVertexColor(0.4, 0.4, 0.4, GHOST_ALPHA)
ghost.bgTexture = tex
local label = ghost:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
label:SetPoint("CENTER")
ghost.label = label
ghost:Hide()
dragState.ghost = ghost
return ghost
end
-- ----------------------------------------------------------------------------
-- CalcDropIndex: Determine which bar slot the cursor is over
-- ----------------------------------------------------------------------------
-- Is this group laid out bottom-to-top by index? (growDirection == "UP")
local function GroupGrowsUp(groupFrame)
local fd = BarWardenDB and BarWardenDB.frames and BarWardenDB.frames[groupFrame.frameIndex]
return fd and fd.growDirection == "UP" or false
end
local function CalcDropIndex(groupFrame)
local cx, cy = GetCursorPosition()
local scale = groupFrame:GetEffectiveScale()
cx, cy = cx / scale, cy / scale
local bars = groupFrame.bars
if not bars or #bars == 0 then return 1 end
-- Find the visible bar nearest the cursor in 2D (so multi-column layouts
-- pick the actual nearest bar, not just the first bar of a row), then decide
-- insert-before vs -after from which side the cursor is on. Growth direction
-- flips the vertical sense: DOWN lays bars[1] at the top (higher Y = earlier
-- index), UP lays bars[1] at the bottom (higher Y = later index).
local growUp = GroupGrowsUp(groupFrame)
local nearest, nearestDist, nearestY
for i, bar in ipairs(bars) do
if bar:IsShown() then
local bx, by = bar:GetCenter()
if bx and by then
local dx, dy = cx - bx, cy - by
local d = dx * dx + dy * dy
if not nearestDist or d < nearestDist then
nearestDist, nearest, nearestY = d, i, by
end
end
end
end
if not nearest then return #bars + 1 end
local before = cy > nearestY -- cursor above the nearest bar's centre
if growUp then before = not before end
return before and nearest or (nearest + 1)
end
-- ----------------------------------------------------------------------------
-- UpdateIndicatorPosition: Move the drop indicator to the target slot
-- ----------------------------------------------------------------------------
local function UpdateIndicatorPosition(groupFrame, dropIdx)
local ind = GetIndicator()
local bars = groupFrame.bars
if not bars then
ind:Hide()
return
end
local visual = ns:GetVisual()
local barWidth = visual.barWidth or 200
local frameData = BarWardenDB and BarWardenDB.frames and BarWardenDB.frames[groupFrame.frameIndex]
if frameData and frameData.width then
barWidth = frameData.width
end
ind:SetWidth(barWidth)
ind:ClearAllPoints()
-- The drop line sits at the boundary just "before" bars[dropIdx]. In a
-- DOWN group that boundary is the target bar's TOP edge; in an UP group the
-- earlier-index side is below, so it's the BOTTOM edge. Past the last index
-- the line sits on the far edge of the last bar.
local growUp = GroupGrowsUp(groupFrame)
local anchorBar = (dropIdx <= #bars) and bars[dropIdx] or nil
if anchorBar and anchorBar:IsShown() then
if growUp then
ind:SetPoint("TOPLEFT", anchorBar, "BOTTOMLEFT", 0, -1)
else
ind:SetPoint("BOTTOMLEFT", anchorBar, "TOPLEFT", 0, 1)
end
elseif #bars > 0 and bars[#bars]:IsShown() then
if growUp then
ind:SetPoint("BOTTOMLEFT", bars[#bars], "TOPLEFT", 0, 1)
else
ind:SetPoint("TOPLEFT", bars[#bars], "BOTTOMLEFT", 0, -1)
end
else
ind:Hide()
return
end
ind:Show()
end
-- ----------------------------------------------------------------------------
-- SwapBars: Swap bar positions in BarWardenDB and rebuild layout
-- ----------------------------------------------------------------------------
local function SwapBars(frameIndex, fromIndex, toIndex)
if fromIndex == toIndex or fromIndex == toIndex - 1 then return end
local frameData = BarWardenDB and BarWardenDB.frames and BarWardenDB.frames[frameIndex]
if not frameData or not frameData.bars then return end
local bars = frameData.bars
if fromIndex < 1 or fromIndex > #bars then return end
-- Remove the bar from its old position
local barData = table.remove(bars, fromIndex)
-- Adjust target index after removal
local insertIdx = toIndex
if toIndex > fromIndex then
insertIdx = insertIdx - 1
end
insertIdx = math.max(1, math.min(insertIdx, #bars + 1))
table.insert(bars, insertIdx, barData)
-- Rebuild bars for this frame
local groupFrame = ns.groupFrames[frameIndex]
if groupFrame then
ns:BuildBarsForFrame(frameIndex)
ns:UpdateGroupLayout(groupFrame)
end
end
-- ----------------------------------------------------------------------------
-- Drag tracker: shared OnUpdate that follows the cursor while a drag is in
-- progress. Lives on its own frame so that Bar_OnUpdate (BarEngine's
-- countdown handler) is never clobbered. An active countdown bar stays
-- smooth while being drag-reordered.
-- ----------------------------------------------------------------------------
local dragUpdater = CreateFrame("Frame", "BarWardenDragUpdater", UIParent)
dragUpdater:Hide()
dragUpdater:SetScript("OnUpdate", function()
local bar = dragState.bar
if not bar or not dragState.frameIndex then return end
local _, cursorY = GetCursorPosition()
-- Threshold gate: don't kick off the ghost until the user has actually
-- moved far enough to mean "drag", not "click".
if not dragState.active then
if math.abs(cursorY - dragState.startY) < DRAG_THRESHOLD then
return
end
dragState.active = true
local ghost = GetGhost()
ghost:SetWidth(bar:GetWidth())
ghost:SetHeight(bar:GetHeight())
local barData = bar.barData
ghost.label:SetText(barData and barData.spellName or barData and barData.name or "")
ghost:Show()
end
local ghost = GetGhost()
local scale = bar:GetEffectiveScale()
local cx, cy = GetCursorPosition()
ghost:ClearAllPoints()
ghost:SetPoint("CENTER", UIParent, "BOTTOMLEFT", cx / scale, cy / scale)
local groupFrame = ns.groupFrames[dragState.frameIndex]
if groupFrame then
dragState.dropIndex = CalcDropIndex(groupFrame)
UpdateIndicatorPosition(groupFrame, dragState.dropIndex)
end
end)
-- Reset drag state and hide all drag visuals. Called from OnMouseUp and
-- from DisableDragReorder on lock / teardown.
local function ClearDrag()
dragState.active = false
dragState.bar = nil
dragState.frameIndex = nil
dragState.startBarIndex = nil
dragState.dropIndex = nil
dragUpdater:Hide()
if dragState.ghost then dragState.ghost:Hide() end
if dragState.indicator then dragState.indicator:Hide() end
end
-- ----------------------------------------------------------------------------
-- Bar OnMouseDown: Begin tracking potential drag
-- ----------------------------------------------------------------------------
local function Bar_OnMouseDown(self, button)
if button ~= "LeftButton" then return end
if BarWardenDB and BarWardenDB.global.locked then return end
if not self.frameIndex or not self.barIndex then return end
local _, cursorY = GetCursorPosition()
dragState.startY = cursorY
dragState.bar = self
dragState.frameIndex = self.frameIndex
dragState.startBarIndex = self.barIndex
dragState.active = false
dragState.dropIndex = nil
-- Spin up the shared tracker; its first tick will check the threshold.
dragUpdater:Show()
end
-- ----------------------------------------------------------------------------
-- Bar OnMouseUp: Complete or cancel drag
-- ----------------------------------------------------------------------------
local function Bar_OnMouseUp(self, button)
if button ~= "LeftButton" then return end
if dragState.active and dragState.dropIndex then
SwapBars(dragState.frameIndex, dragState.startBarIndex, dragState.dropIndex)
end
ClearDrag()
end
-- ----------------------------------------------------------------------------
-- MoveBarUp / MoveBarDown: Accessibility fallback via buttons
-- ----------------------------------------------------------------------------
function ns:MoveBarUp(frameIndex, barIndex)
if barIndex <= 1 then return end
SwapBars(frameIndex, barIndex, barIndex - 1)
end
function ns:MoveBarDown(frameIndex, barIndex)
local frameData = BarWardenDB and BarWardenDB.frames and BarWardenDB.frames[frameIndex]
if not frameData or not frameData.bars then return end
if barIndex >= #frameData.bars then return end
-- toIndex is barIndex + 2 because SwapBars removes the bar first (shifting
-- indices down by one) then inserts at (toIndex - 1). The net effect is a
-- swap with the bar immediately below: remove@barIndex → insert@(barIndex+1).
SwapBars(frameIndex, barIndex, barIndex + 2)
end
-- ----------------------------------------------------------------------------
-- EnableDragReorder / DisableDragReorder: per-group toggle.
--
-- Drag tracking uses a dedicated update frame (see dragUpdater above) rather
-- than the bar's own OnUpdate, so enabling drag does not disturb BarEngine's
-- countdown handler on active bars. That means a cooldown that is currently
-- ticking down can still be drag-reordered while unlocked.
-- ----------------------------------------------------------------------------
function ns:EnableDragReorder(groupFrame)
if not groupFrame or not groupFrame.bars then return end
for _, bar in ipairs(groupFrame.bars) do
bar:EnableMouse(true)
bar:SetScript("OnMouseDown", Bar_OnMouseDown)
bar:SetScript("OnMouseUp", Bar_OnMouseUp)
bar.dragEnabled = true
end
end
function ns:DisableDragReorder(groupFrame)
if not groupFrame or not groupFrame.bars then return end
for _, bar in ipairs(groupFrame.bars) do
bar:SetScript("OnMouseDown", nil)
bar:SetScript("OnMouseUp", nil)
-- Hand the mouse back. Bars are created mouse-disabled so clicks pass
-- through to the world; leaving it enabled after a lock meant a locked
-- group silently ate every click landing on a bar.
bar:EnableMouse(false)
bar.dragEnabled = false
end
-- Cancel any in-flight drag rooted in this group so the ghost/indicator
-- don't linger after a lock toggle.
if dragState.bar and dragState.bar:GetParent() == groupFrame then
ClearDrag()
end
end
-- Convenience: apply to every live group frame. Called from the global
-- lock/unlock transitions in FrameManager.lua and from the
-- `global.locked` toggle in Options_General.lua, so the drag wiring
-- tracks frame-lock state without each call site repeating the loop.
function ns:EnableDragReorderAll()
for _, gf in pairs(ns.groupFrames or {}) do
ns:EnableDragReorder(gf)
end
end
function ns:DisableDragReorderAll()
for _, gf in pairs(ns.groupFrames or {}) do
ns:DisableDragReorder(gf)
end
end