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