2009
06.25

I ran into an interesting problem with a TabNavigator. I wanted to intercept the closing of a tab, and if the tab was in an unsaved state present the user with a chance to rethink their action. Unfortunately, With default TabNavigator class, this is impossible because it doesn’t expose the event outside itself. There’s even a comment in the SuperTabBar class that talks about preventing tab closure, which is where I got my start.

Fortunately, we already had an extended version of the TabNavigator, similar to the SlidingTabNav seen here. So, how to actually make the magic work?

The first thing was to override the createChildren function. This is where the TabNavigator creates the tabBar, so we simply wait until that is done and then tag an event listener to the tabBar close event.

?View Code ACTIONSCRIPT
override protected function createChildren():void {
	super.createChildren();
	tabBar.addEventListener(SuperTabEvent.TAB_CLOSE, tabClosedHandler, false, 0, true);
}

Now that we’re catching the close event, we can prevent the closure by throwing an event of our own. This is the place where my greenhorns may show through, since I couldn’t seem to just dispatch the existing event and have it work. I’m guessing it’s a side effect of the way the event model works, but I haven’t really looked into it yet. Regardless, I simply created my own event (by cloning the caught event) and dispatched it. Then once it was dealt with, I steal a page from the SuperTabBar itself and check for the default being prevented:

?View Code ACTIONSCRIPT
protected function tabClosedHandler(event:SuperTabEvent):void {
	var newEvent:SuperTabEvent = event.clone() as SuperTabEvent;
	dispatchEvent(newEvent);
	if(newEvent.isDefaultPrevented()) {
		event.preventDefault();
	}
}

Of course the class has a nice new Event tag at the top:

?View Code ACTIONSCRIPT
[Event(name="tabClose", type="flexlib.events.SuperTabEvent")]

And now it’s ready to be dealt with like any other component. If I don’t want the tab closed, I simply call the preventDefault() method of the event.

In my case, I always prevent the default, then I call a check function in the tab referenced (SuperTabEvent has a tabIndex property) and if it has unsaved data I throw up a Popup asking for confirmation. If I want to close the tab (confirmed, or no unsaved data) I remove the child and let the tabs rebuild themselves.

Related Posts:

2009
06.18

I just did a lot of upgrading of Wordpress (to version 2.8) and the Theme (to 4.0.1). Unfortunately, the theme broke the codebox format, so I had to do some css hacking to clean that up. I think everything’s back to normal though.

Related Posts:

  • No Related Posts
2009
05.27

I highly recommend the 360 Flex conference. Unfortunately there won’t be another one until next year (in the past they’ve been semiannual affairs). My experience was great, with to little sleep and a who lot of Flex. I did some work in the charity code jam and, as you can see in the previous post, quashed some bugs. I’ve got lots of frameworks to check out once my workload lightens up. I came back to a herd of bug reports since we’re in the testing phase of our current product.

Next week is a real vacation, so I know I’m not going to get much done, but I would like to do some write ups on some of the frameworks that intrigued me at 360Flex. Here’s what’s on my plate:

  • Axiis, a data visualization framework
  • Structured Log Testing, a logging system allowing external applications to plug in an pull logs.
  • Degrafa, we already use it, but I really don’t know all the ins and outs
  • BirdEye, another data visualization framework. Mentioned at the Degrafa talk

That’ll keep me busy, plus I’ve already got a Mashup in mind, so I’ll be digging through Programmable Web at some point as well.

Related Posts:

2009
05.19

I participated in the Bug Quash at 360|Flex. Well, technically it was a day before the conference as a pre-conference event. It went really well and I personally killed 3 bugs.

  • SDK-21202 was the fix to a pet bug of my friend at Flex Ninja which he pretty much assigned to me when he heard I was going. The problem is that event listeners of NavBar children aren’t removed, which means if you change a label or icon or such on a child that was removed, the function will be called but then you’ll get a crash since the child is no longer there. It was easier since the workaround was already there as well.
  • SDK-21204 was the same bug, only with accordians. Ryan Frishberg actually pointed this out to me, which meant another similar fix. Ryan was at the Quash from adobe to help speed up the patch acceptance process, but he also discovered that a strange guy (me) would be harassing him to look at stuff.
  • SDK-21211 was an interesting bug to look at. It turns out that there wasn’t a default setting for the border of tooltips. Even better, the border is what triggered the drawing of the background box. That meant if you used any non-standard border, you’d loose the background. The fix was pretty easy, just make the default value for a tooltip border “toolTipBorder” the default all the time, not just when the border is set to “toolTipBorder”.

