AS3, AIR, Starling sliding panel like MadComponents - actionscript-3

I am developing an app using AS3/Air for mobile devices. I am using Feathers UI and Starling but I would like to create a panel slide (menu, information panel etc) that comes with MadComponents. I was considering just creating a class which held the 'panel', import that on to my screen and use Greensock for the tweening.
My questions are:
Would this be the best way using Starling and Feathers?
Can I use MadComponents with Starling considering it is built using Flash display not Starling?
Any other suggestions how I could achieve the same result?
Basically I just want a button, user clicks the button and the screen tweens left/right and 'opens' the information panel. Push the button again and it closes it.
Many thanks

If I've understood what you've described correctly then it sounds like the ScreenNavigator Class of Feathers is exactly the thing you need?
See ScreenNavigator API
And the Demo of the Feathers Component Explorer that uses the SlideNavigator in the same way you describe.

It looks like Josh Tynjala is looking to add this feature to Feathers: https://github.com/joshtynjala/feathers/issues/527
In the meantime, I wanted the same thing, so I just whipped this up, and I am happy to share it (WARNING: Has not been tested extensively):
package com.hiddenachievement.space.components
{
import feathers.core.FeathersControl;
import starling.animation.Transitions;
import starling.animation.Tween;
import starling.core.Starling;
import starling.display.Sprite;
/**
* Creates a control that emerges from one side of the stage, while moving the main view to show it.
*/
public class EdgeSlideControl extends FeathersControl
{
private var _mainView:Sprite;
private var _open:Boolean;
protected var _tween:Tween;
protected var _side:int;
protected var _callback:Function;
protected var _uncover:Boolean;
static public const NORTH:int = 0;
static public const SOUTH:int = 1;
static public const EAST:int = 2;
static public const WEST:int = 3;
/**
* Creates an EdgeSlideControl.
*
* #param mainView The main view (possibly a ScreenNavigator) that will move to show the menu.
* #param side The side that the control will emerge from.
* #param uncover When true, the main view will slide away to uncover the control. When false, the control will slide in from the side, as the main view slides out of the way.
*
*/
public function EdgeSlideControl(mainView:Sprite, side:int, uncover:Boolean = true)
{
super();
_mainView = mainView;
_side = side;
_uncover = uncover;
}
/**
* Implements the standard FeathersControl's initialize.
*/
override protected function initialize():void
{
_open = false;
visible = false;
}
/**
* Begin the animation to display the control.
*/
public function show(callback:Function=null):void
{
_callback = callback;
_tween = new Tween(_mainView, 0.2, Transitions.LINEAR);
_tween.onUpdate = updateSlide;
_tween.onComplete = slideComplete;
switch(_side)
{
case NORTH:
_tween.animate("y", height);
break;
case SOUTH:
_tween.animate("y", -width);
break;
case EAST:
_tween.animate("x", -height);
break;
case WEST:
_tween.animate("x", width);
break;
}
Starling.juggler.add(_tween);
_open = true;
visible = true;
}
/**
* Begin the animation to hide the control.
*/
public function hide(callback:Function=null):void
{
_callback = callback;
_tween = new Tween(_mainView, 0.2, Transitions.LINEAR);
_tween.onUpdate = updateSlide;
_tween.onComplete = slideComplete;
switch(_side)
{
case NORTH:
case SOUTH:
_tween.animate("y", 0);
break;
case EAST:
case WEST:
_tween.animate("x", 0);
break;
}
Starling.juggler.add(_tween);
_open = false;
}
/**
* If the control is moving (not in "uncover" mode), move the control into place, next to the main view.
*/
public function updateSlide():void
{
if (!_uncover)
{
switch(_side)
{
case NORTH:
y = _mainView.y - height;
break;
case SOUTH:
y = _mainView.height - _mainView.y;
break;
case EAST:
x = _mainView.width - _mainView.x;
break;
case WEST:
x = _mainView.x - width;
break;
}
}
}
/**
* Perform any actions needed after the transition is done animating.
*/
public function slideComplete():void
{
if (_callback != null)
{
_callback();
}
visible = _open;
}
public function get isOpen():Boolean
{
return _open;
}
}
}
You may need to add an explicit size to the component, in the direction of the sliding, if it's coming up zero. Otherwise, the use should be pretty straightforward. Let me know if you need any more guidance on how to use it.

