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