Flex collision testing with hitTestObject - apache-flex

I'm trying to test clipping on two canvases. Both canvases are 100px wide. They're 20px apart. I've placed a label inside one and made it 200px wide. Scroll bars will show up on the canvas. When I don't have the label inside and use hitTestObject it returns false. When I place the label inside it returns true. Is there any way to alter the canvas with the label inside so that it doesn't expand to the width of the label?
<?xml version="1.0" encoding="utf-8"?>
private function init() : void {
var hitBox:Sprite = new Sprite;
hitBox.graphics.drawRect(box1.x, box1.y, 100, 100);
box1.hitArea = hitBox;
box1.mouseEnabled = false;
trace('box hit area: ' + box1.getBounds(box1));
trace('hitbox: ' + hitBox);
trace('box hit test: ' + box1.hitTestObject(box2));
}
]]>
</mx:Script>
<mx:Canvas id="box1" x="10" y="10" width="100" height="100" backgroundColor="#FFFFFF">
<mx:Label text="This is a test" width="200" />
</mx:Canvas>
<mx:Canvas id="box2" x="120" y="10" width="100" height="100" backgroundColor="#FFFFFF" />

Unfortunately, it doesn't look like you can accomplish what you want with hitTestObject. I played around with setting the clipContent and horizontalScrollPolicy properties on your canvases, to no use. What I think is happening is that hitTestObject considers your canvas to be as wide as the longest child component, regardless of any clip masks or scroll bars.
Are you forced to use hitTestObject? If not, I'd suggest writing your own collision detection function along the lines of:
public static function componentsCollide( obj1:DisplayObject, obj2:DisplayObject ):Boolean {
var bounds1:Rectangle = new Rectangle( obj1.x, obj1.y, obj1.width, obj1.height );
var bounds2:Rectangle = new Rectangle( obj2.x, obj2.y, obj2.width, obj2.height );
return bounds1.intersects( bounds2 );
}

Related

Simulate includeInLayout=false in pure Actionscript code

If you know Flex, you probably know what the property "includeInLayout" does. If not, this property make the parent of your component disregard the bounds (like width and height) of your component in render their own bounds.
Description in reference below:
Specifies whether this component is
included in the layout of the parent
container. If true, the object is
included in its parent container's
layout and is sized and positioned by
its parent container as per its layout
rules. If false, the object size and
position are not affected by its
parent container's layout.
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/core/UIComponent.html#includeInLayout
In Flex, for example:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
creationComplete="application1_creationCompleteHandler(event)">
<mx:Script>
<![CDATA[
import mx.events.FlexEvent;
protected function application1_creationCompleteHandler( event:FlexEvent ):void
{
trace( container.width, container.height ); // output: 200 200
}
]]>
</mx:Script>
<mx:Canvas id="container">
<mx:Button label="Test"
width="100"
height="100" />
<mx:Button label="Test2"
width="200"
height="200" />
</mx:Canvas>
</mx:Application>
Now if I set includeInLayout="false" in second button:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
creationComplete="application1_creationCompleteHandler(event)">
<mx:Script>
<![CDATA[
import mx.events.FlexEvent;
protected function application1_creationCompleteHandler( event:FlexEvent ):void
{
trace( container.width, container.height ); // output: 100 100
}
]]>
</mx:Script>
<mx:Canvas id="container">
<mx:Button label="Test"
width="100"
height="100" />
<mx:Button label="Test2"
width="200"
height="200"
includeInLayout="false" />
</mx:Canvas>
</mx:Application>
I know of all framework architecture involved to implement this property and know than this property is a property from Flex Framework. What I wanna is this behavior in pure actionscript. For example:
import flash.display.Shape;
var myBox:Shape = new Shape();
myBox.graphics.beginFill(0xFF0000);
myBox.graphics.drawRect(0, 0, 100, 100);
myBox.graphics.endFill();
addChild(myBox);
trace(width, height); // output: 100 100
var myAnotherBox:Shape = new Shape();
myAnotherBox.graphics.beginFill(0xFF00FF, .5);
myAnotherBox.graphics.drawRect(0, 0, 200, 200);
myAnotherBox.graphics.endFill();
addChild(myAnotherBox);
trace(width, height); // output: 200 200
Is there some equivalent implementation in pure Actionscript to reproduce this behavior on "myAnotherBox"?
I already tried:
Change transform matrix;
Change transform pixelBounds;
Change scrollRect;
Apply masks;
And no successful.
Cheers...
You are trying to compare to different things.
In the first example, the width and height are from the UIComponent class, which actually represents a "measured" value, as provided by the Flex Framework.
In the second case, the width and height are the actual size of the rendered shape, as provided by the Flash Player.
If you open the UIComponent's implementation, you will notice that it is actually hiding the original meaning of width and height into $width and $height and provide their own implementation to it.
So to answer your question, you cannot achieve what you want working directly with Shapes (pure Flash components)
Every time you draw something in a graphics context, the width and height will update accordingly.
You options:
draw the first one in one graphics, and the second one in another graphics and measure only the first shape
compute the width and height yourself. This is what actually the containers do, and just skip the includeInLayout=false child components.
If you look up in the UIComponent source code, you'll find, that flag includeInLayout is used in invalidation mechanism (exatcly in size invalidation). If includeInLayout of the component is false, then its parent's size and its size is not recalculated.
Invalidation is native flex framework mechanism, which does not exist in pure AS projects.
You need to override width and height in your container:
override public function get width() : Number {
// place your code here
return 100;
}
override public function get height() : Number {
// place your code here
return 100;
}