Related

Collision in flixel not working with updated tilemap

I have been following the tutorial on jumper and I am now up to part 3. Here is the link for
referencing:
[url]http://chipacabra.blogspot.in/2010/12/project-jumper-part-3.html[/url]
For part 2, I have successfully made my player collide with the map but the collision stopped and my character started free falling again when I updated the game to include instructions from part 3. I made some modifications to the code in order to make the program compile (since some of the code written in the tutorial is out of date, and even the code written in the comments was out of date). Which leads to believe there's something more that might be missing in the code. The cvs and png files for the maps that I am using are those which are included in the source code made available at the end of part 3 through a download link.
Here is the code I have:
Playstate.as:
package com.chipacabra.Jumper
{
import org.flixel.*;
public class PlayState extends FlxState
{
[Embed(source = '../../../../levels/mapCSV_Group1_Map1.csv', mimeType =
'application/octet-stream')]public var levelMap:Class;
[Embed(source = "../../../../levels/mapCSV_Group1_Map1back.csv", mimeType =
"application/octet-stream")]public var backgroundMap:Class;
//[Embed(source = '../../../../art/tilemap.png')]public var levelTiles:Class;
// This was the old art, not using it anymore.
[Embed(source = "../../../../art/area02_level_tiles2.png")]public var levelTiles:Class;
//This is the new art.
public var map:FlxTilemap = new FlxTilemap;
public var background:FlxTilemap = new FlxTilemap;
public var player:Player;
override public function create():void
{
add(background.loadMap(new backgroundMap, levelTiles, 16, 16));
background.scrollFactor.x = background.scrollFactor.y = .5;
add(map.loadMap(new levelMap, levelTiles, 16, 16));
add(player = new Player(10, 10));
FlxG.camera.setBounds(0, 0, 1600, 800);
FlxG.camera.follow(player, FlxCamera.STYLE_PLATFORMER);
//FlxG.follow(player);
//FlxG.worldBounds.x = 0;
//FlxG.worldBounds.y = 0;
//FlxG.worldBounds.width = 1600;
//FlxG.worldBounds.height = 800;
super.create();
}
override public function update():void
{
super.update();
FlxG.collide(map, player);
}
}
}
Player.as:
package com.chipacabra.Jumper
{
import org.flixel.*;
/**
* ...
* #author A. Velitsky
*/
public class Player extends FlxSprite
{
[Embed(source = "../../../../art/helmutguy.png")]public var Helmutguy:Class;
protected static const RUN_SPEED: int = 80;
protected static const GRAVITY: int = 300; //originaly 420
protected static const JUMP_SPEED: int = 200;
public function Player(X:int,Y:int):void
{
super(X, Y);
loadGraphic(Helmutguy, true, true);
addAnimation("walking", [1, 2], 12, true);
addAnimation("idle", [0]);
drag.x = RUN_SPEED * 8
// Drag is how quickly you slow down when you're not
// pushing a button. By using a multiplier, it will
// always scale to the run speed, even if we change it.
acceleration.y = GRAVITY;
// Always try to push helmutguy in the direction of gravity
maxVelocity.x = RUN_SPEED;
maxVelocity.y = JUMP_SPEED;
}
public override function update(): void
{
super.update();
acceleration.x = 0;
// Reset to 0 when no button is
if (FlxG.keys.LEFT)
{
facing = LEFT;
acceleration.x = -drag.x;
}
else if (FlxG.keys.RIGHT)
{
facing = RIGHT;
acceleration.x = drag.x;
}
if (FlxG.keys.justPressed("UP") && !velocity.y)
{
velocity.y = -JUMP_SPEED;
}
//Animation
if (velocity.x != 0) { play("walking"); }
else if (!velocity.x) { play("idle"); }
super.update();
}
}
}
Jumper.as:
package
{
import org.flixel.*; //Allows you to refer to flixel objects in your code
import com.chipacabra.Jumper.PlayState;
[SWF(width = "640", height = "480", backgroundColor = "#000000")] //Set the size and color of the
Flash file
[Frame(factoryClass="Preloader")]
public class Jumper extends FlxGame
{
public function Jumper()
{
super(320, 240, PlayState, 2); //Create a new FlxGame object at 320x240 with 2x pixels,
then load PlayState
FlxG.bgColor = 0x8DEBFC;
}
}
}
Preloader.as:
package
{
import org.flixel.system.FlxPreloader;
/**
* ...
* #author A. Velitsky
*/
public class Preloader extends FlxPreloader
{
public function Preloader()
{
className = "Jumper";
super();
}
}
}
Edit: Um...I seem to have found out that the code I got from the comment to the tutorial...was almost correct...the only thing that was wrong was that the camera's view style was not specified. It also seems that FlxG.camera.setBounds(0, 0, 1600, 800); does not needs to be called... Although I would like to know why I would want to use FlxG.camera.setBounds(); I this something I would use perhaps in an airplane/spaceship shooter game?
FlxG.camera.setBounds() sets the boundaries of the level, which can be used to tell the camera where it is and is not allowed to move. If the setBounds are smaller than the actual world dimensions, then the game will not update in that area and the camera will stop moving as well.
In your example, FlxG.camera.setBounds(0, 0, 1600, 800) sets up a static width of 1600, which is probably more than you need, so you don't really see any effects on the camera.
I think it's best to set your bounds based on the actual width of your game. Some of my game code looks something like this:
//Set the max bounds of the stage
FlxG.worldBounds = new FlxRect (0,0,collisionMap.width-TILE_WIDTH,collisionMap.height);
//Attach the camera
FlxG.camera.setBounds(32,-64,FlxG.worldBounds.width-64,FlxG.worldBounds.height);
FlxG.camera.follow(player,FlxCamera.STYLE_PLATFORMER);
Hope that helps!

