Skip to content

Coupling Display Hierarchies and Composure Using EaselJS example

Tom Byrne edited this page Jan 27, 2015 · 1 revision

In most cases, the display system of an app is arranged hierarchically, and you'll want your composure hierarchy to automatically modify this display hierarchy as you add/remove traits from your ComposeGroups.

In this example, I'll show how this coupling can be achieved with EaselJS (via easelhx), but examples for NME or native flash would be very similar.

Firstly we must define a trait to wrap the core display objects of the Display system (in the case of EaselJS this is a createjs.easel.DisplayObject):

	class DisplayObjectTrait extends AbsPosSizeAwareTrait
	{
		public var displayObject(default, null):DisplayObject;
	
		public function new(displayObject:DisplayObject=null) 
		{
			super();
			setDisplayObject(displayObject);
		}
		
		private function setDisplayObject(value:DisplayObject):Void {
			this.displayObject = value;
		}
	}

This trait gets added to any item which you want to display in EaselJS.

Next we must define the container trait which will search descendent items for DisplayObjectTraits, and add their DisplayObjects to it's own createjs.easel.Container.

	class ContainerTrait extends DisplayObjectTrait
	{
		public var container(default, null):Container;
	
		public function new(container:Container = null) {
			super();
			setContainer(container);
			
			var injector = new Injector(DisplaySkin, onChildAdded, onChildRemoved, false, true);
			injector.stopDescendingAt = TraitChecker.createTraitCheck([ContainerTrait]);
			addInjector(injector);
		}
		private function setContainer(container:Container):Void {
			this.container = container;
			setDisplayObject(container);
		}
	
		private function onChildAdded(child:DisplayObjectTrait):Void {
			container.addChild(child.displayObject);
		}
		private function onChildRemoved(child:DisplayObjectTrait):Void {
			container.removeChild(child.displayObject);
		}
	}

ContainerTrait uses manually added injectors (as opposed to metadata based ones). This is done so that a stopDescendingAt rule can be added, this is a function which returns true when the injector should stop searching below a certain descendant. Using the utility function TraitChecker.createTraitCheck we prevent this trait from adding DisplayObjects which are already added to a child Container.

Lastly, we need to create a top-level StageTrait, which behaves the same as a ContainerTrait except that it adds itself to the canvas tag in the HTML output:

	class StageTrait extends ContainerTrait
	{
		var canvas:HTMLCanvasElement;
		var easelStage:Stage;
	
		public function new(canvasId:String) 
		{
			canvas = cast Lib.document.getElementById(canvasId);
			easelStage = new Stage(canvas);
			super(easelStage);
		}
		override private function onChildAdded(child:DisplayObjectTrait):Void {
			super.onChildAdded(child.displayObject);
			easelStage.update();
		}
		override private function onChildRemoved(child:DisplayObjectTrait):Void {
			super.onChildRemoved(child.displayObject);
			easelStage.update();
		}
	}

Now you could use easelJS from within haxe (and with a completely abstracted display layer) like this:

	class TestEaselJS 
	{
		public static function main() {
			new TestEaselJS();
		}
	
		public function new() 
		{
			var root = new ComposeRoot();

			var stage:ComposeGroup = new ComposeGroup([new StageTrait("canvas-element")]);
			root.addChild(stage);
			
			stage.addChild(createRect(0 ,0 ,40,40));
			stage.addChild(createRect(0 ,80,40,40));
			stage.addChild(createRect(80,80,40,40));
			stage.addChild(createRect(80,0 ,40,40));
		}
		private function createRect(x:Int, y:Int, w:Int, h:Int):ComposeGroup{
			var display:Shape = new Shape();
			display.x = x;
			display.y = y;
			
			display.graphics.beginFill("#888").drawRect(0, 0, w, h);
			return new ComposeGroup([new DisplayObjectTrait(display)]);
		}
	}