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