Drag and Drop allow only one object per target

I'm sure this is simple but I have the drag and drop code that works wonderfully...however, it allows me to drop muliple objects per target. What I want is to allow only one object per target and I've looked and can't seem to find how to test to see if an object had already been dropt. Here's the code I have:
package com {
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.geom.Point;
import com.greensock.*;
public class DraggableObject extends MovieClip
{
protected var origPos:Point;
public function DraggableObject()
{
origPos = new Point(x, y);
buttonMode = true; // changes cursor to hand
addEventListener(MouseEvent.MOUSE_DOWN, down);
}
protected function down (event:MouseEvent): void
{
parent.addChild(this); // adds object to the top of the display list to keep the object on top
startDrag(); // built in Flash method
stage.addEventListener(MouseEvent.MOUSE_UP, stageUp);
var upPosX = x + 5;
var upPosY = y - 5;
TweenMax.to(this, .2, {dropShadowFilter:{color:0x666666, alpha:1, blurX:12, blurY:12, distance:12}});
TweenLite.to(this, .2, {x: upPosX, y: upPosY});
mouseEnabled = false;
}
protected function stageUp (event:MouseEvent): void
{
stage.removeEventListener(MouseEvent.MOUSE_UP, stageUp); // good coding
stopDrag(); // built in Flash method
TweenMax.to(this, .5, {dropShadowFilter:{color:0x666666, alpha:0, blurX:12, blurY:12, distance:12}});
if (dropTarget.parent.name == "root1"){
returnToOrigPos();
}
else if(dropTarget)// dropTarget is a built in property this means if dropTarget != null
{
trace("dropt on "+dropTarget.parent.name);
x = dropTarget.parent.x;
y = dropTarget.parent.y;
//buttonMode = false; //only use these to keep the user's selection...can't change if these are active.
//removeEventListener(MouseEvent.MOUSE_DOWN, down);
}
mouseEnabled = true;
}
protected function returnToOrigPos(): void
{
x = origPos.x;
y = origPos.y;
}
}
I was thinking that hitTestObject or dropTarget would work but can't figure out how to use them.
Thanks for any help!
Mike
Add a public property (eg: _hasDroppedObject) to whatever kind of object you're dropping objects into and set it to true when an object is dropped into it:
dropTarget.hasDroppedObject = true;
Then change your "if (dropTarget.parent.name == "root1")" line to:
if (dropTarget.parent.name == "root1" || dropTarget.hasDroppedObject) {
(This assumes you have access to the drop-receiving Class).

Actionscript: Am I deleting this class instance correctly?

Okay by deleting correctly I mean am I actually getting rid of the instance or is it just not being drawn anymore? I should mention that I'm trying to delete the instance from within its own class, that is it deletes itself. It 'works' in that the square it draws no longer appears on the screen but again I'm not sure if it's really gone or just not being drawn. Anyway here's the class:
package
{
import flash.display.*;
import flash.events.*;
public class OBJECT_bullet_1 extends Sprite
{
public var X:int = 0; public var Y:int = 0;
public var Y_SPEED:int = 5;
public var DEPTH:int = 9;
public var CONTAINER:Sprite = new Sprite();
public function CREATE(CONTAINER:Sprite,X:int,Y:int):void
{
this.CONTAINER = CONTAINER;
CONTAINER.stage.addEventListener(Event.ENTER_FRAME,STEP);
this.X = X; this.Y = Y;
DRAW();
}
public function STEP(event:Event):void
{
this.graphics.clear();
Y -= Y_SPEED;
if (Y < 20) {Y = 300; CONTAINER.removeChild(this); CONTAINER.stage.removeEventListener(Event.ENTER_FRAME,STEP); CONTAINER.(delete this); CONTAINER = null; return;}
DRAW();
}
public function DRAW():void
{
this.graphics.beginFill(0xCCCC00,1);
this.graphics.drawRect(X - 2,Y - 2,4,4);
this.graphics.endFill();
CONTAINER.addChild(this);
}
}
}
The part I'm concerned about is in the STEP function when it checks to see if Y < 20. You'll notice that it does several things afterwords. Am I deleting it correctly? If so is there anything I am doing to delete it that I don't need to?
Yes to both questions. To ensure an object is deleted, all you have to do is remove all references to it. The child reference and event callback are the only ones the above code is aware of, and you have taken care to remove them both. Nullifying your own container reference is unnecessary, as is whatever you think CONTAINER.(delete this) does.
There are some other significant problems with your supplied code. I made some improvements and heavily commented all changes to explain why I made them.
// You should avoid using the default package. Using the default package
// can make it difficult later on if you start having naming conflicts.
package com.stackoverflow.example {
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Point;
import flash.utils.getTimer;
// Class names are spelled in CamelCase by convention. Also, note
// that "Object" has a special meaning in AS3 so you should avoid
// using it to refer to anything else. I used here "Entity" instead.
public class EntityBullet1 extends Sprite {
// ALLCAPS when used are reserved for static const names.
// A good use of static consts is to store "magic numbers".
public static const DEFAULT_COLOR:uint = 0xCCCC00;
public static const DEFAULT_SPEED_X:Number = 0;
public static const DEFAULT_SPEED_Y:Number = -100;
public static const DEFAULT_SIZE:Number = 4;
// I'm calculating the time between frames for smoother movement.
public var lastTime:int;
public var color:uint = DEFAULT_COLOR;
public var size:int = DEFAULT_SIZE;
// Instead of separate x and y vars, you can use the Point class.
public var pos:Point;
public var speed:Point;
// Instead of a "create" method do all creation inside the constructor!
public function EntityBullet1(x:Number = 0, y:Number = 0) {
pos = new Point(x, y);
speed = new Point(DEFAULT_SPEED_X, DEFAULT_SPEED_Y);
// You don't need the parent container to access the ENTER_FRAME
// event. Every DisplayObject has its own. Much simpler.
addEventListener(Event.ENTER_FRAME, firstStep);
}
public function draw():void {
// Keep all drawing inside the draw function. Previously,
// clear() was being called inside the step method.
graphics.clear();
graphics.beginFill(color);
graphics.drawRect(pos.x - size/2, pos.y - size/2, size, size);
graphics.endFill();
}
// On the first frame, the field "lastTime" is still uninitialized.
// This method initializes it to the current time and hands off
// future events to the proper step() method.
public function firstStep(event:Event):void {
removeEventListener(Event.ENTER_FRAME, firstStep);
addEventListener(Event.ENTER_FRAME, step);
lastTime = getTimer();
step(event);
}
public function step(event:Event):void {
// To move at a fixed rate regardless of how fast the framerate is,
// you need to calculate the time delta.
var cur:int = getTimer();
var delta:Number = (cur - lastTime) / 1000.0;
lastTime = cur;
// Position equals velocity times time.
pos.x += speed.x * delta;
pos.y += speed.y * delta;
draw();
// Note that all DisplayObjects already have references to their
// parent containers called "parent"!
if (pos.y < 20) {
if (parent != null) parent.removeChild(this);
removeEventListener(Event.ENTER_FRAME, step);
}
}
}
}

What is the most effective way to test for combined keyboard arrow direction in ActionScript 3.0?

I need to monitor the direction a user is indicating using the four directional arrow keys on a keyboard in ActionScript 3.0 and I want to know the most efficient and effective way to do this.
I've got several ideas of how to do it, and I'm not sure which would be best. I've found that when tracking Keyboard.KEY_DOWN events, the event repeats as long as the key is down, so the event function is repeated as well. This broke the method I had originally chosen to use, and the methods I've been able to think of require a lot of comparison operators.
The best way I've been able to think of would be to use bitwise operators on a uint variable. Here's what I'm thinking
var _direction:uint = 0x0; // The Current Direction
That's the current direction variable. In the Keyboard.KEY_DOWN event handler I'll have it check what key is down, and use a bitwise AND operation to see if it's already toggled on, and if it's not, I'll add it in using basic addition. So, up would be 0x1 and down would be 0x2 and both up and down would be 0x3, for example. It would look something like this:
private function keyDownHandler(e:KeyboardEvent):void
{
switch(e.keyCode)
{
case Keyboard.UP:
if(!(_direction & 0x1)) _direction += 0x1;
break;
case Keyboard.DOWN:
if(!(_direction & 0x2)) _direction += 0x2;
break;
// And So On...
}
}
The keyUpHandler wouldn't need the if operation since it only triggers once when the key goes up, instead of repeating. I'll be able to test the current direction by using a switch statement labeled with numbers from 0 to 15 for the sixteen possible combinations. That should work, but it doesn't seem terribly elegant to me, given all of the if statements in the repeating keyDown event handler, and the huge switch.
private function checkDirection():void
{
switch(_direction)
{
case 0:
// Center
break;
case 1:
// Up
break;
case 2:
// Down
break;
case 3:
// Up and Down
break;
case 4:
// Left
break;
// And So On...
}
}
Is there a better way to do this?
You can keep track of whether each key is down or not by listening for all KEY_DOWN and KEY_UP events, and storing each key state in an array. I wrote a class a while ago to do just that (included at the end of my answer).
Then you are no longer tied to the event model to know if a certain key is down or not; you can periodically check every frame (or every timer interval). So you could have a function like:
function enterFrameCallback(e:Event):void
{
var speed:Number = 1.0; // net pixels per frame movement
thing.x += (
-(int)Input.isKeyDown(Keyboard.LEFT)
+(int)Input.isKeyDown(Keyboard.RIGHT)
) * speed;
thing.y += (
-(int)Input.isKeyDown(Keyboard.UP)
+(int)Input.isKeyDown(Keyboard.DOWN)
) * speed;
}
which would take into account all possible combinations of arrow key presses. If you want the net displacement to be constant (e.g. when going right and down at same time, the object moves X pixels diagonally, as opposed to X pixels in both horizontal and vertical directions), the code becomes:
function enterFrameCallback(e:Event):void
{
var speed:Number = 1.0; // net pixels per frame movement
var displacement:Point = new Point();
displacement.x = (
-(int)Input.isKeyDown(Keyboard.LEFT)
+(int)Input.isKeyDown(Keyboard.RIGHT)
);
displacement.y = (
-(int)Input.isKeyDown(Keyboard.UP)
+(int)Input.isKeyDown(Keyboard.DOWN)
);
displacement.normalize(speed);
thing.x += displacement.x;
thing.y += displacement.y;
}
Here is the Input class I wrote (don't forget to call init from the document class). Note that it also keeps track of mouse stuff; you can delete that if you don't need it:
/*******************************************************************************
* DESCRIPTION: Defines a simple input class that allows the programmer to
* determine at any instant whether a specific key is down or not,
* or if the mouse button is down or not (and where the cursor
* is respective to a certain DisplayObject).
* USAGE: Call init once before using any other methods, and pass a reference to
* the stage. Use the public methods commented below to query input states.
*******************************************************************************/
package
{
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.display.Stage;
import flash.geom.Point;
import flash.display.DisplayObject;
public class Input
{
private static var keyState:Array = new Array();
private static var _mouseDown:Boolean = false;
private static var mouseLoc:Point = new Point();
private static var mouseDownLoc:Point = new Point();
// Call before any other functions in this class:
public static function init(stage:Stage):void
{
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown, false, 10);
stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp, false, 10);
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown, false, 10);
stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp, false, 10);
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove, false, 10);
}
// Call to query whether a certain keyboard key is down.
// For a non-printable key: Input.isKeyDown(Keyboard.KEY)
// For a letter (case insensitive): Input.isKeyDown('A')
public static function isKeyDown(key:*):Boolean
{
if (typeof key == "string") {
key = key.toUpperCase().charCodeAt(0);
}
return keyState[key];
}
// Property that is true if the mouse is down, false otherwise:
public static function get mouseDown():Boolean
{
return _mouseDown;
}
// Gets the current coordinates of the mouse with respect to a certain DisplayObject.
// Leaving out the DisplayObject paramter will return the mouse location with respect
// to the stage (global coordinates):
public static function getMouseLoc(respectiveTo:DisplayObject = null):Point
{
if (respectiveTo == null) {
return mouseLoc.clone();
}
return respectiveTo.globalToLocal(mouseLoc);
}
// Gets the coordinates where the mouse was when it was last down or up, with respect
// to a certain DisplayObject. Leaving out the DisplayObject paramter will return the
// location with respect to the stage (global coordinates):
public static function getMouseDownLoc(respectiveTo:DisplayObject = null):Point
{
if (respectiveTo == null) {
return mouseDownLoc.clone();
}
return respectiveTo.globalToLocal(mouseDownLoc);
}
// Resets the state of the keyboard and mouse:
public static function reset():void
{
for (var i:String in keyState) {
keyState[i] = false;
}
_mouseDown = false;
mouseLoc = new Point();
mouseDownLoc = new Point();
}
///// PRIVATE METHODS BEWLOW /////
private static function onMouseDown(e:MouseEvent):void
{
_mouseDown = true;
mouseDownLoc = new Point(e.stageX, e.stageY);
}
private static function onMouseUp(e:MouseEvent):void
{
_mouseDown = false;
mouseDownLoc = new Point(e.stageX, e.stageY);
}
private static function onMouseMove(e:MouseEvent):void
{
mouseLoc = new Point(e.stageX, e.stageY);
}
private static function onKeyDown(e:KeyboardEvent):void
{
keyState[e.keyCode] = true;
}
private static function onKeyUp(e:KeyboardEvent):void
{
keyState[e.keyCode] = false;
}
}
}