In flex, how to get coordinates when using VBox for stacking up components?

In flex, I am using VBox & HBox to stack components. When I try to get x,y coordinate of a component, I always get 0. If I specify coordinate like mx:VBox x="120", then I get the value.
How can I get the coordinate without explicitly stating it.
You can translate the coordinates relatively to the stage. Check out the code below:
var box:VBox = new VBox;
var child:DisplayObject = new DisplayObject;
box.addChild(child);
child.addEventListener(FlexEvent.UPDATE_COMPLETE, updateCompleteHandler);
...
private function updateCompleteHandler(fe:FlexEvent):void{
// find global coordinates
var globalCoords:Point = child.localToGlobal(new Point(0,0));
// coordsInBox is what you are looking for
var coordsInBox:Point = box.globalToLocal(globalCoords);
}
The point is to use localToGlobal for the child components and then globalToLocal to translate the global coordinates so that they were expressed relatively to the box container.
Please notice, that the coordinates won't be processed until UPDATE_COMPLETE is called by the child object.
The X and Y values of a component are always relative to the component's parent. directionsHelp.x and directionsHelp.y will both return the position relative to the VBox containing them which, unless you explicitly set the values or insert other components around it, will be 0 by default.
The thing to remember about localToGlobal() is that you must call it from the child. If you have an Application and you just call localToGlobal( new Point(child.x, child.y) ), it will try to return the given point within the Application relative to the stage (because you haven't specified what "local" is), which will therefore conduct no transformations, and it will therefore stay equal to (0, 0).
If however you call child.localToGlobal( new Point(child.x, child.y) ), it will return the value of the child's position relative to the stage, transforming the given point by however much the child is offset on the stage.
Here's a quick app to demonstrate:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" backgroundColor="#FFFFFF">
<mx:Script>
<![CDATA[
private function updateCoords():void
{
var point:Point = new Point(directionsHelp.x, directionsHelp.y);
point = directionsHelp.localToGlobal(point);
directionsHelp.text = "My stage coordinates are ("+point.x+", "+point.y+")";
}
]]>
</mx:Script>
<mx:VBox>
<mx:Box height="100" width="100" borderStyle="solid" borderColor="#000000"
horizontalAlign="center" verticalAlign="middle">
<mx:Label text="100 x 100" />
</mx:Box>
<mx:VBox>
<mx:Text id="directionsHelp" color="#4FA4CD" fontSize="8" fontWeight="bold"
text="Click the button to display my position on the stage." />
<mx:Button label="Get Position" click="updateCoords()" />
</mx:VBox>
</mx:VBox>
</mx:Application>

Flex container transform matrix problems

I have a Box container that has a label element inside it. When the box is transformed using a Matrix the label element is not visible anymore. How do I make the elements visible?
<mx:Script>
<![CDATA[
private function onBoxClick(event:MouseEvent):void
{
var transformMatrix:Matrix = this.box.transform.matrix;
transformMatrix.c = Math.PI * 2 * -15 / 360;;
this.box.transform.matrix = transformMatrix;
}
]]>
</mx:Script>
<mx:HBox id="box"
x="100" y="100"
width="100" height="100"
backgroundColor="0x000000"
click="onBoxClick(event)">
<mx:Label id="textLabel" text="This is a test" color="#FFFFFF" visible="true"/>
</mx:HBox>
I'm guessing the TextField inside the Label component doesn't have the font embedded. If you plan to use .rotation or .alpha on a dynamic text you must embed the font.
You can easily test this with a regular TextField:
var t:TextField = new TextField();
t.defaultTextFormat = new TextFormat('Verdana',12,0x000000);
t.embedFonts = true;
t.rotation = 10;
t.text = 'rotated';
addChild(t);
That is assuming you have the Verdana font embedded in this example. If you comment out the 3rd line you will see the text disappear.

How to make the Canvas clip its contents in Flex?

I draw a line on a Canvas object with the moveTo and lineTo graphics methods. If one end of the line lies outside the Canvas, the line spills out and is drawn over or under other elements in the application.
How do I make the Canvas keep the line contained within itself?
Thanks!
<mx:Canvas id="canvas" top="0" right="51" left="0" bottom="32">
<mx:Canvas x="-1" y="0" width="0" height="0"> <!-- This is a HACK to make the canvas clip -->
</mx:Canvas>
</mx:Canvas>
I had a similar problem some time ago. You need to embed another container inside the canvas, and draw the primitive graphics in that instead. I believe this is because the Canvas component only clips child components, and not primitive graphics.
Example here: http://www.adobe.com/cfusion/webforums/forum/messageview.cfm?forumid=60&catid=585&threadid=1421196. It includes some sample code about half way down the page.
The link in the recommended answer is broken. I solved the problem by placing another canvas inside my canvas that is larger than the outer canvas.
Example:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" applicationComplete="onComplete()">
<mx:Script><![CDATA[
private function onComplete():void
{
canvas.graphics.lineStyle(1);
canvas.graphics.moveTo( -100, -100);
canvas.graphics.lineTo(400, 400);
}
]]></mx:Script>
<mx:Canvas id="window"
height="300"
width="300"
clipContent="true"
horizontalScrollPolicy="off"
verticalScrollPolicy="off"
backgroundColor="white">
<mx:Canvas id="canvas" width="301" height="301">
</mx:Canvas>
</mx:Canvas>
</mx:Application>
If the window Canvas is going to be resized at runtime, add a resize event listener to resize the canvas Canvas also.
I have just developed a Flex Box component, which acts as a regular component container, but draws a rounded rectangle background, with another rounded rectangle indicated a fill-level. For that I needed to clip the upper section that should not get filled. Drawing the fill rectangle to the fill height was no option since the rounded corners would not match.
What I learned:
I created a Canvas component just for drawing the fill-level with bounds 0/0 and width/height of the Box
I added that canvas to the Box at index 0 via addChildAt()
I set the includeInLayout property to false for that canvas since it should not take part in the layouting of the Box itself but rather act as some floating drawing pane on top
I then added another Canvas as the mask to that fill-canvas (addChild(), and set mask property)
Here is some code:
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
// super
super.updateDisplayList(unscaledWidth, unscaledHeight);
// prep
var g:Graphics = this.graphics;
var fgColor:int = this.getStyle("fillColor");
var bgColor:int = this.getStyle("backgroundFillColor");
var radius:int = this.getStyle("cornerRadius");
// clear
g.clear();
// draw background
g.beginFill(bgColor, 1);
g.drawRoundRect(0, 0, unscaledWidth, unscaledHeight, radius, radius);
g.endFill();
// draw fill level
if (this._fillLevel > 0) {
var fillHeight:int = int(unscaledHeight * this._fillLevel);
// extra component for drawing fill-level, so we can apply mask just to this
if (this._fillLevelCanvas == null) {
this._fillLevelCanvas = new Canvas();
this._fillLevelCanvas.x = 0;
this._fillLevelCanvas.y = 0;
this._fillLevelCanvas.includeInLayout = false;
this.addChildAt(this._fillLevelCanvas, 0);
}
this._fillLevelCanvas.width = unscaledWidth;
this._fillLevelCanvas.height = unscaledHeight;
// mask
if (this._fillLevelMask == null) {
this._fillLevelMask = new Canvas();
this._fillLevelMask.x = 0;
this._fillLevelMask.y = 0;
this._fillLevelCanvas.addChild(this._fillLevelMask);
this._fillLevelCanvas.mask = this._fillLevelMask;
}
this._fillLevelMask.width = this.width;
this._fillLevelMask.height = this.height;
this._fillLevelMask.graphics.beginFill(0xFFFFFF);
this._fillLevelMask.graphics.drawRect(0, this.height-fillHeight, this._fillLevelMask.width, this._fillLevelMask.height);
this._fillLevelMask.graphics.endFill();
this._fillLevelCanvas.graphics.beginFill(fgColor, 1);
this._fillLevelCanvas.graphics.drawRoundRect(0, 0, unscaledWidth, unscaledHeight, radius, radius);
this._fillLevelCanvas.graphics.endFill();
}
}
Looks like this might be useful:
http://forums.adobe.com/message/199071#199071
Set ClipToBounds property of the Canvas to true:
<Canvas ClipToBounds="True">... Content ...</Canvas>

