1 local AceGUI
= LibStub("AceGUI-3.0")
4 local next, pairs
, ipairs
, assert, type = next, pairs
, ipairs
, assert, type
5 local math_min
, math_max
, floor = math
.min, math
.max, floor
6 local select
, tremove, unpack
= select
, table.remove, unpack
9 local CreateFrame
, UIParent
= CreateFrame
, UIParent
11 -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
12 -- List them here for Mikk's FindGlobals script
13 -- GLOBALS: GameTooltip, FONT_COLOR_CODE_CLOSE
15 -- Recycling functions
18 local pool
= setmetatable({},{__mode
='k'})
41 local Type
= "TreeGroup"
44 local DEFAULT_TREE_WIDTH
= 175
45 local DEFAULT_TREE_SIZABLE
= true
47 local PaneBackdrop
= {
48 bgFile
= "Interface\\ChatFrame\\ChatFrameBackground",
49 edgeFile
= "Interface\\Tooltips\\UI-Tooltip-Border",
50 tile
= true, tileSize
= 16, edgeSize
= 16,
51 insets
= { left
= 3, right
= 3, top
= 5, bottom
= 3 }
54 local DraggerBackdrop
= {
55 bgFile
= "Interface\\Tooltips\\UI-Tooltip-Background",
57 tile
= true, tileSize
= 16, edgeSize
= 0,
58 insets
= { left
= 3, right
= 3, top
= 7, bottom
= 7 }
61 local function OnAcquire(self
)
62 self
:SetTreeWidth(DEFAULT_TREE_WIDTH
,DEFAULT_TREE_SIZABLE
)
63 self
:EnableButtonTooltips(true)
66 local function OnRelease(self
)
68 self
.frame
:ClearAllPoints()
71 for k
, v
in pairs(self
.localstatus
) do
77 self
.localstatus
[k
] = nil
80 self
.localstatus
.scrollvalue
= 0
81 self
.localstatus
.treewidth
= DEFAULT_TREE_WIDTH
82 self
.localstatus
.treesizable
= DEFAULT_TREE_SIZABLE
85 local function GetButtonParents(line
)
86 local parent
= line
.parent
87 if parent
and parent
.value
then
88 return parent
.value
, GetButtonParents(parent
)
92 local function GetButtonUniqueValue(line
)
93 local parent
= line
.parent
94 if parent
and parent
.value
then
95 return GetButtonUniqueValue(parent
).."\001"..line
.value
101 local function ButtonOnClick(this
)
102 local self
= this
.obj
103 self
:Fire("OnClick",this
.uniquevalue
, this
.selected
)
104 if not this
.selected
then
105 self
:SetSelected(this
.uniquevalue
)
113 local function ExpandOnClick(this
)
114 local button
= this
.button
115 local self
= button
.obj
116 local status
= (self
.status
or self
.localstatus
).groups
117 status
[button
.uniquevalue
] = not status
[button
.uniquevalue
]
121 local function ButtonOnDoubleClick(button
)
122 local self
= button
.obj
123 local status
= self
.status
or self
.localstatus
124 local status
= (self
.status
or self
.localstatus
).groups
125 status
[button
.uniquevalue
] = not status
[button
.uniquevalue
]
129 local function EnableButtonTooltips(self
, enable
)
130 self
.enabletooltips
= enable
133 local function Button_OnEnter(this
)
134 local self
= this
.obj
135 self
:Fire("OnButtonEnter", this
.uniquevalue
, this
)
137 if self
.enabletooltips
then
138 GameTooltip
:SetOwner(this
, "ANCHOR_NONE")
139 GameTooltip
:SetPoint("LEFT",this
,"RIGHT")
140 GameTooltip
:SetText(this
.text
:GetText() or "", 1, .82, 0, 1)
146 local function Button_OnLeave(this
)
147 local self
= this
.obj
148 self
:Fire("OnButtonLeave", this
.uniquevalue
, this
)
150 if self
.enabletooltips
then
156 local buttoncount
= 1
157 local function CreateButton(self
)
158 local button
= CreateFrame("Button",("AceGUI30TreeButton%d"):format(buttoncount
),self
.treeframe
, "OptionsListButtonTemplate")
159 buttoncount
= buttoncount
+ 1
162 local icon
= button
:CreateTexture(nil, "OVERLAY")
167 button
:SetScript("OnClick",ButtonOnClick
)
168 button
:SetScript("OnDoubleClick", ButtonOnDoubleClick
)
169 button
:SetScript("OnEnter",Button_OnEnter
)
170 button
:SetScript("OnLeave",Button_OnLeave
)
172 button
.toggle
.button
= button
173 button
.toggle
:SetScript("OnClick",ExpandOnClick
)
178 local function UpdateButton(button
, treeline
, selected
, canExpand
, isExpanded
)
179 local self
= button
.obj
180 local toggle
= button
.toggle
181 local frame
= self
.frame
182 local text
= treeline
.text
or ""
183 local icon
= treeline
.icon
184 local iconCoords
= treeline
.iconCoords
185 local level
= treeline
.level
186 local value
= treeline
.value
187 local uniquevalue
= treeline
.uniquevalue
188 local disabled
= treeline
.disabled
190 button
.treeline
= treeline
192 button
.uniquevalue
= uniquevalue
194 button
:LockHighlight()
195 button
.selected
= true
197 button
:UnlockHighlight()
198 button
.selected
= false
200 local normalTexture
= button
:GetNormalTexture()
201 local line
= button
.line
203 if ( level
== 1 ) then
204 button
:SetNormalFontObject("GameFontNormal")
205 button
:SetHighlightFontObject("GameFontHighlight")
206 button
.text
:SetPoint("LEFT", (icon
and 16 or 0) + 8, 2)
208 button
:SetNormalFontObject("GameFontHighlightSmall")
209 button
:SetHighlightFontObject("GameFontHighlightSmall")
210 button
.text
:SetPoint("LEFT", (icon
and 16 or 0) + 8 * level
, 2)
214 button
:EnableMouse(false)
215 button
.text
:SetText("|cff808080"..text
..FONT_COLOR_CODE_CLOSE
)
217 button
.text
:SetText(text
)
218 button
:EnableMouse(true)
222 button
.icon
:SetTexture(icon
)
223 button
.icon
:SetPoint("LEFT", button
, "LEFT", 8 * level
, (level
== 1) and 0 or 1)
225 button
.icon
:SetTexture(nil)
229 button
.icon
:SetTexCoord(unpack(iconCoords
))
231 button
.icon
:SetTexCoord(0, 1, 0, 1)
235 if not isExpanded
then
236 toggle
:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-UP")
237 toggle
:SetPushedTexture("Interface\\Buttons\\UI-PlusButton-DOWN")
239 toggle
:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-UP")
240 toggle
:SetPushedTexture("Interface\\Buttons\\UI-MinusButton-DOWN")
249 local function OnScrollValueChanged(this
, value
)
250 if this
.obj
.noupdate
then return end
251 local self
= this
.obj
252 local status
= self
.status
or self
.localstatus
253 status
.scrollvalue
= value
258 -- called to set an external table to store status in
259 local function SetStatusTable(self
, status
)
260 assert(type(status
) == "table")
262 if not status
.groups
then
265 if not status
.scrollvalue
then
266 status
.scrollvalue
= 0
268 if not status
.treewidth
then
269 status
.treewidth
= DEFAULT_TREE_WIDTH
271 if not status
.treesizable
then
272 status
.treesizable
= DEFAULT_TREE_SIZABLE
274 self
:SetTreeWidth(status
.treewidth
,status
.treesizable
)
278 --sets the tree to be displayed
320 local function SetTree(self
, tree
, filter
)
323 assert(type(tree
) == "table")
329 local function ShouldDisplayLevel(tree
)
331 for k
, v
in ipairs(tree
) do
332 if v
.children
== nil and v
.visible
~= false then
334 elseif v
.children
then
335 result
= result
or ShouldDisplayLevel(v
.children
)
337 if result
then return result
end
342 local function addLine(self
, v
, tree
, level
, parent
)
347 line
.iconCoords
= v
.iconCoords
348 line
.disabled
= v
.disabled
352 line
.visible
= v
.visible
353 line
.uniquevalue
= GetButtonUniqueValue(line
)
355 line
.hasChildren
= true
357 line
.hasChildren
= nil
359 self
.lines
[#self
.lines
+1] = line
363 local function BuildLevel(self
, tree
, level
, parent
)
364 local groups
= (self
.status
or self
.localstatus
).groups
365 local hasChildren
= self
.hasChildren
367 for i
, v
in ipairs(tree
) do
369 if not self
.filter
or ShouldDisplayLevel(v
.children
) then
370 local line
= addLine(self
, v
, tree
, level
, parent
)
371 if groups
[line
.uniquevalue
] then
372 self
:BuildLevel(v
.children
, level
+1, line
)
375 elseif v
.visible
~= false or not self
.filter
then
376 addLine(self
, v
, tree
, level
, parent
)
381 --fire an update after one frame to catch the treeframes height
382 local function FirstFrameUpdate(this
)
383 local self
= this
.obj
384 this
:SetScript("OnUpdate",nil)
388 local function ResizeUpdate(this
)
389 this
.obj
:RefreshTree()
392 local function RefreshTree(self
)
393 local buttons
= self
.buttons
394 local lines
= self
.lines
396 for i
, v
in ipairs(buttons
) do
400 local t
= tremove(lines
)
407 if not self
.tree
then return end
408 --Build the list of visible entries from the tree and status tables
409 local status
= self
.status
or self
.localstatus
410 local groupstatus
= status
.groups
411 local tree
= self
.tree
413 local treeframe
= self
.treeframe
415 self
:BuildLevel(tree
, 1)
417 local numlines
= #lines
419 local maxlines
= (floor(((self
.treeframe
:GetHeight()or 0) - 20 ) / 18))
423 if numlines
<= maxlines
then
424 --the whole tree fits in the frame
425 status
.scrollvalue
= 0
426 self
:ShowScroll(false)
427 first
, last
= 1, numlines
429 self
:ShowScroll(true)
430 --scrolling will be needed
432 self
.scrollbar
:SetMinMaxValues(0, numlines
- maxlines
)
433 --check if we are scrolled down too far
434 if numlines
- status
.scrollvalue
< maxlines
then
435 status
.scrollvalue
= numlines
- maxlines
436 self
.scrollbar
:SetValue(status
.scrollvalue
)
439 first
, last
= status
.scrollvalue
+1, status
.scrollvalue
+ maxlines
443 for i
= first
, last
do
444 local line
= lines
[i
]
445 local button
= buttons
[buttonnum
]
447 button
= self
:CreateButton()
449 buttons
[buttonnum
] = button
450 button
:SetParent(treeframe
)
451 button
:SetFrameLevel(treeframe
:GetFrameLevel()+1)
452 button
:ClearAllPoints()
454 if self
.showscroll
then
455 button
:SetPoint("TOPRIGHT", self
.treeframe
,"TOPRIGHT",-22,-10)
456 button
:SetPoint("TOPLEFT", self
.treeframe
, "TOPLEFT", 0, -10)
458 button
:SetPoint("TOPRIGHT", self
.treeframe
,"TOPRIGHT",0,-10)
459 button
:SetPoint("TOPLEFT", self
.treeframe
, "TOPLEFT", 0, -10)
462 button
:SetPoint("TOPRIGHT", buttons
[buttonnum
-1], "BOTTOMRIGHT",0,0)
463 button
:SetPoint("TOPLEFT", buttons
[buttonnum
-1], "BOTTOMLEFT",0,0)
467 UpdateButton(button
, line
, status
.selected
== line
.uniquevalue
, line
.hasChildren
, groupstatus
[line
.uniquevalue
] )
469 buttonnum
= buttonnum
+ 1
474 local function SetSelected(self
, value
)
475 local status
= self
.status
or self
.localstatus
476 if status
.selected
~= value
then
477 status
.selected
= value
478 self
:Fire("OnGroupSelected", value
)
482 local function BuildUniqueValue(...)
483 local n
= select('#', ...)
487 return (...).."\001"..BuildUniqueValue(select(2,...))
491 local function Select(self
, uniquevalue
, ...)
493 local status
= self
.status
or self
.localstatus
494 local groups
= status
.groups
495 for i
= 1, select('#', ...) do
496 groups
[BuildUniqueValue(select(i
, ...))] = true
498 status
.selected
= uniquevalue
500 self
:Fire("OnGroupSelected", uniquevalue
)
503 local function SelectByPath(self
, ...)
504 self
:Select(BuildUniqueValue(...), ...)
507 --Selects a tree node by UniqueValue
508 local function SelectByValue(self
, uniquevalue
)
509 self
:Select(uniquevalue
, ("\001"):split(uniquevalue
))
513 local function ShowScroll(self
, show
)
514 self
.showscroll
= show
516 self
.scrollbar
:Show()
517 if self
.buttons
[1] then
518 self
.buttons
[1]:SetPoint("TOPRIGHT", self
.treeframe
,"TOPRIGHT",-22,-10)
521 self
.scrollbar
:Hide()
522 if self
.buttons
[1] then
523 self
.buttons
[1]:SetPoint("TOPRIGHT", self
.treeframe
,"TOPRIGHT",0,-10)
528 local function OnWidthSet(self
, width
)
529 local content
= self
.content
530 local treeframe
= self
.treeframe
531 local status
= self
.status
or self
.localstatus
532 status
.fullwidth
= width
534 local contentwidth
= width
- status
.treewidth
- 20
535 if contentwidth
< 0 then
538 content
:SetWidth(contentwidth
)
539 content
.width
= contentwidth
541 local maxtreewidth
= math_min(400, width
- 50)
543 if maxtreewidth
> 100 and status
.treewidth
> maxtreewidth
then
544 self
:SetTreeWidth(maxtreewidth
, status
.treesizable
)
546 treeframe
:SetMaxResize(maxtreewidth
,1600)
550 local function OnHeightSet(self
, height
)
551 local content
= self
.content
552 local contentheight
= height
- 20
553 if contentheight
< 0 then
556 content
:SetHeight(contentheight
)
557 content
.height
= contentheight
561 local function TreeOnMouseWheel(this
, delta
)
562 local self
= this
.obj
563 if self
.showscroll
then
564 local scrollbar
= self
.scrollbar
565 local min, max = scrollbar
:GetMinMaxValues()
566 local value
= scrollbar
:GetValue()
567 local newvalue
= math_min(max,math_max(min,value
- delta
))
568 if value
~= newvalue
then
569 scrollbar
:SetValue(newvalue
)
574 local function SetTreeWidth(self
, treewidth
, resizable
)
575 if not resizable
then
576 if type(treewidth
) == 'number' then
578 elseif type(treewidth
) == 'boolean' then
579 resizable
= treewidth
580 treewidth
= DEFAULT_TREE_WIDTH
583 treewidth
= DEFAULT_TREE_WIDTH
586 self
.treeframe
:SetWidth(treewidth
)
587 self
.dragger
:EnableMouse(resizable
)
589 local status
= self
.status
or self
.localstatus
590 status
.treewidth
= treewidth
591 status
.treesizable
= resizable
593 -- recalculate the content width
594 if status
.fullwidth
then
595 self
:OnWidthSet(status
.fullwidth
)
599 local function draggerLeave(this
)
600 this
:SetBackdropColor(1, 1, 1, 0)
603 local function draggerEnter(this
)
604 this
:SetBackdropColor(1, 1, 1, 0.8)
607 local function draggerDown(this
)
608 local treeframe
= this
:GetParent()
609 treeframe
:StartSizing("RIGHT")
612 local function draggerUp(this
)
613 local treeframe
= this
:GetParent()
614 local self
= treeframe
.obj
615 local frame
= treeframe
:GetParent()
616 treeframe
:StopMovingOrSizing()
617 --treeframe:SetScript("OnUpdate", nil)
618 treeframe
:SetUserPlaced(false)
619 --Without this :GetHeight will get stuck on the current height, causing the tree contents to not resize
620 treeframe
:SetHeight(0)
621 treeframe
:SetPoint("TOPLEFT",frame
,"TOPLEFT",0,0)
622 treeframe
:SetPoint("BOTTOMLEFT",frame
,"BOTTOMLEFT",0,0)
624 local status
= self
.status
or self
.localstatus
625 status
.treewidth
= treeframe
:GetWidth()
627 treeframe
.obj
:Fire("OnTreeResize",treeframe
:GetWidth())
628 -- recalculate the content width
629 treeframe
.obj
:OnWidthSet(status
.fullwidth
)
630 -- update the layout of the content
631 treeframe
.obj
:DoLayout()
634 local function LayoutFinished(self
, width
, height
)
635 if self
.noAutoHeight
then return end
636 self
:SetHeight((height
or 0) + 20)
639 local createdcount
= 0
640 local function Constructor()
641 local frame
= CreateFrame("Frame",nil,UIParent
)
647 self
.hasChildren
= {}
648 self
.localstatus
= {}
649 self
.localstatus
.groups
= {}
652 local treeframe
= CreateFrame("Frame",nil,frame
)
654 treeframe
:SetPoint("TOPLEFT",frame
,"TOPLEFT",0,0)
655 treeframe
:SetPoint("BOTTOMLEFT",frame
,"BOTTOMLEFT",0,0)
656 treeframe
:SetWidth(DEFAULT_TREE_WIDTH
)
657 treeframe
:SetScript("OnUpdate",FirstFrameUpdate
)
658 treeframe
:SetScript("OnSizeChanged",ResizeUpdate
)
660 treeframe
:EnableMouseWheel(true)
661 treeframe
:SetScript("OnMouseWheel", TreeOnMouseWheel
)
662 treeframe
:SetBackdrop(PaneBackdrop
)
663 treeframe
:SetBackdropColor(0.1,0.1,0.1,0.5)
664 treeframe
:SetBackdropBorderColor(0.4,0.4,0.4)
666 treeframe
:SetResizable(true)
667 treeframe
:SetMinResize(100, 1)
668 treeframe
:SetMaxResize(400,1600)
669 local dragger
= CreateFrame("Frame", nil, treeframe
)
671 dragger
:SetPoint("TOP", treeframe
, "TOPRIGHT")
672 dragger
:SetPoint("BOTTOM", treeframe
, "BOTTOMRIGHT")
673 dragger
:SetBackdrop(DraggerBackdrop
)
674 dragger
:SetBackdropColor(1, 1, 1, 0)
675 dragger
:SetScript("OnMouseDown", draggerDown
)
676 dragger
:SetScript("OnMouseUp", draggerUp
)
677 dragger
:SetScript("OnEnter", draggerEnter
)
678 dragger
:SetScript("OnLeave", draggerLeave
)
680 self
.dragger
= dragger
681 self
.treeframe
= treeframe
682 self
.OnRelease
= OnRelease
683 self
.OnAcquire
= OnAcquire
685 self
.SetTree
= SetTree
686 self
.SetTreeWidth
= SetTreeWidth
687 self
.RefreshTree
= RefreshTree
688 self
.SetStatusTable
= SetStatusTable
689 self
.BuildLevel
= BuildLevel
690 self
.CreateButton
= CreateButton
691 self
.SetSelected
= SetSelected
692 self
.ShowScroll
= ShowScroll
693 self
.SetStatusTable
= SetStatusTable
695 self
.SelectByValue
= SelectByValue
696 self
.SelectByPath
= SelectByPath
697 self
.OnWidthSet
= OnWidthSet
698 self
.OnHeightSet
= OnHeightSet
699 self
.EnableButtonTooltips
= EnableButtonTooltips
700 --self.Filter = Filter
701 self
.LayoutFinished
= LayoutFinished
706 createdcount
= createdcount
+ 1
707 local scrollbar
= CreateFrame("Slider",("AceConfigDialogTreeGroup%dScrollBar"):format(createdcount
),treeframe
,"UIPanelScrollBarTemplate")
708 self
.scrollbar
= scrollbar
709 local scrollbg
= scrollbar
:CreateTexture(nil,"BACKGROUND")
710 scrollbg
:SetAllPoints(scrollbar
)
711 scrollbg
:SetTexture(0,0,0,0.4)
714 scrollbar
:SetPoint("TOPRIGHT",treeframe
,"TOPRIGHT",-10,-26)
715 scrollbar
:SetPoint("BOTTOMRIGHT",treeframe
,"BOTTOMRIGHT",-10,26)
716 scrollbar
:SetScript("OnValueChanged", OnScrollValueChanged
)
717 scrollbar
:SetMinMaxValues(0,0)
718 self
.localstatus
.scrollvalue
= 0
719 scrollbar
:SetValueStep(1)
720 scrollbar
:SetValue(0)
721 scrollbar
:SetWidth(16)
724 local border
= CreateFrame("Frame",nil,frame
)
726 border
:SetPoint("TOPLEFT",treeframe
,"TOPRIGHT", 0,0)
727 border
:SetPoint("BOTTOMRIGHT",frame
,"BOTTOMRIGHT",0,0)
729 border
:SetBackdrop(PaneBackdrop
)
730 border
:SetBackdropColor(0.1,0.1,0.1,0.5)
731 border
:SetBackdropBorderColor(0.4,0.4,0.4)
734 local content
= CreateFrame("Frame",nil,border
)
735 self
.content
= content
737 content
:SetPoint("TOPLEFT",border
,"TOPLEFT",10,-10)
738 content
:SetPoint("BOTTOMRIGHT",border
,"BOTTOMRIGHT",-10,10)
740 AceGUI
:RegisterAsContainer(self
)
741 --AceGUI:RegisterAsWidget(self)
745 AceGUI
:RegisterWidgetType(Type
,Constructor
,Version
)