comparison classes/ReBar.lua @ 4:dfd829db3ad0

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