Final Thoughts? Flex SDK Bugs actually aren’t that bad to fix. You just have to find stuff that’s simple and try not to overthink what’s going on. Then submit a patch and hope it’s accepted. It’s a little overwhelming to look at the entire SDK, but you don’t have to worry about the whole thing, just deal with your little window, monkey patch a fix first in a simple Flex project (some bugs even have test cases you can use) then once the patch works, tie it back into the SDK itself.

I submitted another patch (SDK-21207) which was rejected, but it had some nice feedback. I’ll probably revisit it and try it again sometime in the future.

Related Posts:

2009
04.07

I have just started using the pushbutton engine which went into public beta recently. It’s a flex/flash game engine that is designed around modularity between entities so that you can share code. Unfortunately, their documentation is rather sparse and can be hard to find. The best reference, the API documentation is very difficult to get to since it’s hosted in their google code project and not directly linked to on their website.

Everything they give you is set up for a single player experience that is built around xml files describing the components and their entities. So what if you want to dynamically load information? I did a lot or tracing and here is how I loaded a tile map that I would be able to dynamically create on the server and pass to client as xml. FYI I’m using the Lost Garden PlanetCute tiles since they’re not only Creative Commons, but really good looking.

Since I’m going to already have all the graphics embedded on the client, I can use the xml loader that the pushbutton documentation uses. I also included my Simple Spatial Manager and Scene components, which handle the placement and drawing, respectively.

?Download Sprites.xml
<things version="1">
	<entity name="SimpleSpatial">
		<component type="PBLabs.Rendering2D.BasicSpatialManager2D" name="Manager"/>
	</entity>
	<entity name="Scene">
		<component type="PBLabs.Rendering2D.Scene2DComponent" name="Scene">
			<SpatialDatabase componentReference="SimpleSpatial" componentName="Manager"/>
			<SceneViewName>MainView</SceneViewName>
			<Position>
				<x>0</x>
				<y>0</y>
			</Position>
			<RenderMask childType="String">
				<_0>Renderable</_0>
			</RenderMask>
		</component>
	</entity>
	<entity name="brown">
		<component type="PBLabs.Rendering2D.SpriteSheetComponent" name="SpriteSheet">
			<Image filename="../Assets/Images/Level/Brown Block.png"/>
		</component>
	</entity>
	<entity name="dirt">
		<component type="PBLabs.Rendering2D.SpriteSheetComponent" name="SpriteSheet">
			<Image filename="../Assets/Images/Level/Dirt Block.png"/>
		</component>
	</entity>
	<entity name="grass">
		<component type="PBLabs.Rendering2D.SpriteSheetComponent" name="SpriteSheet">
			<Image filename="../Assets/Images/Level/Grass Block.png"/>
		</component>
	</entity>
	<entity name="plain">
		<component type="PBLabs.Rendering2D.SpriteSheetComponent" name="SpriteSheet">
			<Image filename="../Assets/Images/Level/Plain Block.png"/>
		</component>
	</entity>
	<entity name="stone">
		<component type="PBLabs.Rendering2D.SpriteSheetComponent" name="SpriteSheet">
			<Image filename="../Assets/Images/Level/Stone Block.png"/>
		</component>
	</entity>
	<entity name="water">
		<component type="PBLabs.Rendering2D.SpriteSheetComponent" name="SpriteSheet">
			<Image filename="../Assets/Images/Level/Water Block.png"/>
		</component>
	</entity>
	<entity name="wood">
		<component type="PBLabs.Rendering2D.SpriteSheetComponent" name="SpriteSheet">
			<Image filename="../Assets/Images/Level/Wood Block.png"/>
		</component>
	</entity>
	<group name="SpriteData">
		<objectReference name="SimpleSpatial"/>
		<objectReference name="Scene"/>
		<objectReference name="brown"/>
		<objectReference name="dirt"/>
		<objectReference name="grass"/>
		<objectReference name="plain"/>
		<objectReference name="stone"/>
		<objectReference name="water"/>
		<objectReference name="wood"/>
	</group>
</things>

And I embed the images in the swf, again, following the documentation

