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@57: -- @release $Id: AceGUI-3.0.lua 1102 2013-10-25 14:15:23Z nevcairiel $ yellowfive@57: local ACEGUI_MAJOR, ACEGUI_MINOR = "AceGUI-3.0", 34 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)