yellowfive@57: --- **AceGUI-3.0** provides access to numerous widgets which can be used to create GUIs. yellowfive@57: -- AceGUI is used by AceConfigDialog to create the option GUIs, but you can use it by itself yellowfive@57: -- to create any custom GUI. There are more extensive examples in the test suite in the Ace3 yellowfive@57: -- stand-alone distribution. yellowfive@57: -- yellowfive@57: -- **Note**: When using AceGUI-3.0 directly, please do not modify the frames of the widgets directly, yellowfive@57: -- as any "unknown" change to the widgets will cause addons that get your widget out of the widget pool yellowfive@57: -- to misbehave. If you think some part of a widget should be modifiable, please open a ticket, and we"ll yellowfive@57: -- implement a proper API to modify it. yellowfive@57: -- @usage yellowfive@57: -- local AceGUI = LibStub("AceGUI-3.0") yellowfive@57: -- -- Create a container frame yellowfive@57: -- local f = AceGUI:Create("Frame") yellowfive@57: -- f:SetCallback("OnClose",function(widget) AceGUI:Release(widget) end) yellowfive@57: -- f:SetTitle("AceGUI-3.0 Example") yellowfive@57: -- f:SetStatusText("Status Bar") yellowfive@57: -- f:SetLayout("Flow") yellowfive@57: -- -- Create a button yellowfive@57: -- local btn = AceGUI:Create("Button") yellowfive@57: -- btn:SetWidth(170) yellowfive@57: -- btn:SetText("Button !") yellowfive@57: -- btn:SetCallback("OnClick", function() print("Click!") end) yellowfive@57: -- -- Add the button to the container yellowfive@57: -- f:AddChild(btn) yellowfive@57: -- @class file yellowfive@57: -- @name AceGUI-3.0 yellowfive@124: -- @release $Id: AceGUI-3.0.lua 1177 2018-06-25 12:12:48Z nevcairiel $ yellowfive@124: local ACEGUI_MAJOR, ACEGUI_MINOR = "AceGUI-3.0", 36 yellowfive@57: local AceGUI, oldminor = LibStub:NewLibrary(ACEGUI_MAJOR, ACEGUI_MINOR) yellowfive@57: yellowfive@57: if not AceGUI then return end -- No upgrade needed yellowfive@57: yellowfive@57: -- Lua APIs yellowfive@57: local tconcat, tremove, tinsert = table.concat, table.remove, table.insert yellowfive@57: local select, pairs, next, type = select, pairs, next, type yellowfive@57: local error, assert, loadstring = error, assert, loadstring yellowfive@57: local setmetatable, rawget, rawset = setmetatable, rawget, rawset yellowfive@57: local math_max = math.max yellowfive@57: yellowfive@57: -- WoW APIs yellowfive@57: local UIParent = UIParent yellowfive@57: yellowfive@57: -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded yellowfive@57: -- List them here for Mikk's FindGlobals script yellowfive@57: -- GLOBALS: geterrorhandler, LibStub yellowfive@57: yellowfive@57: --local con = LibStub("AceConsole-3.0",true) yellowfive@57: yellowfive@57: AceGUI.WidgetRegistry = AceGUI.WidgetRegistry or {} yellowfive@57: AceGUI.LayoutRegistry = AceGUI.LayoutRegistry or {} yellowfive@57: AceGUI.WidgetBase = AceGUI.WidgetBase or {} yellowfive@57: AceGUI.WidgetContainerBase = AceGUI.WidgetContainerBase or {} yellowfive@57: AceGUI.WidgetVersions = AceGUI.WidgetVersions or {} yellowfive@57: yellowfive@57: -- local upvalues yellowfive@57: local WidgetRegistry = AceGUI.WidgetRegistry yellowfive@57: local LayoutRegistry = AceGUI.LayoutRegistry yellowfive@57: local WidgetVersions = AceGUI.WidgetVersions yellowfive@57: yellowfive@57: --[[ yellowfive@57: xpcall safecall implementation yellowfive@57: ]] yellowfive@57: local xpcall = xpcall yellowfive@57: yellowfive@57: local function errorhandler(err) yellowfive@57: return geterrorhandler()(err) yellowfive@57: end yellowfive@57: yellowfive@57: local function CreateDispatcher(argCount) yellowfive@57: local code = [[ yellowfive@57: local xpcall, eh = ... yellowfive@57: local method, ARGS yellowfive@57: local function call() return method(ARGS) end yellowfive@57: yellowfive@57: local function dispatch(func, ...) yellowfive@57: method = func yellowfive@57: if not method then return end yellowfive@57: ARGS = ... yellowfive@57: return xpcall(call, eh) yellowfive@57: end yellowfive@57: yellowfive@57: return dispatch yellowfive@57: ]] yellowfive@57: yellowfive@57: local ARGS = {} yellowfive@57: for i = 1, argCount do ARGS[i] = "arg"..i end yellowfive@57: code = code:gsub("ARGS", tconcat(ARGS, ", ")) yellowfive@57: return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler) yellowfive@57: end yellowfive@57: yellowfive@57: local Dispatchers = setmetatable({}, {__index=function(self, argCount) yellowfive@57: local dispatcher = CreateDispatcher(argCount) yellowfive@57: rawset(self, argCount, dispatcher) yellowfive@57: return dispatcher yellowfive@57: end}) yellowfive@57: Dispatchers[0] = function(func) yellowfive@57: return xpcall(func, errorhandler) yellowfive@57: end yellowfive@57: yellowfive@57: local function safecall(func, ...) yellowfive@57: return Dispatchers[select("#", ...)](func, ...) yellowfive@57: end yellowfive@57: yellowfive@57: -- Recycling functions yellowfive@57: local newWidget, delWidget yellowfive@57: do yellowfive@57: -- Version Upgrade in Minor 29 yellowfive@57: -- Internal Storage of the objects changed, from an array table yellowfive@57: -- to a hash table, and additionally we introduced versioning on yellowfive@57: -- the widgets which would discard all widgets from a pre-29 version yellowfive@57: -- anyway, so we just clear the storage now, and don't try to yellowfive@57: -- convert the storage tables to the new format. yellowfive@57: -- This should generally not cause *many* widgets to end up in trash, yellowfive@57: -- since once dialogs are opened, all addons should be loaded already yellowfive@57: -- and AceGUI should be on the latest version available on the users yellowfive@57: -- setup. yellowfive@57: -- -- nevcairiel - Nov 2nd, 2009 yellowfive@57: if oldminor and oldminor < 29 and AceGUI.objPools then yellowfive@57: AceGUI.objPools = nil yellowfive@57: end yellowfive@57: yellowfive@57: AceGUI.objPools = AceGUI.objPools or {} yellowfive@57: local objPools = AceGUI.objPools yellowfive@57: --Returns a new instance, if none are available either returns a new table or calls the given contructor yellowfive@57: function newWidget(type) yellowfive@57: if not WidgetRegistry[type] then yellowfive@57: error("Attempt to instantiate unknown widget type", 2) yellowfive@57: end yellowfive@57: yellowfive@57: if not objPools[type] then yellowfive@57: objPools[type] = {} yellowfive@57: end yellowfive@57: yellowfive@57: local newObj = next(objPools[type]) yellowfive@57: if not newObj then yellowfive@57: newObj = WidgetRegistry[type]() yellowfive@57: newObj.AceGUIWidgetVersion = WidgetVersions[type] yellowfive@57: else yellowfive@57: objPools[type][newObj] = nil yellowfive@57: -- if the widget is older then the latest, don't even try to reuse it yellowfive@57: -- just forget about it, and grab a new one. yellowfive@57: if not newObj.AceGUIWidgetVersion or newObj.AceGUIWidgetVersion < WidgetVersions[type] then yellowfive@57: return newWidget(type) yellowfive@57: end yellowfive@57: end yellowfive@57: return newObj yellowfive@57: end yellowfive@57: -- Releases an instance to the Pool yellowfive@57: function delWidget(obj,type) yellowfive@57: if not objPools[type] then yellowfive@57: objPools[type] = {} yellowfive@57: end yellowfive@57: if objPools[type][obj] then yellowfive@57: error("Attempt to Release Widget that is already released", 2) yellowfive@57: end yellowfive@57: objPools[type][obj] = true yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: yellowfive@57: ------------------- yellowfive@57: -- API Functions -- yellowfive@57: ------------------- yellowfive@57: yellowfive@57: -- Gets a widget Object yellowfive@57: yellowfive@57: --- Create a new Widget of the given type. yellowfive@57: -- This function will instantiate a new widget (or use one from the widget pool), and call the yellowfive@57: -- OnAcquire function on it, before returning. yellowfive@57: -- @param type The type of the widget. yellowfive@57: -- @return The newly created widget. yellowfive@57: function AceGUI:Create(type) yellowfive@57: if WidgetRegistry[type] then yellowfive@57: local widget = newWidget(type) yellowfive@57: yellowfive@57: if rawget(widget, "Acquire") then yellowfive@57: widget.OnAcquire = widget.Acquire yellowfive@57: widget.Acquire = nil yellowfive@57: elseif rawget(widget, "Aquire") then yellowfive@57: widget.OnAcquire = widget.Aquire yellowfive@57: widget.Aquire = nil yellowfive@57: end yellowfive@57: yellowfive@57: if rawget(widget, "Release") then yellowfive@57: widget.OnRelease = rawget(widget, "Release") yellowfive@57: widget.Release = nil yellowfive@57: end yellowfive@57: yellowfive@57: if widget.OnAcquire then yellowfive@57: widget:OnAcquire() yellowfive@57: else yellowfive@57: error(("Widget type %s doesn't supply an OnAcquire Function"):format(type)) yellowfive@57: end yellowfive@57: -- Set the default Layout ("List") yellowfive@57: safecall(widget.SetLayout, widget, "List") yellowfive@57: safecall(widget.ResumeLayout, widget) yellowfive@57: return widget yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: --- Releases a widget Object. yellowfive@57: -- This function calls OnRelease on the widget and places it back in the widget pool. yellowfive@57: -- Any data on the widget is being erased, and the widget will be hidden.\\ yellowfive@57: -- If this widget is a Container-Widget, all of its Child-Widgets will be releases as well. yellowfive@57: -- @param widget The widget to release yellowfive@57: function AceGUI:Release(widget) yellowfive@57: safecall(widget.PauseLayout, widget) yellowfive@57: widget:Fire("OnRelease") yellowfive@57: safecall(widget.ReleaseChildren, widget) yellowfive@57: yellowfive@57: if widget.OnRelease then yellowfive@57: widget:OnRelease() yellowfive@57: -- else yellowfive@57: -- error(("Widget type %s doesn't supply an OnRelease Function"):format(widget.type)) yellowfive@57: end yellowfive@57: for k in pairs(widget.userdata) do yellowfive@57: widget.userdata[k] = nil yellowfive@57: end yellowfive@57: for k in pairs(widget.events) do yellowfive@57: widget.events[k] = nil yellowfive@57: end yellowfive@57: widget.width = nil yellowfive@57: widget.relWidth = nil yellowfive@57: widget.height = nil yellowfive@57: widget.relHeight = nil yellowfive@57: widget.noAutoHeight = nil yellowfive@57: widget.frame:ClearAllPoints() yellowfive@57: widget.frame:Hide() yellowfive@57: widget.frame:SetParent(UIParent) yellowfive@57: widget.frame.width = nil yellowfive@57: widget.frame.height = nil yellowfive@57: if widget.content then yellowfive@57: widget.content.width = nil yellowfive@57: widget.content.height = nil yellowfive@57: end yellowfive@57: delWidget(widget, widget.type) yellowfive@57: end yellowfive@57: yellowfive@57: ----------- yellowfive@57: -- Focus -- yellowfive@57: ----------- yellowfive@57: yellowfive@57: yellowfive@57: --- Called when a widget has taken focus. yellowfive@57: -- e.g. Dropdowns opening, Editboxes gaining kb focus yellowfive@57: -- @param widget The widget that should be focused yellowfive@57: function AceGUI:SetFocus(widget) yellowfive@57: if self.FocusedWidget and self.FocusedWidget ~= widget then yellowfive@57: safecall(self.FocusedWidget.ClearFocus, self.FocusedWidget) yellowfive@57: end yellowfive@57: self.FocusedWidget = widget yellowfive@57: end yellowfive@57: yellowfive@57: yellowfive@57: --- Called when something has happened that could cause widgets with focus to drop it yellowfive@57: -- e.g. titlebar of a frame being clicked yellowfive@57: function AceGUI:ClearFocus() yellowfive@57: if self.FocusedWidget then yellowfive@57: safecall(self.FocusedWidget.ClearFocus, self.FocusedWidget) yellowfive@57: self.FocusedWidget = nil yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: ------------- yellowfive@57: -- Widgets -- yellowfive@57: ------------- yellowfive@57: --[[ yellowfive@57: Widgets must provide the following functions yellowfive@57: OnAcquire() - Called when the object is acquired, should set everything to a default hidden state yellowfive@57: yellowfive@57: And the following members yellowfive@57: frame - the frame or derivitive object that will be treated as the widget for size and anchoring purposes yellowfive@57: type - the type of the object, same as the name given to :RegisterWidget() yellowfive@57: yellowfive@57: Widgets contain a table called userdata, this is a safe place to store data associated with the wigdet yellowfive@57: It will be cleared automatically when a widget is released yellowfive@57: Placing values directly into a widget object should be avoided yellowfive@57: yellowfive@57: If the Widget can act as a container for other Widgets the following yellowfive@57: content - frame or derivitive that children will be anchored to yellowfive@57: yellowfive@57: The Widget can supply the following Optional Members yellowfive@57: :OnRelease() - Called when the object is Released, should remove any additional anchors and clear any data yellowfive@57: :OnWidthSet(width) - Called when the width of the widget is changed yellowfive@57: :OnHeightSet(height) - Called when the height of the widget is changed yellowfive@57: Widgets should not use the OnSizeChanged events of thier frame or content members, use these methods instead yellowfive@57: AceGUI already sets a handler to the event yellowfive@57: :LayoutFinished(width, height) - called after a layout has finished, the width and height will be the width and height of the yellowfive@57: area used for controls. These can be nil if the layout used the existing size to layout the controls. yellowfive@57: yellowfive@57: ]] yellowfive@57: yellowfive@57: -------------------------- yellowfive@57: -- Widget Base Template -- yellowfive@57: -------------------------- yellowfive@57: do yellowfive@57: local WidgetBase = AceGUI.WidgetBase yellowfive@57: yellowfive@57: WidgetBase.SetParent = function(self, parent) yellowfive@57: local frame = self.frame yellowfive@57: frame:SetParent(nil) yellowfive@57: frame:SetParent(parent.content) yellowfive@57: self.parent = parent yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.SetCallback = function(self, name, func) yellowfive@57: if type(func) == "function" then yellowfive@57: self.events[name] = func yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.Fire = function(self, name, ...) yellowfive@57: if self.events[name] then yellowfive@57: local success, ret = safecall(self.events[name], self, name, ...) yellowfive@57: if success then yellowfive@57: return ret yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.SetWidth = function(self, width) yellowfive@57: self.frame:SetWidth(width) yellowfive@57: self.frame.width = width yellowfive@57: if self.OnWidthSet then yellowfive@57: self:OnWidthSet(width) yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.SetRelativeWidth = function(self, width) yellowfive@57: if width <= 0 or width > 1 then yellowfive@57: error(":SetRelativeWidth(width): Invalid relative width.", 2) yellowfive@57: end yellowfive@57: self.relWidth = width yellowfive@57: self.width = "relative" yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.SetHeight = function(self, height) yellowfive@57: self.frame:SetHeight(height) yellowfive@57: self.frame.height = height yellowfive@57: if self.OnHeightSet then yellowfive@57: self:OnHeightSet(height) yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: --[[ WidgetBase.SetRelativeHeight = function(self, height) yellowfive@57: if height <= 0 or height > 1 then yellowfive@57: error(":SetRelativeHeight(height): Invalid relative height.", 2) yellowfive@57: end yellowfive@57: self.relHeight = height yellowfive@57: self.height = "relative" yellowfive@57: end ]] yellowfive@57: yellowfive@57: WidgetBase.IsVisible = function(self) yellowfive@57: return self.frame:IsVisible() yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.IsShown= function(self) yellowfive@57: return self.frame:IsShown() yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.Release = function(self) yellowfive@57: AceGUI:Release(self) yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.SetPoint = function(self, ...) yellowfive@57: return self.frame:SetPoint(...) yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.ClearAllPoints = function(self) yellowfive@57: return self.frame:ClearAllPoints() yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.GetNumPoints = function(self) yellowfive@57: return self.frame:GetNumPoints() yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.GetPoint = function(self, ...) yellowfive@57: return self.frame:GetPoint(...) yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.GetUserDataTable = function(self) yellowfive@57: return self.userdata yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.SetUserData = function(self, key, value) yellowfive@57: self.userdata[key] = value yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.GetUserData = function(self, key) yellowfive@57: return self.userdata[key] yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.IsFullHeight = function(self) yellowfive@57: return self.height == "fill" yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.SetFullHeight = function(self, isFull) yellowfive@57: if isFull then yellowfive@57: self.height = "fill" yellowfive@57: else yellowfive@57: self.height = nil yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.IsFullWidth = function(self) yellowfive@57: return self.width == "fill" yellowfive@57: end yellowfive@57: yellowfive@57: WidgetBase.SetFullWidth = function(self, isFull) yellowfive@57: if isFull then yellowfive@57: self.width = "fill" yellowfive@57: else yellowfive@57: self.width = nil yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: -- local function LayoutOnUpdate(this) yellowfive@57: -- this:SetScript("OnUpdate",nil) yellowfive@57: -- this.obj:PerformLayout() yellowfive@57: -- end yellowfive@57: yellowfive@57: local WidgetContainerBase = AceGUI.WidgetContainerBase yellowfive@57: yellowfive@57: WidgetContainerBase.PauseLayout = function(self) yellowfive@57: self.LayoutPaused = true yellowfive@57: end yellowfive@57: yellowfive@57: WidgetContainerBase.ResumeLayout = function(self) yellowfive@57: self.LayoutPaused = nil yellowfive@57: end yellowfive@57: yellowfive@57: WidgetContainerBase.PerformLayout = function(self) yellowfive@57: if self.LayoutPaused then yellowfive@57: return yellowfive@57: end yellowfive@57: safecall(self.LayoutFunc, self.content, self.children) yellowfive@57: end yellowfive@57: yellowfive@57: --call this function to layout, makes sure layed out objects get a frame to get sizes etc yellowfive@57: WidgetContainerBase.DoLayout = function(self) yellowfive@57: self:PerformLayout() yellowfive@57: -- if not self.parent then yellowfive@57: -- self.frame:SetScript("OnUpdate", LayoutOnUpdate) yellowfive@57: -- end yellowfive@57: end yellowfive@57: yellowfive@57: WidgetContainerBase.AddChild = function(self, child, beforeWidget) yellowfive@57: if beforeWidget then yellowfive@57: local siblingIndex = 1 yellowfive@57: for _, widget in pairs(self.children) do yellowfive@57: if widget == beforeWidget then yellowfive@57: break yellowfive@57: end yellowfive@57: siblingIndex = siblingIndex + 1 yellowfive@57: end yellowfive@57: tinsert(self.children, siblingIndex, child) yellowfive@57: else yellowfive@57: tinsert(self.children, child) yellowfive@57: end yellowfive@57: child:SetParent(self) yellowfive@57: child.frame:Show() yellowfive@57: self:DoLayout() yellowfive@57: end yellowfive@57: yellowfive@57: WidgetContainerBase.AddChildren = function(self, ...) yellowfive@57: for i = 1, select("#", ...) do yellowfive@57: local child = select(i, ...) yellowfive@57: tinsert(self.children, child) yellowfive@57: child:SetParent(self) yellowfive@57: child.frame:Show() yellowfive@57: end yellowfive@57: self:DoLayout() yellowfive@57: end yellowfive@57: yellowfive@57: WidgetContainerBase.ReleaseChildren = function(self) yellowfive@57: local children = self.children yellowfive@57: for i = 1,#children do yellowfive@57: AceGUI:Release(children[i]) yellowfive@57: children[i] = nil yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: WidgetContainerBase.SetLayout = function(self, Layout) yellowfive@57: self.LayoutFunc = AceGUI:GetLayout(Layout) yellowfive@57: end yellowfive@57: yellowfive@57: WidgetContainerBase.SetAutoAdjustHeight = function(self, adjust) yellowfive@57: if adjust then yellowfive@57: self.noAutoHeight = nil yellowfive@57: else yellowfive@57: self.noAutoHeight = true yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: local function FrameResize(this) yellowfive@57: local self = this.obj yellowfive@57: if this:GetWidth() and this:GetHeight() then yellowfive@57: if self.OnWidthSet then yellowfive@57: self:OnWidthSet(this:GetWidth()) yellowfive@57: end yellowfive@57: if self.OnHeightSet then yellowfive@57: self:OnHeightSet(this:GetHeight()) yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: local function ContentResize(this) yellowfive@57: if this:GetWidth() and this:GetHeight() then yellowfive@57: this.width = this:GetWidth() yellowfive@57: this.height = this:GetHeight() yellowfive@57: this.obj:DoLayout() yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: setmetatable(WidgetContainerBase, {__index=WidgetBase}) yellowfive@57: yellowfive@57: --One of these function should be called on each Widget Instance as part of its creation process yellowfive@57: yellowfive@57: --- Register a widget-class as a container for newly created widgets. yellowfive@57: -- @param widget The widget class yellowfive@57: function AceGUI:RegisterAsContainer(widget) yellowfive@57: widget.children = {} yellowfive@57: widget.userdata = {} yellowfive@57: widget.events = {} yellowfive@57: widget.base = WidgetContainerBase yellowfive@57: widget.content.obj = widget yellowfive@57: widget.frame.obj = widget yellowfive@57: widget.content:SetScript("OnSizeChanged", ContentResize) yellowfive@57: widget.frame:SetScript("OnSizeChanged", FrameResize) yellowfive@57: setmetatable(widget, {__index = WidgetContainerBase}) yellowfive@57: widget:SetLayout("List") yellowfive@57: return widget yellowfive@57: end yellowfive@57: yellowfive@57: --- Register a widget-class as a widget. yellowfive@57: -- @param widget The widget class yellowfive@57: function AceGUI:RegisterAsWidget(widget) yellowfive@57: widget.userdata = {} yellowfive@57: widget.events = {} yellowfive@57: widget.base = WidgetBase yellowfive@57: widget.frame.obj = widget yellowfive@57: widget.frame:SetScript("OnSizeChanged", FrameResize) yellowfive@57: setmetatable(widget, {__index = WidgetBase}) yellowfive@57: return widget yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: yellowfive@57: yellowfive@57: yellowfive@57: ------------------ yellowfive@57: -- Widget API -- yellowfive@57: ------------------ yellowfive@57: yellowfive@57: --- Registers a widget Constructor, this function returns a new instance of the Widget yellowfive@57: -- @param Name The name of the widget yellowfive@57: -- @param Constructor The widget constructor function yellowfive@57: -- @param Version The version of the widget yellowfive@57: function AceGUI:RegisterWidgetType(Name, Constructor, Version) yellowfive@57: assert(type(Constructor) == "function") yellowfive@57: assert(type(Version) == "number") yellowfive@57: yellowfive@57: local oldVersion = WidgetVersions[Name] yellowfive@57: if oldVersion and oldVersion >= Version then return end yellowfive@57: yellowfive@57: WidgetVersions[Name] = Version yellowfive@57: WidgetRegistry[Name] = Constructor yellowfive@57: end yellowfive@57: yellowfive@57: --- Registers a Layout Function yellowfive@57: -- @param Name The name of the layout yellowfive@57: -- @param LayoutFunc Reference to the layout function yellowfive@57: function AceGUI:RegisterLayout(Name, LayoutFunc) yellowfive@57: assert(type(LayoutFunc) == "function") yellowfive@57: if type(Name) == "string" then yellowfive@57: Name = Name:upper() yellowfive@57: end yellowfive@57: LayoutRegistry[Name] = LayoutFunc yellowfive@57: end yellowfive@57: yellowfive@57: --- Get a Layout Function from the registry yellowfive@57: -- @param Name The name of the layout yellowfive@57: function AceGUI:GetLayout(Name) yellowfive@57: if type(Name) == "string" then yellowfive@57: Name = Name:upper() yellowfive@57: end yellowfive@57: return LayoutRegistry[Name] yellowfive@57: end yellowfive@57: yellowfive@57: AceGUI.counts = AceGUI.counts or {} yellowfive@57: yellowfive@57: --- A type-based counter to count the number of widgets created. yellowfive@57: -- This is used by widgets that require a named frame, e.g. when a Blizzard yellowfive@57: -- Template requires it. yellowfive@57: -- @param type The widget type yellowfive@57: function AceGUI:GetNextWidgetNum(type) yellowfive@57: if not self.counts[type] then yellowfive@57: self.counts[type] = 0 yellowfive@57: end yellowfive@57: self.counts[type] = self.counts[type] + 1 yellowfive@57: return self.counts[type] yellowfive@57: end yellowfive@57: yellowfive@57: --- Return the number of created widgets for this type. yellowfive@57: -- In contrast to GetNextWidgetNum, the number is not incremented. yellowfive@57: -- @param type The widget type yellowfive@57: function AceGUI:GetWidgetCount(type) yellowfive@57: return self.counts[type] or 0 yellowfive@57: end yellowfive@57: yellowfive@57: --- Return the version of the currently registered widget type. yellowfive@57: -- @param type The widget type yellowfive@57: function AceGUI:GetWidgetVersion(type) yellowfive@57: return WidgetVersions[type] yellowfive@57: end yellowfive@57: yellowfive@57: ------------- yellowfive@57: -- Layouts -- yellowfive@57: ------------- yellowfive@57: yellowfive@57: --[[ yellowfive@57: A Layout is a func that takes 2 parameters yellowfive@57: content - the frame that widgets will be placed inside yellowfive@57: children - a table containing the widgets to layout yellowfive@57: ]] yellowfive@57: yellowfive@57: -- Very simple Layout, Children are stacked on top of each other down the left side yellowfive@57: AceGUI:RegisterLayout("List", yellowfive@57: function(content, children) yellowfive@57: local height = 0 yellowfive@57: local width = content.width or content:GetWidth() or 0 yellowfive@57: for i = 1, #children do yellowfive@57: local child = children[i] yellowfive@57: yellowfive@57: local frame = child.frame yellowfive@57: frame:ClearAllPoints() yellowfive@57: frame:Show() yellowfive@57: if i == 1 then yellowfive@57: frame:SetPoint("TOPLEFT", content) yellowfive@57: else yellowfive@57: frame:SetPoint("TOPLEFT", children[i-1].frame, "BOTTOMLEFT") yellowfive@57: end yellowfive@57: yellowfive@57: if child.width == "fill" then yellowfive@57: child:SetWidth(width) yellowfive@57: frame:SetPoint("RIGHT", content) yellowfive@57: yellowfive@57: if child.DoLayout then yellowfive@57: child:DoLayout() yellowfive@57: end yellowfive@57: elseif child.width == "relative" then yellowfive@57: child:SetWidth(width * child.relWidth) yellowfive@57: yellowfive@57: if child.DoLayout then yellowfive@57: child:DoLayout() yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: height = height + (frame.height or frame:GetHeight() or 0) yellowfive@57: end yellowfive@57: safecall(content.obj.LayoutFinished, content.obj, nil, height) yellowfive@57: end) yellowfive@57: yellowfive@57: -- A single control fills the whole content area yellowfive@57: AceGUI:RegisterLayout("Fill", yellowfive@57: function(content, children) yellowfive@57: if children[1] then yellowfive@57: children[1]:SetWidth(content:GetWidth() or 0) yellowfive@57: children[1]:SetHeight(content:GetHeight() or 0) yellowfive@57: children[1].frame:SetAllPoints(content) yellowfive@57: children[1].frame:Show() yellowfive@57: safecall(content.obj.LayoutFinished, content.obj, nil, children[1].frame:GetHeight()) yellowfive@57: end yellowfive@57: end) yellowfive@57: yellowfive@57: local layoutrecursionblock = nil yellowfive@57: local function safelayoutcall(object, func, ...) yellowfive@57: layoutrecursionblock = true yellowfive@57: object[func](object, ...) yellowfive@57: layoutrecursionblock = nil yellowfive@57: end yellowfive@57: yellowfive@57: AceGUI:RegisterLayout("Flow", yellowfive@57: function(content, children) yellowfive@57: if layoutrecursionblock then return end yellowfive@57: --used height so far yellowfive@57: local height = 0 yellowfive@57: --width used in the current row yellowfive@57: local usedwidth = 0 yellowfive@57: --height of the current row yellowfive@57: local rowheight = 0 yellowfive@57: local rowoffset = 0 yellowfive@57: local lastrowoffset yellowfive@57: yellowfive@57: local width = content.width or content:GetWidth() or 0 yellowfive@57: yellowfive@57: --control at the start of the row yellowfive@57: local rowstart yellowfive@57: local rowstartoffset yellowfive@57: local lastrowstart yellowfive@57: local isfullheight yellowfive@57: yellowfive@57: local frameoffset yellowfive@57: local lastframeoffset yellowfive@57: local oversize yellowfive@57: for i = 1, #children do yellowfive@57: local child = children[i] yellowfive@57: oversize = nil yellowfive@57: local frame = child.frame yellowfive@57: local frameheight = frame.height or frame:GetHeight() or 0 yellowfive@57: local framewidth = frame.width or frame:GetWidth() or 0 yellowfive@57: lastframeoffset = frameoffset yellowfive@57: -- HACK: Why did we set a frameoffset of (frameheight / 2) ? yellowfive@57: -- That was moving all widgets half the widgets size down, is that intended? yellowfive@57: -- Actually, it seems to be neccessary for many cases, we'll leave it in for now. yellowfive@57: -- If widgets seem to anchor weirdly with this, provide a valid alignoffset for them. yellowfive@57: -- TODO: Investigate moar! yellowfive@57: frameoffset = child.alignoffset or (frameheight / 2) yellowfive@57: yellowfive@57: if child.width == "relative" then yellowfive@57: framewidth = width * child.relWidth yellowfive@57: end yellowfive@57: yellowfive@57: frame:Show() yellowfive@57: frame:ClearAllPoints() yellowfive@57: if i == 1 then yellowfive@57: -- anchor the first control to the top left yellowfive@57: frame:SetPoint("TOPLEFT", content) yellowfive@57: rowheight = frameheight yellowfive@57: rowoffset = frameoffset yellowfive@57: rowstart = frame yellowfive@57: rowstartoffset = frameoffset yellowfive@57: usedwidth = framewidth yellowfive@57: if usedwidth > width then yellowfive@57: oversize = true yellowfive@57: end yellowfive@57: else yellowfive@57: -- if there isn't available width for the control start a new row yellowfive@57: -- if a control is "fill" it will be on a row of its own full width yellowfive@57: if usedwidth == 0 or ((framewidth) + usedwidth > width) or child.width == "fill" then yellowfive@57: if isfullheight then yellowfive@57: -- a previous row has already filled the entire height, there's nothing we can usefully do anymore yellowfive@57: -- (maybe error/warn about this?) yellowfive@57: break yellowfive@57: end yellowfive@57: --anchor the previous row, we will now know its height and offset yellowfive@57: rowstart:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -(height + (rowoffset - rowstartoffset) + 3)) yellowfive@57: height = height + rowheight + 3 yellowfive@57: --save this as the rowstart so we can anchor it after the row is complete and we have the max height and offset of controls in it yellowfive@57: rowstart = frame yellowfive@57: rowstartoffset = frameoffset yellowfive@57: rowheight = frameheight yellowfive@57: rowoffset = frameoffset yellowfive@57: usedwidth = framewidth yellowfive@57: if usedwidth > width then yellowfive@57: oversize = true yellowfive@57: end yellowfive@57: -- put the control on the current row, adding it to the width and checking if the height needs to be increased yellowfive@57: else yellowfive@57: --handles cases where the new height is higher than either control because of the offsets yellowfive@57: --math.max(rowheight-rowoffset+frameoffset, frameheight-frameoffset+rowoffset) yellowfive@57: yellowfive@57: --offset is always the larger of the two offsets yellowfive@57: rowoffset = math_max(rowoffset, frameoffset) yellowfive@57: rowheight = math_max(rowheight, rowoffset + (frameheight / 2)) yellowfive@57: yellowfive@57: frame:SetPoint("TOPLEFT", children[i-1].frame, "TOPRIGHT", 0, frameoffset - lastframeoffset) yellowfive@57: usedwidth = framewidth + usedwidth yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: if child.width == "fill" then yellowfive@57: safelayoutcall(child, "SetWidth", width) yellowfive@57: frame:SetPoint("RIGHT", content) yellowfive@57: yellowfive@57: usedwidth = 0 yellowfive@57: rowstart = frame yellowfive@57: rowstartoffset = frameoffset yellowfive@57: yellowfive@57: if child.DoLayout then yellowfive@57: child:DoLayout() yellowfive@57: end yellowfive@57: rowheight = frame.height or frame:GetHeight() or 0 yellowfive@57: rowoffset = child.alignoffset or (rowheight / 2) yellowfive@57: rowstartoffset = rowoffset yellowfive@57: elseif child.width == "relative" then yellowfive@57: safelayoutcall(child, "SetWidth", width * child.relWidth) yellowfive@57: yellowfive@57: if child.DoLayout then yellowfive@57: child:DoLayout() yellowfive@57: end yellowfive@57: elseif oversize then yellowfive@57: if width > 1 then yellowfive@57: frame:SetPoint("RIGHT", content) yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: if child.height == "fill" then yellowfive@57: frame:SetPoint("BOTTOM", content) yellowfive@57: isfullheight = true yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: --anchor the last row, if its full height needs a special case since its height has just been changed by the anchor yellowfive@57: if isfullheight then yellowfive@57: rowstart:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -height) yellowfive@57: elseif rowstart then yellowfive@57: rowstart:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -(height + (rowoffset - rowstartoffset) + 3)) yellowfive@57: end yellowfive@57: yellowfive@57: height = height + rowheight + 3 yellowfive@57: safecall(content.obj.LayoutFinished, content.obj, nil, height) yellowfive@57: end) yellowfive@124: yellowfive@124: -- Get alignment method and value. Possible alignment methods are a callback, a number, "start", "middle", "end", "fill" or "TOPLEFT", "BOTTOMRIGHT" etc. yellowfive@124: local GetCellAlign = function (dir, tableObj, colObj, cellObj, cell, child) yellowfive@124: local fn = cellObj and (cellObj["align" .. dir] or cellObj.align) yellowfive@124: or colObj and (colObj["align" .. dir] or colObj.align) yellowfive@124: or tableObj["align" .. dir] or tableObj.align yellowfive@124: or "CENTERLEFT" yellowfive@124: local child, cell, val = child or 0, cell or 0, nil yellowfive@124: yellowfive@124: if type(fn) == "string" then yellowfive@124: fn = fn:lower() yellowfive@124: fn = dir == "V" and (fn:sub(1, 3) == "top" and "start" or fn:sub(1, 6) == "bottom" and "end" or fn:sub(1, 6) == "center" and "middle") yellowfive@124: or dir == "H" and (fn:sub(-4) == "left" and "start" or fn:sub(-5) == "right" and "end" or fn:sub(-6) == "center" and "middle") yellowfive@124: or fn yellowfive@124: val = (fn == "start" or fn == "fill") and 0 or fn == "end" and cell - child or (cell - child) / 2 yellowfive@124: elseif type(fn) == "function" then yellowfive@124: val = fn(child or 0, cell, dir) yellowfive@124: else yellowfive@124: val = fn yellowfive@124: end yellowfive@124: yellowfive@124: return fn, max(0, min(val, cell)) yellowfive@124: end yellowfive@124: yellowfive@124: -- Get width or height for multiple cells combined yellowfive@124: local GetCellDimension = function (dir, laneDim, from, to, space) yellowfive@124: local dim = 0 yellowfive@124: for cell=from,to do yellowfive@124: dim = dim + (laneDim[cell] or 0) yellowfive@124: end yellowfive@124: return dim + max(0, to - from) * (space or 0) yellowfive@124: end yellowfive@124: yellowfive@124: --[[ Options yellowfive@124: ============ yellowfive@124: Container: yellowfive@124: - columns ({col, col, ...}): Column settings. "col" can be a number (<= 0: content width, <1: rel. width, <10: weight, >=10: abs. width) or a table with column setting. yellowfive@124: - space, spaceH, spaceV: Overall, horizontal and vertical spacing between cells. yellowfive@124: - align, alignH, alignV: Overall, horizontal and vertical cell alignment. See GetCellAlign() for possible values. yellowfive@124: Columns: yellowfive@124: - width: Fixed column width (nil or <=0: content width, <1: rel. width, >=1: abs. width). yellowfive@124: - min or 1: Min width for content based width yellowfive@124: - max or 2: Max width for content based width yellowfive@124: - weight: Flexible column width. The leftover width after accounting for fixed-width columns is distributed to weighted columns according to their weights. yellowfive@124: - align, alignH, alignV: Overwrites the container setting for alignment. yellowfive@124: Cell: yellowfive@124: - colspan: Makes a cell span multiple columns. yellowfive@124: - rowspan: Makes a cell span multiple rows. yellowfive@124: - align, alignH, alignV: Overwrites the container and column setting for alignment. yellowfive@124: ]] yellowfive@124: AceGUI:RegisterLayout("Table", yellowfive@124: function (content, children) yellowfive@124: local obj = content.obj yellowfive@124: obj:PauseLayout() yellowfive@124: yellowfive@124: local tableObj = obj:GetUserData("table") yellowfive@124: local cols = tableObj.columns yellowfive@124: local spaceH = tableObj.spaceH or tableObj.space or 0 yellowfive@124: local spaceV = tableObj.spaceV or tableObj.space or 0 yellowfive@124: local totalH = (content:GetWidth() or content.width or 0) - spaceH * (#cols - 1) yellowfive@124: yellowfive@124: -- We need to reuse these because layout events can come in very frequently yellowfive@124: local layoutCache = obj:GetUserData("layoutCache") yellowfive@124: if not layoutCache then yellowfive@124: layoutCache = {{}, {}, {}, {}, {}, {}} yellowfive@124: obj:SetUserData("layoutCache", layoutCache) yellowfive@124: end yellowfive@124: local t, laneH, laneV, rowspans, rowStart, colStart = unpack(layoutCache) yellowfive@124: yellowfive@124: -- Create the grid yellowfive@124: local n, slotFound = 0 yellowfive@124: for i,child in ipairs(children) do yellowfive@124: if child:IsShown() then yellowfive@124: repeat yellowfive@124: n = n + 1 yellowfive@124: local col = (n - 1) % #cols + 1 yellowfive@124: local row = ceil(n / #cols) yellowfive@124: local rowspan = rowspans[col] yellowfive@124: local cell = rowspan and rowspan.child or child yellowfive@124: local cellObj = cell:GetUserData("cell") yellowfive@124: slotFound = not rowspan yellowfive@124: yellowfive@124: -- Rowspan yellowfive@124: if not rowspan and cellObj and cellObj.rowspan then yellowfive@124: rowspan = {child = child, from = row, to = row + cellObj.rowspan - 1} yellowfive@124: rowspans[col] = rowspan yellowfive@124: end yellowfive@124: if rowspan and i == #children then yellowfive@124: rowspan.to = row yellowfive@124: end yellowfive@124: yellowfive@124: -- Colspan yellowfive@124: local colspan = max(0, min((cellObj and cellObj.colspan or 1) - 1, #cols - col)) yellowfive@124: n = n + colspan yellowfive@124: yellowfive@124: -- Place the cell yellowfive@124: if not rowspan or rowspan.to == row then yellowfive@124: t[n] = cell yellowfive@124: rowStart[cell] = rowspan and rowspan.from or row yellowfive@124: colStart[cell] = col yellowfive@124: yellowfive@124: if rowspan then yellowfive@124: rowspans[col] = nil yellowfive@124: end yellowfive@124: end yellowfive@124: until slotFound yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@124: local rows = ceil(n / #cols) yellowfive@124: yellowfive@124: -- Determine fixed size cols and collect weights yellowfive@124: local extantH, totalWeight = totalH, 0 yellowfive@124: for col,colObj in ipairs(cols) do yellowfive@124: laneH[col] = 0 yellowfive@124: yellowfive@124: if type(colObj) == "number" then yellowfive@124: colObj = {[colObj >= 1 and colObj < 10 and "weight" or "width"] = colObj} yellowfive@124: cols[col] = colObj yellowfive@124: end yellowfive@124: yellowfive@124: if colObj.weight then yellowfive@124: -- Weight yellowfive@124: totalWeight = totalWeight + (colObj.weight or 1) yellowfive@124: else yellowfive@124: if not colObj.width or colObj.width <= 0 then yellowfive@124: -- Content width yellowfive@124: for row=1,rows do yellowfive@124: local child = t[(row - 1) * #cols + col] yellowfive@124: if child then yellowfive@124: local f = child.frame yellowfive@124: f:ClearAllPoints() yellowfive@124: local childH = f:GetWidth() or 0 yellowfive@124: yellowfive@124: laneH[col] = max(laneH[col], childH - GetCellDimension("H", laneH, colStart[child], col - 1, spaceH)) yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@124: laneH[col] = max(colObj.min or colObj[1] or 0, min(laneH[col], colObj.max or colObj[2] or laneH[col])) yellowfive@124: else yellowfive@124: -- Rel./Abs. width yellowfive@124: laneH[col] = colObj.width < 1 and colObj.width * totalH or colObj.width yellowfive@124: end yellowfive@124: extantH = max(0, extantH - laneH[col]) yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@124: -- Determine sizes based on weight yellowfive@124: local scale = totalWeight > 0 and extantH / totalWeight or 0 yellowfive@124: for col,colObj in pairs(cols) do yellowfive@124: if colObj.weight then yellowfive@124: laneH[col] = scale * colObj.weight yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@124: -- Arrange children yellowfive@124: for row=1,rows do yellowfive@124: local rowV = 0 yellowfive@124: yellowfive@124: -- Horizontal placement and sizing yellowfive@124: for col=1,#cols do yellowfive@124: local child = t[(row - 1) * #cols + col] yellowfive@124: if child then yellowfive@124: local colObj = cols[colStart[child]] yellowfive@124: local cellObj = child:GetUserData("cell") yellowfive@124: local offsetH = GetCellDimension("H", laneH, 1, colStart[child] - 1, spaceH) + (colStart[child] == 1 and 0 or spaceH) yellowfive@124: local cellH = GetCellDimension("H", laneH, colStart[child], col, spaceH) yellowfive@124: yellowfive@124: local f = child.frame yellowfive@124: f:ClearAllPoints() yellowfive@124: local childH = f:GetWidth() or 0 yellowfive@124: yellowfive@124: local alignFn, align = GetCellAlign("H", tableObj, colObj, cellObj, cellH, childH) yellowfive@124: f:SetPoint("LEFT", content, offsetH + align, 0) yellowfive@124: if child:IsFullWidth() or alignFn == "fill" or childH > cellH then yellowfive@124: f:SetPoint("RIGHT", content, "LEFT", offsetH + align + cellH, 0) yellowfive@124: end yellowfive@124: yellowfive@124: if child.DoLayout then yellowfive@124: child:DoLayout() yellowfive@124: end yellowfive@124: yellowfive@124: rowV = max(rowV, (f:GetHeight() or 0) - GetCellDimension("V", laneV, rowStart[child], row - 1, spaceV)) yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@124: laneV[row] = rowV yellowfive@124: yellowfive@124: -- Vertical placement and sizing yellowfive@124: for col=1,#cols do yellowfive@124: local child = t[(row - 1) * #cols + col] yellowfive@124: if child then yellowfive@124: local colObj = cols[colStart[child]] yellowfive@124: local cellObj = child:GetUserData("cell") yellowfive@124: local offsetV = GetCellDimension("V", laneV, 1, rowStart[child] - 1, spaceV) + (rowStart[child] == 1 and 0 or spaceV) yellowfive@124: local cellV = GetCellDimension("V", laneV, rowStart[child], row, spaceV) yellowfive@124: yellowfive@124: local f = child.frame yellowfive@124: local childV = f:GetHeight() or 0 yellowfive@124: yellowfive@124: local alignFn, align = GetCellAlign("V", tableObj, colObj, cellObj, cellV, childV) yellowfive@124: if child:IsFullHeight() or alignFn == "fill" then yellowfive@124: f:SetHeight(cellV) yellowfive@124: end yellowfive@124: f:SetPoint("TOP", content, 0, -(offsetV + align)) yellowfive@124: end yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@124: -- Calculate total height yellowfive@124: local totalV = GetCellDimension("V", laneV, 1, #laneV, spaceV) yellowfive@124: yellowfive@124: -- Cleanup yellowfive@124: for _,v in pairs(layoutCache) do wipe(v) end yellowfive@124: yellowfive@124: safecall(obj.LayoutFinished, obj, nil, totalV) yellowfive@124: obj:ResumeLayout() yellowfive@124: end)