package com.enigmatic
{
	import PBLabs.Engine.Resource.ResourceManager;
	import PBLabs.Rendering2D.ImageResource;
 
	import flash.utils.ByteArray;
 
	public class LevelTiles
	{
		//Embeds
		[Embed(source="../Assets/Images/Level/Brown Block.png", mimeType='application/octet-stream')]
		private var _brownBlock:Class;
		[Embed(source="../Assets/Images/Level/Dirt Block.png", mimeType='application/octet-stream')]
		private var _dirtBlock:Class;
		[Embed(source="../Assets/Images/Level/Grass Block.png", mimeType='application/octet-stream')]
		private var _grassBlock:Class;
		[Embed(source="../Assets/Images/Level/Plain Block.png", mimeType='application/octet-stream')]
		private var _plainBlock:Class;
		[Embed(source="../Assets/Images/Level/Stone Block.png", mimeType='application/octet-stream')]
		private var _stoneBlock:Class;
		[Embed(source="../Assets/Images/Level/Water Block.png", mimeType='application/octet-stream')]
		private var _waterBlock:Class;
		[Embed(source="../Assets/Images/Level/Wood Block.png", mimeType='application/octet-stream')]
		private var _woodBlock:Class;
 
		public function LevelTiles()
		{
			ResourceManager.Instance.RegisterEmbeddedResource("../Assets/Images/Level/Brown Block.png", ImageResource, new _brownBlock() as ByteArray);
			ResourceManager.Instance.RegisterEmbeddedResource("../Assets/Images/Level/Dirt Block.png", ImageResource, new _dirtBlock() as ByteArray);
			ResourceManager.Instance.RegisterEmbeddedResource("../Assets/Images/Level/Grass Block.png", ImageResource, new _grassBlock() as ByteArray);
			ResourceManager.Instance.RegisterEmbeddedResource("../Assets/Images/Level/Plain Block.png", ImageResource, new _plainBlock() as ByteArray);
			ResourceManager.Instance.RegisterEmbeddedResource("../Assets/Images/Level/Stone Block.png", ImageResource, new _stoneBlock() as ByteArray);
			ResourceManager.Instance.RegisterEmbeddedResource("../Assets/Images/Level/Water Block.png", ImageResource, new _waterBlock() as ByteArray);
			ResourceManager.Instance.RegisterEmbeddedResource("../Assets/Images/Level/Wood Block.png", ImageResource, new _woodBlock() as ByteArray);
		}
	}
}

Then, to load this, we just use the level manager. Note the last argument to AddLevelFileReference, which is true. The means that the sprite data will be persistent, and we won’t have to rebuild the basic entities of the level every time we load a new level.

package com.enigmatic
{
	public class LevelLoader
	{
		import PBLabs.Rendering2D.Scene2DComponent;
		import PBLabs.Rendering2D.BasicSpatialManager2D;
		import PBLabs.Rendering2D.SpriteSheetComponent;
		import PBLabs.Engine.Core.LevelManager;
 
		public function LevelLoader()
		{
			var _scene2DComponent:Scene2DComponent;
			var _managerComponent:BasicSpatialManager2D;
			var _spriteSheet:SpriteSheetComponent;
 
			LevelManager.Instance.AddLevelFileReference("../Assets/Level/Sprites.xml",0,true);
			LevelManager.Instance.AddGroupReference("SpriteData",0);
		}
 
	}
}

And finally, to complete the circle, the level itself, in xml.

?Download level.xml
<level>
	<row>
		<tile name="wood"/>
		<tile name="dirt"/>
		<tile name="grass"/>
		<tile name="plain"/>
		<tile name="stone"/>
		<tile name="water"/>
		<tile name="brown"/>
		<tile name="wood"/>
		<tile name="dirt"/>
		<tile name="grass"/>
		<tile name="plain"/>
		<tile name="stone"/>
		<tile name="water"/>
		<tile name="brown"/>
	</row>
	<row>
		<tile name="dirt"/>
		<tile name="grass"/>
		<tile name="plain"/>
		<tile name="stone"/>
		<tile name="water"/>
		<tile name="brown"/>
		<tile name="wood"/>
		<tile name="dirt"/>
		<tile name="grass"/>
		<tile name="plain"/>
		<tile name="stone"/>
		<tile name="water"/>
		<tile name="brown"/>
		<tile name="wood"/>
	</row>
	<row>
		<tile name="grass"/>
		<tile name="plain"/>
		<tile name="stone"/>
		<tile name="water"/>
		<tile name="brown"/>
		<tile name="wood"/>
		<tile name="dirt"/>
		<tile name="grass"/>
		<tile name="plain"/>
		<tile name="stone"/>
		<tile name="water"/>
		<tile name="brown"/>
		<tile name="wood"/>
		<tile name="dirt"/>
	</row>
</level>

And now comes the fun part. I simply use ActionScript’s ability to work with XML to loop through the level data and add a tile at a time. Each tile is made of two basic components. A SpriteRenderComponent to display, and a SimpleSpatialComponent to handle placement. the order you add components don’t matter, even if they reference other components. Pushbutton handles that kind of stuff.

The way pushbutton works, you call a global function AllocateEntity() which returns an IEntity component. Then you manipulate the entity from there. The engine handles all the messy work of resource allocation and whatnot.

?View Code ACTIONSCRIPT
var newTile:IEntity = AllocateEntity();

