comparison Bar.lua @ 52:c9df7866ff31

Added anchor snapping
author Flick <flickerstreak@gmail.com>
date Thu, 24 Apr 2008 19:19:42 +0000
parents c964fb84560c
children 8b81d4b3e73d
comparison
equal deleted inserted replaced
51:c964fb84560c 52:c9df7866ff31
11 11
12 12
13 -- update ReAction revision if this file is newer 13 -- update ReAction revision if this file is newer
14 local revision = tonumber(("$Revision$"):match("%d+")) 14 local revision = tonumber(("$Revision$"):match("%d+"))
15 if revision > ReAction.revision then 15 if revision > ReAction.revision then
16 Reaction.revision = revision 16 ReAction.revision = revision
17 end 17 end
18 18
19 ------ BAR CLASS ------ 19 ------ BAR CLASS ------
20 local Bar = { _classID = {} } 20 local Bar = { _classID = {} }
21 21
60 f:SetWidth(config.width) 60 f:SetWidth(config.width)
61 f:SetHeight(config.height) 61 f:SetHeight(config.height)
62 local anchor = config.anchor 62 local anchor = config.anchor
63 f:ClearAllPoints() 63 f:ClearAllPoints()
64 if anchor then 64 if anchor then
65 local anchorTo 65 local anchorTo = f:GetParent()
66 if config.anchorTo then 66 if config.anchorTo then
67 anchorTo = ReAction:GetBar(config.anchorTo) or _G[config.anchorTo] 67 local bar = ReAction:GetBar(config.anchorTo)
68 end 68 if bar then
69 f:SetPoint(anchor, anchorTo, config.relativePoint, config.x or 0, config.y or 0) 69 anchorTo = bar:GetFrame()
70 else
71 anchorTo = _G[config.anchorTo]
72 end
73 end
74 f:SetPoint(anchor, anchorTo or f:GetParent(), config.relativePoint, config.x or 0, config.y or 0)
70 else 75 else
71 f:SetPoint("CENTER") 76 f:SetPoint("CENTER")
72 end 77 end
73 end 78 end
74 79
162 167
163 168
164 -- 169 --
165 -- Bar config overlay 170 -- Bar config overlay
166 -- 171 --
167 local StoreExtents, RecomputeButtonSize, RecomputeButtonSpacing, RecomputeGrid, ClampToButtons, HideGameTooltip, CreateControls 172 local CreateControls
168 173
169 do 174 do
170 -- upvalue some of these for small OnUpdate performance boost 175 -- upvalue some of these for small OnUpdate performance boost
171 local GetSize = Bar.GetSize 176 local GetSize = Bar.GetSize
172 local GetButtonSize = Bar.GetButtonSize 177 local GetButtonSize = Bar.GetButtonSize
174 local SetSize = Bar.SetSize 179 local SetSize = Bar.SetSize
175 local SetButtonSize = Bar.SetButtonSize 180 local SetButtonSize = Bar.SetButtonSize
176 local SetButtonGrid = Bar.SetButtonGrid 181 local SetButtonGrid = Bar.SetButtonGrid
177 local ApplyAnchor = Bar.ApplyAnchor 182 local ApplyAnchor = Bar.ApplyAnchor
178 183
179 StoreExtents = function(bar) 184 local function StoreExtents(bar)
180 local f = bar.frame 185 local f = bar.frame
181 local point, relativeTo, relativePoint, x, y = f:GetPoint(1) 186 local point, relativeTo, relativePoint, x, y = f:GetPoint(1)
182 relativeTo = relativeTo or f:GetParent() 187 relativeTo = relativeTo or f:GetParent()
183 local anchorTo 188 local anchorTo
184 for name, b in pairs(ReAction.bars) do 189 for name, b in pairs(ReAction.bars) do
185 if b then 190 if b and b:GetFrame() == relativeTo then
186 if b:GetFrame() == relativeTo then 191 anchorTo = name
187 anchorTo = name 192 break
188 break
189 end
190 end 193 end
191 end 194 end
192 anchorTo = anchorTo or relativeTo:GetName() 195 anchorTo = anchorTo or relativeTo:GetName()
193 local c = bar.config 196 local c = bar.config
194 c.anchor = point 197 c.anchor = point
197 c.x = x 200 c.x = x
198 c.y = y 201 c.y = y
199 c.width, c.height = f:GetWidth(), f:GetHeight() 202 c.width, c.height = f:GetWidth(), f:GetHeight()
200 end 203 end
201 204
202 RecomputeButtonSize = function(bar) 205 local function StoreSize(bar)
206 local f = bar.frame
207 local c = bar.config
208 c.width, c.height = f:GetWidth(), f:GetHeight()
209 end
210
211 local function RecomputeButtonSize(bar)
203 local w, h = GetSize(bar) 212 local w, h = GetSize(bar)
204 local bw, bh = GetButtonSize(bar) 213 local bw, bh = GetButtonSize(bar)
205 local r, c, s = GetButtonGrid(bar) 214 local r, c, s = GetButtonGrid(bar)
206 215
207 local scaleW = (floor(w/c) - s) / bw 216 local scaleW = (floor(w/c) - s) / bw
209 local scale = min(scaleW, scaleH) 218 local scale = min(scaleW, scaleH)
210 219
211 SetButtonSize(bar, scale * bw, scale * bh, s) 220 SetButtonSize(bar, scale * bw, scale * bh, s)
212 end 221 end
213 222
214 RecomputeButtonSpacing = function(bar) 223 local function RecomputeButtonSpacing(bar)
215 local w, h = GetSize(bar) 224 local w, h = GetSize(bar)
216 local bw, bh = GetButtonSize(bar) 225 local bw, bh = GetButtonSize(bar)
217 local r, c, s = GetButtonGrid(bar) 226 local r, c, s = GetButtonGrid(bar)
218 227
219 SetButtonGrid(bar,r,c,min(floor(w/c) - bw, floor(h/r) - bh)) 228 SetButtonGrid(bar,r,c,min(floor(w/c) - bw, floor(h/r) - bh))
220 end 229 end
221 230
222 RecomputeGrid = function(bar) 231 local function RecomputeGrid(bar)
223 local w, h = GetSize(bar) 232 local w, h = GetSize(bar)
224 local bw, bh = GetButtonSize(bar) 233 local bw, bh = GetButtonSize(bar)
225 local r, c, s = GetButtonGrid(bar) 234 local r, c, s = GetButtonGrid(bar)
226 235
227 SetButtonGrid(bar, floor(h/(bh+s)), floor(w/(bw+s)), s) 236 SetButtonGrid(bar, floor(h/(bh+s)), floor(w/(bw+s)), s)
228 end 237 end
229 238
230 ClampToButtons = function(bar) 239 local function ClampToButtons(bar)
231 local bw, bh = GetButtonSize(bar) 240 local bw, bh = GetButtonSize(bar)
232 local r, c, s = GetButtonGrid(bar) 241 local r, c, s = GetButtonGrid(bar)
233 SetSize(bar, (bw+s)*c + 1, (bh+s)*r + 1) 242 SetSize(bar, (bw+s)*c + 1, (bh+s)*r + 1)
234 end 243 end
235 244
236 HideGameTooltip = function() 245 local function HideGameTooltip()
237 GameTooltip:Hide() 246 GameTooltip:Hide()
247 end
248
249 local anchorInside = { inside = true }
250 local anchorOutside = { outside = true }
251 local edges = { "BOTTOM", "TOP", "LEFT", "RIGHT" }
252 local oppositeEdges = {
253 TOP = "BOTTOM",
254 BOTTOM = "TOP",
255 LEFT = "RIGHT",
256 RIGHT = "LEFT"
257 }
258 local pointsOnEdge = {
259 BOTTOM = { "BOTTOM", "BOTTOMLEFT", "BOTTOMRIGHT", },
260 TOP = { "TOP", "TOPLEFT", "TOPRIGHT", },
261 RIGHT = { "RIGHT", "BOTTOMRIGHT", "TOPRIGHT", },
262 LEFT = { "LEFT", "BOTTOMLEFT", "TOPLEFT", },
263 }
264 local edgeSelector = {
265 BOTTOM = 1, -- select x of x,y
266 TOP = 1, -- select x of x,y
267 LEFT = 2, -- select y of x,y
268 RIGHT = 2, -- select y of x,y
269 }
270 local snapPoints = {
271 [anchorOutside] = {
272 BOTTOMLEFT = {"BOTTOMRIGHT","TOPLEFT","TOPRIGHT"},
273 BOTTOM = {"TOP"},
274 BOTTOMRIGHT = {"BOTTOMLEFT","TOPRIGHT","TOPLEFT"},
275 RIGHT = {"LEFT"},
276 TOPRIGHT = {"TOPLEFT","BOTTOMRIGHT","BOTTOMLEFT"},
277 TOP = {"BOTTOM"},
278 TOPLEFT = {"TOPRIGHT","BOTTOMLEFT","BOTTOMRIGHT"},
279 LEFT = {"RIGHT"},
280 CENTER = {"CENTER"}
281 },
282 [anchorInside] = {
283 BOTTOMLEFT = {"BOTTOMLEFT"},
284 BOTTOM = {"BOTTOM"},
285 BOTTOMRIGHT = {"BOTTOMRIGHT"},
286 RIGHT = {"RIGHT"},
287 TOPRIGHT = {"TOPRIGHT"},
288 TOP = {"TOP"},
289 TOPLEFT = {"TOPLEFT"},
290 LEFT = {"LEFT"},
291 CENTER = {"CENTER"}
292 }
293 }
294 local insidePointOffsetFuncs = {
295 BOTTOMLEFT = function(x, y) return x, y end,
296 BOTTOM = function(x, y) return 0, y end,
297 BOTTOMRIGHT = function(x, y) return -x, y end,
298 RIGHT = function(x, y) return -x, 0 end,
299 TOPRIGHT = function(x, y) return -x, -y end,
300 TOP = function(x, y) return 0, -y end,
301 TOPLEFT = function(x, y) return x, -y end,
302 LEFT = function(x, y) return x, 0 end,
303 CENTER = function(x, y) return 0, 0 end,
304 }
305 local pointCoordFuncs = {
306 BOTTOMLEFT = function(f) return f:GetLeft(), f:GetBottom() end,
307 BOTTOM = function(f) return nil, f:GetBottom() end,
308 BOTTOMRIGHT = function(f) return f:GetRight(), f:GetBottom() end,
309 RIGHT = function(f) return f:GetRight(), nil end,
310 TOPRIGHT = function(f) return f:GetRight(), f:GetTop() end,
311 TOP = function(f) return nil, f:GetTop() end,
312 TOPLEFT = function(f) return f:GetLeft(), f:GetTop() end,
313 LEFT = function(f) return f:GetLeft(), nil end,
314 CENTER = function(f) return f:GetCenter() end,
315 }
316 local edgeBoundsFuncs = {
317 BOTTOM = function(f) return f:GetLeft(), f:GetRight() end,
318 LEFT = function(f) return f:GetBottom(), f:GetTop() end
319 }
320 edgeBoundsFuncs.TOP = edgeBoundsFuncs.BOTTOM
321 edgeBoundsFuncs.RIGHT = edgeBoundsFuncs.LEFT
322
323
324 -- Returns absolute coordinates x,y of the named point 'p' of frame 'f'
325 local function GetPointCoords( f, p )
326 local x, y = pointCoordFuncs[p](f)
327 if not(x and y) then
328 local cx, cy = f:GetCenter()
329 x = x or cx
330 y = y or cy
331 end
332 return x, y
333 end
334
335
336 -- Returns true if frame 'f1' can be anchored to frame 'f2'
337 local function CheckAnchorable( f1, f2 )
338 -- can't anchor a frame to itself or to nil
339 if f1 == f2 or f2 == nil then
340 return false
341 end
342
343 -- can always anchor to UIParent
344 if f2 == UIParent then
345 return true
346 end
347
348 -- also can't do circular anchoring of frames
349 -- walk the anchor chain, which generally shouldn't be that expensive
350 -- (who nests draggables that deep anyway?)
351 for i = 1, f2:GetNumPoints() do
352 local _, f = f2:GetPoint(i)
353 if not f then f = f2:GetParent() end
354 return CheckAnchorable(f1,f)
355 end
356
357 return true
358 end
359
360 -- Returns true if frames f1 and f2 specified edges overlap
361 local function CheckEdgeOverlap( f1, f2, e )
362 local l1, u1 = edgeBoundsFuncs[e](f1)
363 local l2, u2 = edgeBoundsFuncs[e](f2)
364 return l1 <= l2 and l2 <= u1 or l2 <= l1 and l1 <= u2
365 end
366
367 -- Returns true if point p1 on frame f1 overlaps edge e2 on frame f2
368 local function CheckPointEdgeOverlap( f1, p1, f2, e2 )
369 local l, u = edgeBoundsFuncs[e2](f2)
370 local x, y = GetPointCoords(f1,p1)
371 x = select(edgeSelector[e2], x, y)
372 return l <= x and x <= u
373 end
374
375 -- Returns the distance between corresponding edges. It is
376 -- assumed that the passed in edges e1 and e2 are the same or opposites
377 local function GetEdgeDistance( f1, f2, e1, e2 )
378 local x1, y1 = pointCoordFuncs[e1](f1)
379 local x2, y2 = pointCoordFuncs[e2](f2)
380 return math.abs((x1 or y1) - (x2 or y2))
381 end
382
383 local globalSnapTargets = { [UIParent] = anchorInside }
384
385 local function GetClosestFrameEdge(f1,f2,a)
386 local dist, edge, opp
387 if f2:IsVisible() and CheckAnchorable(f1,f2) then
388 for _, e in pairs(edges) do
389 local o = a.inside and e or oppositeEdges[e]
390 if CheckEdgeOverlap(f1,f2,e) then
391 local d = GetEdgeDistance(f1, f2, e, o)
392 if not dist or (d < dist) then
393 dist, edge, opp = d, e, o
394 end
395 end
396 end
397 end
398 return dist, edge, opp
399 end
400
401 local function GetClosestVisibleEdge( f )
402 local r, o, e1, e2
403 local a = anchorOutside
404 for _, b in pairs(ReAction.bars) do
405 local d, e, opp = GetClosestFrameEdge(f,b:GetFrame(),a)
406 if d and (not r or d < r) then
407 r, o, e1, e2 = d, b:GetFrame(), e, opp
408 end
409 end
410 for f2, a2 in pairs(globalSnapTargets) do
411 local d, e, opp = GetClosestFrameEdge(f,f2,a2)
412 if d and (not r or d < r) then
413 r, o, e1, e2, a = d, f2, e, opp, a2
414 end
415 end
416 return o, e1, e2, a
417 end
418
419 local function GetClosestVisiblePoint(f1)
420 local f2, e1, e2, a = GetClosestVisibleEdge(f1)
421 if f2 then
422 local rsq, p, rp, x, y
423 -- iterate pointsOnEdge in order and use < to prefer edge centers to corners
424 for _, p1 in ipairs(pointsOnEdge[e1]) do
425 if CheckPointEdgeOverlap(f1,p1,f2,e2) then
426 for _, p2 in pairs(snapPoints[a][p1]) do
427 local x1, y1 = GetPointCoords(f1,p1)
428 local x2, y2 = GetPointCoords(f2,p2)
429 local dx = x1 - x2
430 local dy = y1 - y2
431 local rsq2 = dx*dx + dy*dy
432 if not rsq or rsq2 < rsq then
433 rsq, p, rp, x, y = rsq2, p1, p2, dx, dy
434 end
435 end
436 end
437 end
438 return f2, p, rp, x, y
439 end
440 end
441
442 local function GetClosestPointSnapped(f1, rx, ry, xOff, yOff)
443 local o, p, rp, x, y = GetClosestVisiblePoint(f1)
444 local s = false
445
446 local sx, sy = insidePointOffsetFuncs[p](xOff or 0, yOff or 0)
447 local xx, yy = pointCoordFuncs[p](f1)
448 if xx and yy then
449 if math.abs(x) <= rx then
450 x = sx
451 s = true
452 end
453 if math.abs(y) <= ry then
454 y = sy
455 s = true
456 end
457 elseif xx then
458 if math.abs(x) <= rx then
459 x = sx
460 s = true
461 if math.abs(y) <= ry then
462 y = sy
463 end
464 end
465 elseif yy then
466 if math.abs(y) <= ry then
467 y = sy
468 s = true
469 if math.abs(x) <= rx then
470 x = sx
471 end
472 end
473 end
474
475 if x == -0 then x = 0 end
476 if y == -0 then y = 0 end
477
478 if s then
479 return o, p, rp, math.floor(x), math.floor(y)
480 end
481 end
482
483 local function CreateSnapIndicator()
484 local si = CreateFrame("Frame",nil,UIParent)
485 si:SetFrameStrata("HIGH")
486 si:SetHeight(8)
487 si:SetWidth(8)
488 local tex = si:CreateTexture()
489 tex:SetAllPoints()
490 tex:SetTexture(1.0, 0.82, 0, 0.8)
491 tex:SetBlendMode("ADD")
492 tex:SetDrawLayer("OVERLAY")
493 return si
494 end
495
496 local si1 = CreateSnapIndicator()
497 local si2 = CreateSnapIndicator()
498
499 local function DisplaySnapIndicator( f, rx, ry, xOff, yOff )
500 local o, p, rp, x, y, snap = GetClosestPointSnapped(f, rx, ry, xOff, yOff)
501 if o then
502 si1:ClearAllPoints()
503 si2:ClearAllPoints()
504 si1:SetPoint("CENTER", f, p, 0, 0)
505 local xx, yy = pointCoordFuncs[rp](o)
506 x = math.abs(x) <=rx and xx and 0 or x
507 y = math.abs(y) <=ry and yy and 0 or y
508 si2:SetPoint("CENTER", o, rp, x, y)
509 si1:Show()
510 si2:Show()
511 else
512 if si1:IsVisible() then
513 si1:Hide()
514 si2:Hide()
515 end
516 end
517 end
518
519 local function HideSnapIndicator()
520 if si1:IsVisible() then
521 si1:Hide()
522 si2:Hide()
523 end
524 end
525
526 local function RefreshLayoutEditor()
527 ReAction:CallModuleMethod("ConfigUI","RefreshLayoutEditor")
238 end 528 end
239 529
240 CreateControls = function(bar) 530 CreateControls = function(bar)
241 local f = bar.frame 531 local f = bar.frame
242 532
289 579
290 local StopResize = function() 580 local StopResize = function()
291 f:StopMovingOrSizing() 581 f:StopMovingOrSizing()
292 f.isMoving = false 582 f.isMoving = false
293 f:SetScript("OnUpdate",nil) 583 f:SetScript("OnUpdate",nil)
294 StoreExtents(bar) 584 StoreSize(bar)
295 ClampToButtons(bar) 585 ClampToButtons(bar)
296 ApplyAnchor(bar) 586 ApplyAnchor(bar)
587 RefreshLayoutEditor()
297 end 588 end
298 589
299 -- edge drag handles 590 -- edge drag handles
300 for _, point in pairs({"LEFT","TOP","RIGHT","BOTTOM"}) do 591 for _, point in pairs({"LEFT","TOP","RIGHT","BOTTOM"}) do
301 local edge = CreateFrame("Frame",nil,control) 592 local edge = CreateFrame("Frame",nil,control)
416 707
417 control:SetScript("OnDragStart", 708 control:SetScript("OnDragStart",
418 function() 709 function()
419 f:StartMoving() 710 f:StartMoving()
420 f.isMoving = true 711 f.isMoving = true
421 -- TODO: snap indicator update install 712 local w,h = bar:GetButtonSize()
713 f:ClearAllPoints()
714 f:SetScript("OnUpdate", function()
715 if IsShiftKeyDown() then
716 DisplaySnapIndicator(f,w,h)
717 else
718 HideSnapIndicator()
719 end
720 end)
422 end 721 end
423 ) 722 )
723
724 local function updateDragTooltip()
725 GameTooltip:SetOwner(f, "ANCHOR_TOPRIGHT")
726 GameTooltip:AddLine(bar.name)
727 GameTooltip:AddLine(L["Drag to move"])
728 GameTooltip:AddLine(("|cff00ff00%s|r %s"):format(L["Shift-drag"],L["to anchor to nearby frames"]))
729 GameTooltip:AddLine(("|cff00cccc%s|r %s"):format(L["Right-click"],L["for options"]))
730 local _, a = bar:GetAnchor()
731 if a and a ~= "UIParent" then
732 GameTooltip:AddLine(L["Currently anchored to <%s>"]:format(a))
733 end
734 GameTooltip:Show()
735 end
424 736
425 control:SetScript("OnDragStop", 737 control:SetScript("OnDragStop",
426 function() 738 function()
427 f:StopMovingOrSizing() 739 f:StopMovingOrSizing()
428 f.isMoving = false 740 f.isMoving = false
429 f:SetScript("OnUpdate",nil) 741 f:SetScript("OnUpdate",nil)
430 -- TODO: snap frame here 742
743 if IsShiftKeyDown() then
744 local w, h = bar:GetButtonSize()
745 local a, p, rp, x, y = GetClosestPointSnapped(f,w,h)
746 if a then
747 f:ClearAllPoints()
748 f:SetPoint(p,a,rp,x,y)
749 end
750 HideSnapIndicator()
751 end
752
431 StoreExtents(bar) 753 StoreExtents(bar)
754 RefreshLayoutEditor()
755 updateDragTooltip()
432 end 756 end
433 ) 757 )
434 758
435 control:SetScript("OnEnter", 759 control:SetScript("OnEnter",
436 function() 760 function()
442 if suffix then 766 if suffix then
443 name = ("%s %s"):format(name,suffix) 767 name = ("%s %s"):format(name,suffix)
444 end 768 end
445 --]] 769 --]]
446 end 770 end
447 771
448 GameTooltip:SetOwner(f, "ANCHOR_TOPRIGHT") 772 updateDragTooltip()
449 GameTooltip:AddLine(name)
450 GameTooltip:AddLine(L["Drag to move"])
451 --GameTooltip:AddLine(L["Shift-drag for sticky mode"])
452 GameTooltip:AddLine(L["Right-click for options"])
453 GameTooltip:Show()
454 end 773 end
455 ) 774 )
456 775
457 control:SetScript("OnLeave", HideGameTooltip) 776 control:SetScript("OnLeave", HideGameTooltip)
458 777