Displaying HTML node in tree - height issue

Grrr so close yet still failing...
I display this tree, in Flex, which contains two nodes types:
Regular nodes, which are rendered normally like text (because they are!)
Rich (HTML) nodes - that's where things get twisted
Note that my issue is when I dynamically add a new (HTML) node to my tree.
So... How do I display HTML nodes?
I subclass TreeItemRenderer
In that subclass, I override set data() and add a text child to my renderer
Therefore I now have:
[icon] [label]
[text component]
Why?
The default label is a pure text component, not HTML-capable, hence the extra component: I want to display the new guy and forget the default label.
(continued) I override updateDisplayList() and, if the node is a rich one, I set label's height to zero, set my component's x and y to label'x and and y.
So...what am I missing? Ah, yes: I need to set my node's height since HTML text can be bigger or smaller than its text counterpart.
(continued) I override measure()
If my node is not a rich one, I simply invoke super.measure() and return
If it is a rich one, I give my html component a width (htmlComponent.width = explicitWidth - super.label.x;) and its height should be automatically computed.
This gives me a fairly reliably unreliable result!
When I fold/unfold my tree, every other time, I seem to get a correct height for my HTML node. The other time I get a height of '4' which happens to be the HTML component's padding alone, without content.
I know that I must be doing something fairly stupid here...but I am not sure what. I will post my code if my rambling is too incoherent to make any sense of...
**** EDIT: here is the source code for my renderer
As you can see, only 'notes' nodes use HTML.
I add a 'htmlComponent' child that will display the rich text while the default label is zero-sized and disappears.
It's definitely very raw code, as it's in progress!
package com.voilaweb.tfd
{
import mx.collections.*;
import mx.controls.Text;
import mx.controls.treeClasses.*;
import mx.core.UITextField;
import mx.core.UIComponent;
import flash.text.TextLineMetrics;
public class OutlinerRenderer extends TreeItemRenderer
{
private function get is_note():Boolean
{
return ('outlinerNodeNote' == XML(super.data).name().localName);
}
override public function set data(value:Object):void
{
super.data = value;
var htmlComponent:Text = super.getChildByName("htmlComponent") as Text;
if(!htmlComponent)
{
htmlComponent = new Text();
htmlComponent.name = "htmlComponent";
addChild(htmlComponent);
}
if(is_note)
htmlComponent.htmlText = XML(super.data).attribute('nodeText');
else
htmlComponent.htmlText = null;
setStyle('verticalAlign', 'top');
}
/*
* Today we've learnt a valuable lesson: there is no guarantee of when createChildren() will be invoked.
* Better be dirty and add children in set data()
override protected function createChildren():void
{
super.createChildren();
var htmlComponent:Text = new Text();
htmlComponent.name = "htmlComponent";
addChild(htmlComponent);
}
*/
override protected function measure():void
{
if(is_note)
{
super.measure();
var htmlComponent:Text = super.getChildByName("htmlComponent") as Text;
//Setting the width of the description field
//causes the height calculation to happen
htmlComponent.width = explicitWidth - super.label.x;
//We add the measuredHeight to the renderers measured height
//measuredHeight += (htmlComponent.measuredHeight - label.measuredHeight);
// Note the silly trick here...hopefully in the future I figure out how to avoid it
//
// Here is what happens: we check if measuredHeight is equal to decoration such as margin, insets...rather than that + some height
// If so, then we need to come up with an actual height which we do by adding textHeight to this height
// Note that I care about text being equal to margin etc but do not have proper access to these
// For instance UITextField.TEXT_HEIGHT_PADDING == 4 but is not accessible
// I am going to check if "<10" that will cover this case...
trace("For text " + htmlComponent.htmlText);
trace("width = " + htmlComponent.getExplicitOrMeasuredWidth()+" x height = " + htmlComponent.getExplicitOrMeasuredHeight());
var m:TextLineMetrics = htmlComponent.measureHTMLText(htmlComponent.htmlText);
//if(10 > htmlComponent.measuredHeight && !isNaN(htmlComponent.explicitHeight))
//htmlComponent.explicitHeight = m.height + htmlComponent.measuredHeight;
//if(htmlComponent.measuredHeight < 10) htmlComponent.explicitHeight = 50;
//measuredHeight += (htmlComponent.getExplicitOrMeasuredHeight() - super.label.getExplicitOrMeasuredHeight());
measuredHeight += (htmlComponent.getExplicitOrMeasuredHeight() - label.getExplicitOrMeasuredHeight());
trace("m:"+m.height+" Height: " + htmlComponent.getExplicitOrMeasuredHeight());
}
else
{
super.measure();
}
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
label.height = label.getExplicitOrMeasuredHeight(); // If you tell me my height, then I shall use my variable height!
graphics.clear();
if(is_note)
{
label.height = 0;
var htmlComponent:Text = super.getChildByName("htmlComponent") as Text;
htmlComponent.x = label.x;
htmlComponent.y = label.y;
htmlComponent.height = htmlComponent.getExplicitOrMeasuredHeight();
graphics.beginFill(0x555555);
graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
graphics.endFill();
}
var complete:XMLList = XML(super.data).attribute('complete');
if(complete.length() > 0 && true == complete[0])
{
var startx:Number = data ? TreeListData(listData).indent : 0;
if(disclosureIcon)
startx += disclosureIcon.measuredWidth;
if(icon)
startx += icon.measuredWidth;
graphics.lineStyle(3, getStyle("color"));
var y:Number = label.y + label.getExplicitOrMeasuredHeight() / 2;
graphics.moveTo(startx, y);
graphics.lineTo(startx + label.getExplicitOrMeasuredWidth(), y);
}
}
}
}
You made false assumption about label component in default renderer - it is capable of displaying html content. This renderer works for me:
public class HtmlTreeItemRenderer extends TreeItemRenderer {
override protected function commitProperties():void {
super.commitProperties();
label.htmlText = data ? listData.label : "";
invalidateDisplayList();
}
}
It would certainly help if you could post some code.
I was wondering though why you are using a custom html renderer. Is it because you want to display an icon next to the label since you mention [icon] [label]? If so, you're probably better off using an iconField or iconFunction.
Another thing that comes to mind is the variableRowHeight property. You might need to set this if your nodes have different heights.
Try it.
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
width="100%" height="100%" verticalScrollPolicy="off"
horizontalScrollPolicy="off">
<mx:Script>
<![CDATA[
import mx.core.UITextField;
private var texto:UITextField;
override protected function createChildren():void
{
super.createChildren();
texto = new UITextField();
texto.setColor(0xFFFFFF);
texto.multiline = true;
texto.wordWrap = true;
texto.autoSize = TextFieldAutoSize.LEFT;
this.addChild(texto);
//texto.text = data.title;
}
override public function set data(value:Object):void
{
super.data = value;
if (value)
{
texto.htmlText = value.title;
this.invalidateDisplayList();
}
}
override protected function measure():void
{
super.measure();
}
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if (texto)
texto.width = this.width;
}
]]>
</mx:Script>
</mx:Canvas>