Mercurial > wow > askmrrobot
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua Fri Jun 05 11:05:15 2015 -0700 @@ -0,0 +1,707 @@ +--[[----------------------------------------------------------------------------- +TreeGroup Container +Container that uses a tree control to switch between groups. +-------------------------------------------------------------------------------]] +local Type, Version = "TreeGroup", 37 +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +-- Lua APIs +local next, pairs, ipairs, assert, type = next, pairs, ipairs, assert, type +local math_min, math_max, floor = math.min, math.max, floor +local select, tremove, unpack, tconcat = select, table.remove, unpack, table.concat + +-- WoW APIs +local CreateFrame, UIParent = CreateFrame, UIParent + +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: GameTooltip, FONT_COLOR_CODE_CLOSE + +-- Recycling functions +local new, del +do + local pool = setmetatable({},{__mode='k'}) + function new() + local t = next(pool) + if t then + pool[t] = nil + return t + else + return {} + end + end + function del(t) + for k in pairs(t) do + t[k] = nil + end + pool[t] = true + end +end + +local DEFAULT_TREE_WIDTH = 175 +local DEFAULT_TREE_SIZABLE = true + +--[[----------------------------------------------------------------------------- +Support functions +-------------------------------------------------------------------------------]] +local function GetButtonUniqueValue(line) + local parent = line.parent + if parent and parent.value then + return GetButtonUniqueValue(parent).."\001"..line.value + else + return line.value + end +end + +local function UpdateButton(button, treeline, selected, canExpand, isExpanded) + local self = button.obj + local toggle = button.toggle + local frame = self.frame + local text = treeline.text or "" + local icon = treeline.icon + local iconCoords = treeline.iconCoords + local level = treeline.level + local value = treeline.value + local uniquevalue = treeline.uniquevalue + local disabled = treeline.disabled + + button.treeline = treeline + button.value = value + button.uniquevalue = uniquevalue + if selected then + button:LockHighlight() + button.selected = true + else + button:UnlockHighlight() + button.selected = false + end + local normalTexture = button:GetNormalTexture() + local line = button.line + button.level = level + if ( level == 1 ) then + button:SetNormalFontObject("GameFontNormal") + button:SetHighlightFontObject("GameFontHighlight") + button.text:SetPoint("LEFT", (icon and 16 or 0) + 8, 2) + else + button:SetNormalFontObject("GameFontHighlightSmall") + button:SetHighlightFontObject("GameFontHighlightSmall") + button.text:SetPoint("LEFT", (icon and 16 or 0) + 8 * level, 2) + end + + if disabled then + button:EnableMouse(false) + button.text:SetText("|cff808080"..text..FONT_COLOR_CODE_CLOSE) + else + button.text:SetText(text) + button:EnableMouse(true) + end + + if icon then + button.icon:SetTexture(icon) + button.icon:SetPoint("LEFT", 8 * level, (level == 1) and 0 or 1) + else + button.icon:SetTexture(nil) + end + + if iconCoords then + button.icon:SetTexCoord(unpack(iconCoords)) + else + button.icon:SetTexCoord(0, 1, 0, 1) + end + + if canExpand then + if not isExpanded then + toggle:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-UP") + toggle:SetPushedTexture("Interface\\Buttons\\UI-PlusButton-DOWN") + else + toggle:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-UP") + toggle:SetPushedTexture("Interface\\Buttons\\UI-MinusButton-DOWN") + end + toggle:Show() + else + toggle:Hide() + end +end + +local function ShouldDisplayLevel(tree) + local result = false + for k, v in ipairs(tree) do + if v.children == nil and v.visible ~= false then + result = true + elseif v.children then + result = result or ShouldDisplayLevel(v.children) + end + if result then return result end + end + return false +end + +local function addLine(self, v, tree, level, parent) + local line = new() + line.value = v.value + line.text = v.text + line.icon = v.icon + line.iconCoords = v.iconCoords + line.disabled = v.disabled + line.tree = tree + line.level = level + line.parent = parent + line.visible = v.visible + line.uniquevalue = GetButtonUniqueValue(line) + if v.children then + line.hasChildren = true + else + line.hasChildren = nil + end + self.lines[#self.lines+1] = line + return line +end + +--fire an update after one frame to catch the treeframes height +local function FirstFrameUpdate(frame) + local self = frame.obj + frame:SetScript("OnUpdate", nil) + self:RefreshTree() +end + +local function BuildUniqueValue(...) + local n = select('#', ...) + if n == 1 then + return ... + else + return (...).."\001"..BuildUniqueValue(select(2,...)) + end +end + +--[[----------------------------------------------------------------------------- +Scripts +-------------------------------------------------------------------------------]] +local function Expand_OnClick(frame) + local button = frame.button + local self = button.obj + local status = (self.status or self.localstatus).groups + status[button.uniquevalue] = not status[button.uniquevalue] + self:RefreshTree() +end + +local function Button_OnClick(frame) + local self = frame.obj + self:Fire("OnClick", frame.uniquevalue, frame.selected) + if not frame.selected then + self:SetSelected(frame.uniquevalue) + frame.selected = true + frame:LockHighlight() + self:RefreshTree() + end + AceGUI:ClearFocus() +end + +local function Button_OnDoubleClick(button) + local self = button.obj + local status = self.status or self.localstatus + local status = (self.status or self.localstatus).groups + status[button.uniquevalue] = not status[button.uniquevalue] + self:RefreshTree() +end + +local function Button_OnEnter(frame) + local self = frame.obj + self:Fire("OnButtonEnter", frame.uniquevalue, frame) + + if self.enabletooltips then + GameTooltip:SetOwner(frame, "ANCHOR_NONE") + GameTooltip:SetPoint("LEFT",frame,"RIGHT") + GameTooltip:SetText(frame.text:GetText() or "", 1, .82, 0, true) + + GameTooltip:Show() + end +end + +local function Button_OnLeave(frame) + local self = frame.obj + self:Fire("OnButtonLeave", frame.uniquevalue, frame) + + if self.enabletooltips then + GameTooltip:Hide() + end +end + +local function OnScrollValueChanged(frame, value) + if frame.obj.noupdate then return end + local self = frame.obj + local status = self.status or self.localstatus + status.scrollvalue = floor(value + 0.5) + self:RefreshTree() + AceGUI:ClearFocus() +end + +local function Tree_OnSizeChanged(frame) + frame.obj:RefreshTree() +end + +local function Tree_OnMouseWheel(frame, delta) + local self = frame.obj + if self.showscroll then + local scrollbar = self.scrollbar + local min, max = scrollbar:GetMinMaxValues() + local value = scrollbar:GetValue() + local newvalue = math_min(max,math_max(min,value - delta)) + if value ~= newvalue then + scrollbar:SetValue(newvalue) + end + end +end + +local function Dragger_OnLeave(frame) + frame:SetBackdropColor(1, 1, 1, 0) +end + +local function Dragger_OnEnter(frame) + frame:SetBackdropColor(1, 1, 1, 0.8) +end + +local function Dragger_OnMouseDown(frame) + local treeframe = frame:GetParent() + treeframe:StartSizing("RIGHT") +end + +local function Dragger_OnMouseUp(frame) + local treeframe = frame:GetParent() + local self = treeframe.obj + local frame = treeframe:GetParent() + treeframe:StopMovingOrSizing() + --treeframe:SetScript("OnUpdate", nil) + treeframe:SetUserPlaced(false) + --Without this :GetHeight will get stuck on the current height, causing the tree contents to not resize + treeframe:SetHeight(0) + treeframe:SetPoint("TOPLEFT", frame, "TOPLEFT",0,0) + treeframe:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT",0,0) + + local status = self.status or self.localstatus + status.treewidth = treeframe:GetWidth() + + treeframe.obj:Fire("OnTreeResize",treeframe:GetWidth()) + -- recalculate the content width + treeframe.obj:OnWidthSet(status.fullwidth) + -- update the layout of the content + treeframe.obj:DoLayout() +end + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] +local methods = { + ["OnAcquire"] = function(self) + self:SetTreeWidth(DEFAULT_TREE_WIDTH, DEFAULT_TREE_SIZABLE) + self:EnableButtonTooltips(true) + end, + + ["OnRelease"] = function(self) + self.status = nil + for k, v in pairs(self.localstatus) do + if k == "groups" then + for k2 in pairs(v) do + v[k2] = nil + end + else + self.localstatus[k] = nil + end + end + self.localstatus.scrollvalue = 0 + self.localstatus.treewidth = DEFAULT_TREE_WIDTH + self.localstatus.treesizable = DEFAULT_TREE_SIZABLE + end, + + ["EnableButtonTooltips"] = function(self, enable) + self.enabletooltips = enable + end, + + ["CreateButton"] = function(self) + local num = AceGUI:GetNextWidgetNum("TreeGroupButton") + local button = CreateFrame("Button", ("AceGUI30TreeButton%d"):format(num), self.treeframe, "OptionsListButtonTemplate") + button.obj = self + + local icon = button:CreateTexture(nil, "OVERLAY") + icon:SetWidth(14) + icon:SetHeight(14) + button.icon = icon + + button:SetScript("OnClick",Button_OnClick) + button:SetScript("OnDoubleClick", Button_OnDoubleClick) + button:SetScript("OnEnter",Button_OnEnter) + button:SetScript("OnLeave",Button_OnLeave) + + button.toggle.button = button + button.toggle:SetScript("OnClick",Expand_OnClick) + + return button + end, + + ["SetStatusTable"] = function(self, status) + assert(type(status) == "table") + self.status = status + if not status.groups then + status.groups = {} + end + if not status.scrollvalue then + status.scrollvalue = 0 + end + if not status.treewidth then + status.treewidth = DEFAULT_TREE_WIDTH + end + if status.treesizable == nil then + status.treesizable = DEFAULT_TREE_SIZABLE + end + self:SetTreeWidth(status.treewidth,status.treesizable) + self:RefreshTree() + end, + + --sets the tree to be displayed + ["SetTree"] = function(self, tree, filter) + self.filter = filter + if tree then + assert(type(tree) == "table") + end + self.tree = tree + self:RefreshTree() + end, + + ["BuildLevel"] = function(self, tree, level, parent) + local groups = (self.status or self.localstatus).groups + local hasChildren = self.hasChildren + + for i, v in ipairs(tree) do + if v.children then + if not self.filter or ShouldDisplayLevel(v.children) then + local line = addLine(self, v, tree, level, parent) + if groups[line.uniquevalue] then + self:BuildLevel(v.children, level+1, line) + end + end + elseif v.visible ~= false or not self.filter then + addLine(self, v, tree, level, parent) + end + end + end, + + ["RefreshTree"] = function(self,scrollToSelection) + local buttons = self.buttons + local lines = self.lines + + for i, v in ipairs(buttons) do + v:Hide() + end + while lines[1] do + local t = tremove(lines) + for k in pairs(t) do + t[k] = nil + end + del(t) + end + + if not self.tree then return end + --Build the list of visible entries from the tree and status tables + local status = self.status or self.localstatus + local groupstatus = status.groups + local tree = self.tree + + local treeframe = self.treeframe + + status.scrollToSelection = status.scrollToSelection or scrollToSelection -- needs to be cached in case the control hasn't been drawn yet (code bails out below) + + self:BuildLevel(tree, 1) + + local numlines = #lines + + local maxlines = (floor(((self.treeframe:GetHeight()or 0) - 20 ) / 18)) + if maxlines <= 0 then return end + + local first, last + + scrollToSelection = status.scrollToSelection + status.scrollToSelection = nil + + if numlines <= maxlines then + --the whole tree fits in the frame + status.scrollvalue = 0 + self:ShowScroll(false) + first, last = 1, numlines + else + self:ShowScroll(true) + --scrolling will be needed + self.noupdate = true + self.scrollbar:SetMinMaxValues(0, numlines - maxlines) + --check if we are scrolled down too far + if numlines - status.scrollvalue < maxlines then + status.scrollvalue = numlines - maxlines + end + self.noupdate = nil + first, last = status.scrollvalue+1, status.scrollvalue + maxlines + --show selection? + if scrollToSelection and status.selected then + local show + for i,line in ipairs(lines) do -- find the line number + if line.uniquevalue==status.selected then + show=i + end + end + if not show then + -- selection was deleted or something? + elseif show>=first and show<=last then + -- all good + else + -- scrolling needed! + if show<first then + status.scrollvalue = show-1 + else + status.scrollvalue = show-maxlines + end + first, last = status.scrollvalue+1, status.scrollvalue + maxlines + end + end + if self.scrollbar:GetValue() ~= status.scrollvalue then + self.scrollbar:SetValue(status.scrollvalue) + end + end + + local buttonnum = 1 + for i = first, last do + local line = lines[i] + local button = buttons[buttonnum] + if not button then + button = self:CreateButton() + + buttons[buttonnum] = button + button:SetParent(treeframe) + button:SetFrameLevel(treeframe:GetFrameLevel()+1) + button:ClearAllPoints() + if buttonnum == 1 then + if self.showscroll then + button:SetPoint("TOPRIGHT", -22, -10) + button:SetPoint("TOPLEFT", 0, -10) + else + button:SetPoint("TOPRIGHT", 0, -10) + button:SetPoint("TOPLEFT", 0, -10) + end + else + button:SetPoint("TOPRIGHT", buttons[buttonnum-1], "BOTTOMRIGHT",0,0) + button:SetPoint("TOPLEFT", buttons[buttonnum-1], "BOTTOMLEFT",0,0) + end + end + + UpdateButton(button, line, status.selected == line.uniquevalue, line.hasChildren, groupstatus[line.uniquevalue] ) + button:Show() + buttonnum = buttonnum + 1 + end + + end, + + ["SetSelected"] = function(self, value) + local status = self.status or self.localstatus + if status.selected ~= value then + status.selected = value + self:Fire("OnGroupSelected", value) + end + end, + + ["Select"] = function(self, uniquevalue, ...) + self.filter = false + local status = self.status or self.localstatus + local groups = status.groups + local path = {...} + for i = 1, #path do + groups[tconcat(path, "\001", 1, i)] = true + end + status.selected = uniquevalue + self:RefreshTree(true) + self:Fire("OnGroupSelected", uniquevalue) + end, + + ["SelectByPath"] = function(self, ...) + self:Select(BuildUniqueValue(...), ...) + end, + + ["SelectByValue"] = function(self, uniquevalue) + self:Select(uniquevalue, ("\001"):split(uniquevalue)) + end, + + ["ShowScroll"] = function(self, show) + self.showscroll = show + if show then + self.scrollbar:Show() + if self.buttons[1] then + self.buttons[1]:SetPoint("TOPRIGHT", self.treeframe,"TOPRIGHT",-22,-10) + end + else + self.scrollbar:Hide() + if self.buttons[1] then + self.buttons[1]:SetPoint("TOPRIGHT", self.treeframe,"TOPRIGHT",0,-10) + end + end + end, + + ["OnWidthSet"] = function(self, width) + local content = self.content + local treeframe = self.treeframe + local status = self.status or self.localstatus + status.fullwidth = width + + local contentwidth = width - status.treewidth - 20 + if contentwidth < 0 then + contentwidth = 0 + end + content:SetWidth(contentwidth) + content.width = contentwidth + + local maxtreewidth = math_min(400, width - 50) + + if maxtreewidth > 100 and status.treewidth > maxtreewidth then + self:SetTreeWidth(maxtreewidth, status.treesizable) + end + treeframe:SetMaxResize(maxtreewidth, 1600) + end, + + ["OnHeightSet"] = function(self, height) + local content = self.content + local contentheight = height - 20 + if contentheight < 0 then + contentheight = 0 + end + content:SetHeight(contentheight) + content.height = contentheight + end, + + ["SetTreeWidth"] = function(self, treewidth, resizable) + if not resizable then + if type(treewidth) == 'number' then + resizable = false + elseif type(treewidth) == 'boolean' then + resizable = treewidth + treewidth = DEFAULT_TREE_WIDTH + else + resizable = false + treewidth = DEFAULT_TREE_WIDTH + end + end + self.treeframe:SetWidth(treewidth) + self.dragger:EnableMouse(resizable) + + local status = self.status or self.localstatus + status.treewidth = treewidth + status.treesizable = resizable + + -- recalculate the content width + if status.fullwidth then + self:OnWidthSet(status.fullwidth) + end + end, + + ["GetTreeWidth"] = function(self) + local status = self.status or self.localstatus + return status.treewidth or DEFAULT_TREE_WIDTH + end, + + ["LayoutFinished"] = function(self, width, height) + if self.noAutoHeight then return end + self:SetHeight((height or 0) + 20) + end +} + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] +local PaneBackdrop = { + bgFile = "Interface\\ChatFrame\\ChatFrameBackground", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, tileSize = 16, edgeSize = 16, + insets = { left = 3, right = 3, top = 5, bottom = 3 } +} + +local DraggerBackdrop = { + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = nil, + tile = true, tileSize = 16, edgeSize = 0, + insets = { left = 3, right = 3, top = 7, bottom = 7 } +} + +local function Constructor() + local num = AceGUI:GetNextWidgetNum(Type) + local frame = CreateFrame("Frame", nil, UIParent) + + local treeframe = CreateFrame("Frame", nil, frame) + treeframe:SetPoint("TOPLEFT") + treeframe:SetPoint("BOTTOMLEFT") + treeframe:SetWidth(DEFAULT_TREE_WIDTH) + treeframe:EnableMouseWheel(true) + treeframe:SetBackdrop(PaneBackdrop) + treeframe:SetBackdropColor(0.1, 0.1, 0.1, 0.5) + treeframe:SetBackdropBorderColor(0.4, 0.4, 0.4) + treeframe:SetResizable(true) + treeframe:SetMinResize(100, 1) + treeframe:SetMaxResize(400, 1600) + treeframe:SetScript("OnUpdate", FirstFrameUpdate) + treeframe:SetScript("OnSizeChanged", Tree_OnSizeChanged) + treeframe:SetScript("OnMouseWheel", Tree_OnMouseWheel) + + local dragger = CreateFrame("Frame", nil, treeframe) + dragger:SetWidth(8) + dragger:SetPoint("TOP", treeframe, "TOPRIGHT") + dragger:SetPoint("BOTTOM", treeframe, "BOTTOMRIGHT") + dragger:SetBackdrop(DraggerBackdrop) + dragger:SetBackdropColor(1, 1, 1, 0) + dragger:SetScript("OnEnter", Dragger_OnEnter) + dragger:SetScript("OnLeave", Dragger_OnLeave) + dragger:SetScript("OnMouseDown", Dragger_OnMouseDown) + dragger:SetScript("OnMouseUp", Dragger_OnMouseUp) + + local scrollbar = CreateFrame("Slider", ("AceConfigDialogTreeGroup%dScrollBar"):format(num), treeframe, "UIPanelScrollBarTemplate") + scrollbar:SetScript("OnValueChanged", nil) + scrollbar:SetPoint("TOPRIGHT", -10, -26) + scrollbar:SetPoint("BOTTOMRIGHT", -10, 26) + scrollbar:SetMinMaxValues(0,0) + scrollbar:SetValueStep(1) + scrollbar:SetValue(0) + scrollbar:SetWidth(16) + scrollbar:SetScript("OnValueChanged", OnScrollValueChanged) + + local scrollbg = scrollbar:CreateTexture(nil, "BACKGROUND") + scrollbg:SetAllPoints(scrollbar) + scrollbg:SetTexture(0,0,0,0.4) + + local border = CreateFrame("Frame",nil,frame) + border:SetPoint("TOPLEFT", treeframe, "TOPRIGHT") + border:SetPoint("BOTTOMRIGHT") + border:SetBackdrop(PaneBackdrop) + border:SetBackdropColor(0.1, 0.1, 0.1, 0.5) + border:SetBackdropBorderColor(0.4, 0.4, 0.4) + + --Container Support + local content = CreateFrame("Frame", nil, border) + content:SetPoint("TOPLEFT", 10, -10) + content:SetPoint("BOTTOMRIGHT", -10, 10) + + local widget = { + frame = frame, + lines = {}, + levels = {}, + buttons = {}, + hasChildren = {}, + localstatus = { groups = {}, scrollvalue = 0 }, + filter = false, + treeframe = treeframe, + dragger = dragger, + scrollbar = scrollbar, + border = border, + content = content, + type = Type + } + for method, func in pairs(methods) do + widget[method] = func + end + treeframe.obj, dragger.obj, scrollbar.obj = widget, widget, widget + + return AceGUI:RegisterAsContainer(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version)