Xiiph@0
|
1 --- **AceGUI-3.0** provides access to numerous widgets which can be used to create GUIs.
|
Xiiph@0
|
2 -- AceGUI is used by AceConfigDialog to create the option GUIs, but you can use it by itself
|
Xiiph@0
|
3 -- to create any custom GUI. There are more extensive examples in the test suite in the Ace3
|
Xiiph@0
|
4 -- stand-alone distribution.
|
Xiiph@0
|
5 --
|
Xiiph@0
|
6 -- **Note**: When using AceGUI-3.0 directly, please do not modify the frames of the widgets directly,
|
Xiiph@0
|
7 -- as any "unknown" change to the widgets will cause addons that get your widget out of the widget pool
|
Xiiph@0
|
8 -- to misbehave. If you think some part of a widget should be modifiable, please open a ticket, and we"ll
|
Xiiph@0
|
9 -- implement a proper API to modify it.
|
Xiiph@0
|
10 -- @usage
|
Xiiph@0
|
11 -- local AceGUI = LibStub("AceGUI-3.0")
|
Xiiph@0
|
12 -- -- Create a container frame
|
Xiiph@0
|
13 -- local f = AceGUI:Create("Frame")
|
Xiiph@0
|
14 -- f:SetCallback("OnClose",function(widget) AceGUI:Release(widget) end)
|
Xiiph@0
|
15 -- f:SetTitle("AceGUI-3.0 Example")
|
Xiiph@0
|
16 -- f:SetStatusText("Status Bar")
|
Xiiph@0
|
17 -- f:SetLayout("Flow")
|
Xiiph@0
|
18 -- -- Create a button
|
Xiiph@0
|
19 -- local btn = AceGUI:Create("Button")
|
Xiiph@0
|
20 -- btn:SetWidth(170)
|
Xiiph@0
|
21 -- btn:SetText("Button !")
|
Xiiph@0
|
22 -- btn:SetCallback("OnClick", function() print("Click!") end)
|
Xiiph@0
|
23 -- -- Add the button to the container
|
Xiiph@0
|
24 -- f:AddChild(btn)
|
Xiiph@0
|
25 -- @class file
|
Xiiph@0
|
26 -- @name AceGUI-3.0
|
Xiiph@0
|
27 -- @release $Id: AceGUI-3.0.lua 924 2010-05-13 15:12:20Z nevcairiel $
|
Xiiph@0
|
28 local ACEGUI_MAJOR, ACEGUI_MINOR = "AceGUI-3.0", 33
|
Xiiph@0
|
29 local AceGUI, oldminor = LibStub:NewLibrary(ACEGUI_MAJOR, ACEGUI_MINOR)
|
Xiiph@0
|
30
|
Xiiph@0
|
31 if not AceGUI then return end -- No upgrade needed
|
Xiiph@0
|
32
|
Xiiph@0
|
33 -- Lua APIs
|
Xiiph@0
|
34 local tconcat, tremove, tinsert = table.concat, table.remove, table.insert
|
Xiiph@0
|
35 local select, pairs, next, type = select, pairs, next, type
|
Xiiph@0
|
36 local error, assert, loadstring = error, assert, loadstring
|
Xiiph@0
|
37 local setmetatable, rawget, rawset = setmetatable, rawget, rawset
|
Xiiph@0
|
38 local math_max = math.max
|
Xiiph@0
|
39
|
Xiiph@0
|
40 -- WoW APIs
|
Xiiph@0
|
41 local UIParent = UIParent
|
Xiiph@0
|
42
|
Xiiph@0
|
43 -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
Xiiph@0
|
44 -- List them here for Mikk's FindGlobals script
|
Xiiph@0
|
45 -- GLOBALS: geterrorhandler, LibStub
|
Xiiph@0
|
46
|
Xiiph@0
|
47 --local con = LibStub("AceConsole-3.0",true)
|
Xiiph@0
|
48
|
Xiiph@0
|
49 AceGUI.WidgetRegistry = AceGUI.WidgetRegistry or {}
|
Xiiph@0
|
50 AceGUI.LayoutRegistry = AceGUI.LayoutRegistry or {}
|
Xiiph@0
|
51 AceGUI.WidgetBase = AceGUI.WidgetBase or {}
|
Xiiph@0
|
52 AceGUI.WidgetContainerBase = AceGUI.WidgetContainerBase or {}
|
Xiiph@0
|
53 AceGUI.WidgetVersions = AceGUI.WidgetVersions or {}
|
Xiiph@0
|
54
|
Xiiph@0
|
55 -- local upvalues
|
Xiiph@0
|
56 local WidgetRegistry = AceGUI.WidgetRegistry
|
Xiiph@0
|
57 local LayoutRegistry = AceGUI.LayoutRegistry
|
Xiiph@0
|
58 local WidgetVersions = AceGUI.WidgetVersions
|
Xiiph@0
|
59
|
Xiiph@0
|
60 --[[
|
Xiiph@0
|
61 xpcall safecall implementation
|
Xiiph@0
|
62 ]]
|
Xiiph@0
|
63 local xpcall = xpcall
|
Xiiph@0
|
64
|
Xiiph@0
|
65 local function errorhandler(err)
|
Xiiph@0
|
66 return geterrorhandler()(err)
|
Xiiph@0
|
67 end
|
Xiiph@0
|
68
|
Xiiph@0
|
69 local function CreateDispatcher(argCount)
|
Xiiph@0
|
70 local code = [[
|
Xiiph@0
|
71 local xpcall, eh = ...
|
Xiiph@0
|
72 local method, ARGS
|
Xiiph@0
|
73 local function call() return method(ARGS) end
|
Xiiph@0
|
74
|
Xiiph@0
|
75 local function dispatch(func, ...)
|
Xiiph@0
|
76 method = func
|
Xiiph@0
|
77 if not method then return end
|
Xiiph@0
|
78 ARGS = ...
|
Xiiph@0
|
79 return xpcall(call, eh)
|
Xiiph@0
|
80 end
|
Xiiph@0
|
81
|
Xiiph@0
|
82 return dispatch
|
Xiiph@0
|
83 ]]
|
Xiiph@0
|
84
|
Xiiph@0
|
85 local ARGS = {}
|
Xiiph@0
|
86 for i = 1, argCount do ARGS[i] = "arg"..i end
|
Xiiph@0
|
87 code = code:gsub("ARGS", tconcat(ARGS, ", "))
|
Xiiph@0
|
88 return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler)
|
Xiiph@0
|
89 end
|
Xiiph@0
|
90
|
Xiiph@0
|
91 local Dispatchers = setmetatable({}, {__index=function(self, argCount)
|
Xiiph@0
|
92 local dispatcher = CreateDispatcher(argCount)
|
Xiiph@0
|
93 rawset(self, argCount, dispatcher)
|
Xiiph@0
|
94 return dispatcher
|
Xiiph@0
|
95 end})
|
Xiiph@0
|
96 Dispatchers[0] = function(func)
|
Xiiph@0
|
97 return xpcall(func, errorhandler)
|
Xiiph@0
|
98 end
|
Xiiph@0
|
99
|
Xiiph@0
|
100 local function safecall(func, ...)
|
Xiiph@0
|
101 return Dispatchers[select("#", ...)](func, ...)
|
Xiiph@0
|
102 end
|
Xiiph@0
|
103
|
Xiiph@0
|
104 -- Recycling functions
|
Xiiph@0
|
105 local newWidget, delWidget
|
Xiiph@0
|
106 do
|
Xiiph@0
|
107 -- Version Upgrade in Minor 29
|
Xiiph@0
|
108 -- Internal Storage of the objects changed, from an array table
|
Xiiph@0
|
109 -- to a hash table, and additionally we introduced versioning on
|
Xiiph@0
|
110 -- the widgets which would discard all widgets from a pre-29 version
|
Xiiph@0
|
111 -- anyway, so we just clear the storage now, and don't try to
|
Xiiph@0
|
112 -- convert the storage tables to the new format.
|
Xiiph@0
|
113 -- This should generally not cause *many* widgets to end up in trash,
|
Xiiph@0
|
114 -- since once dialogs are opened, all addons should be loaded already
|
Xiiph@0
|
115 -- and AceGUI should be on the latest version available on the users
|
Xiiph@0
|
116 -- setup.
|
Xiiph@0
|
117 -- -- nevcairiel - Nov 2nd, 2009
|
Xiiph@0
|
118 if oldminor and oldminor < 29 and AceGUI.objPools then
|
Xiiph@0
|
119 AceGUI.objPools = nil
|
Xiiph@0
|
120 end
|
Xiiph@0
|
121
|
Xiiph@0
|
122 AceGUI.objPools = AceGUI.objPools or {}
|
Xiiph@0
|
123 local objPools = AceGUI.objPools
|
Xiiph@0
|
124 --Returns a new instance, if none are available either returns a new table or calls the given contructor
|
Xiiph@0
|
125 function newWidget(type)
|
Xiiph@0
|
126 if not WidgetRegistry[type] then
|
Xiiph@0
|
127 error("Attempt to instantiate unknown widget type", 2)
|
Xiiph@0
|
128 end
|
Xiiph@0
|
129
|
Xiiph@0
|
130 if not objPools[type] then
|
Xiiph@0
|
131 objPools[type] = {}
|
Xiiph@0
|
132 end
|
Xiiph@0
|
133
|
Xiiph@0
|
134 local newObj = next(objPools[type])
|
Xiiph@0
|
135 if not newObj then
|
Xiiph@0
|
136 newObj = WidgetRegistry[type]()
|
Xiiph@0
|
137 newObj.AceGUIWidgetVersion = WidgetVersions[type]
|
Xiiph@0
|
138 else
|
Xiiph@0
|
139 objPools[type][newObj] = nil
|
Xiiph@0
|
140 -- if the widget is older then the latest, don't even try to reuse it
|
Xiiph@0
|
141 -- just forget about it, and grab a new one.
|
Xiiph@0
|
142 if not newObj.AceGUIWidgetVersion or newObj.AceGUIWidgetVersion < WidgetVersions[type] then
|
Xiiph@0
|
143 return newWidget(type)
|
Xiiph@0
|
144 end
|
Xiiph@0
|
145 end
|
Xiiph@0
|
146 return newObj
|
Xiiph@0
|
147 end
|
Xiiph@0
|
148 -- Releases an instance to the Pool
|
Xiiph@0
|
149 function delWidget(obj,type)
|
Xiiph@0
|
150 if not objPools[type] then
|
Xiiph@0
|
151 objPools[type] = {}
|
Xiiph@0
|
152 end
|
Xiiph@0
|
153 if objPools[type][obj] then
|
Xiiph@0
|
154 error("Attempt to Release Widget that is already released", 2)
|
Xiiph@0
|
155 end
|
Xiiph@0
|
156 objPools[type][obj] = true
|
Xiiph@0
|
157 end
|
Xiiph@0
|
158 end
|
Xiiph@0
|
159
|
Xiiph@0
|
160
|
Xiiph@0
|
161 -------------------
|
Xiiph@0
|
162 -- API Functions --
|
Xiiph@0
|
163 -------------------
|
Xiiph@0
|
164
|
Xiiph@0
|
165 -- Gets a widget Object
|
Xiiph@0
|
166
|
Xiiph@0
|
167 --- Create a new Widget of the given type.
|
Xiiph@0
|
168 -- This function will instantiate a new widget (or use one from the widget pool), and call the
|
Xiiph@0
|
169 -- OnAcquire function on it, before returning.
|
Xiiph@0
|
170 -- @param type The type of the widget.
|
Xiiph@0
|
171 -- @return The newly created widget.
|
Xiiph@0
|
172 function AceGUI:Create(type)
|
Xiiph@0
|
173 if WidgetRegistry[type] then
|
Xiiph@0
|
174 local widget = newWidget(type)
|
Xiiph@0
|
175
|
Xiiph@0
|
176 if rawget(widget, "Acquire") then
|
Xiiph@0
|
177 widget.OnAcquire = widget.Acquire
|
Xiiph@0
|
178 widget.Acquire = nil
|
Xiiph@0
|
179 elseif rawget(widget, "Aquire") then
|
Xiiph@0
|
180 widget.OnAcquire = widget.Aquire
|
Xiiph@0
|
181 widget.Aquire = nil
|
Xiiph@0
|
182 end
|
Xiiph@0
|
183
|
Xiiph@0
|
184 if rawget(widget, "Release") then
|
Xiiph@0
|
185 widget.OnRelease = rawget(widget, "Release")
|
Xiiph@0
|
186 widget.Release = nil
|
Xiiph@0
|
187 end
|
Xiiph@0
|
188
|
Xiiph@0
|
189 if widget.OnAcquire then
|
Xiiph@0
|
190 widget:OnAcquire()
|
Xiiph@0
|
191 else
|
Xiiph@0
|
192 error(("Widget type %s doesn't supply an OnAcquire Function"):format(type))
|
Xiiph@0
|
193 end
|
Xiiph@0
|
194 -- Set the default Layout ("List")
|
Xiiph@0
|
195 safecall(widget.SetLayout, widget, "List")
|
Xiiph@0
|
196 safecall(widget.ResumeLayout, widget)
|
Xiiph@0
|
197 return widget
|
Xiiph@0
|
198 end
|
Xiiph@0
|
199 end
|
Xiiph@0
|
200
|
Xiiph@0
|
201 --- Releases a widget Object.
|
Xiiph@0
|
202 -- This function calls OnRelease on the widget and places it back in the widget pool.
|
Xiiph@0
|
203 -- Any data on the widget is being erased, and the widget will be hidden.\\
|
Xiiph@0
|
204 -- If this widget is a Container-Widget, all of its Child-Widgets will be releases as well.
|
Xiiph@0
|
205 -- @param widget The widget to release
|
Xiiph@0
|
206 function AceGUI:Release(widget)
|
Xiiph@0
|
207 safecall(widget.PauseLayout, widget)
|
Xiiph@0
|
208 widget:Fire("OnRelease")
|
Xiiph@0
|
209 safecall(widget.ReleaseChildren, widget)
|
Xiiph@0
|
210
|
Xiiph@0
|
211 if widget.OnRelease then
|
Xiiph@0
|
212 widget:OnRelease()
|
Xiiph@0
|
213 -- else
|
Xiiph@0
|
214 -- error(("Widget type %s doesn't supply an OnRelease Function"):format(widget.type))
|
Xiiph@0
|
215 end
|
Xiiph@0
|
216 for k in pairs(widget.userdata) do
|
Xiiph@0
|
217 widget.userdata[k] = nil
|
Xiiph@0
|
218 end
|
Xiiph@0
|
219 for k in pairs(widget.events) do
|
Xiiph@0
|
220 widget.events[k] = nil
|
Xiiph@0
|
221 end
|
Xiiph@0
|
222 widget.width = nil
|
Xiiph@0
|
223 widget.relWidth = nil
|
Xiiph@0
|
224 widget.height = nil
|
Xiiph@0
|
225 widget.relHeight = nil
|
Xiiph@0
|
226 widget.noAutoHeight = nil
|
Xiiph@0
|
227 widget.frame:ClearAllPoints()
|
Xiiph@0
|
228 widget.frame:Hide()
|
Xiiph@0
|
229 widget.frame:SetParent(UIParent)
|
Xiiph@0
|
230 widget.frame.width = nil
|
Xiiph@0
|
231 widget.frame.height = nil
|
Xiiph@0
|
232 if widget.content then
|
Xiiph@0
|
233 widget.content.width = nil
|
Xiiph@0
|
234 widget.content.height = nil
|
Xiiph@0
|
235 end
|
Xiiph@0
|
236 delWidget(widget, widget.type)
|
Xiiph@0
|
237 end
|
Xiiph@0
|
238
|
Xiiph@0
|
239 -----------
|
Xiiph@0
|
240 -- Focus --
|
Xiiph@0
|
241 -----------
|
Xiiph@0
|
242
|
Xiiph@0
|
243
|
Xiiph@0
|
244 --- Called when a widget has taken focus.
|
Xiiph@0
|
245 -- e.g. Dropdowns opening, Editboxes gaining kb focus
|
Xiiph@0
|
246 -- @param widget The widget that should be focused
|
Xiiph@0
|
247 function AceGUI:SetFocus(widget)
|
Xiiph@0
|
248 if self.FocusedWidget and self.FocusedWidget ~= widget then
|
Xiiph@0
|
249 safecall(self.FocusedWidget.ClearFocus, self.FocusedWidget)
|
Xiiph@0
|
250 end
|
Xiiph@0
|
251 self.FocusedWidget = widget
|
Xiiph@0
|
252 end
|
Xiiph@0
|
253
|
Xiiph@0
|
254
|
Xiiph@0
|
255 --- Called when something has happened that could cause widgets with focus to drop it
|
Xiiph@0
|
256 -- e.g. titlebar of a frame being clicked
|
Xiiph@0
|
257 function AceGUI:ClearFocus()
|
Xiiph@0
|
258 if self.FocusedWidget then
|
Xiiph@0
|
259 safecall(self.FocusedWidget.ClearFocus, self.FocusedWidget)
|
Xiiph@0
|
260 self.FocusedWidget = nil
|
Xiiph@0
|
261 end
|
Xiiph@0
|
262 end
|
Xiiph@0
|
263
|
Xiiph@0
|
264 -------------
|
Xiiph@0
|
265 -- Widgets --
|
Xiiph@0
|
266 -------------
|
Xiiph@0
|
267 --[[
|
Xiiph@0
|
268 Widgets must provide the following functions
|
Xiiph@0
|
269 OnAcquire() - Called when the object is acquired, should set everything to a default hidden state
|
Xiiph@0
|
270
|
Xiiph@0
|
271 And the following members
|
Xiiph@0
|
272 frame - the frame or derivitive object that will be treated as the widget for size and anchoring purposes
|
Xiiph@0
|
273 type - the type of the object, same as the name given to :RegisterWidget()
|
Xiiph@0
|
274
|
Xiiph@0
|
275 Widgets contain a table called userdata, this is a safe place to store data associated with the wigdet
|
Xiiph@0
|
276 It will be cleared automatically when a widget is released
|
Xiiph@0
|
277 Placing values directly into a widget object should be avoided
|
Xiiph@0
|
278
|
Xiiph@0
|
279 If the Widget can act as a container for other Widgets the following
|
Xiiph@0
|
280 content - frame or derivitive that children will be anchored to
|
Xiiph@0
|
281
|
Xiiph@0
|
282 The Widget can supply the following Optional Members
|
Xiiph@0
|
283 :OnRelease() - Called when the object is Released, should remove any additional anchors and clear any data
|
Xiiph@0
|
284 :OnWidthSet(width) - Called when the width of the widget is changed
|
Xiiph@0
|
285 :OnHeightSet(height) - Called when the height of the widget is changed
|
Xiiph@0
|
286 Widgets should not use the OnSizeChanged events of thier frame or content members, use these methods instead
|
Xiiph@0
|
287 AceGUI already sets a handler to the event
|
Xiiph@0
|
288 :LayoutFinished(width, height) - called after a layout has finished, the width and height will be the width and height of the
|
Xiiph@0
|
289 area used for controls. These can be nil if the layout used the existing size to layout the controls.
|
Xiiph@0
|
290
|
Xiiph@0
|
291 ]]
|
Xiiph@0
|
292
|
Xiiph@0
|
293 --------------------------
|
Xiiph@0
|
294 -- Widget Base Template --
|
Xiiph@0
|
295 --------------------------
|
Xiiph@0
|
296 do
|
Xiiph@0
|
297 local WidgetBase = AceGUI.WidgetBase
|
Xiiph@0
|
298
|
Xiiph@0
|
299 WidgetBase.SetParent = function(self, parent)
|
Xiiph@0
|
300 local frame = self.frame
|
Xiiph@0
|
301 frame:SetParent(nil)
|
Xiiph@0
|
302 frame:SetParent(parent.content)
|
Xiiph@0
|
303 self.parent = parent
|
Xiiph@0
|
304 end
|
Xiiph@0
|
305
|
Xiiph@0
|
306 WidgetBase.SetCallback = function(self, name, func)
|
Xiiph@0
|
307 if type(func) == "function" then
|
Xiiph@0
|
308 self.events[name] = func
|
Xiiph@0
|
309 end
|
Xiiph@0
|
310 end
|
Xiiph@0
|
311
|
Xiiph@0
|
312 WidgetBase.Fire = function(self, name, ...)
|
Xiiph@0
|
313 if self.events[name] then
|
Xiiph@0
|
314 local success, ret = safecall(self.events[name], self, name, ...)
|
Xiiph@0
|
315 if success then
|
Xiiph@0
|
316 return ret
|
Xiiph@0
|
317 end
|
Xiiph@0
|
318 end
|
Xiiph@0
|
319 end
|
Xiiph@0
|
320
|
Xiiph@0
|
321 WidgetBase.SetWidth = function(self, width)
|
Xiiph@0
|
322 self.frame:SetWidth(width)
|
Xiiph@0
|
323 self.frame.width = width
|
Xiiph@0
|
324 if self.OnWidthSet then
|
Xiiph@0
|
325 self:OnWidthSet(width)
|
Xiiph@0
|
326 end
|
Xiiph@0
|
327 end
|
Xiiph@0
|
328
|
Xiiph@0
|
329 WidgetBase.SetRelativeWidth = function(self, width)
|
Xiiph@0
|
330 if width <= 0 or width > 1 then
|
Xiiph@0
|
331 error(":SetRelativeWidth(width): Invalid relative width.", 2)
|
Xiiph@0
|
332 end
|
Xiiph@0
|
333 self.relWidth = width
|
Xiiph@0
|
334 self.width = "relative"
|
Xiiph@0
|
335 end
|
Xiiph@0
|
336
|
Xiiph@0
|
337 WidgetBase.SetHeight = function(self, height)
|
Xiiph@0
|
338 self.frame:SetHeight(height)
|
Xiiph@0
|
339 self.frame.height = height
|
Xiiph@0
|
340 if self.OnHeightSet then
|
Xiiph@0
|
341 self:OnHeightSet(height)
|
Xiiph@0
|
342 end
|
Xiiph@0
|
343 end
|
Xiiph@0
|
344
|
Xiiph@0
|
345 --[[ WidgetBase.SetRelativeHeight = function(self, height)
|
Xiiph@0
|
346 if height <= 0 or height > 1 then
|
Xiiph@0
|
347 error(":SetRelativeHeight(height): Invalid relative height.", 2)
|
Xiiph@0
|
348 end
|
Xiiph@0
|
349 self.relHeight = height
|
Xiiph@0
|
350 self.height = "relative"
|
Xiiph@0
|
351 end ]]
|
Xiiph@0
|
352
|
Xiiph@0
|
353 WidgetBase.IsVisible = function(self)
|
Xiiph@0
|
354 return self.frame:IsVisible()
|
Xiiph@0
|
355 end
|
Xiiph@0
|
356
|
Xiiph@0
|
357 WidgetBase.IsShown= function(self)
|
Xiiph@0
|
358 return self.frame:IsShown()
|
Xiiph@0
|
359 end
|
Xiiph@0
|
360
|
Xiiph@0
|
361 WidgetBase.Release = function(self)
|
Xiiph@0
|
362 AceGUI:Release(self)
|
Xiiph@0
|
363 end
|
Xiiph@0
|
364
|
Xiiph@0
|
365 WidgetBase.SetPoint = function(self, ...)
|
Xiiph@0
|
366 return self.frame:SetPoint(...)
|
Xiiph@0
|
367 end
|
Xiiph@0
|
368
|
Xiiph@0
|
369 WidgetBase.ClearAllPoints = function(self)
|
Xiiph@0
|
370 return self.frame:ClearAllPoints()
|
Xiiph@0
|
371 end
|
Xiiph@0
|
372
|
Xiiph@0
|
373 WidgetBase.GetNumPoints = function(self)
|
Xiiph@0
|
374 return self.frame:GetNumPoints()
|
Xiiph@0
|
375 end
|
Xiiph@0
|
376
|
Xiiph@0
|
377 WidgetBase.GetPoint = function(self, ...)
|
Xiiph@0
|
378 return self.frame:GetPoint(...)
|
Xiiph@0
|
379 end
|
Xiiph@0
|
380
|
Xiiph@0
|
381 WidgetBase.GetUserDataTable = function(self)
|
Xiiph@0
|
382 return self.userdata
|
Xiiph@0
|
383 end
|
Xiiph@0
|
384
|
Xiiph@0
|
385 WidgetBase.SetUserData = function(self, key, value)
|
Xiiph@0
|
386 self.userdata[key] = value
|
Xiiph@0
|
387 end
|
Xiiph@0
|
388
|
Xiiph@0
|
389 WidgetBase.GetUserData = function(self, key)
|
Xiiph@0
|
390 return self.userdata[key]
|
Xiiph@0
|
391 end
|
Xiiph@0
|
392
|
Xiiph@0
|
393 WidgetBase.IsFullHeight = function(self)
|
Xiiph@0
|
394 return self.height == "fill"
|
Xiiph@0
|
395 end
|
Xiiph@0
|
396
|
Xiiph@0
|
397 WidgetBase.SetFullHeight = function(self, isFull)
|
Xiiph@0
|
398 if isFull then
|
Xiiph@0
|
399 self.height = "fill"
|
Xiiph@0
|
400 else
|
Xiiph@0
|
401 self.height = nil
|
Xiiph@0
|
402 end
|
Xiiph@0
|
403 end
|
Xiiph@0
|
404
|
Xiiph@0
|
405 WidgetBase.IsFullWidth = function(self)
|
Xiiph@0
|
406 return self.width == "fill"
|
Xiiph@0
|
407 end
|
Xiiph@0
|
408
|
Xiiph@0
|
409 WidgetBase.SetFullWidth = function(self, isFull)
|
Xiiph@0
|
410 if isFull then
|
Xiiph@0
|
411 self.width = "fill"
|
Xiiph@0
|
412 else
|
Xiiph@0
|
413 self.width = nil
|
Xiiph@0
|
414 end
|
Xiiph@0
|
415 end
|
Xiiph@0
|
416
|
Xiiph@0
|
417 -- local function LayoutOnUpdate(this)
|
Xiiph@0
|
418 -- this:SetScript("OnUpdate",nil)
|
Xiiph@0
|
419 -- this.obj:PerformLayout()
|
Xiiph@0
|
420 -- end
|
Xiiph@0
|
421
|
Xiiph@0
|
422 local WidgetContainerBase = AceGUI.WidgetContainerBase
|
Xiiph@0
|
423
|
Xiiph@0
|
424 WidgetContainerBase.PauseLayout = function(self)
|
Xiiph@0
|
425 self.LayoutPaused = true
|
Xiiph@0
|
426 end
|
Xiiph@0
|
427
|
Xiiph@0
|
428 WidgetContainerBase.ResumeLayout = function(self)
|
Xiiph@0
|
429 self.LayoutPaused = nil
|
Xiiph@0
|
430 end
|
Xiiph@0
|
431
|
Xiiph@0
|
432 WidgetContainerBase.PerformLayout = function(self)
|
Xiiph@0
|
433 if self.LayoutPaused then
|
Xiiph@0
|
434 return
|
Xiiph@0
|
435 end
|
Xiiph@0
|
436 safecall(self.LayoutFunc, self.content, self.children)
|
Xiiph@0
|
437 end
|
Xiiph@0
|
438
|
Xiiph@0
|
439 --call this function to layout, makes sure layed out objects get a frame to get sizes etc
|
Xiiph@0
|
440 WidgetContainerBase.DoLayout = function(self)
|
Xiiph@0
|
441 self:PerformLayout()
|
Xiiph@0
|
442 -- if not self.parent then
|
Xiiph@0
|
443 -- self.frame:SetScript("OnUpdate", LayoutOnUpdate)
|
Xiiph@0
|
444 -- end
|
Xiiph@0
|
445 end
|
Xiiph@0
|
446
|
Xiiph@0
|
447 WidgetContainerBase.AddChild = function(self, child, beforeWidget)
|
Xiiph@0
|
448 if beforeWidget then
|
Xiiph@0
|
449 local siblingIndex = 1
|
Xiiph@0
|
450 for _, widget in pairs(self.children) do
|
Xiiph@0
|
451 if widget == beforeWidget then
|
Xiiph@0
|
452 break
|
Xiiph@0
|
453 end
|
Xiiph@0
|
454 siblingIndex = siblingIndex + 1
|
Xiiph@0
|
455 end
|
Xiiph@0
|
456 tinsert(self.children, siblingIndex, child)
|
Xiiph@0
|
457 else
|
Xiiph@0
|
458 tinsert(self.children, child)
|
Xiiph@0
|
459 end
|
Xiiph@0
|
460 child:SetParent(self)
|
Xiiph@0
|
461 child.frame:Show()
|
Xiiph@0
|
462 self:DoLayout()
|
Xiiph@0
|
463 end
|
Xiiph@0
|
464
|
Xiiph@0
|
465 WidgetContainerBase.AddChildren = function(self, ...)
|
Xiiph@0
|
466 for i = 1, select("#", ...) do
|
Xiiph@0
|
467 local child = select(i, ...)
|
Xiiph@0
|
468 tinsert(self.children, child)
|
Xiiph@0
|
469 child:SetParent(self)
|
Xiiph@0
|
470 child.frame:Show()
|
Xiiph@0
|
471 end
|
Xiiph@0
|
472 self:DoLayout()
|
Xiiph@0
|
473 end
|
Xiiph@0
|
474
|
Xiiph@0
|
475 WidgetContainerBase.ReleaseChildren = function(self)
|
Xiiph@0
|
476 local children = self.children
|
Xiiph@0
|
477 for i = 1,#children do
|
Xiiph@0
|
478 AceGUI:Release(children[i])
|
Xiiph@0
|
479 children[i] = nil
|
Xiiph@0
|
480 end
|
Xiiph@0
|
481 end
|
Xiiph@0
|
482
|
Xiiph@0
|
483 WidgetContainerBase.SetLayout = function(self, Layout)
|
Xiiph@0
|
484 self.LayoutFunc = AceGUI:GetLayout(Layout)
|
Xiiph@0
|
485 end
|
Xiiph@0
|
486
|
Xiiph@0
|
487 WidgetContainerBase.SetAutoAdjustHeight = function(self, adjust)
|
Xiiph@0
|
488 if adjust then
|
Xiiph@0
|
489 self.noAutoHeight = nil
|
Xiiph@0
|
490 else
|
Xiiph@0
|
491 self.noAutoHeight = true
|
Xiiph@0
|
492 end
|
Xiiph@0
|
493 end
|
Xiiph@0
|
494
|
Xiiph@0
|
495 local function FrameResize(this)
|
Xiiph@0
|
496 local self = this.obj
|
Xiiph@0
|
497 if this:GetWidth() and this:GetHeight() then
|
Xiiph@0
|
498 if self.OnWidthSet then
|
Xiiph@0
|
499 self:OnWidthSet(this:GetWidth())
|
Xiiph@0
|
500 end
|
Xiiph@0
|
501 if self.OnHeightSet then
|
Xiiph@0
|
502 self:OnHeightSet(this:GetHeight())
|
Xiiph@0
|
503 end
|
Xiiph@0
|
504 end
|
Xiiph@0
|
505 end
|
Xiiph@0
|
506
|
Xiiph@0
|
507 local function ContentResize(this)
|
Xiiph@0
|
508 if this:GetWidth() and this:GetHeight() then
|
Xiiph@0
|
509 this.width = this:GetWidth()
|
Xiiph@0
|
510 this.height = this:GetHeight()
|
Xiiph@0
|
511 this.obj:DoLayout()
|
Xiiph@0
|
512 end
|
Xiiph@0
|
513 end
|
Xiiph@0
|
514
|
Xiiph@0
|
515 setmetatable(WidgetContainerBase, {__index=WidgetBase})
|
Xiiph@0
|
516
|
Xiiph@0
|
517 --One of these function should be called on each Widget Instance as part of its creation process
|
Xiiph@0
|
518
|
Xiiph@0
|
519 --- Register a widget-class as a container for newly created widgets.
|
Xiiph@0
|
520 -- @param widget The widget class
|
Xiiph@0
|
521 function AceGUI:RegisterAsContainer(widget)
|
Xiiph@0
|
522 widget.children = {}
|
Xiiph@0
|
523 widget.userdata = {}
|
Xiiph@0
|
524 widget.events = {}
|
Xiiph@0
|
525 widget.base = WidgetContainerBase
|
Xiiph@0
|
526 widget.content.obj = widget
|
Xiiph@0
|
527 widget.frame.obj = widget
|
Xiiph@0
|
528 widget.content:SetScript("OnSizeChanged", ContentResize)
|
Xiiph@0
|
529 widget.frame:SetScript("OnSizeChanged", FrameResize)
|
Xiiph@0
|
530 setmetatable(widget, {__index = WidgetContainerBase})
|
Xiiph@0
|
531 widget:SetLayout("List")
|
Xiiph@0
|
532 return widget
|
Xiiph@0
|
533 end
|
Xiiph@0
|
534
|
Xiiph@0
|
535 --- Register a widget-class as a widget.
|
Xiiph@0
|
536 -- @param widget The widget class
|
Xiiph@0
|
537 function AceGUI:RegisterAsWidget(widget)
|
Xiiph@0
|
538 widget.userdata = {}
|
Xiiph@0
|
539 widget.events = {}
|
Xiiph@0
|
540 widget.base = WidgetBase
|
Xiiph@0
|
541 widget.frame.obj = widget
|
Xiiph@0
|
542 widget.frame:SetScript("OnSizeChanged", FrameResize)
|
Xiiph@0
|
543 setmetatable(widget, {__index = WidgetBase})
|
Xiiph@0
|
544 return widget
|
Xiiph@0
|
545 end
|
Xiiph@0
|
546 end
|
Xiiph@0
|
547
|
Xiiph@0
|
548
|
Xiiph@0
|
549
|
Xiiph@0
|
550
|
Xiiph@0
|
551 ------------------
|
Xiiph@0
|
552 -- Widget API --
|
Xiiph@0
|
553 ------------------
|
Xiiph@0
|
554
|
Xiiph@0
|
555 --- Registers a widget Constructor, this function returns a new instance of the Widget
|
Xiiph@0
|
556 -- @param Name The name of the widget
|
Xiiph@0
|
557 -- @param Constructor The widget constructor function
|
Xiiph@0
|
558 -- @param Version The version of the widget
|
Xiiph@0
|
559 function AceGUI:RegisterWidgetType(Name, Constructor, Version)
|
Xiiph@0
|
560 assert(type(Constructor) == "function")
|
Xiiph@0
|
561 assert(type(Version) == "number")
|
Xiiph@0
|
562
|
Xiiph@0
|
563 local oldVersion = WidgetVersions[Name]
|
Xiiph@0
|
564 if oldVersion and oldVersion >= Version then return end
|
Xiiph@0
|
565
|
Xiiph@0
|
566 WidgetVersions[Name] = Version
|
Xiiph@0
|
567 WidgetRegistry[Name] = Constructor
|
Xiiph@0
|
568 end
|
Xiiph@0
|
569
|
Xiiph@0
|
570 --- Registers a Layout Function
|
Xiiph@0
|
571 -- @param Name The name of the layout
|
Xiiph@0
|
572 -- @param LayoutFunc Reference to the layout function
|
Xiiph@0
|
573 function AceGUI:RegisterLayout(Name, LayoutFunc)
|
Xiiph@0
|
574 assert(type(LayoutFunc) == "function")
|
Xiiph@0
|
575 if type(Name) == "string" then
|
Xiiph@0
|
576 Name = Name:upper()
|
Xiiph@0
|
577 end
|
Xiiph@0
|
578 LayoutRegistry[Name] = LayoutFunc
|
Xiiph@0
|
579 end
|
Xiiph@0
|
580
|
Xiiph@0
|
581 --- Get a Layout Function from the registry
|
Xiiph@0
|
582 -- @param Name The name of the layout
|
Xiiph@0
|
583 function AceGUI:GetLayout(Name)
|
Xiiph@0
|
584 if type(Name) == "string" then
|
Xiiph@0
|
585 Name = Name:upper()
|
Xiiph@0
|
586 end
|
Xiiph@0
|
587 return LayoutRegistry[Name]
|
Xiiph@0
|
588 end
|
Xiiph@0
|
589
|
Xiiph@0
|
590 AceGUI.counts = AceGUI.counts or {}
|
Xiiph@0
|
591
|
Xiiph@0
|
592 --- A type-based counter to count the number of widgets created.
|
Xiiph@0
|
593 -- This is used by widgets that require a named frame, e.g. when a Blizzard
|
Xiiph@0
|
594 -- Template requires it.
|
Xiiph@0
|
595 -- @param type The widget type
|
Xiiph@0
|
596 function AceGUI:GetNextWidgetNum(type)
|
Xiiph@0
|
597 if not self.counts[type] then
|
Xiiph@0
|
598 self.counts[type] = 0
|
Xiiph@0
|
599 end
|
Xiiph@0
|
600 self.counts[type] = self.counts[type] + 1
|
Xiiph@0
|
601 return self.counts[type]
|
Xiiph@0
|
602 end
|
Xiiph@0
|
603
|
Xiiph@0
|
604 --- Return the number of created widgets for this type.
|
Xiiph@0
|
605 -- In contrast to GetNextWidgetNum, the number is not incremented.
|
Xiiph@0
|
606 -- @param type The widget type
|
Xiiph@0
|
607 function AceGUI:GetWidgetCount(type)
|
Xiiph@0
|
608 return self.counts[type] or 0
|
Xiiph@0
|
609 end
|
Xiiph@0
|
610
|
Xiiph@0
|
611 --- Return the version of the currently registered widget type.
|
Xiiph@0
|
612 -- @param type The widget type
|
Xiiph@0
|
613 function AceGUI:GetWidgetVersion(type)
|
Xiiph@0
|
614 return WidgetVersions[type]
|
Xiiph@0
|
615 end
|
Xiiph@0
|
616
|
Xiiph@0
|
617 -------------
|
Xiiph@0
|
618 -- Layouts --
|
Xiiph@0
|
619 -------------
|
Xiiph@0
|
620
|
Xiiph@0
|
621 --[[
|
Xiiph@0
|
622 A Layout is a func that takes 2 parameters
|
Xiiph@0
|
623 content - the frame that widgets will be placed inside
|
Xiiph@0
|
624 children - a table containing the widgets to layout
|
Xiiph@0
|
625 ]]
|
Xiiph@0
|
626
|
Xiiph@0
|
627 -- Very simple Layout, Children are stacked on top of each other down the left side
|
Xiiph@0
|
628 AceGUI:RegisterLayout("List",
|
Xiiph@0
|
629 function(content, children)
|
Xiiph@0
|
630 local height = 0
|
Xiiph@0
|
631 local width = content.width or content:GetWidth() or 0
|
Xiiph@0
|
632 for i = 1, #children do
|
Xiiph@0
|
633 local child = children[i]
|
Xiiph@0
|
634
|
Xiiph@0
|
635 local frame = child.frame
|
Xiiph@0
|
636 frame:ClearAllPoints()
|
Xiiph@0
|
637 frame:Show()
|
Xiiph@0
|
638 if i == 1 then
|
Xiiph@0
|
639 frame:SetPoint("TOPLEFT", content)
|
Xiiph@0
|
640 else
|
Xiiph@0
|
641 frame:SetPoint("TOPLEFT", children[i-1].frame, "BOTTOMLEFT")
|
Xiiph@0
|
642 end
|
Xiiph@0
|
643
|
Xiiph@0
|
644 if child.width == "fill" then
|
Xiiph@0
|
645 child:SetWidth(width)
|
Xiiph@0
|
646 frame:SetPoint("RIGHT", content)
|
Xiiph@0
|
647
|
Xiiph@0
|
648 if child.DoLayout then
|
Xiiph@0
|
649 child:DoLayout()
|
Xiiph@0
|
650 end
|
Xiiph@0
|
651 elseif child.width == "relative" then
|
Xiiph@0
|
652 child:SetWidth(width * child.relWidth)
|
Xiiph@0
|
653
|
Xiiph@0
|
654 if child.DoLayout then
|
Xiiph@0
|
655 child:DoLayout()
|
Xiiph@0
|
656 end
|
Xiiph@0
|
657 end
|
Xiiph@0
|
658
|
Xiiph@0
|
659 height = height + (frame.height or frame:GetHeight() or 0)
|
Xiiph@0
|
660 end
|
Xiiph@0
|
661 safecall(content.obj.LayoutFinished, content.obj, nil, height)
|
Xiiph@0
|
662 end)
|
Xiiph@0
|
663
|
Xiiph@0
|
664 -- A single control fills the whole content area
|
Xiiph@0
|
665 AceGUI:RegisterLayout("Fill",
|
Xiiph@0
|
666 function(content, children)
|
Xiiph@0
|
667 if children[1] then
|
Xiiph@0
|
668 children[1]:SetWidth(content:GetWidth() or 0)
|
Xiiph@0
|
669 children[1]:SetHeight(content:GetHeight() or 0)
|
Xiiph@0
|
670 children[1].frame:SetAllPoints(content)
|
Xiiph@0
|
671 children[1].frame:Show()
|
Xiiph@0
|
672 safecall(content.obj.LayoutFinished, content.obj, nil, children[1].frame:GetHeight())
|
Xiiph@0
|
673 end
|
Xiiph@0
|
674 end)
|
Xiiph@0
|
675
|
Xiiph@0
|
676 AceGUI:RegisterLayout("Flow",
|
Xiiph@0
|
677 function(content, children)
|
Xiiph@0
|
678 --used height so far
|
Xiiph@0
|
679 local height = 0
|
Xiiph@0
|
680 --width used in the current row
|
Xiiph@0
|
681 local usedwidth = 0
|
Xiiph@0
|
682 --height of the current row
|
Xiiph@0
|
683 local rowheight = 0
|
Xiiph@0
|
684 local rowoffset = 0
|
Xiiph@0
|
685 local lastrowoffset
|
Xiiph@0
|
686
|
Xiiph@0
|
687 local width = content.width or content:GetWidth() or 0
|
Xiiph@0
|
688
|
Xiiph@0
|
689 --control at the start of the row
|
Xiiph@0
|
690 local rowstart
|
Xiiph@0
|
691 local rowstartoffset
|
Xiiph@0
|
692 local lastrowstart
|
Xiiph@0
|
693 local isfullheight
|
Xiiph@0
|
694
|
Xiiph@0
|
695 local frameoffset
|
Xiiph@0
|
696 local lastframeoffset
|
Xiiph@0
|
697 local oversize
|
Xiiph@0
|
698 for i = 1, #children do
|
Xiiph@0
|
699 local child = children[i]
|
Xiiph@0
|
700 oversize = nil
|
Xiiph@0
|
701 local frame = child.frame
|
Xiiph@0
|
702 local frameheight = frame.height or frame:GetHeight() or 0
|
Xiiph@0
|
703 local framewidth = frame.width or frame:GetWidth() or 0
|
Xiiph@0
|
704 lastframeoffset = frameoffset
|
Xiiph@0
|
705 -- HACK: Why did we set a frameoffset of (frameheight / 2) ?
|
Xiiph@0
|
706 -- That was moving all widgets half the widgets size down, is that intended?
|
Xiiph@0
|
707 -- Actually, it seems to be neccessary for many cases, we'll leave it in for now.
|
Xiiph@0
|
708 -- If widgets seem to anchor weirdly with this, provide a valid alignoffset for them.
|
Xiiph@0
|
709 -- TODO: Investigate moar!
|
Xiiph@0
|
710 frameoffset = child.alignoffset or (frameheight / 2)
|
Xiiph@0
|
711
|
Xiiph@0
|
712 if child.width == "relative" then
|
Xiiph@0
|
713 framewidth = width * child.relWidth
|
Xiiph@0
|
714 end
|
Xiiph@0
|
715
|
Xiiph@0
|
716 frame:Show()
|
Xiiph@0
|
717 frame:ClearAllPoints()
|
Xiiph@0
|
718 if i == 1 then
|
Xiiph@0
|
719 -- anchor the first control to the top left
|
Xiiph@0
|
720 frame:SetPoint("TOPLEFT", content)
|
Xiiph@0
|
721 rowheight = frameheight
|
Xiiph@0
|
722 rowoffset = frameoffset
|
Xiiph@0
|
723 rowstart = frame
|
Xiiph@0
|
724 rowstartoffset = frameoffset
|
Xiiph@0
|
725 usedwidth = framewidth
|
Xiiph@0
|
726 if usedwidth > width then
|
Xiiph@0
|
727 oversize = true
|
Xiiph@0
|
728 end
|
Xiiph@0
|
729 else
|
Xiiph@0
|
730 -- if there isn't available width for the control start a new row
|
Xiiph@0
|
731 -- if a control is "fill" it will be on a row of its own full width
|
Xiiph@0
|
732 if usedwidth == 0 or ((framewidth) + usedwidth > width) or child.width == "fill" then
|
Xiiph@0
|
733 if isfullheight then
|
Xiiph@0
|
734 -- a previous row has already filled the entire height, there's nothing we can usefully do anymore
|
Xiiph@0
|
735 -- (maybe error/warn about this?)
|
Xiiph@0
|
736 break
|
Xiiph@0
|
737 end
|
Xiiph@0
|
738 --anchor the previous row, we will now know its height and offset
|
Xiiph@0
|
739 rowstart:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -(height + (rowoffset - rowstartoffset) + 3))
|
Xiiph@0
|
740 height = height + rowheight + 3
|
Xiiph@0
|
741 --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
|
742 rowstart = frame
|
Xiiph@0
|
743 rowstartoffset = frameoffset
|
Xiiph@0
|
744 rowheight = frameheight
|
Xiiph@0
|
745 rowoffset = frameoffset
|
Xiiph@0
|
746 usedwidth = framewidth
|
Xiiph@0
|
747 if usedwidth > width then
|
Xiiph@0
|
748 oversize = true
|
Xiiph@0
|
749 end
|
Xiiph@0
|
750 -- put the control on the current row, adding it to the width and checking if the height needs to be increased
|
Xiiph@0
|
751 else
|
Xiiph@0
|
752 --handles cases where the new height is higher than either control because of the offsets
|
Xiiph@0
|
753 --math.max(rowheight-rowoffset+frameoffset, frameheight-frameoffset+rowoffset)
|
Xiiph@0
|
754
|
Xiiph@0
|
755 --offset is always the larger of the two offsets
|
Xiiph@0
|
756 rowoffset = math_max(rowoffset, frameoffset)
|
Xiiph@0
|
757 rowheight = math_max(rowheight, rowoffset + (frameheight / 2))
|
Xiiph@0
|
758
|
Xiiph@0
|
759 frame:SetPoint("TOPLEFT", children[i-1].frame, "TOPRIGHT", 0, frameoffset - lastframeoffset)
|
Xiiph@0
|
760 usedwidth = framewidth + usedwidth
|
Xiiph@0
|
761 end
|
Xiiph@0
|
762 end
|
Xiiph@0
|
763
|
Xiiph@0
|
764 if child.width == "fill" then
|
Xiiph@0
|
765 child:SetWidth(width)
|
Xiiph@0
|
766 frame:SetPoint("RIGHT", content)
|
Xiiph@0
|
767
|
Xiiph@0
|
768 usedwidth = 0
|
Xiiph@0
|
769 rowstart = frame
|
Xiiph@0
|
770 rowstartoffset = frameoffset
|
Xiiph@0
|
771
|
Xiiph@0
|
772 if child.DoLayout then
|
Xiiph@0
|
773 child:DoLayout()
|
Xiiph@0
|
774 end
|
Xiiph@0
|
775 rowheight = frame.height or frame:GetHeight() or 0
|
Xiiph@0
|
776 rowoffset = child.alignoffset or (rowheight / 2)
|
Xiiph@0
|
777 rowstartoffset = rowoffset
|
Xiiph@0
|
778 elseif child.width == "relative" then
|
Xiiph@0
|
779 child:SetWidth(width * child.relWidth)
|
Xiiph@0
|
780
|
Xiiph@0
|
781 if child.DoLayout then
|
Xiiph@0
|
782 child:DoLayout()
|
Xiiph@0
|
783 end
|
Xiiph@0
|
784 elseif oversize then
|
Xiiph@0
|
785 if width > 1 then
|
Xiiph@0
|
786 frame:SetPoint("RIGHT", content)
|
Xiiph@0
|
787 end
|
Xiiph@0
|
788 end
|
Xiiph@0
|
789
|
Xiiph@0
|
790 if child.height == "fill" then
|
Xiiph@0
|
791 frame:SetPoint("BOTTOM", content)
|
Xiiph@0
|
792 isfullheight = true
|
Xiiph@0
|
793 end
|
Xiiph@0
|
794 end
|
Xiiph@0
|
795
|
Xiiph@0
|
796 --anchor the last row, if its full height needs a special case since its height has just been changed by the anchor
|
Xiiph@0
|
797 if isfullheight then
|
Xiiph@0
|
798 rowstart:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -height)
|
Xiiph@0
|
799 elseif rowstart then
|
Xiiph@0
|
800 rowstart:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -(height + (rowoffset - rowstartoffset) + 3))
|
Xiiph@0
|
801 end
|
Xiiph@0
|
802
|
Xiiph@0
|
803 height = height + rowheight + 3
|
Xiiph@0
|
804 safecall(content.obj.LayoutFinished, content.obj, nil, height)
|
Xiiph@0
|
805 end)
|