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