Tutorials, extensions, and source files for ActionScript, Flash, and other Adobe products.

 

senocular.com ActionScript Library

LayoutManager.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
package com.senocular.display {
	
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.geom.Rectangle;
	import flash.utils.Dictionary;

	/*
	 * Dispatched when any registered layout changes.
	 */
	[Event("change")] // [Event(name="change", type="flash.events.Event")]
	
	/**
	 * The LayoutManager class is used to help layout instances
	 * keep up to date. By registering a diaplay object with a 
	 * LayoutManager instance, a layout object is created and
	 * associated with that display object.  This layout object can
	 * then be recognized when a parent display object within the 
	 * display list with a layout is updated and update
	 * itself with that parent's changes.  Additionally, by using
	 * LayoutManager.initializeAutoUpdate(), with a reference to stage
	 * the RENDER event will be used to automatically update registered
	 * layouts at the end of a frame when they've changed. When
	 * registered layouts change, the LayoutManager will dispatch a
	 * CHANGE event.
	 * <br/><br/>
	 * The LayoutManager works through instances as well as a singleton.
	 * If you do not want to manage multiple instances of LayoutManager,
	 * you can call LayoutManager methods directly from the LayoutManager
	 * class (meaning the class has two sets of methods, one set for 
	 * instances and one set static).
	 * <br/><br/>
	 * Use of the LayoutManager is optional for layouts.  If you do not
	 * use the LayoutManager class, then you will need to make sure to
	 * call draw() for all of your layouts to make sure they are
	 * properly updated.
	 *
	 * @author Trevor McCauley, www.senocular.com
	 * @date August 22, 2008
	 * @version 1.0.1
	 */
	public class LayoutManager extends EventDispatcher {
		
		
		private static var _instance:LayoutManager = new LayoutManager();

		/**
		 * Returns the LayoutManager instance associated with
		 * the LayoutManager class if the LayoutManager is
		 * being used as a singleton.
		 */
		public static function getInstance():LayoutManager {
			return _instance;
		}
		
		/**
		 * Registers a display object with a layout. As a 
		 * registered layout, it will be available for updates
		 * if the Layout class is initialized with auto updates
		 * and for propagation of changes from parent layouts. If the
		 * target display object already has a registered layout
		 * for this same LayoutManager, that layout is returned. If the
		 * target is registered to another layout manager, it will 
		 * continue to be registered to that layout manager with a 
		 * separate layout instance.
		 * @param target The display object to get a layout for.
		 * @param changeHandler If a new Layout instance is created, this
		 * 		handler will be used to update the target during the CHANGE
		 * 		event [optional].
		 */
		public static function registerNewLayout(target:DisplayObject, useDefaultChangeHandler:Boolean = true):Layout {
			return _instance.registerNewLayout(target, useDefaultChangeHandler);
		}
		
		/**
		 * Returns the current layout object associated with 
		 * the passed display object.  If no layout has been
		 * registered for that object, null is returned.
		 * @param target The display object to get a layout for.
		 */
		public static function getLayout(target:DisplayObject):Layout {
			return _instance.getLayout(target);
		}
		
		/**
		 * Unregisters a display object's layout. As a 
		 * registered layout, it will be available for updates
		 * if the LayoutManager class is initialized with auto updates
		 * and for propagation of changes from parent layouts. When
		 * unregistered, updates will have to be made manually.
		 * @param target The display object to unregister from the manager.
		 */
		public static function unregisterLayout(target:DisplayObject):Layout {
			return _instance.unregisterLayout(target);
		}
		
		/**
		 * Determines if the display object has a registered layout.
		 * @param target A display object to check if registered
		 * 		to this LayoutManager instance.
		 */
		public static function isRegisteredLayout(target:DisplayObject):Boolean {
			return _instance.isRegisteredLayout(target);
		}
		
		/**
		 * Initializes the Layout class to perform automatic updates
		 * for all registered layouts.  Updates happen during the RENDER
		 * event and only occur if there was a change in a layout. If
		 * already initialized the Layout class for auto updates and
		 * want to stop the auto updates, call initializeAutoUpdate
		 * again but pass null instead of a reference to the stage.
		 * @param stage A reference to the stage to be used to 
		 * 		allow for updates in the RENDER event.
		 */
		public static function initializeAutoUpdate(stage:Stage):void {
			_instance.initializeAutoUpdate(stage);
		}
		
		/**
		 * Draws and updates all layouts in the layout manager
		 */
		public static function draw():void {
			_instance.validate(null);
		}
		
		/**
		 * @private
		 */
		internal var invalidList:Dictionary = new Dictionary(true);
		
		/**
		 * @private
		 */
		internal var registeredList:Dictionary = new Dictionary(true);
		
		private var stage:Stage;
		private var invalid:Boolean;
			
		/**
		 * Constructor. Creates a new LayoutManager instance from which
		 * you can register layouts for diaply objects.  As an alternative
		 * to making your own LayoutManager instances, you can also use
		 * the static methods from the LayoutManager class to handle all
		 * layouts.
		 */
		public function LayoutManager() {}
		
		/**
		 * Registers a display object with a layout. As a 
		 * registered layout, it will be available for updates
		 * if the Layout class is initialized with auto updates
		 * and for propagation of changes from parent layouts. If the
		 * target display object already has a registered layout
		 * for this same LayoutManager, that layout is returned. If the
		 * target is registered to another layout manager, it will 
		 * continue to be registered to that layout manager with a 
		 * separate layout instance.
		 * @param target The display object to get a layout for.
		 * @param changeHandler If a new Layout instance is created, this
		 * 		handler will be used to update the target during the CHANGE
		 * 		event [optional].
		 */
		public function registerNewLayout(target:DisplayObject, useDefaultChangeHandler:Boolean = true):Layout {
			// create new layout and associate with target
			// if doesn't already exist in registeredList
			if (!(target in registeredList)) {
				var layout:Layout = new Layout(target, useDefaultChangeHandler);
				layout._manager = this;
				registeredList[target] = layout;
			}
			
			return Layout(registeredList[target]);
		}
		
		/**
		 * Returns the current layout object associated with 
		 * the passed display object.  If no layout has been
		 * registered for that object, null is returned.
		 * @param target The display object to get a layout for.
		 */
		public function getLayout(target:DisplayObject):Layout {
			if (target in registeredList) {
				return Layout(registeredList[target]);
			}
			return null;
		}
		
		/**
		 * Unregisters a display object's layout. As a 
		 * registered layout, it will be available for updates
		 * if the LayoutManager class is initialized with auto updates
		 * and for propagation of changes from parent layouts. When
		 * unregistered, updates will have to be made manually.
		 * @param target The display object to unregister from the manager.
		 */
		public function unregisterLayout(target:DisplayObject):Layout {
			if (target in registeredList) {
				var layout:Layout = Layout(registeredList[target]);
				layout._manager = null;
				delete registeredList[target];
				return layout;
			}
			return null;
		}
		
		/**
		 * Determines if the display object has a registered layout.
		 * @param target A display object to check if registered
		 * 		to this LayoutManager instance.
		 */
		public function isRegisteredLayout(target:DisplayObject):Boolean {
			return Boolean(target in registeredList);
		}
		
		/**
		 * Initializes the Layout class to perform automatic updates
		 * for all registered layouts.  Updates happen during the RENDER
		 * event and only occur if there was a change in a layout. If
		 * already initialized the Layout class for auto updates and
		 * want to stop the auto updates, call initializeAutoUpdate
		 * again but pass null instead of a reference to the stage.
		 * @param stage A reference to the stage to be used to 
		 * 		allow for updates in the RENDER event.
		 */
		public function initializeAutoUpdate(stage:Stage):void {
			if (this.stage){
				this.stage.removeEventListener(Event.RENDER, validate, false);
			}
			this.stage = stage;
			if (this.stage){
				this.stage.addEventListener(Event.RENDER, validate, false, 1, true);
			}
		}
		
		/**
		 * Draws and updates all layouts in the layout manager
		 */
		public function draw():void {
			validate(null);
		}
		
		/**
		 * Adds the passed layout to the invalid list of the manager
		 * and invalidates the stage (if available) to ensure that
		 * validate will be called in the next RENDER event
		 * @private
		 */
		internal function invalidate(layout:Layout):void {
			invalidList[layout] = true;
			if (stage){
				if (!invalid) {
					stage.invalidate();
					invalid = true;
				}
				
				// WORKAROUND: needed to retain render listeners
				// in case another action uses
				// removeEventListener(Event.RENDER, ... )
				// which removes all RENDER listeners (bug)
				initializeAutoUpdate(stage);
			}
		}
		
		/*
		 * Called in the RENDER event, updates all invalid
		 * layouts in the manager
		 */
		private function validate(event:Event):void {
			removeInvalidRedundancies();
			
			// draw each layout in invalid list
			var changedList:Dictionary = new Dictionary(true);
			for (var element:* in invalidList) {
				Layout(element)._draw(changedList);
			}
			Layout.updateChanged(changedList);
			
			// dispatch manager CHANGE if
			// changedList has any layouts
			for (element in changedList) {
				dispatchEvent(new Event(Event.CHANGE));
				break;
			}
			
			invalid = false;
		}
		
		/*
		 * Each invalid layout will also update it's children to fit
		 * within its new contraints/bounds.  If these children are
		 * also invalid, they can be removed from the invalid list since
		 * they will automatically be drawn when their parent layout is
		 */
		private function removeInvalidRedundancies():void {
			for (var element:* in invalidList) {
				removeRedundantChildren(Layout(element));
			}	
		}
		private function removeRedundantChildren(layout:Layout):void {
			// first check any children within the childList
			for (var element:* in layout.childList){
				if (element in invalidList) {
					delete invalidList[element];
				}
			}
				
			// second check registered layouts in the target container
			if (layout._target is DisplayObjectContainer) {
				
				var targetContainer:DisplayObjectContainer = DisplayObjectContainer(layout._target);
				var i:int = targetContainer.numChildren;
				var child:DisplayObject;
				while(i--) {
					child = targetContainer.getChildAt(i);
					if (child in registeredList) {
						if (registeredList[child] in invalidList) {
							delete invalidList[registeredList[child]];
						}
					}	
				}
			}
		}
	}
}