comparison classes/ReBar.lua @ 7:f920db5fc6b1

version 0.3
author Flick <flickerstreak@gmail.com>
date Tue, 20 Mar 2007 21:25:29 +0000
parents dfd829db3ad0
children c05fd3e18b4f
comparison
equal deleted inserted replaced
6:2da5089ab7ff 7:f920db5fc6b1
1
2 -- private constants 1 -- private constants
3 local insideFrame = 1 2 local anchoredLabelColor = { r = 1.0, g = 0.82, b = 0.0 }
4 local outsideFrame = 2 3 local nonAnchoredLabelColor = { r = 1.0, g = 1.0, b = 1.0 }
5 4
6 local pointFindTable = { 5 local DRAG_UPDATE_RATE = 0.125 -- cap at 8 Hz
7 BOTTOMLEFT = function(f) return f:GetLeft(), f:GetBottom() end, 6
8 BOTTOM = function(f) return nil, f:GetBottom() end, 7 local nStancePages = {
9 BOTTOMRIGHT = function(f) return f:GetRight(), f:GetBottom() end, 8 WARRIOR = 3,
10 RIGHT = function(f) return f:GetRight(), nil end, 9 ROGUE = 0,
11 TOPRIGHT = function(f) return f:GetRight(), f:GetTop() end, 10 HUNTER = 0,
12 TOP = function(f) return nil, f:GetTop() end, 11 DRUID = 3, -- updated to 4 if talented
13 TOPLEFT = function(f) return f:GetLeft(), f:GetTop() end, 12 PALADIN = 0,
14 LEFT = function(f) return f:GetLeft(), nil end, 13 SHAMAN = 0, -- As far as I know, ghost wolf is not a proper shapeshift form, it's just a buff
15 } 14 MAGE = 0,
16 15 PRIEST = 0, -- updated to 1 if talented
17 local oppositePointTable = { 16 WARLOCK = 0,
18 BOTTOMLEFT = "TOPRIGHT", 17 }
19 BOTTOM = "TOP", 18
20 BOTTOMRIGHT = "TOPLEFT", 19 local stanceMaps = {
21 RIGHT = "LEFT", 20 WARRIOR = {
22 TOPRIGHT = "BOTTOMLEFT", 21 -- note: warriors are never in shapeshift form 0.
23 TOP = "BOTTOM", 22 [1] = "*:1", -- battle stance. All states go to state (page) 1.
24 TOPLEFT = "BOTTOMRIGHT", 23 [2] = "*:2", -- defensive stance. All states go to state (page) 2.
25 LEFT = "RIGHT" 24 [3] = "*:3", -- berserker stance. All states go to state (page) 3.
26 } 25 },
27 26 ROGUE = {},
28 local anchoredLabelColor = { r =0.6, g = 0.2, b = 1.0 } 27 HUNTER = {},
29 local nonAnchoredLabelColor = { r = 1.0, g = 0.82, b = 0.0 } 28 DRUID = {
30 29 [0] = "*:1", -- humanoid form. All states go to state (page) 1.
31 -- private variables 30 [1] = "*:2", -- bear/dire bear form. All states go to state (page) 2.
32 local stickyTargets = { 31 [2] = "*:1", -- aquatic form. All states to go state (page) 1 (same as humanoid)
33 [UIParent] = insideFrame, 32 [3] = "*:3", -- cat form. All states go to state (page) 3
34 [WorldFrame] = insideFrame 33 [4] = "*:1", -- travel form. All states go to state (page) 1 (same as humanoid)
35 } 34 [5] = "*:4", -- oomkin/tree form [talent only]. All states go to state (page) 4
35 [6] = "*:1", -- flight form. All states go to state (page) 1 (same as humanoid) (??? How does this work with oomkin/tree?)
36 },
37 PALADIN = {},
38 SHAMAN = {},
39 MAGE = {},
40 PRIEST = {
41 [0] = "*:1", -- normal form. All states go to page 1.
42 [1] = "*:2", -- shadowform (talent only). All states go to page 2.
43 },
44 WARLOCK = {},
45 }
46
47 local stealthMaps = {
48 WARRIOR = "",
49 ROGUE = "1:2", -- go to page 2 for rogues
50 HUNTER = "",
51 DRUID = "3:5", -- we'll replace with 1:2 if stance mapping is not enabled, and page 5 with 6 if oomkin/tree
52 PALADIN = "",
53 SHAMAN = "",
54 MAGE = "",
55 PRIEST = "",
56 }
57
58 local unstealthMaps = {
59 WARRIOR = "",
60 ROGUE = "2:1", -- go to page 1 for rogues
61 HUNTER = "",
62 DRUID = "5:3", -- we'll replace with 2:1 if stance mapping is not enabled, and page 5 with 6 if oomkin/tree
63 PALADIN = "",
64 SHAMAN = "",
65 MAGE = "",
66 PRIEST = "",
67 }
68
69 -- Immediately fix if a druid with oomkin or tree (note: can't have both)
70 local _, playerClass = UnitClass("player")
71 if playerClass == "DRUID" and GetNumShapeshiftForms() > 4 then
72 nStancePages.DRUID = 4
73 stanceMaps.DRUID[5] = "*:4"
74 stealthMaps.DRUID = "3:6"
75 unstealthMaps.DRUID ="6:3"
76 end
77
78 -- Immediately fix if a priest with shadowform
79 if playerClass == "PRIEST" and GetNumShapeshiftForms() > 1 then
80 nStancePages.PRIEST = 2
81 end
82
83 local AceOO = AceLibrary("AceOO-2.0")
36 84
37 -- ReBar is an Ace 2 class prototype object. 85 -- ReBar is an Ace 2 class prototype object.
38 ReBar = AceLibrary("AceOO-2.0").Class("AceEvent-2.0") 86 ReBar = AceOO.Class("AceEvent-2.0", ReAnchor, ReAnchor.IAnchorable)
39 87
40 local dewdrop = AceLibrary("Dewdrop-2.0") 88
41 89 ----------------------
90 -- Static members
91 ----------------------
92 -- IButtonClass is an interface required of any button class type (not the class objects themselves) to be used with the bar
93 ReBar.IButtonClass = AceOO.Interface {
94 Acquire = "function", -- btn = Acquire(config, barIdx, pages, buttonsPerPage)
95 -- analogous to new(), this should re-use recycled frames for memory efficiency
96 Release = "function", -- Release(btn) should hide and dispose of the button (and recycle it)
97 }
98
99 -- IButton is an interface required of any button objects to be used with the bar
100 ReBar.IButton = AceOO.Interface {
101 GetActionFrame = "function", -- obtains the action frame, presumably inherited from SecureActionButtonTemplate or similar.
102 BarLocked = "function", -- BarLocked() called when the bar is locked.
103 BarUnlocked = "function", -- BarUnlocked() called when the bar is unlocked.
104 PlaceButton = "function", -- PlaceButton(parent, anchorPoint, offsetX, offsetY, squareSz), one-stop position and size setting
105 SetPages = "function", -- SetPages(n): sets number of pages for multi-paging. Can error if paging not supported.
106 }
107
108 -- wrap UIParent and WorldFrame in objects which implement the ReAnchor.IAnchorable interface
109 local UIParentPlaceable = {
110 GetFrame = function() return UIParent end,
111 GetAnchorage = function() return ReAnchor.anchorInside end,
112 }
113
114 local WorldFramePlaceable = {
115 GetFrame = function() return WorldFrame end,
116 GetAnchorage = function() return ReAnchor.anchorInside end,
117 }
118
119 ReBar.anchorTargets = {
120 UIParentPlaceable,
121 WorldFramePlaceable
122 }
123
124 ReBar.UIParentAnchorTarget = {
125 UIParentPlaceable
126 }
127
128
129
130
131
132
133 --------------------
134 -- instance methods
135 --------------------
136 -- construction and destruction
42 function ReBar.prototype:init( config, id ) 137 function ReBar.prototype:init( config, id )
43 ReBar.super.prototype.init(self) 138 ReBar.super.prototype.init(self)
44 139
45 local buttonClass = config and config.btnConfig and config.btnConfig.type and getglobal(config.btnConfig.type)
46 self.config = config 140 self.config = config
47 self.barID = id 141 self.barID = id
48 self.class = { button = buttonClass }
49 self.buttons = { } 142 self.buttons = { }
143 self.locked = true
144
145 self.buttonClass = config.btnConfig and config.btnConfig.type and getglobal(config.btnConfig.type)
146 if not AceOO.inherits(self.buttonClass, ReBar.IButton) then
147 error("ReBar: Supplied Button type does not meet required interface.")
148 end
149
50 150
51 -- create the bar and control widgets 151 -- create the bar and control widgets
52 self.barFrame = CreateFrame("Frame", "ReBar_"..self.barID, UIParent, "ReBarTemplate") 152 self.barFrame = CreateFrame("Frame", "ReBar"..id, config.parent and getglobal(config.parent) or UIParent, "ReBarTemplate")
53 self.controlFrame = getglobal(self.barFrame:GetName().."Controls") 153 self.controlFrame = getglobal(self.barFrame:GetName().."Controls")
54 self.controlFrame.reBar = self 154 self.controlFrame.reBar = self
55 self.barFrame:SetClampedToScreen(true) 155 self.barFrame:SetClampedToScreen(true)
56 156
57 -- set the text label on the control widget 157 -- set the text label on the control widget
58 self.labelString = getglobal(self.controlFrame:GetName().."LabelString") 158 self.labelString = getglobal(self.controlFrame:GetName().."LabelString")
59 self.labelString:SetText(id) 159 self.labelString:SetText(id)
60 160
161 -- initial stateheader state
162 self.barFrame.StateChanged = function() self:StateChanged() end
163 self.barFrame:SetAttribute("state",config.pages and config.pages.currentPage or 1) -- initial state
164
61 -- initialize the bar layout 165 -- initialize the bar layout
62 self:ApplySize() 166 self:ApplySize()
63 self:ApplyAnchor() 167 self:ApplyAnchor()
168 self:AcquireButtons()
64 self:LayoutButtons() 169 self:LayoutButtons()
65 self:ApplyVisibility() 170 self:ApplyVisibility()
66 171
67 -- add bar to stickyTargets list 172 if self.config.pages then
68 stickyTargets[self.barFrame] = outsideFrame 173 self:SetPages(self.config.pages.n)
69 174 self:ApplyAutoStanceSwitch()
70 -- initialize dewdrop menu 175 self:ApplyAutoStealthSwitch()
71 dewdrop:Register(self.controlFrame, 'children', function() 176 self:RefreshPageControls()
72 dewdrop:FeedAceOptionsTable(ReActionGlobalMenuOptions) 177 end
73 dewdrop:FeedAceOptionsTable(GenerateReActionBarOptions(self)) 178
74 dewdrop:FeedAceOptionsTable(GenerateReActionButtonOptions(self)) 179 -- register page up/down buttons with ReBound for keybinding
75 end, 180 if ReBound then
76 'cursorX', true, 181 ReBound:AddKeybindTarget(getglobal(self.barFrame:GetName().."PageUp"))
77 'cursorY', true 182 ReBound:AddKeybindTarget(getglobal(self.barFrame:GetName().."PageDown"))
78 ) 183 end
79 end 184
80 185 -- add bar to anchorTargets list
186 table.insert(ReBar.anchorTargets, self)
187 end
81 188
82 function ReBar.prototype:Destroy() 189 function ReBar.prototype:Destroy()
83 if self.barFrame == dewdrop:GetOpenedParent() then 190 local f = self.barFrame
84 dewdrop:Close()
85 dewdrop:Unregister(self.barFrame)
86 end
87 191
88 self:HideControls() 192 self:HideControls()
89 self.barFrame:Hide() 193 f:Hide()
90 self.barFrame:ClearAllPoints() 194 f:ClearAllPoints()
91 self.barFrame:SetParent(nil) 195 f:SetParent(nil)
92 self.barFrame:SetPoint("BOTTOMRIGHT", UIParent, "TOPLEFT", 0, 0) 196 f:SetPoint("BOTTOMRIGHT", UIParent, "TOPLEFT", 0, 0)
93 197
94 -- need to keep around self.config for dewdrop menus in the process of deleting self
95
96 while #self.buttons > 0 do 198 while #self.buttons > 0 do
97 self.class.button:release(table.remove(self.buttons)) 199 self.buttonClass:Release(table.remove(self.buttons))
98 end 200 end
99 201
100 -- remove from sticky targets table 202 -- remove from anchorTargets table
101 stickyTargets[self.barFrame] = nil 203 for idx, b in ipairs(ReBar.anchorTargets) do
102 204 if b == self then
103 -- remove from global table 205 table.remove(ReBar.anchorTargets, idx)
104 -- for some reason after a destroy/recreate the globals still reference 206 break
105 -- the old frames 207 end
106 setglobal(self.barFrame:GetName(), nil) 208 end
107 setglobal(self.barFrame:GetName().."Controls", nil) 209
108 setglobal(self.controlFrame:GetName().."LabelString", nil) 210 -- remove from globals
109 end 211 local n = f:GetName()
212 setglobal(n, nil)
213 setglobal(n.."Control", nil)
214 setglobal(n.."Controls", nil)
215 setglobal(n.."ControlsLabelString", nil)
216 setglobal(n.."PageUp", nil)
217 setglobal(n.."PageDown", nil)
218 setglobal(n.."Page", nil)
219 setglobal(n.."PageNumber", nil)
220 setglobal(n.."PageNumberLabel", nil)
221 setglobal(n.."PageNumberLabelText", nil)
222 end
223
224
225
226
227 -- ReAnchor.IAnchorable interface implementation
228 function ReBar.prototype:GetFrame()
229 return self.barFrame
230 end
231
232 function ReBar.prototype:GetAnchorage()
233 return ReAnchor.anchorOutside
234 end
235
236
110 237
111 238
112 -- show/hide the control frame 239 -- show/hide the control frame
113 function ReBar.prototype:ShowControls() 240 function ReBar.prototype:ShowControls()
241 self.locked = false
114 self.controlFrame:Show() 242 self.controlFrame:Show()
115 for _, b in ipairs(self.buttons) do 243 for _, b in ipairs(self.buttons) do
116 b:BarUnlocked() 244 b:BarUnlocked()
117 end 245 b:DisplayVisibility()
246 end
247 self:ApplyVisibility()
118 end 248 end
119 249
120 function ReBar.prototype:HideControls() 250 function ReBar.prototype:HideControls()
251 self.locked = true
121 local b = self.barFrame 252 local b = self.barFrame
122 if b.isMoving or b.resizing then 253 if b.isMoving or b.resizing then
123 b:StopMovingOrSizing() 254 b:StopMovingOrSizing()
124 b:SetScript("OnUpdate",nil) 255 b:SetScript("OnUpdate",nil)
125 end 256 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 257 for _, b in ipairs(self.buttons) do
131 b:BarLocked() 258 b:BarLocked()
259 b:DisplayVisibility()
132 end 260 end
133 self.controlFrame:Hide() 261 self.controlFrame:Hide()
134 end 262 self:ApplyVisibility()
135 263 end
136 264
265 function ReBar.prototype:GetControlFrame()
266 return self.controlFrame
267 end
137 268
138 269
139 -- accessors 270 -- accessors
140 function ReBar.prototype:GetVisibility() 271 function ReBar.prototype:GetVisibility()
141 return self.config.visible 272 return self.config.visible
142 end 273 end
143 274
144 function ReBar.prototype:ToggleVisibility() 275 function ReBar.prototype:ToggleVisibility()
145 self.config.visible = not self.config.visible 276 self:SetVisibility( not self:GetVisibility() )
277 end
278
279 function ReBar.prototype:SetVisibility(v)
280 self.config.visible = v and true or false -- force data integrity
146 self:ApplyVisibility() 281 self:ApplyVisibility()
147 end 282 end
148 283
149 function ReBar.prototype:GetOpacity() 284 function ReBar.prototype:GetOpacity()
150 return self.config.opacity or 100 285 return tonumber(self.config.opacity) or 100
151 end 286 end
152 287
153 function ReBar.prototype:SetOpacity( o ) 288 function ReBar.prototype:SetOpacity( o )
154 self.config.opacity = tonumber(o) 289 o = tonumber(o)
155 self:ApplyVisibility() 290 if o then
156 return self.config.opacity 291 self.config.opacity = o
157 end 292 self:ApplyVisibility()
293 return self.config.opacity
294 end
295 end
296
297 function ReBar.prototype:GetButtonList()
298 return self.buttons
299 end
300
301 function ReBar.prototype:GetGrowLeft()
302 return self.config.growLeft
303 end
304
305 function ReBar.prototype:SetGrowLeft(g)
306 self.config.growLeft = g and true or false
307 self:LayoutButtons()
308 end
309
310 function ReBar.prototype:GetGrowUp()
311 return self.config.growUp
312 end
313
314 function ReBar.prototype:SetGrowUp(g)
315 self.config.growUp = g and true or false
316 self:LayoutButtons()
317 end
318
319 function ReBar.prototype:GetColumnMajor()
320 return self.config.columnMajor
321 end
322
323 function ReBar.prototype:SetColumnMajor(m)
324 self.config.columnMajor = m and true or false
325 self:LayoutButtons()
326 end
327
328
329 -- paging methods
330 function ReBar.prototype:IsPagingDisabled()
331 return not (self.config.pages and self.config.pages.n > 1)
332 end
333
334 function ReBar.prototype:GetPages()
335 return self.config.pages and self.config.pages.n or 1
336 end
337
338 function ReBar.prototype:SetPages(n)
339 n = tonumber(n)
340 if n and n >= 1 then
341 self.config.pages = self.config.pages or { }
342 self.config.pages.n = n
343 for _, btn in pairs(self.buttons) do
344 btn:SetPages(n)
345 end
346 if n > 1 then
347 local statebutton = "0:page1;"
348 -- map states 1-n to 'page1'-'pagen'
349 -- page 0 is the same as page 1.
350 for i = 1, n do
351 statebutton = statebutton..i..":page"..i..";"
352 end
353 self.barFrame:SetAttribute("statebutton",statebutton)
354 else
355 self.barFrame:SetAttribute("statebutton",ATTRIBUTE_NOOP)
356 end
357 self.barFrame:SetAttribute("statemap-anchor-next","1-"..n)
358 self.barFrame:SetAttribute("statemap-anchor-prev",tostring(n).."-1")
359 self:RefreshPageControls()
360 end
361 end
362
363 function ReBar.prototype:GetAutoStanceSwitch()
364 return self.config.pages and self.config.pages.autoStanceSwitch
365 end
366
367 function ReBar.prototype:SetAutoStanceSwitch( s )
368 if not self.config.pages then
369 self.config.pages = { n = 1 }
370 end
371 self.config.pages.autoStanceSwitch = s and true or false
372 self:ApplyAutoStanceSwitch()
373 end
374
375 function ReBar.prototype:ToggleAutoStanceSwitch()
376 self:SetAutoStanceSwitch( not self:GetAutoStanceSwitch() )
377 end
378
379 function ReBar.prototype:ApplyAutoStanceSwitch()
380 local switch = self:GetAutoStanceSwitch()
381 local _, class = UnitClass("player")
382 if switch then
383 -- check that the number of pages available is sufficient
384 local totalPages = nStancePages[class] + (self:GetAutoStealthSwitch() and 1 or 0)
385 if self:GetPages() < totalPages then
386 self:SetPages(totalPages)
387 end
388 for form, spec in pairs(stanceMaps[class]) do
389 self.barFrame:SetAttribute("statemap-stance-"..form,spec)
390 end
391 -- set initial value
392 self.barFrame:SetAttribute("state-stance",GetShapeshiftForm(true))
393 else
394 for form, _ in pairs(stanceMaps[class]) do
395 self.barFrame:SetAttribute("statemap-stance-"..form, ATTRIBUTE_NOOP)
396 end
397 end
398 end
399
400 function ReBar.prototype:GetAutoStealthSwitch()
401 return self.config.pages and self.config.pages.autoStealthSwitch
402 end
403
404 function ReBar.prototype:SetAutoStealthSwitch( s )
405 if not self.config.pages then
406 self.config.pages = { n = 1 }
407 end
408 self.config.pages.autoStealthSwitch = s and true or false
409 self:ApplyAutoStealthSwitch()
410 end
411
412 function ReBar.prototype:ToggleAutoStealthSwitch()
413 self:SetAutoStealthSwitch( not self:GetAutoStealthSwitch() )
414 end
415
416 function ReBar.prototype:ApplyAutoStealthSwitch()
417 local switch = self:GetAutoStealthSwitch()
418 local _, class = UnitClass("player")
419 if switch then
420 -- check that the number of pages available is sufficient
421 local totalPages = (self:GetAutoStanceSwitch() and nStancePages[class] > 0 and nStancePages[class] or 1) + 1
422 if self:GetPages() < totalPages then
423 self:SetPages(totalPages)
424 end
425 local s, s2
426 if class == "DRUID" and not self:GetAutoStanceSwitch() then
427 -- change mapping for cat->prowl and prowl->cat to 1:2 and 2:1 since no stance mapping
428 s = "1:2"
429 s2 = "2:1"
430 end
431 self.barFrame:SetAttribute("statemap-stealth-1",s or stealthMaps[class])
432 self.barFrame:SetAttribute("statemap-stealth-0",s2 or unstealthMaps[class])
433 -- set initial value
434 self.barFrame:SetAttribute("state-stealth",IsStealthed() or 0)
435 else
436 self.barFrame:SetAttribute("statemap-stealth-1",ATTRIBUTE_NOOP)
437 self.barFrame:SetAttribute("statemap-stealth-0",ATTRIBUTE_NOOP)
438 end
439 end
440
441 function ReBar.prototype:ArePageControlsHidden()
442 return not ( self.config.pages and self.config.pages.showControls )
443 end
444
445 function ReBar.prototype:TogglePageControlsHidden()
446 if self.config.pages then
447 self.config.pages.showControls = not self.config.pages.showControls
448 self:RefreshPageControls()
449 end
450 end
451
452 function ReBar.prototype:GetPageControlsLoc()
453 return self.config.pages and self.config.pages.controlsLoc
454 end
455
456 function ReBar.prototype:SetPageControlsLoc(loc)
457 if self.config.pages then
458 self.config.pages.controlsLoc = loc
459 self:RefreshPageControls()
460 end
461 end
462
463 function ReBar.prototype:RefreshPageControls()
464 local b = self.barFrame;
465 local upArrow = getglobal(b:GetName().."PageUp")
466 local downArrow = getglobal(b:GetName().."PageDown")
467 local pageNum = getglobal(b:GetName().."PageNumber")
468
469 if self:GetPages() > 1 and self.config.pages.showControls then
470 local loc = self.config.pages.controlsLoc
471
472 pageNum:ClearAllPoints()
473 upArrow:ClearAllPoints()
474 downArrow:ClearAllPoints()
475
476 local vertical = { 0,0,0,1,1,0,1,1 }
477 local horizontal = { 0,1,1,1,0,0,1,0 }
478 local textures = {
479 upArrow:GetNormalTexture(),
480 upArrow:GetPushedTexture(),
481 upArrow:GetDisabledTexture(),
482 upArrow:GetHighlightTexture(),
483 downArrow:GetNormalTexture(),
484 downArrow:GetPushedTexture(),
485 downArrow:GetDisabledTexture(),
486 downArrow:GetHighlightTexture(),
487 }
488
489 local offset = 10
490 local mult = (loc == "RIGHT" or loc == "TOP") and 1 or -1
491 local pageNumOffset = mult * (self.config.spacing/2 - offset)
492 local arrowOffset = mult * offset
493
494 if loc == "Blizzard" or loc == nil then
495 pageNum:SetPoint("LEFT", b, "RIGHT", 28, 0)
496 upArrow:SetPoint("LEFT", b, "RIGHT", -2, 9)
497 downArrow:SetPoint("LEFT", b, "RIGHT", -2, -10)
498 for _, tex in ipairs(textures) do
499 tex:SetTexCoord(unpack(vertical))
500 end
501 elseif loc == "RIGHT" or loc == "LEFT" then
502 local relPoint = loc == "RIGHT" and "LEFT" or "RIGHT"
503 pageNum:SetPoint(relPoint, b, loc, pageNumOffset, 0)
504 upArrow:SetPoint("BOTTOM",pageNum,"TOP",arrowOffset, -12)
505 downArrow:SetPoint("TOP",pageNum,"BOTTOM",arrowOffset, 12)
506 for _, tex in ipairs(textures) do
507 tex:SetTexCoord(unpack(vertical))
508 end
509 else
510 pageNum:SetPoint(loc == "BOTTOM" and "TOP" or "BOTTOM", b, loc, 0, pageNumOffset)
511 upArrow:SetPoint("LEFT",pageNum,"RIGHT",-12,arrowOffset)
512 downArrow:SetPoint("RIGHT",pageNum,"LEFT",12,arrowOffset)
513 for _, tex in ipairs(textures) do
514 tex:SetTexCoord(unpack(horizontal))
515 end
516 end
517 self:StateChanged()
518 upArrow:Show()
519 downArrow:Show()
520 pageNum:Show()
521 else
522 upArrow:Hide()
523 downArrow:Hide()
524 pageNum:Hide()
525 end
526 end
527
528 function ReBar.prototype:StateChanged()
529 local page = self.barFrame:GetAttribute("state") or 1
530 getglobal(self.barFrame:GetName().."PageNumberLabelText"):SetText(page)
531 if self.config.pages then self.config.pages.currentPage = page end
532 end
533
158 534
159 535
160 -- layout methods 536 -- layout methods
161 function ReBar.prototype:ApplySize() 537 function ReBar.prototype:ApplySize()
162 local buttonSz = self.config.size or 36 538 local buttonSz = self.config.size or 36
163 local spacing = self.config.spacing or 4 539 local spacing = self.config.spacing or 4
164 local rows = self.config.rows or 1 540 local rows = self.config.rows or 1
165 local columns = self.config.columns or 12 541 local columns = self.config.columns or 12
166 local w = buttonSz * columns + spacing * (columns + 1) 542 local w = buttonSz * columns + spacing * columns
167 local h = buttonSz * rows + spacing * (rows + 1) 543 local h = buttonSz * rows + spacing * rows
168 local f = self.barFrame 544 local f = self.barFrame
169 545
170 -- +1: avoid resizing oddities caused by fractional UI scale setting 546 -- + 0.1: avoid issues with UI scaling
171 f:SetMinResize(buttonSz + spacing*2 + 1, buttonSz + spacing*2 + 1) 547 f:SetMinResize(buttonSz + spacing + 0.1, buttonSz + spacing + 0.1)
172 f:SetWidth(w + 1) 548 f:SetWidth(w + 0.1)
173 f:SetHeight(h + 1) 549 f:SetHeight(h + 0.1)
174 end 550 end
175 551
176 function ReBar.prototype:ApplyAnchor() 552 function ReBar.prototype:ApplyAnchor()
177 local a = self.config.anchor 553 local a = self.config.anchor
178 local f = self.barFrame 554 local f = self.barFrame
179 if a then 555 if a then
180 f:ClearAllPoints() 556 f:ClearAllPoints()
181 f:SetPoint(a.point,getglobal(a.to),a.relPoint,a.x,a.y) 557 f:SetPoint(a.point,getglobal(a.frame),a.relPoint,a.x,a.y)
182 local color = anchoredLabelColor 558 local color = anchoredLabelColor
183 if a.to == "UIParent" or a.to == "WorldFrame" then 559 if a.frame == "UIParent" or a.frame == "WorldFrame" then
184 color = nonAnchoredLabelColor 560 color = nonAnchoredLabelColor
185 end 561 end
186 self.labelString:SetTextColor(color.r, color.g, color.b) 562 self.labelString:SetTextColor(color.r, color.g, color.b)
187 end 563 end
188 end 564 end
189 565
190 function ReBar.prototype:ApplyVisibility() 566 function ReBar.prototype:ApplyVisibility()
191 local v = self.config.visibility 567 local v = self.config.visible or not self.locked
192 if type(v) == "table" then 568
193 if v.class then 569 if tonumber(self.config.opacity) 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) 570 self.barFrame:SetAlpha(self.config.opacity / 100)
204 end 571 end
205 572
206 if v then 573 if v then
207 self.barFrame:Show() 574 self.barFrame:Show()
211 end 578 end
212 579
213 function ReBar.prototype:LayoutButtons() 580 function ReBar.prototype:LayoutButtons()
214 local r = self.config.rows 581 local r = self.config.rows
215 local c = self.config.columns 582 local c = self.config.columns
216 local n = r * c
217 local sp = self.config.spacing 583 local sp = self.config.spacing
218 local sz = self.config.size 584 local sz = self.config.size
219 local gSize = sp + sz 585 local gSize = sp + sz
586 local point = (self.config.growUp and "BOTTOM" or "TOP")..(self.config.growLeft and "RIGHT" or "LEFT")
587 local major = self.config.columnMajor and r or c
588
589 for i, b in ipairs(self.buttons) do
590 local x = sp/2 + gSize * math.fmod(i-1,major)
591 local y = sp/2 + gSize * math.floor((i-1)/major)
592 if self.config.columnMajor then x,y = y,x end
593 if self.config.growLeft then x = -x end
594 if not self.config.growUp then y = -y end
595 b:PlaceButton(self.barFrame, point, x, y, sz)
596 end
597 end
598
599 function ReBar.prototype:FlipRowsColumns()
600 self.config.rows, self.config.columns = self.config.columns, self.config.rows
601 self.config.columnMajor = not self.config.columnMajor
602 self:ApplySize()
603 self:LayoutButtons()
604 end
605
606 function ReBar.prototype:AcquireButtons()
607 local n = self.config.rows * self.config.columns
220 608
221 for i = 1, n do 609 for i = 1, n do
222 if self.buttons[i] == nil then 610 if self.buttons[i] == nil then
223 table.insert(self.buttons, self.class.button:acquire(self.barFrame, self.config.btnConfig, i)) 611 local b = self.buttonClass:Acquire(self.config.btnConfig, i, self.config.pages and self.config.pages.n, n)
224 end 612 if b then
613 if not AceOO.inherits(b, ReBar.IButton) then
614 error("ReBar: specified Button class object did not meet required interface")
615 end
616 if self.locked == false then
617 b:BarUnlocked() -- buttons assume they are created in a barlocked state
618 end
619 self.buttons[i] = b
620 self.barFrame:SetAttribute("addchild",b:GetActionFrame())
621 end
622 end
623 end
624
625 local maxn = table.maxn(self.buttons)
626 for i = n+1, table.maxn(self.buttons) do
225 local b = self.buttons[i] 627 local b = self.buttons[i]
226 if b == nil then 628 self.buttons[i] = nil
227 break -- handling for button types that support limited numbers 629 if b then
228 end 630 self.buttonClass:Release(b)
229 b:PlaceButton("TOPLEFT", sp + gSize * math.fmod(i-1,c), - (sp + gSize * math.floor((i-1)/c)), sz) 631 end
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 632 end
237 633
238 end 634 end
239 635
240 636
241 function ReBar.prototype:StoreAnchor(f, p, rp, x, y) 637 function ReBar.prototype:StoreAnchor(f, p, rp, x, y)
242 local name = f:GetName() 638 local name = f:GetName()
243 -- no point if we can't store the name or the offsets are incomplete 639 -- no point if we can't store the name or the offsets are incomplete
244 if name and x and y then 640 if name and x and y then
245 self.config.anchor = { 641 self.config.anchor = {
246 to = name, 642 frame = name,
247 point = p, 643 point = p,
248 relPoint = rp or p, 644 relPoint = rp or p,
249 x = x, 645 x = x,
250 y = y 646 y = y
251 } 647 }
252 end 648 end
253 end 649 end
254 650
255 651
256 652
653 function ReBar.prototype:StickyIndicatorUpdate()
654 if IsShiftKeyDown() then
655 local snapRange = self.config.size + self.config.spacing
656 self:DisplaySnapIndicator(ReBar.anchorTargets, snapRange, 0, 0)
657 else
658 self:HideSnapIndicator()
659 end
660 end
661
662
257 -- mouse event handlers (clicking/dragging/resizing the bar) 663 -- mouse event handlers (clicking/dragging/resizing the bar)
258 function ReBar.prototype:BeginDrag() 664 function ReBar.prototype:BeginDrag()
259 local f = self.barFrame 665 local f = self.barFrame
260 f:StartMoving() 666 f:StartMoving()
261 f.isMoving = true 667 f.isMoving = true
262 f:SetScript("OnUpdate", function() self:StickyIndicatorUpdate() end) 668 self.updateTime = DRAG_UPDATE_RATE
669 f:SetScript("OnUpdate", function(frame, elapsed) self:StickyIndicatorUpdate(elapsed) end)
263 end 670 end
264 671
265 function ReBar.prototype:FinishDrag() 672 function ReBar.prototype:FinishDrag()
266 local f, p, rp, x, y 673 local o, p, rp, x, y
267 local bf = self.barFrame 674 local bf = self.barFrame
675 local snapRange = self.config.size + self.config.spacing
268 676
269 bf:StopMovingOrSizing() 677 bf:StopMovingOrSizing()
270 bf.isMoving = false 678 bf.isMoving = false
271 679
272 bf:SetScript("OnUpdate",nil) 680 bf:SetScript("OnUpdate",nil)
273 if IsShiftKeyDown() then 681 if IsShiftKeyDown() then
274 f, p, rp, x, y = self:GetStickyAnchor() 682 o, p, rp, x, y = self:GetClosestPointSnapped(ReBar.anchorTargets, snapRange, 0, 0)
275 ReBarStickyIndicator1:Hide() 683 end
276 ReBarStickyIndicator2:Hide() 684
277 end 685 if o == nil then
278 686 o, p, rp, x, y = self:GetClosestVisiblePoint(ReBar.UIParentAnchorTarget, snapRange, 0, 0)
279 if f == nil then 687 end
280 f = UIParent 688
281 local _ 689 self:HideSnapIndicator()
282 _, p,rp,x,y = self:GetClosestPointTo(f) 690 self:StoreAnchor(o:GetFrame(), p, rp, x, y)
283 end 691 self:ApplyAnchor()
284
285 if f then
286 self:StoreAnchor(f,p,rp,x,y)
287 self:ApplyAnchor()
288 end
289 end 692 end
290 693
291 function ReBar.prototype:BeginBarResize( sizingPoint ) 694 function ReBar.prototype:BeginBarResize( sizingPoint )
292 local f = self.barFrame 695 local f = self.barFrame
293 f:StartSizing(sizingPoint) 696 f:StartSizing(sizingPoint)
294 f.resizing = true 697 f.resizing = true
295 f:SetScript("OnUpdate",function() self:ReflowButtons() end) 698 self.updateTime = DRAG_UPDATE_RATE
699 f:SetScript("OnUpdate",function(frame, elapsed) self:ReflowButtons(elapsed) end)
296 end 700 end
297 701
298 function ReBar.prototype:BeginButtonResize( sizingPoint, mouseBtn ) 702 function ReBar.prototype:BeginButtonResize( sizingPoint, mouseBtn )
299 local f = self.barFrame 703 local f = self.barFrame
300 f:StartSizing(sizingPoint) 704 f:StartSizing(sizingPoint)
301 f.resizing = true 705 f.resizing = true
302 local r = self.config.rows 706 local r = self.config.rows
303 local c = self.config.columns 707 local c = self.config.columns
304 local s = self.config.spacing 708 local s = self.config.spacing
305 local sz = self.config.size 709 local sz = self.config.size
710 self.updateTime = DRAG_UPDATE_RATE
306 if mouseBtn == "LeftButton" then 711 if mouseBtn == "LeftButton" then
307 f:SetMinResize(c*(12 + 2*s) +1, r*(12 + 2*s) +1) 712 f:SetMinResize(c*(12 + s) +0.1, r*(12 + s) +0.1)
308 f:SetScript("OnUpdate",function() self:DragSizeButtons() end) 713 f:SetScript("OnUpdate",function(frame, elapsed) self:DragSizeButtons(elapsed) end)
309 elseif mouseBtn == "RightButton" then 714 elseif mouseBtn == "RightButton" then
310 f:SetMinResize(c*sz+1, r*sz+1) 715 f:SetMinResize(c*sz+0.1, r*sz+0.1)
311 f:SetScript("OnUpdate",function() self:DragSizeSpacing() end) 716 f:SetScript("OnUpdate",function(frame, elapsed) self:DragSizeSpacing(elapsed) end)
312 end 717 end
313 end 718 end
314 719
315 function ReBar.prototype:FinishResize() 720 function ReBar.prototype:FinishResize()
316 local f = self.barFrame 721 local f = self.barFrame
321 end 726 end
322 727
323 728
324 729
325 730
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 731
443 732
444 733
445 -- utility function to get the height, width, and button size attributes 734 -- utility function to get the height, width, and button size attributes
446 function ReBar.prototype:GetLayout() 735 function ReBar.prototype:GetLayout()
447 local c = self.config 736 local c = self.config
448 local f = self.barFrame 737 local f = self.barFrame
449 return f:GetWidth(), f:GetHeight(), c.size, c.rows, c.columns, c.spacing 738 return f:GetWidth(), f:GetHeight(), c.size, c.rows, c.columns, c.spacing
450 end 739 end
451 740
741
452 -- add and remove buttons dynamically as the bar is resized 742 -- add and remove buttons dynamically as the bar is resized
453 function ReBar.prototype:ReflowButtons() 743 function ReBar.prototype:ReflowButtons( elapsed )
454 local w, h, sz, r, c, sp = self:GetLayout() 744 self.updateTime = self.updateTime - elapsed
455 745 if self.updateTime <= 0 then
456 self.config.rows = math.floor( (h - sp) / (sz + sp) ) 746 self.updateTime = DRAG_UPDATE_RATE
457 self.config.columns = math.floor( (w - sp) / (sz + sp) ) 747
458 748 local w, h, sz, r, c, sp = self:GetLayout()
459 if self.config.rows ~= r or self.config.columns ~= c then 749
460 self:LayoutButtons() 750 self.config.rows = math.floor( (h+1) / (sz + sp) )
751 self.config.columns = math.floor( (w+1) / (sz + sp) )
752
753 if self.config.rows ~= r or self.config.columns ~= c then
754 self:AcquireButtons()
755 self:LayoutButtons()
756 end
461 end 757 end
462 end 758 end
463 759
464 760
465 -- change the size of buttons as the bar is resized 761 -- change the size of buttons as the bar is resized
466 function ReBar.prototype:DragSizeButtons() 762 function ReBar.prototype:DragSizeButtons( elapsed )
467 local w, h, sz, r, c, sp = self:GetLayout() 763 self.updateTime = self.updateTime - elapsed
468 764 if self.updateTime <= 0 then
469 local newSzW = math.floor((w - (c+1)*sp)/c) 765 self.updateTime = DRAG_UPDATE_RATE
470 local newSzH = math.floor((h - (r+1)*sp)/r) 766
471 767 local w, h, sz, r, c, sp = self:GetLayout()
472 self.config.size = math.max(12, math.min(newSzW, newSzH)) 768
473 769 local newSzW = math.floor((w - c*sp)/c)
474 if self.config.size ~= sz then 770 local newSzH = math.floor((h - r*sp)/r)
475 self:LayoutButtons() 771
476 self:UpdateResizeTooltip() 772 self.config.size = math.max(12, math.min(newSzW, newSzH))
773
774 if self.config.size ~= sz then
775 self:LayoutButtons()
776 self:UpdateResizeTooltip()
777 end
477 end 778 end
478 end 779 end
479 780
480 781
481 -- change the spacing of buttons as the bar is resized 782 -- change the spacing of buttons as the bar is resized
482 function ReBar.prototype:DragSizeSpacing() 783 function ReBar.prototype:DragSizeSpacing( elapsed )
483 local w, h, sz, r, c, sp = self:GetLayout() 784 self.updateTime = self.updateTime - elapsed
484 785 if self.updateTime <= 0 then
485 local newSpW = math.floor((w - c*sz)/(c+1)) 786 self.updateTime = DRAG_UPDATE_RATE
486 local newSpH = math.floor((h - r*sz)/(r+1)) 787
487 788 local w, h, sz, r, c, sp = self:GetLayout()
488 self.config.spacing = math.max(0, math.min(newSpW, newSpH)) 789
489 790 local newSpW = math.floor((w - c*sz)/c)
490 if self.config.spacing ~= sp then 791 local newSpH = math.floor((h - r*sz)/r)
491 self:LayoutButtons() 792
492 self:UpdateResizeTooltip() 793 self.config.spacing = math.max(0, math.min(newSpW, newSpH))
794
795 if self.config.spacing ~= sp then
796 self:LayoutButtons()
797 self:UpdateResizeTooltip()
798 end
493 end 799 end
494 end 800 end
495 801
496 802
497 -- update the drag tooltip to indicate current sizes 803 -- update the drag tooltip to indicate current sizes
501 GameTooltip:Show() 807 GameTooltip:Show()
502 end 808 end
503 809
504 function ReBar.prototype:ShowTooltip() 810 function ReBar.prototype:ShowTooltip()
505 GameTooltip:SetOwner(self.barFrame, "ANCHOR_TOPRIGHT") 811 GameTooltip:SetOwner(self.barFrame, "ANCHOR_TOPRIGHT")
506 GameTooltip:AddLine("Bar "..self.barID) 812 GameTooltip:AddLine(self.config.btnConfig.subtype.." Bar "..self.barID..(self.config.visible and "" or " (Hidden)"))
507 GameTooltip:AddLine("Drag to move") 813 GameTooltip:AddLine("Drag to move")
508 GameTooltip:AddLine("Shift-drag for sticky mode") 814 GameTooltip:AddLine("Shift-drag for sticky mode")
509 GameTooltip:AddLine("Right-click for options") 815 GameTooltip:AddLine("Right-click for options")
510 GameTooltip:Show() 816 GameTooltip:Show()
511 end 817 end