Mercurial > wow > reaction
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 |