DisplayObject snapshot in flex 3

i'm creating a visual editor in flex and need to let users export thier projects into Image format. But i have one problem: size of the canvas is fixed and when user add element which is out of these sizes some scroll bars added. And user continue working on the project. but when he want to take snapshot of the canvas he just get the visible part ot the canvas with scrollbars. how to get image of the fullsize canvas?
The only solution i found is to check the positions and sizes of canvas child objects and rezise it to fit them. then take snap and resize back. But it hmmmm... too complicated i think. Is there some "easy methods"?
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
<mx:Script>
<![CDATA[
import mx.graphics.ImageSnapshot;
private function SnapshotButtonHandler():void
{
var snapshot:ImageSnapshot = ImageSnapshot.captureImage(AppCanvas);
var file:FileReference = new FileReference();
file.save(snapshot.data, "canvas.png");
}
]]>
</mx:Script>
<mx:Canvas id="AppCanvas" width="800" height="300" backgroundColor="0xFFFFFF">
<mx:Box x="750" y="100" width="100" height="100" backgroundColor="0xCCCCCC" />
</mx:Canvas>
<mx:Button id="SnapshotButton" label="take snapshot" click="SnapshotButtonHandler()" />
</mx:Application>
i would put one container into the scrollable canvas, that adapts it's size according to objects in it ... maybe even UIComponent would do the trick ... then do a snapshot of that container ... the canvas only adds the scrollbars, so to speak ...
greetz
back2dos
Solution i found
BaseCanvas - canvas with fixed height and width
EditCanvas - dynamic canvas which params depends on it's children position.
Snapshot takes from EditCanvas. Here part of code
private function SnapshotButtonHandler():void
{
var snapshot:ImageSnapshot = ImageSnapshot.captureImage(EditCanvas);
var file:FileReference = new FileReference();
file.save(snapshot.data, "canvas.png");
}
private function ResizeCanvas():void
{
for each (var child:* in AppCanvas.getChildren())
{
if ((child.x + child.width) > AppCanvas.width)
AppCanvas.width = child.x+child.width;
if ((child.y + child.height) > AppCanvas.height)
AppCanvas.height = child.y+child.height;
}
}
<mx:Canvas id="BaseCanvas" width="300" height="200">
<mx:Canvas id="EditCanvas" width="300" height="200" backgroundColor="0xFFFFF0" horizontalScrollPolicy="off" verticalScrollPolicy="off"/>
</mx:Canvas>
krishna: make sure you're targeting flash player 10 in your build path.

Resources