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