Let's say you have a generic Graphics object with a fill and a a stroke. Is there an easy way to slice that object, for instance vertically, and get a Graphics object that represents a subset of it?
In other words, what would function slice below look like?
var gOriginal:Graphics;
var gNew:Graphics;
gNew = slice(gOriginal, "vertical", "0.5);
so that if gOriginal was an ellipse I now get half an ellipse?
thank you
f
I'm tagging onto Daniel's concept basically (except I have some deeply rooted, unexplained, disdain for degrafa), you should be able to do this:
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
minWidth="955"
minHeight="600"
creationComplete="application1_creationCompleteHandler(event)">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
import mx.core.UIComponent;
import mx.events.FlexEvent;
import spark.components.Group;
public var someGraphics:UIComponent;
[Bindable]
public var bd:BitmapData;
[Bindable]
public var finalBD:BitmapData;
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth,unscaledHeight);
if(someGraphics)
{
someGraphics.graphics.beginFill(0xFF0000);
someGraphics.graphics.drawEllipse(0,0,100,20);
someGraphics.graphics.endFill();
bd.draw(someGraphics);
finalBD.copyPixels(bd,new Rectangle(0,0,bd.width*.5,bd.height),new Point());
}
}
protected function application1_creationCompleteHandler(event:FlexEvent):void
{
someGraphics = new UIComponent();
bd = new BitmapData(100,100);
finalBD = new BitmapData(100,100);
invalidateDisplayList();
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:BitmapImage source="{bd}" width="100" height="100"/>
<s:BitmapImage source="{finalBD}" width="100" height="100"/>
</s:Application>
This is obviously far from a pretty solution but just doing the graphics into some object that has a graphics property, then using the BitmapData draw method to get the pixels into a bitmap data then copying out a section of those pixels to another BitmapData for display, this is probably more memory but less processor intensive than Jeremy's. Also I'm using UIComponent the requirement for the draw method is that the class implements IBitmapDrawable so whatever is the lightest implementation of that is probably what you want to use, UIComponent is a guess.
you could draw() it into a bitmap object and select a rectangle to include which could have the same effect, but there is no simple way of doing that.
you can also look into degrafa but that could become too complicated
There isn't a built in method for removing portions of a Graphics object. You will most likely need to treat each type of shape ( rect, ellipse, poly ) uniquely, in regard to "slicing" it. For instance to slice an elipse, similarly to taking a slice out of a pie chart, you would need to draw the shapes using a little trig to create a wedge.
Here is a basic function for drawing wedges:
protected function drawWedge():void
{
var g:Graphics = shape.graphics;
g.clear();
g.lineStyle( 1, 0xFFFFFF );
g.beginFill( 0xCCCCCC );
var radiusX:Number = 100;
var radiusY:Number = 100;
var angle:Number = 0;
var xpos:Number = 0;
var ypos:Number = 0;
var segments:Number = 25;
var degrees:Number = 45;
var inc:Number = degrees / ( segments );
for( var i:int = 0; i <= segments; i++ )
{
xpos = Math.cos( Math.PI * angle / 180 ) * radiusX;
ypos = Math.sin( Math.PI * angle / 180 ) * radiusY;
g.lineTo( xpos, ypos );
angle += inc;
}
g.endFill();
}
Adjusting the angleX and angleY vars when they are initialized will begin drawing the wedge at a different point along the circumference. If you change degrees to 360 you will see a complete circle, and at 180 you will get a half circle. Change radiusX and radiusY to create an oval. Also, segments controls the resolution of the drawing. Bump it up and you get a smoother curve.
Hope this helps.
Related
I have 2 canvas objects that both contain a tiling image. My client recently requested that both the images be able to appear at the same time. I simply allowed them to be both enabled at the same time thinking they would tile appropriately but they do not.
After some careful thought I realized this is because they are of varying width so although my tiling image is 7 pixels in width, it may not always end at 7 pixels thus causing it to not appear to tile cleanly.
I can't just lop off the remainders because the imagery is being used to assess quantities of items (without going into too much detail) and having item 1 with 97 quantity next to item 2 with 200 quantity on one row compared to row 2 with item 1 having 100 quantity and item 2 having 300 quantity will show up strange to the end-user.
Does anyone know of how I could get started with maybe splicing the two canvas objects together or rather using 1 canvas object and then using BOTH background images and setting a percentWidth or something that the other one comes into affect?
You can use the copyPixels() method of the BitmapData class to achieve this sort of result. To do this, you need to load each image with a Loader. When the images are loaded, you have two Bitmap objects. From there, you access the corresponding BitmapData for each Bitmap and can splice them together.
After the splice, you create a new Bitmap object from the combined BitmapData. Then you feed the combined Bitmap to some other component. Unfortunately, a Canvas won't accept a Bitmap for its backgroundImage style. But you can add the combined Bitmap to a UIComponent or set it as the source of an Image component.
Finally, add either the UIComponent or Image to a Canvas. Since you can layer things on the Canvas, you effectively have a background image as long as place (and leave) the combined image at index 0 of the Canvas.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical"
minWidth="955" minHeight="600"
creationComplete="onCreationComplete()">
<mx:Script>
<![CDATA[
import mx.containers.Canvas;
import mx.controls.Image;
import mx.core.UIComponent;
import mx.events.FlexEvent;
private var loader1:Loader;
private var loader2:Loader;
private var bitmap1:BitmapData;
private var bitmap2:BitmapData;
protected function onCreationComplete():void
{
loader1 = new Loader();
loader1.load(new URLRequest("Android_Robot_100.png"));
loader1.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoader1Complete, false,0,true);
loader2 = new Loader();
loader2.load(new URLRequest("rails.png"));
loader2.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoader2Complete, false,0,true);
}
protected function onLoader1Complete(event:Event):void
{
bitmap1 = (loader1.content as Bitmap).bitmapData;
if (bitmap1 && bitmap2)
copyPixels();
}
private function onLoader2Complete(event:Event):void
{
bitmap2 = (loader2.content as Bitmap).bitmapData;
if (bitmap1 && bitmap2)
copyPixels();
}
protected function copyPixels():void
{
// false = non-transparent background
var combined:BitmapData = new BitmapData(200,200, false);
var sourceRect1:Rectangle = new Rectangle(0,0,bitmap1.width, bitmap1.height);
var sourceRect2:Rectangle = new Rectangle(0,0,bitmap2.width, bitmap2.height);
combined.copyPixels(bitmap1, sourceRect1, new Point(0,0));
combined.copyPixels(bitmap2, sourceRect2, new Point(bitmap1.width, 0));
var result:Bitmap = new Bitmap(combined);
var image:Image = new Image();
image.source = result;
var canvas:Canvas = new Canvas();
canvas.addChildAt(image, 0);
addChild(canvas);
// as an alternative, just add the bitmap to a UIComponent
// note you can't do this and the above at the same time,
// becuase the 'result` Bitmap can only be the child of one
// object, adding it to one thing removes it from the other...
// var uic:UIComponent = new UIComponent();
// uic.addChild(result);
// addChild(uic);
}
]]>
</mx:Script>
</mx:Application>
Ultimately I was overcomplicating it. I simply did this with masking like...
<mx:Canvas width="100%" height="100%">
<mx:Image id="myFirstUnrelatedImage" width="50%" height="65%" maintainAspectRatio="false"
verticalCenter="0" left="3"
source="#Embed('../assets/image1.png')" visible="true"/>
<!-- not sure why borderStyle here is required but it won't show up without? -->
<mx:Canvas id="image1mask" width="30%" height="94%"
borderStyle="none" visible="false"/>
<mx:Canvas id="image1" width="100%" height="100%"
minWidth="0" styleName="myStyle"
mask="{image1mask}"/>
<!-- not sure why borderStyle here is required but it won't show up without? -->
<mx:Canvas id="image2mask" width="20%" height="94%"
right="0" borderStyle="none" visible="false"
includeInLayout="false"/>
<mx:Canvas id="image2" width="100%" height="100%"
minWidth="0" styleName="myStyle2"
mask="{image2mask}"/>
</mx:Canvas>
Where my styles were controlling the backgroundImage and backgroundRepeat properties in css.
Could someone please explain why this doesn't want to work? It's a bit of decoration to go behind a logo.
When the dMove var is commented out I get the appropriate line of squares fading in and out at random spots along the x = 78 axis, but when introduced nothing appears at all...
private var floatBG:UIComponent = new UIComponent();
private function addDP(event:TimerEvent):void{
var dY:Number = 5+Math.ceil(Math.random()*60);
var dSize:Number = Math.ceil(Math.random()*12);
var dAlpha:Number = Math.random()*0.5 + 0.2
var dDuration:Number = Math.ceil(Math.random()*5000) + 1000;
var d:Sprite = new Sprite();
d.graphics.beginFill(0x1C75BC,dAlpha);
d.graphics.drawRect(78, dY, dSize,dSize);
d.graphics.endFill();
floatBG.addChild(d);
var dMove:Move = new Move(d);
dMove.xBy = 300;
dMove.duration = dDuration;
dMove.play();
var dFade:Fade = new Fade(d);
dFade.alphaFrom = 1;
dFade.alphaTo = 0;
dFade.duration = dDuration;
dFade.play();
this.addElement(floatBG);
}
Also, what would be the best/correct method for destroying the sprites at the end of each cycle?
Thanks a lot in advance, massively appreciate it!
Josh
Ok, so after a bit of playing around with it I managed to find a solution, I really appreciated Brian Bishop's help, who managed to very much point me in the right direction.
As Brian suggested, simply entering d as a UIComponent rather a sprite allowed the dMove to effect (I'm still not sure why this is, as the dFade effect worked when it was still a sprite). However adding the floatBG (BG being background, the UIComponent to which all the d UIComponents are added) each time was unnecessary, so I made this.addElement(floatBG) part of my creationComplete function
I then ran into the issue that the application began to fill with the d instances (d originally stood for "datapoint" by the way, which was kind of the theme of the effect which I was trying to create, a plume of them), and progressively slowed the application to a crawl. To continuously add d elements is necessary as it's part of the logo that sits in the corner of the screen looking as pretty as I can make it...
Eventually I came to the below solution, the crux of it was to load floatBG with 600 dummy UIComponents, and then with each cycle of the addDP function both add a moving d UIComponent to floatBG and remove another UIComponent off of the total array of children which it contains.
Thus, in the end it looked something like this:
<?xml version="1.0" encoding="utf-8"?><s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" creationComplete="init(event)">
<fx:Script>
<![CDATA[
import mx.core.DesignLayer;
import mx.core.UIComponent;
import mx.effects.effectClasses.MoveInstance;
import mx.events.EffectEvent;
import mx.events.FlexEvent;
import mx.states.RemoveChild;
import spark.effects.AnimateColor;
import spark.effects.AnimateFilter;
import spark.effects.Fade;
import spark.effects.Move;
import spark.effects.Rotate;
import spark.effects.easing.Sine;
private var floatBG:UIComponent = new UIComponent;
private var floatColour:uint = 0x1C75BC
protected function init(event:FlexEvent):void
{
//Float BackGround set up
floatBG.mask = floatMaskBlock;
floatBG.depth = -1;
this.addElement(floatBG);
//Fill FloatBG with 600 filler elements
for(var i:int=0; i<600; i++)
{
var filler:UIComponent = new UIComponent;
floatBG.addChild(filler);
}
//Float Animation Timer
var floatTimer:Timer = new Timer(1,0);
floatTimer.addEventListener(TimerEvent.TIMER,DPstartup)
floatTimer.start();
private function DPstartup(event:TimerEvent):void
{
this.addDP(5,60)
this.addDP(22,14);
this.addDP(18,18);
}
private function addDP(yf:Number,yt:Number):void
{
this.title.text = floatBG.numChildren.toString();
var dY:Number = yf+Math.ceil(Math.random()*yt);
var dXby:Number = Math.ceil(Math.random()*1000 + 50);
var dSize:Number = Math.ceil(Math.random()*12);
var dAlpha:Number = Math.random()*0.5 + 0.2
var dMotionDuration:Number = Math.ceil(Math.random()*15000) + 1000;
var dFadeDuration:Number = Math.random()*dMotionDuration;
var d:UIComponent = new UIComponent;
d.graphics.beginFill(floatColour,dAlpha);
d.graphics.drawRect(78, dY, dSize,dSize);
d.graphics.endFill();
var dMove:Move = new Move(d);
dMove.xBy = dXby;
dMove.duration = dMotionDuration;
dMove.easer = Linear;
dMove.play();
var dFade:Fade = new Fade(d);
dFade.alphaFrom = dAlpha;
dFade.alphaTo = 0;
dFade.duration = dFadeDuration;
dFade.play();
floatBG.addChild(d);
floatBG.removeChildAt(0);
};
]]>
</fx:Script>
<fx:Declarations>
<s:Sine id="Sine"/>
<s:Bounce id="Bounce"/>
<s:Linear id="Linear"/>
</fx:Declarations>
<s:Group x="88" width="1000" height="80" id="floatMaskBlock">
<s:Rect width="100%" height="100%">
<s:fill>
<s:SolidColor color="0xffffff"/>
</s:fill>
</s:Rect>
</s:Group></s:Group>
Ok, apologies if that was pretty long-winded, but someone may find it useful... it's a nice effect (I think so anyway)
I use the mask to give a sharp edge to the point at which the d elements appear. Alternatively one could just place it behind another object, but if, as in my case, the object is a line of width less than the size of some of the d elements, such a mask is necessary.
The colour is declared as a variable at the beginning as I'll likely add a function to change it on click, or something similar.
Thanks!
Josh
I ran this in Flex 4, only change I made was to use a UIComponent instead of a spirte, which works fine. Maybe your using pure AS3 project though? Not sure about destroying after they play.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
<fx:Script>
<![CDATA[
import mx.core.UIComponent;
import mx.effects.Fade;
import mx.effects.Move;
private var floatBG:UIComponent = new UIComponent();
private function addDP(event:TimerEvent):void{
var dY:Number = 5+Math.ceil(Math.random()*60);
var dSize:Number = Math.ceil(Math.random()*12);
var dAlpha:Number = Math.random()*0.5 + 0.2
var dDuration:Number = Math.ceil(Math.random()*5000) + 1000;
var d:UIComponent = new UIComponent();
d.graphics.beginFill(0x1C75BC,dAlpha);
d.graphics.drawRect(78, dY, dSize,dSize);
d.graphics.endFill();
floatBG.addChild(d);
var dMove:Move = new Move(d);
dMove.xBy = 300;
dMove.duration = dDuration;
dMove.play();
var dFade:Fade = new Fade(d);
dFade.alphaFrom = 1;
dFade.alphaTo = 0;
dFade.duration = dDuration;
dFade.play();
this.addElement(floatBG);
}
private function onClick():void{
var t:Timer = new Timer(100, 10);
t.start();
t.addEventListener(TimerEvent.TIMER, addDP);
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:Button click="onClick()" />
</s:Application>
I have a form that pops up in flex, it is a movieclip contained within a UIComponent. Within this movieclip are form fields.
For some reason I can't get the tab button working to be able to tab between the fields. I set up each field with a tabindex, but that doesn't work.
Is there anything else I need to do besides this? This is basically all the code I am using:
email.tabIndex = 1;
city.tabIndex = 2;
firstName.tabIndex = 3;
etc.
Is this not working because of flex? And if so, what is a workaround?
have you tried adding a new FocusManager instance to the UIComponent that contains the movieclip? I am not sure but maybe.
Also another thing I can think of could be the tabEnabled property of the form elements and the containing Objects.
Hope it gets you closer.
Good luck,
Rob
////////// UPDATE ///////////////
Unfortunately I couldn't make it work, but I send you my code, maybe you can do something with it.
I tried to add a movieclip and the textfields into it, but it's the same, no TAB at all. So I simply added the textfields to the UIComponent and set all the possible tab and focus related properties to true - no luck :(.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
import mx.core.UIComponent;
import flash.display.MovieClip;
import flash.text.TextField;
protected function init(event:Event):void
{
var i:uint = 0;
var l:uint = 4;
while (i < l)
{
var tf:TextField = new TextField();
tf.name = "tf_" + i;
tf.width = 200;
tf.height = 21;
tf.border = true;
tf.type = "input";
tf.tabIndex = i;
tf.tabEnabled = true;
tf.text = "tabIndex = " + String(tf.tabIndex);
tf.x = 50;
tf.y = tf.height * i + 10 * i + 100;
uicomp.addChild(tf);
++i;
}
};
]]>
</fx:Script>
<mx:UIComponent hasFocusableChildren="true" tabChildren="true" tabEnabled="true" tabFocusEnabled="true" id="uicomp" width="100%" height="100%" addedToStage="init(event);" />
</s:Application>
Sorry, that's all I could do for now. If you get the answer please let me know, I am keen.
Cheers,
Rob
I'm trying to create an image component that can be scaled using 9-slice scaling.
I'm 100% positive the grid rectangle is inside the image's bounds. However, the scale9Grid property seems not to affect anything.
I have tried many different things. Here is my last attempt where I try to put the image in a canvas. Any idea what I'm doing wrong?
mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="init()"
>
<mx:Script>
<![CDATA[
import mx.core.BitmapAsset;
import mx.controls.Image;
[Embed(source="assets/image.png")]
private var barImageClass:Class;
private var barImage:Image;
private function init():void
{
barImage = new Image();
barImage.addChild( new Bitmap( (new barImageClass() as BitmapAsset).bitmapData ) );
barImage.scale9Grid = new Rectangle( 120, 4, 2, 2 );
barImage.scaleX = 2;
addChild( barImage );
}
]]>
</mx:Script>
I believe you need to apply the scale9Grid to the bitmap, instead of to the sprite/movieclip it's inside of.
See this adobe doc. I think it will solve your problem. Cheers
I have rows of text data that can vary between 0 and 100, and all need to be visible on the screen at one time. The default behavior is fine for the grid until the rows * rowHeight > gridHeight.
Basically I need a hook into the item height, or row height to calculate it based on the height of the grid. I've set paddingTop and paddingBottom to zero, but there is still a considerable amount of white space in between rows.
My datagrid component...
<mx:DataGrid xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="OnCreationComplete()"
paddingTop="0"
paddingBottom="0"
>
<mx:Script>
<![CDATA[
private function OnCreationComplete():void
{
//setRowHeight(10);
}
override protected function setRowHeight(v:Number):void
{
super.setRowHeight(v);
}
]]>
</mx:Script>
</mx:DataGrid>
setRowHeight() helps, but the itemRender for the cell bigger than the cell, if I set the row height to something like 10.
You might want to look at the DataGrid.variableRowHeight property as, when this is set to false (the default) all rows will be the same height as the largest itemRenderer. You could also look into writing your own itemRenderer for each DataColumn.
If all you really want to do is set the row height based on the number of items in the dataProvider though, you could just set the DataGrid.rowHeight property like this (assuming your grid has a fixed height, say 100%) :
myDataGrid.dataProvider = myArray;
myGrid.rowHeight = Math.floor((myGrid.height - myGrid.headerHeight)/myArray.length);
(I'm taking the floor here because if you end up with a fractional value that rounds up, you'll need a scroll bar)
The only problem with this approach, as I think you've noticed, is that the itemRenderer might not display properly in a row that's too small. I guess you could solve this by changing the font within the renderer based on its height.
Thank you inferis, that helped me a lot. This is my final grid component. It's not really self contained because of a few call-outs, but if it helps someone else get theirs to work, great!
<?xml version="1.0" encoding="utf-8"?>
<mx:DataGrid xmlns:mx="http://www.adobe.com/2006/mxml"
paddingTop="-3"
paddingBottom="-3"
resize="OnResize(event)"
>
<mx:Script>
<![CDATA[
import mx.containers.Panel;
import mx.core.Application;
import mx.events.ResizeEvent;
protected function OnResize(event:ResizeEvent):void
{
this.invalidateDisplayList();
}
/**
* #private
* Sizes and positions the column headers, columns, and items based on the
* size of the DataGrid.
*/
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
if( this.collection.length > 0 ) // so don't divide by zero
{
var appHeight:Number = Application.application.height;
var appMinusMenuHeight:Number = appHeight - Application.application.hboxPrintBar.height;
var boxHeight:Number = Application.application.vboxViewAll.height;
if( boxHeight > 0 )
{
var gridHeight:Number = (appMinusMenuHeight - this.headerHeight) * 0.93;
var calcHeight:Number = gridHeight / this.collection.length;
var calcHeightFloor:Number = Math.floor( calcHeight );
setRowHeight( calcHeightFloor );
//var p:Panel = this.parent as Panel;
//p.title = calcHeightFloor.toString();
if( calcHeightFloor <= 10 )
this.setStyle("fontSize",calcHeightFloor);
}
}
super.updateDisplayList(unscaledWidth,unscaledHeight);
}
]]>
</mx:Script>
</mx:DataGrid>