comparison Libs/AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua @ 0:c6ff7ba0e8f6

Reasonably functional now. Cleaning up some stuff which might have to be reverted.
author Zerotorescue
date Thu, 07 Oct 2010 17:17:43 +0200
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:c6ff7ba0e8f6
1 --[[-----------------------------------------------------------------------------
2 TreeGroup Container
3 Container that uses a tree control to switch between groups.
4 -------------------------------------------------------------------------------]]
5 local Type, Version = "TreeGroup", 30
6 local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
7 if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
8
9 -- Lua APIs
10 local next, pairs, ipairs, assert, type = next, pairs, ipairs, assert, type
11 local math_min, math_max, floor = math.min, math.max, floor
12 local select, tremove, unpack = select, table.remove, unpack
13
14 -- WoW APIs
15 local CreateFrame, UIParent = CreateFrame, UIParent
16
17 -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
18 -- List them here for Mikk's FindGlobals script
19 -- GLOBALS: GameTooltip, FONT_COLOR_CODE_CLOSE
20
21 -- Recycling functions
22 local new, del
23 do
24 local pool = setmetatable({},{__mode='k'})
25 function new()
26 local t = next(pool)
27 if t then
28 pool[t] = nil
29 return t
30 else
31 return {}
32 end
33 end
34 function del(t)
35 for k in pairs(t) do
36 t[k] = nil
37 end
38 pool[t] = true
39 end
40 end
41
42 local DEFAULT_TREE_WIDTH = 175
43 local DEFAULT_TREE_SIZABLE = true
44
45 --[[-----------------------------------------------------------------------------
46 Support functions
47 -------------------------------------------------------------------------------]]
48 local function GetButtonUniqueValue(line)
49 local parent = line.parent
50 if parent and parent.value then
51 return GetButtonUniqueValue(parent).."\001"..line.value
52 else
53 return line.value
54 end
55 end
56
57 local function UpdateButton(button, treeline, selected, canExpand, isExpanded)
58 local self = button.obj
59 local toggle = button.toggle
60 local frame = self.frame
61 local text = treeline.text or ""
62 local icon = treeline.icon
63 local iconCoords = treeline.iconCoords
64 local level = treeline.level
65 local value = treeline.value
66 local uniquevalue = treeline.uniquevalue
67 local disabled = treeline.disabled
68
69 button.treeline = treeline
70 button.value = value
71 button.uniquevalue = uniquevalue
72 if selected then
73 button:LockHighlight()
74 button.selected = true
75 else
76 button:UnlockHighlight()
77 button.selected = false
78 end
79 local normalTexture = button:GetNormalTexture()
80 local line = button.line
81 button.level = level
82 if ( level == 1 ) then
83 button:SetNormalFontObject("GameFontNormal")
84 button:SetHighlightFontObject("GameFontHighlight")
85 button.text:SetPoint("LEFT", (icon and 16 or 0) + 8, 2)
86 else
87 button:SetNormalFontObject("GameFontHighlightSmall")
88 button:SetHighlightFontObject("GameFontHighlightSmall")
89 button.text:SetPoint("LEFT", (icon and 16 or 0) + 8 * level, 2)
90 end
91
92 if disabled then
93 button:EnableMouse(false)
94 button.text:SetText("|cff808080"..text..FONT_COLOR_CODE_CLOSE)
95 else
96 button.text:SetText(text)
97 button:EnableMouse(true)
98 end
99
100 if icon then
101 button.icon:SetTexture(icon)
102 button.icon:SetPoint("LEFT", 8 * level, (level == 1) and 0 or 1)
103 else
104 button.icon:SetTexture(nil)
105 end
106
107 if iconCoords then
108 button.icon:SetTexCoord(unpack(iconCoords))
109 else
110 button.icon:SetTexCoord(0, 1, 0, 1)
111 end
112
113 if canExpand then
114 if not isExpanded then
115 toggle:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-UP")
116 toggle:SetPushedTexture("Interface\\Buttons\\UI-PlusButton-DOWN")
117 else
118 toggle:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-UP")
119 toggle:SetPushedTexture("Interface\\Buttons\\UI-MinusButton-DOWN")
120 end
121 toggle:Show()
122 else
123 toggle:Hide()
124 end
125 end
126
127 local function ShouldDisplayLevel(tree)
128 local result = false
129 for k, v in ipairs(tree) do
130 if v.children == nil and v.visible ~= false then
131 result = true
132 elseif v.children then
133 result = result or ShouldDisplayLevel(v.children)
134 end
135 if result then return result end
136 end
137 return false
138 end
139
140 local function addLine(self, v, tree, level, parent)
141 local line = new()
142 line.value = v.value
143 line.text = v.text
144 line.icon = v.icon
145 line.iconCoords = v.iconCoords
146 line.disabled = v.disabled
147 line.tree = tree
148 line.level = level
149 line.parent = parent
150 line.visible = v.visible
151 line.uniquevalue = GetButtonUniqueValue(line)
152 if v.children then
153 line.hasChildren = true
154 else
155 line.hasChildren = nil
156 end
157 self.lines[#self.lines+1] = line
158 return line
159 end
160
161 --fire an update after one frame to catch the treeframes height
162 local function FirstFrameUpdate(frame)
163 local self = frame.obj
164 frame:SetScript("OnUpdate", nil)
165 self:RefreshTree()
166 end
167
168 local function BuildUniqueValue(...)
169 local n = select('#', ...)
170 if n == 1 then
171 return ...
172 else
173 return (...).."\001"..BuildUniqueValue(select(2,...))
174 end
175 end
176
177 --[[-----------------------------------------------------------------------------
178 Scripts
179 -------------------------------------------------------------------------------]]
180 local function Expand_OnClick(frame)
181 local button = frame.button
182 local self = button.obj
183 local status = (self.status or self.localstatus).groups
184 status[button.uniquevalue] = not status[button.uniquevalue]
185 self:RefreshTree()
186 end
187
188 local function Button_OnClick(frame)
189 local self = frame.obj
190 self:Fire("OnClick", frame.uniquevalue, frame.selected)
191 if not frame.selected then
192 self:SetSelected(frame.uniquevalue)
193 frame.selected = true
194 frame:LockHighlight()
195 self:RefreshTree()
196 end
197 AceGUI:ClearFocus()
198 end
199
200 local function Button_OnDoubleClick(button)
201 local self = button.obj
202 local status = self.status or self.localstatus
203 local status = (self.status or self.localstatus).groups
204 status[button.uniquevalue] = not status[button.uniquevalue]
205 self:RefreshTree()
206 end
207
208 local function Button_OnEnter(frame)
209 local self = frame.obj
210 self:Fire("OnButtonEnter", frame.uniquevalue, frame)
211
212 if self.enabletooltips then
213 GameTooltip:SetOwner(frame, "ANCHOR_NONE")
214 GameTooltip:SetPoint("LEFT",frame,"RIGHT")
215 GameTooltip:SetText(frame.text:GetText() or "", 1, .82, 0, 1)
216
217 GameTooltip:Show()
218 end
219 end
220
221 local function Button_OnLeave(frame)
222 local self = frame.obj
223 self:Fire("OnButtonLeave", frame.uniquevalue, frame)
224
225 if self.enabletooltips then
226 GameTooltip:Hide()
227 end
228 end
229
230 local function OnScrollValueChanged(frame, value)
231 if frame.obj.noupdate then return end
232 local self = frame.obj
233 local status = self.status or self.localstatus
234 status.scrollvalue = value
235 self:RefreshTree()
236 AceGUI:ClearFocus()
237 end
238
239 local function Tree_OnSizeChanged(frame)
240 frame.obj:RefreshTree()
241 end
242
243 local function Tree_OnMouseWheel(frame, delta)
244 local self = frame.obj
245 if self.showscroll then
246 local scrollbar = self.scrollbar
247 local min, max = scrollbar:GetMinMaxValues()
248 local value = scrollbar:GetValue()
249 local newvalue = math_min(max,math_max(min,value - delta))
250 if value ~= newvalue then
251 scrollbar:SetValue(newvalue)
252 end
253 end
254 end
255
256 local function Dragger_OnLeave(frame)
257 frame:SetBackdropColor(1, 1, 1, 0)
258 end
259
260 local function Dragger_OnEnter(frame)
261 frame:SetBackdropColor(1, 1, 1, 0.8)
262 end
263
264 local function Dragger_OnMouseDown(frame)
265 local treeframe = frame:GetParent()
266 treeframe:StartSizing("RIGHT")
267 end
268
269 local function Dragger_OnMouseUp(frame)
270 local treeframe = frame:GetParent()
271 local self = treeframe.obj
272 local frame = treeframe:GetParent()
273 treeframe:StopMovingOrSizing()
274 --treeframe:SetScript("OnUpdate", nil)
275 treeframe:SetUserPlaced(false)
276 --Without this :GetHeight will get stuck on the current height, causing the tree contents to not resize
277 treeframe:SetHeight(0)
278 treeframe:SetPoint("TOPLEFT", frame, "TOPLEFT",0,0)
279 treeframe:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT",0,0)
280
281 local status = self.status or self.localstatus
282 status.treewidth = treeframe:GetWidth()
283
284 treeframe.obj:Fire("OnTreeResize",treeframe:GetWidth())
285 -- recalculate the content width
286 treeframe.obj:OnWidthSet(status.fullwidth)
287 -- update the layout of the content
288 treeframe.obj:DoLayout()
289 end
290
291 --[[-----------------------------------------------------------------------------
292 Methods
293 -------------------------------------------------------------------------------]]
294 local methods = {
295 ["OnAcquire"] = function(self)
296 self:SetTreeWidth(DEFAULT_TREE_WIDTH, DEFAULT_TREE_SIZABLE)
297 self:EnableButtonTooltips(true)
298 end,
299
300 ["OnRelease"] = function(self)
301 self.status = nil
302 for k, v in pairs(self.localstatus) do
303 if k == "groups" then
304 for k2 in pairs(v) do
305 v[k2] = nil
306 end
307 else
308 self.localstatus[k] = nil
309 end
310 end
311 self.localstatus.scrollvalue = 0
312 self.localstatus.treewidth = DEFAULT_TREE_WIDTH
313 self.localstatus.treesizable = DEFAULT_TREE_SIZABLE
314 end,
315
316 ["EnableButtonTooltips"] = function(self, enable)
317 self.enabletooltips = enable
318 end,
319
320 ["CreateButton"] = function(self)
321 local num = AceGUI:GetNextWidgetNum("TreeGroupButton")
322 local button = CreateFrame("Button", ("AceGUI30TreeButton%d"):format(num), self.treeframe, "OptionsListButtonTemplate")
323 button.obj = self
324
325 local icon = button:CreateTexture(nil, "OVERLAY")
326 icon:SetWidth(14)
327 icon:SetHeight(14)
328 button.icon = icon
329
330 button:SetScript("OnClick",Button_OnClick)
331 button:SetScript("OnDoubleClick", Button_OnDoubleClick)
332 button:SetScript("OnEnter",Button_OnEnter)
333 button:SetScript("OnLeave",Button_OnLeave)
334
335 button.toggle.button = button
336 button.toggle:SetScript("OnClick",Expand_OnClick)
337
338 return button
339 end,
340
341 ["SetStatusTable"] = function(self, status)
342 assert(type(status) == "table")
343 self.status = status
344 if not status.groups then
345 status.groups = {}
346 end
347 if not status.scrollvalue then
348 status.scrollvalue = 0
349 end
350 if not status.treewidth then
351 status.treewidth = DEFAULT_TREE_WIDTH
352 end
353 if not status.treesizable then
354 status.treesizable = DEFAULT_TREE_SIZABLE
355 end
356 self:SetTreeWidth(status.treewidth,status.treesizable)
357 self:RefreshTree()
358 end,
359
360 --sets the tree to be displayed
361 ["SetTree"] = function(self, tree, filter)
362 self.filter = filter
363 if tree then
364 assert(type(tree) == "table")
365 end
366 self.tree = tree
367 self:RefreshTree()
368 end,
369
370 ["BuildLevel"] = function(self, tree, level, parent)
371 local groups = (self.status or self.localstatus).groups
372 local hasChildren = self.hasChildren
373
374 for i, v in ipairs(tree) do
375 if v.children then
376 if not self.filter or ShouldDisplayLevel(v.children) then
377 local line = addLine(self, v, tree, level, parent)
378 if groups[line.uniquevalue] then
379 self:BuildLevel(v.children, level+1, line)
380 end
381 end
382 elseif v.visible ~= false or not self.filter then
383 addLine(self, v, tree, level, parent)
384 end
385 end
386 end,
387
388 ["RefreshTree"] = function(self)
389 local buttons = self.buttons
390 local lines = self.lines
391
392 for i, v in ipairs(buttons) do
393 v:Hide()
394 end
395 while lines[1] do
396 local t = tremove(lines)
397 for k in pairs(t) do
398 t[k] = nil
399 end
400 del(t)
401 end
402
403 if not self.tree then return end
404 --Build the list of visible entries from the tree and status tables
405 local status = self.status or self.localstatus
406 local groupstatus = status.groups
407 local tree = self.tree
408
409 local treeframe = self.treeframe
410
411 self:BuildLevel(tree, 1)
412
413 local numlines = #lines
414
415 local maxlines = (floor(((self.treeframe:GetHeight()or 0) - 20 ) / 18))
416
417 local first, last
418
419 if numlines <= maxlines then
420 --the whole tree fits in the frame
421 status.scrollvalue = 0
422 self:ShowScroll(false)
423 first, last = 1, numlines
424 else
425 self:ShowScroll(true)
426 --scrolling will be needed
427 self.noupdate = true
428 self.scrollbar:SetMinMaxValues(0, numlines - maxlines)
429 --check if we are scrolled down too far
430 if numlines - status.scrollvalue < maxlines then
431 status.scrollvalue = numlines - maxlines
432 self.scrollbar:SetValue(status.scrollvalue)
433 end
434 self.noupdate = nil
435 first, last = status.scrollvalue+1, status.scrollvalue + maxlines
436 end
437
438 local buttonnum = 1
439 for i = first, last do
440 local line = lines[i]
441 local button = buttons[buttonnum]
442 if not button then
443 button = self:CreateButton()
444
445 buttons[buttonnum] = button
446 button:SetParent(treeframe)
447 button:SetFrameLevel(treeframe:GetFrameLevel()+1)
448 button:ClearAllPoints()
449 if i == 1 then
450 if self.showscroll then
451 button:SetPoint("TOPRIGHT", self.treeframe,"TOPRIGHT",-22,-10)
452 button:SetPoint("TOPLEFT", self.treeframe, "TOPLEFT", 0, -10)
453 else
454 button:SetPoint("TOPRIGHT", self.treeframe,"TOPRIGHT",0,-10)
455 button:SetPoint("TOPLEFT", self.treeframe, "TOPLEFT", 0, -10)
456 end
457 else
458 button:SetPoint("TOPRIGHT", buttons[buttonnum-1], "BOTTOMRIGHT",0,0)
459 button:SetPoint("TOPLEFT", buttons[buttonnum-1], "BOTTOMLEFT",0,0)
460 end
461 end
462
463 UpdateButton(button, line, status.selected == line.uniquevalue, line.hasChildren, groupstatus[line.uniquevalue] )
464 button:Show()
465 buttonnum = buttonnum + 1
466 end
467 end,
468
469 ["SetSelected"] = function(self, value)
470 local status = self.status or self.localstatus
471 if status.selected ~= value then
472 status.selected = value
473 self:Fire("OnGroupSelected", value)
474 end
475 end,
476
477 ["Select"] = function(self, uniquevalue, ...)
478 self.filter = false
479 local status = self.status or self.localstatus
480 local groups = status.groups
481 for i = 1, select('#', ...) do
482 groups[BuildUniqueValue(select(i, ...))] = true
483 end
484 status.selected = uniquevalue
485 self:RefreshTree()
486 self:Fire("OnGroupSelected", uniquevalue)
487 end,
488
489 ["SelectByPath"] = function(self, ...)
490 self:Select(BuildUniqueValue(...), ...)
491 end,
492
493 ["SelectByValue"] = function(self, uniquevalue)
494 self:Select(uniquevalue, ("\001"):split(uniquevalue))
495 end,
496
497 ["ShowScroll"] = function(self, show)
498 self.showscroll = show
499 if show then
500 self.scrollbar:Show()
501 if self.buttons[1] then
502 self.buttons[1]:SetPoint("TOPRIGHT", self.treeframe,"TOPRIGHT",-22,-10)
503 end
504 else
505 self.scrollbar:Hide()
506 if self.buttons[1] then
507 self.buttons[1]:SetPoint("TOPRIGHT", self.treeframe,"TOPRIGHT",0,-10)
508 end
509 end
510 end,
511
512 ["OnWidthSet"] = function(self, width)
513 local content = self.content
514 local treeframe = self.treeframe
515 local status = self.status or self.localstatus
516 status.fullwidth = width
517
518 local contentwidth = width - status.treewidth - 20
519 if contentwidth < 0 then
520 contentwidth = 0
521 end
522 content:SetWidth(contentwidth)
523 content.width = contentwidth
524
525 local maxtreewidth = math_min(400, width - 50)
526
527 if maxtreewidth > 100 and status.treewidth > maxtreewidth then
528 self:SetTreeWidth(maxtreewidth, status.treesizable)
529 end
530 treeframe:SetMaxResize(maxtreewidth, 1600)
531 end,
532
533 ["OnHeightSet"] = function(self, height)
534 local content = self.content
535 local contentheight = height - 20
536 if contentheight < 0 then
537 contentheight = 0
538 end
539 content:SetHeight(contentheight)
540 content.height = contentheight
541 end,
542
543 ["SetTreeWidth"] = function(self, treewidth, resizable)
544 if not resizable then
545 if type(treewidth) == 'number' then
546 resizable = false
547 elseif type(treewidth) == 'boolean' then
548 resizable = treewidth
549 treewidth = DEFAULT_TREE_WIDTH
550 else
551 resizable = false
552 treewidth = DEFAULT_TREE_WIDTH
553 end
554 end
555 self.treeframe:SetWidth(treewidth)
556 self.dragger:EnableMouse(resizable)
557
558 local status = self.status or self.localstatus
559 status.treewidth = treewidth
560 status.treesizable = resizable
561
562 -- recalculate the content width
563 if status.fullwidth then
564 self:OnWidthSet(status.fullwidth)
565 end
566 end,
567
568 ["LayoutFinished"] = function(self, width, height)
569 if self.noAutoHeight then return end
570 self:SetHeight((height or 0) + 20)
571 end
572 }
573
574 --[[-----------------------------------------------------------------------------
575 Constructor
576 -------------------------------------------------------------------------------]]
577 local PaneBackdrop = {
578 bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
579 edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
580 tile = true, tileSize = 16, edgeSize = 16,
581 insets = { left = 3, right = 3, top = 5, bottom = 3 }
582 }
583
584 local DraggerBackdrop = {
585 bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
586 edgeFile = nil,
587 tile = true, tileSize = 16, edgeSize = 0,
588 insets = { left = 3, right = 3, top = 7, bottom = 7 }
589 }
590
591 local function Constructor()
592 local num = AceGUI:GetNextWidgetNum(Type)
593 local frame = CreateFrame("Frame", nil, UIParent)
594
595 local treeframe = CreateFrame("Frame", nil, frame)
596 treeframe:SetPoint("TOPLEFT")
597 treeframe:SetPoint("BOTTOMLEFT")
598 treeframe:SetWidth(DEFAULT_TREE_WIDTH)
599 treeframe:EnableMouseWheel(true)
600 treeframe:SetBackdrop(PaneBackdrop)
601 treeframe:SetBackdropColor(0.1, 0.1, 0.1, 0.5)
602 treeframe:SetBackdropBorderColor(0.4, 0.4, 0.4)
603 treeframe:SetResizable(true)
604 treeframe:SetMinResize(100, 1)
605 treeframe:SetMaxResize(400, 1600)
606 treeframe:SetScript("OnUpdate", FirstFrameUpdate)
607 treeframe:SetScript("OnSizeChanged", Tree_OnSizeChanged)
608 treeframe:SetScript("OnMouseWheel", Tree_OnMouseWheel)
609
610 local dragger = CreateFrame("Frame", nil, treeframe)
611 dragger:SetWidth(8)
612 dragger:SetPoint("TOP", treeframe, "TOPRIGHT")
613 dragger:SetPoint("BOTTOM", treeframe, "BOTTOMRIGHT")
614 dragger:SetBackdrop(DraggerBackdrop)
615 dragger:SetBackdropColor(1, 1, 1, 0)
616 dragger:SetScript("OnEnter", Dragger_OnEnter)
617 dragger:SetScript("OnLeave", Dragger_OnLeave)
618 dragger:SetScript("OnMouseDown", Dragger_OnMouseDown)
619 dragger:SetScript("OnMouseUp", Dragger_OnMouseUp)
620
621 local scrollbar = CreateFrame("Slider", ("AceConfigDialogTreeGroup%dScrollBar"):format(num), treeframe, "UIPanelScrollBarTemplate")
622 scrollbar:SetScript("OnValueChanged", nil)
623 scrollbar:SetPoint("TOPRIGHT", -10, -26)
624 scrollbar:SetPoint("BOTTOMRIGHT", -10, 26)
625 scrollbar:SetMinMaxValues(0,0)
626 scrollbar:SetValueStep(1)
627 scrollbar:SetValue(0)
628 scrollbar:SetWidth(16)
629 scrollbar:SetScript("OnValueChanged", OnScrollValueChanged)
630
631 local scrollbg = scrollbar:CreateTexture(nil, "BACKGROUND")
632 scrollbg:SetAllPoints(scrollbar)
633 scrollbg:SetTexture(0,0,0,0.4)
634
635 local border = CreateFrame("Frame",nil,frame)
636 border:SetPoint("TOPLEFT", treeframe, "TOPRIGHT")
637 border:SetPoint("BOTTOMRIGHT")
638 border:SetBackdrop(PaneBackdrop)
639 border:SetBackdropColor(0.1, 0.1, 0.1, 0.5)
640 border:SetBackdropBorderColor(0.4, 0.4, 0.4)
641
642 --Container Support
643 local content = CreateFrame("Frame", nil, border)
644 content:SetPoint("TOPLEFT", 10, -10)
645 content:SetPoint("BOTTOMRIGHT", -10, 10)
646
647 local widget = {
648 frame = frame,
649 lines = {},
650 levels = {},
651 buttons = {},
652 hasChildren = {},
653 localstatus = { groups = {}, scrollvalue = 0 },
654 filter = false,
655 treeframe = treeframe,
656 dragger = dragger,
657 scrollbar = scrollbar,
658 border = border,
659 content = content,
660 type = Type
661 }
662 for method, func in pairs(methods) do
663 widget[method] = func
664 end
665 treeframe.obj, dragger.obj, scrollbar.obj = widget, widget, widget
666
667 return AceGUI:RegisterAsContainer(widget)
668 end
669
670 AceGUI:RegisterWidgetType(Type, Constructor, Version)