comparison Libs/AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua @ 57:01b63b8ed811 v21

total rewrite to version 21
author yellowfive
date Fri, 05 Jun 2015 11:05:15 -0700
parents
children e635cd648e01
comparison
equal deleted inserted replaced
56:75431c084aa0 57:01b63b8ed811
1 --[[-----------------------------------------------------------------------------
2 TreeGroup Container
3 Container that uses a tree control to switch between groups.
4 -------------------------------------------------------------------------------]]
5 local Type, Version = "TreeGroup", 37
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, tconcat = select, table.remove, unpack, table.concat
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, true)
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 = floor(value + 0.5)
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 status.treesizable == nil 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,scrollToSelection)
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 status.scrollToSelection = status.scrollToSelection or scrollToSelection -- needs to be cached in case the control hasn't been drawn yet (code bails out below)
412
413 self:BuildLevel(tree, 1)
414
415 local numlines = #lines
416
417 local maxlines = (floor(((self.treeframe:GetHeight()or 0) - 20 ) / 18))
418 if maxlines <= 0 then return end
419
420 local first, last
421
422 scrollToSelection = status.scrollToSelection
423 status.scrollToSelection = nil
424
425 if numlines <= maxlines then
426 --the whole tree fits in the frame
427 status.scrollvalue = 0
428 self:ShowScroll(false)
429 first, last = 1, numlines
430 else
431 self:ShowScroll(true)
432 --scrolling will be needed
433 self.noupdate = true
434 self.scrollbar:SetMinMaxValues(0, numlines - maxlines)
435 --check if we are scrolled down too far
436 if numlines - status.scrollvalue < maxlines then
437 status.scrollvalue = numlines - maxlines
438 end
439 self.noupdate = nil
440 first, last = status.scrollvalue+1, status.scrollvalue + maxlines
441 --show selection?
442 if scrollToSelection and status.selected then
443 local show
444 for i,line in ipairs(lines) do -- find the line number
445 if line.uniquevalue==status.selected then
446 show=i
447 end
448 end
449 if not show then
450 -- selection was deleted or something?
451 elseif show>=first and show<=last then
452 -- all good
453 else
454 -- scrolling needed!
455 if show<first then
456 status.scrollvalue = show-1
457 else
458 status.scrollvalue = show-maxlines
459 end
460 first, last = status.scrollvalue+1, status.scrollvalue + maxlines
461 end
462 end
463 if self.scrollbar:GetValue() ~= status.scrollvalue then
464 self.scrollbar:SetValue(status.scrollvalue)
465 end
466 end
467
468 local buttonnum = 1
469 for i = first, last do
470 local line = lines[i]
471 local button = buttons[buttonnum]
472 if not button then
473 button = self:CreateButton()
474
475 buttons[buttonnum] = button
476 button:SetParent(treeframe)
477 button:SetFrameLevel(treeframe:GetFrameLevel()+1)
478 button:ClearAllPoints()
479 if buttonnum == 1 then
480 if self.showscroll then
481 button:SetPoint("TOPRIGHT", -22, -10)
482 button:SetPoint("TOPLEFT", 0, -10)
483 else
484 button:SetPoint("TOPRIGHT", 0, -10)
485 button:SetPoint("TOPLEFT", 0, -10)
486 end
487 else
488 button:SetPoint("TOPRIGHT", buttons[buttonnum-1], "BOTTOMRIGHT",0,0)
489 button:SetPoint("TOPLEFT", buttons[buttonnum-1], "BOTTOMLEFT",0,0)
490 end
491 end
492
493 UpdateButton(button, line, status.selected == line.uniquevalue, line.hasChildren, groupstatus[line.uniquevalue] )
494 button:Show()
495 buttonnum = buttonnum + 1
496 end
497
498 end,
499
500 ["SetSelected"] = function(self, value)
501 local status = self.status or self.localstatus
502 if status.selected ~= value then
503 status.selected = value
504 self:Fire("OnGroupSelected", value)
505 end
506 end,
507
508 ["Select"] = function(self, uniquevalue, ...)
509 self.filter = false
510 local status = self.status or self.localstatus
511 local groups = status.groups
512 local path = {...}
513 for i = 1, #path do
514 groups[tconcat(path, "\001", 1, i)] = true
515 end
516 status.selected = uniquevalue
517 self:RefreshTree(true)
518 self:Fire("OnGroupSelected", uniquevalue)
519 end,
520
521 ["SelectByPath"] = function(self, ...)
522 self:Select(BuildUniqueValue(...), ...)
523 end,
524
525 ["SelectByValue"] = function(self, uniquevalue)
526 self:Select(uniquevalue, ("\001"):split(uniquevalue))
527 end,
528
529 ["ShowScroll"] = function(self, show)
530 self.showscroll = show
531 if show then
532 self.scrollbar:Show()
533 if self.buttons[1] then
534 self.buttons[1]:SetPoint("TOPRIGHT", self.treeframe,"TOPRIGHT",-22,-10)
535 end
536 else
537 self.scrollbar:Hide()
538 if self.buttons[1] then
539 self.buttons[1]:SetPoint("TOPRIGHT", self.treeframe,"TOPRIGHT",0,-10)
540 end
541 end
542 end,
543
544 ["OnWidthSet"] = function(self, width)
545 local content = self.content
546 local treeframe = self.treeframe
547 local status = self.status or self.localstatus
548 status.fullwidth = width
549
550 local contentwidth = width - status.treewidth - 20
551 if contentwidth < 0 then
552 contentwidth = 0
553 end
554 content:SetWidth(contentwidth)
555 content.width = contentwidth
556
557 local maxtreewidth = math_min(400, width - 50)
558
559 if maxtreewidth > 100 and status.treewidth > maxtreewidth then
560 self:SetTreeWidth(maxtreewidth, status.treesizable)
561 end
562 treeframe:SetMaxResize(maxtreewidth, 1600)
563 end,
564
565 ["OnHeightSet"] = function(self, height)
566 local content = self.content
567 local contentheight = height - 20
568 if contentheight < 0 then
569 contentheight = 0
570 end
571 content:SetHeight(contentheight)
572 content.height = contentheight
573 end,
574
575 ["SetTreeWidth"] = function(self, treewidth, resizable)
576 if not resizable then
577 if type(treewidth) == 'number' then
578 resizable = false
579 elseif type(treewidth) == 'boolean' then
580 resizable = treewidth
581 treewidth = DEFAULT_TREE_WIDTH
582 else
583 resizable = false
584 treewidth = DEFAULT_TREE_WIDTH
585 end
586 end
587 self.treeframe:SetWidth(treewidth)
588 self.dragger:EnableMouse(resizable)
589
590 local status = self.status or self.localstatus
591 status.treewidth = treewidth
592 status.treesizable = resizable
593
594 -- recalculate the content width
595 if status.fullwidth then
596 self:OnWidthSet(status.fullwidth)
597 end
598 end,
599
600 ["GetTreeWidth"] = function(self)
601 local status = self.status or self.localstatus
602 return status.treewidth or DEFAULT_TREE_WIDTH
603 end,
604
605 ["LayoutFinished"] = function(self, width, height)
606 if self.noAutoHeight then return end
607 self:SetHeight((height or 0) + 20)
608 end
609 }
610
611 --[[-----------------------------------------------------------------------------
612 Constructor
613 -------------------------------------------------------------------------------]]
614 local PaneBackdrop = {
615 bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
616 edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
617 tile = true, tileSize = 16, edgeSize = 16,
618 insets = { left = 3, right = 3, top = 5, bottom = 3 }
619 }
620
621 local DraggerBackdrop = {
622 bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
623 edgeFile = nil,
624 tile = true, tileSize = 16, edgeSize = 0,
625 insets = { left = 3, right = 3, top = 7, bottom = 7 }
626 }
627
628 local function Constructor()
629 local num = AceGUI:GetNextWidgetNum(Type)
630 local frame = CreateFrame("Frame", nil, UIParent)
631
632 local treeframe = CreateFrame("Frame", nil, frame)
633 treeframe:SetPoint("TOPLEFT")
634 treeframe:SetPoint("BOTTOMLEFT")
635 treeframe:SetWidth(DEFAULT_TREE_WIDTH)
636 treeframe:EnableMouseWheel(true)
637 treeframe:SetBackdrop(PaneBackdrop)
638 treeframe:SetBackdropColor(0.1, 0.1, 0.1, 0.5)
639 treeframe:SetBackdropBorderColor(0.4, 0.4, 0.4)
640 treeframe:SetResizable(true)
641 treeframe:SetMinResize(100, 1)
642 treeframe:SetMaxResize(400, 1600)
643 treeframe:SetScript("OnUpdate", FirstFrameUpdate)
644 treeframe:SetScript("OnSizeChanged", Tree_OnSizeChanged)
645 treeframe:SetScript("OnMouseWheel", Tree_OnMouseWheel)
646
647 local dragger = CreateFrame("Frame", nil, treeframe)
648 dragger:SetWidth(8)
649 dragger:SetPoint("TOP", treeframe, "TOPRIGHT")
650 dragger:SetPoint("BOTTOM", treeframe, "BOTTOMRIGHT")
651 dragger:SetBackdrop(DraggerBackdrop)
652 dragger:SetBackdropColor(1, 1, 1, 0)
653 dragger:SetScript("OnEnter", Dragger_OnEnter)
654 dragger:SetScript("OnLeave", Dragger_OnLeave)
655 dragger:SetScript("OnMouseDown", Dragger_OnMouseDown)
656 dragger:SetScript("OnMouseUp", Dragger_OnMouseUp)
657
658 local scrollbar = CreateFrame("Slider", ("AceConfigDialogTreeGroup%dScrollBar"):format(num), treeframe, "UIPanelScrollBarTemplate")
659 scrollbar:SetScript("OnValueChanged", nil)
660 scrollbar:SetPoint("TOPRIGHT", -10, -26)
661 scrollbar:SetPoint("BOTTOMRIGHT", -10, 26)
662 scrollbar:SetMinMaxValues(0,0)
663 scrollbar:SetValueStep(1)
664 scrollbar:SetValue(0)
665 scrollbar:SetWidth(16)
666 scrollbar:SetScript("OnValueChanged", OnScrollValueChanged)
667
668 local scrollbg = scrollbar:CreateTexture(nil, "BACKGROUND")
669 scrollbg:SetAllPoints(scrollbar)
670 scrollbg:SetTexture(0,0,0,0.4)
671
672 local border = CreateFrame("Frame",nil,frame)
673 border:SetPoint("TOPLEFT", treeframe, "TOPRIGHT")
674 border:SetPoint("BOTTOMRIGHT")
675 border:SetBackdrop(PaneBackdrop)
676 border:SetBackdropColor(0.1, 0.1, 0.1, 0.5)
677 border:SetBackdropBorderColor(0.4, 0.4, 0.4)
678
679 --Container Support
680 local content = CreateFrame("Frame", nil, border)
681 content:SetPoint("TOPLEFT", 10, -10)
682 content:SetPoint("BOTTOMRIGHT", -10, 10)
683
684 local widget = {
685 frame = frame,
686 lines = {},
687 levels = {},
688 buttons = {},
689 hasChildren = {},
690 localstatus = { groups = {}, scrollvalue = 0 },
691 filter = false,
692 treeframe = treeframe,
693 dragger = dragger,
694 scrollbar = scrollbar,
695 border = border,
696 content = content,
697 type = Type
698 }
699 for method, func in pairs(methods) do
700 widget[method] = func
701 end
702 treeframe.obj, dragger.obj, scrollbar.obj = widget, widget, widget
703
704 return AceGUI:RegisterAsContainer(widget)
705 end
706
707 AceGUI:RegisterWidgetType(Type, Constructor, Version)