comparison ReBar.lua @ 1:c11ca1d8ed91

Version 0.1
author Flick <flickerstreak@gmail.com>
date Tue, 20 Mar 2007 21:03:57 +0000
parents
children 8e0ff8ae4c08
comparison
equal deleted inserted replaced
0:4e2ce2894c21 1:c11ca1d8ed91
1
2 -- private constants
3 local insideFrame = 1
4 local outsideFrame = 2
5 local _G = getfenv(0) -- global variable table
6
7 local pointFindTable = {
8 BOTTOMLEFT = function(f) return f:GetLeft(), f:GetBottom() end,
9 BOTTOM = function(f) return nil, f:GetBottom() end,
10 BOTTOMRIGHT = function(f) return f:GetRight(), f:GetBottom() end,
11 RIGHT = function(f) return f:GetRight(), nil end,
12 TOPRIGHT = function(f) return f:GetRight(), f:GetTop() end,
13 TOP = function(f) return nil, f:GetTop() end,
14 TOPLEFT = function(f) return f:GetLeft(), f:GetTop() end,
15 LEFT = function(f) return f:GetLeft(), nil end,
16 }
17
18 local oppositePointTable = {
19 BOTTOMLEFT = "TOPRIGHT",
20 BOTTOM = "TOP",
21 BOTTOMRIGHT = "TOPLEFT",
22 RIGHT = "LEFT",
23 TOPRIGHT = "BOTTOMLEFT",
24 TOP = "BOTTOM",
25 TOPLEFT = "BOTTOMRIGHT",
26 LEFT = "RIGHT"
27 }
28
29 -- private variables
30 local stickyTargets = {
31 [UIParent] = insideFrame,
32 [WorldFrame] = insideFrame
33 }
34
35 -- ReBar is an Ace 2 class prototype object.
36 ReBar = AceLibrary("AceOO-2.0").Class("AceEvent-2.0")
37
38 local dewdrop = AceLibrary("Dewdrop-2.0")
39
40 function ReBar.prototype:init( config, id )
41 ReBar.super.prototype.init(self)
42
43 local buttonClass = config and config.btnConfig and config.btnConfig.type and _G[config.btnConfig.type]
44 self.config = config
45 self.barID = id
46 self.class = { button = buttonClass }
47 self.buttons = { }
48
49 -- create the bar and control widgets
50 self.barFrame = CreateFrame("Frame", "ReBar_"..self.barID, UIParent, "ReBarTemplate")
51 self.controlFrame = _G[self.barFrame:GetName().."Controls"]
52 self.controlFrame.reBar = self
53
54 -- set the text label on the control widget
55 _G[self.controlFrame:GetName().."LabelString"]:SetText(id)
56
57 -- initialize the bar layout
58 self:ApplySize()
59 self:ApplyAnchor()
60 self:LayoutButtons()
61 self:ApplyVisibility()
62
63 -- add bar to stickyTargets list
64 stickyTargets[self.barFrame] = outsideFrame
65
66 -- initialize dewdrop menu
67 dewdrop:Register(self.controlFrame, 'children', function()
68 dewdrop:FeedAceOptionsTable(ReActionGlobalMenuOptions)
69 dewdrop:FeedAceOptionsTable(GenerateReActionBarOptions(self))
70 dewdrop:FeedAceOptionsTable(GenerateReActionButtonOptions(self))
71 dewdrop:FeedAceOptionsTable(ReActionProfileMenuOptions)
72 end,
73 'cursorX', true,
74 'cursorY', true
75 )
76 end
77
78
79 function ReBar.prototype:Destroy()
80 if self.barFrame == dewdrop:GetOpenedParent() then
81 dewdrop:Close()
82 dewdrop:Unregister(self.barFrame)
83 end
84
85 self.barFrame:Hide()
86 self.barFrame:ClearAllPoints()
87 self.barFrame:SetParent(nil)
88
89 -- need to keep around self.config for dewdrop menus in the process of deleting self
90
91 while #self.buttons > 0 do
92 self.class.button:release(table.remove(self.buttons))
93 end
94
95 -- remove from sticky targets table
96 stickyTargets[self.barFrame] = nil
97
98 -- remove from global table
99 -- for some reason after a destroy/recreate the globals still reference
100 -- the old frames
101 setglobal(self.barFrame:GetName(), nil)
102 setglobal(self.barFrame:GetName().."Controls", nil)
103 setglobal(self.controlFrame:GetName().."LabelString", nil)
104 end
105
106
107 -- show/hide the control frame
108 function ReBar.prototype:ShowControls()
109 self.controlFrame:Show()
110 end
111
112 function ReBar.prototype:HideControls()
113 local b = self.barFrame
114 if b.isMoving or b.resizing then
115 b:StopMovingOrSizing()
116 b:SetScript("OnUpdate",nil)
117 end
118 -- close any dewdrop menu owned by us
119 if self.barFrame == dewdrop:GetOpenedParent() then
120 dewdrop:Close()
121 end
122 self.controlFrame:Hide()
123 end
124
125
126
127
128 -- accessors
129 function ReBar.prototype:GetVisibility()
130 return self.config.visible
131 end
132
133 function ReBar.prototype:ToggleVisibility()
134 self.config.visible = not self.config.visible
135 self:ApplyVisibility()
136 end
137
138 function ReBar.prototype:GetOpacity()
139 return self.config.opacity or 100
140 end
141
142 function ReBar.prototype:SetOpacity( o )
143 self.config.opacity = tonumber(o)
144 self:ApplyVisibility()
145 return self.config.opacity
146 end
147
148
149 -- layout methods
150 function ReBar.prototype:ApplySize()
151 local buttonSz = self.config.size or 36
152 local spacing = self.config.spacing or 4
153 local rows = self.config.rows or 1
154 local columns = self.config.columns or 12
155 local w = buttonSz * columns + spacing * (columns + 1)
156 local h = buttonSz * rows + spacing * (rows + 1)
157 local f = self.barFrame
158
159 -- +1: avoid resizing oddities caused by fractional UI scale setting
160 f:SetMinResize(buttonSz + spacing*2 + 1, buttonSz + spacing*2 + 1)
161 f:SetWidth(w + 1)
162 f:SetHeight(h + 1)
163 end
164
165 function ReBar.prototype:ApplyAnchor()
166 local a = self.config.anchor
167 local f = self.barFrame
168 f:ClearAllPoints()
169 f:SetPoint(a.point,getglobal(a.to),a.relPoint,a.x,a.y)
170 end
171
172 function ReBar.prototype:ApplyVisibility()
173 local v = self.config.visibility
174 if type(v) == "table" then
175 if v.class then
176 local _, c = UnitClass("player")
177 v = v.class[c]
178 end
179 elseif type(v) == "string" then
180 local value = getglobal(v)
181 v = value
182 end
183
184 if self.config.opacity then
185 self.barFrame:SetAlpha(self.config.opacity / 100)
186 end
187
188 if v then
189 self.barFrame:Show()
190 else
191 self.barFrame:Hide()
192 end
193 end
194
195 function ReBar.prototype:LayoutButtons()
196 local r = self.config.rows
197 local c = self.config.columns
198 local n = r * c
199 local sp = self.config.spacing
200 local sz = self.config.size
201 local gSize = sp + sz
202
203 for i = 1, n do
204 if self.buttons[i] == nil then
205 table.insert(self.buttons, self.class.button:acquire(self.barFrame, self.config.btnConfig, i))
206 end
207 local b = self.buttons[i]
208 if b == nil then
209 break -- handling for button types that support limited numbers
210 end
211 b:PlaceButton("TOPLEFT", sp + gSize * math.fmod(i-1,c), - (sp + gSize * math.floor((i-1)/c)), sz)
212 end
213
214 -- b == nil, above, should always be the case if and only if i == n. ReBar never monkeys
215 -- with buttons in the middle of the sequence: it always adds or removes on the array end
216 while #self.buttons > n do
217 self.class.button:release(table.remove(self.buttons))
218 end
219
220 end
221
222
223 function ReBar.prototype:StoreAnchor(f, p, rp, x, y)
224 local name = f:GetName()
225 -- no point if we can't store the name or the offsets are incomplete
226 if name and x and y then
227 self.config.anchor = {
228 to = name,
229 point = p,
230 relPoint = rp or p,
231 x = x,
232 y = y
233 }
234 end
235 end
236
237
238
239 -- mouse event handlers (clicking/dragging/resizing the bar)
240 function ReBar.prototype:BeginDrag()
241 local f = self.barFrame
242 f:StartMoving()
243 f.isMoving = true
244 f:SetScript("OnUpdate", function() self:StickyIndicatorUpdate() end)
245 end
246
247 function ReBar.prototype:FinishDrag()
248 local f, p, rp, x, y
249 local bf = self.barFrame
250
251 bf:StopMovingOrSizing()
252 bf.isMoving = false
253
254 bf:SetScript("OnUpdate",nil)
255 if IsShiftKeyDown() then
256 f, p, rp, x, y = self:GetStickyAnchor()
257 ReBarStickyIndicator1:Hide()
258 ReBarStickyIndicator2:Hide()
259 end
260
261 if f == nil then
262 f = UIParent
263 local _
264 _, p,rp,x,y = self:GetClosestPointTo(f)
265 end
266
267 if f then
268 self:StoreAnchor(f,p,rp,x,y)
269 self:ApplyAnchor()
270 end
271 end
272
273 function ReBar.prototype:BeginBarResize( sizingPoint )
274 local f = self.barFrame
275 f:StartSizing(sizingPoint)
276 f.resizing = true
277 f:SetScript("OnUpdate",function() self:ReflowButtons() end)
278 end
279
280 function ReBar.prototype:BeginButtonResize( sizingPoint, mouseBtn )
281 local f = self.barFrame
282 f:StartSizing(sizingPoint)
283 f.resizing = true
284 local r = self.config.rows
285 local c = self.config.columns
286 local s = self.config.spacing
287 local sz = self.config.size
288 if mouseBtn == "LeftButton" then
289 f:SetMinResize(c*(12 + 2*s) +1, r*(12 + 2*s) +1)
290 f:SetScript("OnUpdate",function() self:DragSizeButtons() end)
291 elseif mouseBtn == "RightButton" then
292 f:SetMinResize(c*sz+1, r*sz+1)
293 f:SetScript("OnUpdate",function() self:DragSizeSpacing() end)
294 end
295 end
296
297 function ReBar.prototype:FinishResize()
298 local f = self.barFrame
299 f:StopMovingOrSizing()
300 f.resizing = false
301 f:SetScript("OnUpdate",nil)
302 self:ApplySize()
303 end
304
305
306
307
308 -- sticky anchoring functions
309 function ReBar.prototype:StickyIndicatorUpdate()
310 local si1 = ReBarStickyIndicator1
311 local si2 = ReBarStickyIndicator2
312 if IsShiftKeyDown() then
313 local f, p, rp, x, y = self:GetStickyAnchor()
314 if f then
315 si1:ClearAllPoints()
316 si2:ClearAllPoints()
317 si1:SetPoint("CENTER",self.barFrame,p,0,0)
318 si2:SetPoint("CENTER",f,rp,x,y)
319 si1:Show()
320 si2:Show()
321 return nil
322 end
323 end
324 si1:Hide()
325 si2:Hide()
326 si1:ClearAllPoints()
327 si2:ClearAllPoints()
328 end
329
330 function ReBar.prototype:CheckAnchorable(f)
331 -- can't anchor to self or to a hidden frame
332 if f == self.barFrame or not(f:IsShown()) then return false end
333
334 -- also can't anchor to frames that are anchored to self
335 for i = 1, f:GetNumPoints() do
336 local _, f2 = f:GetPoint(i)
337 if f2 == self.barFrame then return false end
338 end
339
340 return true
341 end
342
343
344 function ReBar.prototype:GetStickyAnchor()
345 local snapRange = (self.config.size + self.config.spacing)
346 local r2, f, p, rp, x, y = self:GetClosestAnchor()
347
348 if f and p then
349 local xx, yy = pointFindTable[p](f)
350 if r2 and r2 < (snapRange*snapRange) then
351 if xx or math.abs(x) < snapRange then x = 0 end
352 if yy or math.abs(y) < snapRange then y = 0 end
353 elseif not(yy) and math.abs(x) < snapRange then
354 x = 0
355 elseif not(xx) and math.abs(y) < snapRange then
356 y = 0
357 else
358 f = nil -- nothing in range
359 end
360 end
361 return f, p, rp, x, y
362 end
363
364 function ReBar.prototype:GetClosestAnchor()
365 -- choose the closest anchor point on the list of target frames
366 local range2, frame, point, relPoint, offsetX, offsetY
367
368 for f, tgtRegion in pairs(stickyTargets) do
369 if self:CheckAnchorable(f) then
370 local r2 ,p, rp, x, y = self:GetClosestPointTo(f,tgtRegion)
371 if r2 then
372 if not(range2 and range2 < r2) then
373 range2, frame, point, relPoint, offsetX, offsetY = r2, f, p, rp, x, y
374 end
375 end
376 end
377 end
378
379 return range2, frame, point, relPoint, offsetX, offsetY
380 end
381
382 function ReBar.prototype:GetClosestPointTo(f,inside)
383 local range2, point, relPoint, offsetX, offsetY
384 local pft = pointFindTable
385 local cx, cy = self.barFrame:GetCenter()
386 local fcx, fcy = f:GetCenter()
387 local fh = f:GetHeight()
388 local fw = f:GetWidth()
389
390 -- compute whether edge bisector intersects target edge
391 local dcx = math.abs(cx-fcx) < fw/2 and (cx-fcx)
392 local dcy = math.abs(cy-fcy) < fh/2 and (cy-fcy)
393
394 for p, func in pairs(pft) do
395 local rp, x, y
396 if inside == outsideFrame then
397 rp = oppositePointTable[p]
398 x, y = self:GetOffsetToPoint(f, func, pft[rp])
399 else
400 rp = p
401 x, y = self:GetOffsetToPoint(f, func, func)
402 end
403
404 -- if anchoring to an edge, only anchor if the center point overlaps the other edge
405 if (x or dcx) and (y or dcy) then
406 local r2 = (x or 0)^2 + (y or 0)^2
407 if range2 == nil or r2 < range2 then
408 range2, point, relPoint, offsetX, offsetY = r2, p, rp, x or dcx, y or dcy
409 end
410 end
411 end
412 return range2, point, relPoint, offsetX, offsetY
413 end
414
415 function ReBar.prototype:GetOffsetToPoint(f,func,ffunc)
416 local x, y = func(self.barFrame) -- coordinates of the point on this frame
417 local fx, fy = ffunc(f) -- coordinates of the point on the target frame
418 -- guarantees: if x then fx, if y then fy
419 return x and (x-fx), y and (y-fy)
420 end
421
422
423
424
425
426
427 -- utility function to get the height, width, and button size attributes
428 function ReBar.prototype:GetLayout()
429 local c = self.config
430 local f = self.barFrame
431 return f:GetWidth(), f:GetHeight(), c.size, c.rows, c.columns, c.spacing
432 end
433
434 -- add and remove buttons dynamically as the bar is resized
435 function ReBar.prototype:ReflowButtons()
436 local w, h, sz, r, c, sp = self:GetLayout()
437
438 self.config.rows = math.floor( (h - sp) / (sz + sp) )
439 self.config.columns = math.floor( (w - sp) / (sz + sp) )
440
441 if self.config.rows ~= r or self.config.columns ~= c then
442 self:LayoutButtons()
443 end
444 end
445
446
447 -- change the size of buttons as the bar is resized
448 function ReBar.prototype:DragSizeButtons()
449 local w, h, sz, r, c, sp = self:GetLayout()
450
451 local newSzW = math.floor((w - (c+1)*sp)/c)
452 local newSzH = math.floor((h - (r+1)*sp)/r)
453
454 self.config.size = math.max(12, math.min(newSzW, newSzH))
455
456 if self.config.size ~= sz then
457 self:LayoutButtons()
458 self:UpdateResizeTooltip()
459 end
460 end
461
462
463 -- change the spacing of buttons as the bar is resized
464 function ReBar.prototype:DragSizeSpacing()
465 local w, h, sz, r, c, sp = self:GetLayout()
466
467 local newSpW = math.floor((w - c*sz)/(c+1))
468 local newSpH = math.floor((h - r*sz)/(r+1))
469
470 self.config.spacing = math.max(0, math.min(newSpW, newSpH))
471
472 if self.config.spacing ~= sp then
473 self:LayoutButtons()
474 self:UpdateResizeTooltip()
475 end
476 end
477
478
479 -- update the drag tooltip to indicate current sizes
480 function ReBar.prototype:UpdateResizeTooltip()
481 GameTooltipTextRight4:SetText(self.config.size)
482 GameTooltipTextRight5:SetText(self.config.spacing)
483 GameTooltip:Show()
484 end
485
486 function ReBar.prototype:ShowTooltip()
487 GameTooltip:SetOwner(self.barFrame, "ANCHOR_TOPRIGHT")
488 GameTooltip:AddLine("Bar "..self.barID)
489 GameTooltip:AddLine("Drag to move")
490 GameTooltip:AddLine("Shift-drag for sticky mode")
491 GameTooltip:AddLine("Right-click for options")
492 GameTooltip:Show()
493 end
494
495 function ReBar.prototype:ShowButtonResizeTooltip(point)
496 GameTooltip:SetOwner(self.barFrame, "ANCHOR_"..point)
497 GameTooltip:AddLine("Drag to resize buttons")
498 GameTooltip:AddLine("Right-click-drag")
499 GameTooltip:AddLine("to change spacing")
500 GameTooltip:AddDoubleLine("Size: ", "0")
501 GameTooltip:AddDoubleLine("Spacing: ", "0")
502 self:UpdateResizeTooltip()
503 end
504
505 function ReBar.prototype:ShowBarResizeTooltip(point)
506 GameTooltip:SetOwner(self.barFrame, "ANCHOR_"..point)
507 GameTooltip:AddLine("Drag to add/remove buttons")
508 GameTooltip:Show()
509 end