Now that we have an entity, we add components to it. Since I built this off the documented level xml file, I created the Render Component First.

?View Code ACTIONSCRIPT
var render:SpriteRenderComponent = new SpriteRenderComponent();
render.SpriteSheet = NameManager.Instance.Lookup(tile.attribute("name").toString()).LookupComponentByType(SpriteSheetComponent) as SpriteSheetComponent;
render.PositionReference = new PropertyReference("@Spatial.Position");
render.RotationReference = new PropertyReference("@Spatial.Rotation");
render.SizeReference = new PropertyReference("@Spatial.Size");
newTile.AddComponent(render,"Render");

this is the same as the xml:

<component type="PBLabs.Rendering2D.SpriteRenderComponent" name="Render">
	<SpriteSheet componentReference="<tilename>"/>
	<PositionReference>@Spatial.Position</PositionReference>
	<RotationReference>@Spatial.Rotation</RotationReference>
	<SizeReference>@Spatial.Size</SizeReference>
</component>

The trickiest part was converting the xml line for the sprite sheet to code. That’s where the NameManager comes into play. All named entities are stored there, and if you refer back to the Sprites.xml file, you’ll see that the entities are named based on the type. Once you get the entity, you can pull the type up easily by using the LookupComponent commands. You can use name or type. The Serializer handles the sprite sheet lookup for you if your using xml, which is why there was a extra level of depth not in the xml.

Next is the spatial manager that handles the placement of the tile.

?View Code ACTIONSCRIPT
var spatial:SimpleSpatialComponent = new SimpleSpatialComponent();
spatial.SpatialManager = (NameManager.Instance.Lookup("SimpleSpatial")).LookupComponentByName("Manager") as ISpatialManager2D;
spatial.Position = new Point(x * 50, y * 40);
spatial.QueryMask = new ObjectType();
spatial.QueryMask.TypeNames = ["Floor","Renderable"];
spatial.Size = new Point(50, 85);
newTile.AddComponent(spatial,"Spatial");

this is the same as the xml:

<component type="PBLabs.Rendering2D.SimpleSpatialComponent" name="Spatial">
	<SpatialManager componentReference="SimpleSpatial" componentName="Manager"/>
	<QueryMask childType="String">
		<_0>Floor</_0>
		<_1>Renderable</_1>
	</QueryMask>
	<Position>
		<x>50</x>
		<y>50</y>
	</Position>
	<Size>
		<x>50</x>
		<y>85</y>
	</Size>
</component>

Again, the Spatial Manager was the most complicated, but the extra level was also included in the xml. The other item to note is the QueryMask. This is what the RenderMask is referring to in the Scene from Sprite.xml. If you are using a Box2DSpatialComponent instead, the RenderMask matches up against the CollidesWithTypes, which is what the demo program is using.

Here is the completed function. Make sure you wait until the level loading is complete (simply add an even listener for a LevelEvent.LOADED_EVENT) or you’ll run into null references due to a partially loaded level.

public function buildLevel(xml:XML):void
{
	var x:int = 0;
	var y:int = 0;
 
	for each (var row:XML in xml.*)
	{
		for each (var tile:XML in row.*)
		{
			var newTile:IEntity = AllocateEntity();
			var render:SpriteRenderComponent = new SpriteRenderComponent();
			render.SpriteSheet = NameManager.Instance.Lookup(tile.attribute("name").toString()).LookupComponentByType(SpriteSheetComponent) as SpriteSheetComponent;
			render.PositionReference = new PropertyReference("@Spatial.Position");
			render.RotationReference = new PropertyReference("@Spatial.Rotation");
			render.SizeReference = new PropertyReference("@Spatial.Size");
			newTile.AddComponent(render,"Render");
			var spatial:SimpleSpatialComponent = new SimpleSpatialComponent();
			spatial.SpatialManager = (NameManager.Instance.Lookup("SimpleSpatial")).LookupComponentByName("Manager") as ISpatialManager2D;
			spatial.Position = new Point(x * 50, y * 40);
			spatial.QueryMask = new ObjectType();
			spatial.QueryMask.TypeNames = ["Floor","Renderable"];
			spatial.Size = new Point(50, 85);
			newTile.AddComponent(spatial,"Spatial");
			x += 1;
 
 
		}
		x = 0;
		y += 1;
	}
}

Here is the final result with source code. It’s not much to look at, but it should get you started.

The next step is to create a custom TilemapRendererComponent that would do a lot of the heavy lifting for me. Things like positioning of the tiles based on the grid and having to work with each individual tile. Of course, the point of this post wasn’t tiles, but the entities that happened to be tiles, so that’s a whole other topic to cover later

Related Posts: