draw textfield caret and text selection onto bitmap - actionscript-3

I'm drawing a textfield onto a bitmap which I use as texture for a 3D object.
I'm listening for Event.change, and so whenever the user adds a character I redraw the texture. But to really give the 3D object a 'interactive textfield feeling', I want to draw text selections and draw the caret (the blinking text cursor), but by default these a not drawn when using bitmapData.draw(textField), nor can I find a Event to listen for 'textSelected'.
Any ideas?
//is there any event that catches text selection / blinking of text-cursor?
textField.addEventListener(Event.CHANGE, redrawTexture);
//...
//is there any way to draw text selection / text-cursor in the bitmap?
bmpData.draw(textField);

textField.addEventListener(FocusEvent.FOCUS_IN, redrawTexture, false, 0, true);
textField.addEventListener(FocusEvent.FOCUS_OUT, redrawTexture, false, 0, true);
var bmp:BitmapData = new BitmapData(width, height);
bmp.draw(textField);
var snapshot:Bitmap = new Bitmap(bmp);

Related

AS3 - How to use pixel/point detection with mouse event instead of object detection

This seems like it should be so easy that I'm embarrassed to ask, but I just can't get it.
I have a large round MovieClip (being used as a button). This MovieClip contains a PNG with a transparent background inserted into the MovieClip.
Due to its size there are large empty registration areas on the 4 corners (the bounding box).
How can I have the mouse register as being over only the circle pixels and not the blank space (of Alpha channel pixels) in the square boundary box?
Simple sample code:
public function simpleSample () : void
{
mc1.buttonMode = true;
mc1.addEventListener(MouseEvent.CLICK, doStuff);
}
public function doStuff (event:MouseEvent) : void
{
mc2.gotoAndStop(2);
}
Here are 3 different ways to accomplish this.
EDIT Since you've later explained that your button is an image, this first option won't work for you
If the shape flag on hitTestPoint works with your button (eg it's a shape), you can use hitTestPoint inside your mouse click handler to figure out if the click is actually over the object:
public function doStuff(event:MouseEvent){
//only continue if hit test point is true,
//the x and y values are global (not relative to the mc your testing as one might suppose)
//the third parameter should be true, so it takes into account the shape of object and not just it's bounds
if(mc1.hitTestPoint(stage.mouseX, stage.mouseY, true)){
mc2.gotoAndStop(2);
}
}
If the above doesn't work because you have bimtap data in your button, then an easy way to accomplish this is to just add a shape mask to the button.
So, either inside your button using FlasPro, mask everything with a circle shape, or, do it via code by doing the following when you first show the button:
var s:Shape = new Shape();
s.graphics.beginFill(0);
s.graphics.drawCircle(mc1.x + (mc1.width * .5), mc1.y + (mc1.height * .5), mc1.width / 2);
addChild(s);
mc1.mask = s;
If using an image as the button, or you want to set a threshold of how transparent to consider a click, then you can check the transparency of the pixel under the mouse:
function doStuff(event:MouseEvent){
//only continue if pixel under the mosue is NOT transparent
//first, you need a bitmap to work with
//if you know for sure the position of your bitmap, you can do something like this:
var bm:Bitmap = mc1.getChildAt(0) as Bitmap;
//annoyingly though, FlashPro makes timeline bitmaps shapes,
//so the above won't work UNLESS you take your bitmap in the FlashPro Library
//and export it for actionscript, giving it a class name, then it will be an actual bitmap on the timeline.
//As an alternative, you could (very CPU expensively) draw the whole button as a bitmap
var bmd:BitmapData = new BitmapData(mc1.width,mc1.height,true,0x00000000);
bmd.draw(mc1);
var bm:Bitmap = new Bitmap(bmd);
//we get the 32bit pixel under the mouse point
var pixel:uint = bm.bitmapData.getPixel32(bm.x + event.localX,bm.y + event.localY);
//then we grab just the Alpha part of that pixel ( >> 24 & 0xFF ).
//if the value is 0, it's totally transparent, if it's 255, it's totally opaque.
//for this example, let's say anything greater than 0 is considered good to be a click
if((pixel >> 24 & 0xFF) > 0){
mc2.gotoAndStop(2);
}
}

movieclip sending it to another frame

iv made a colouring game and i want to make it so you can save the drawing (a Movieclip) and send it to another frame or scene, iv been looking into how it may be possible and it looks like it can be done with bitmapdata but i don't know how to even go about it.
You'll need to name your save button, and add a click listener to it.
// create the BitmapData that you'll be drawing to, give it the same dimensions
var savedImage:BitmapData = new BitmapData(holder.width, holder.height, false, 0xFFFFFF);
// add a click listener to your save button
saveBtn.addEventListener(MouseEvent.CLICK, onSave);
function onSave(event:MouseEvent):void
{
// need to apply a transformation matrix because holder's origin is centered
var matrix:Matrix = new Matrix();
matrix.translate(holder.width * .5, holder.height * .5);
// clear out any existing image on savedImage
savedImage.fillRect(savedImage.rect, 0xFFFFFF);
// draw the holder onto the savedImage bitmapdata, using the transform matrix
savedImage.draw(holder, matrix, null, null, null, true);
}
From there on out, you can pass your savedImage BitmapData to a Bitmap instance:
var savedBitmap:Bitmap = new Bitmap(savedImage);
As long as savedBitmap is in the correct scope, you can add it to the display list on another frame:
addChild(savedBitmap);

Actionscript event listener on rectangle containing larger TextField

I have a rectangle on which I would like to display a label. I have tried to do this by creating a rectangle sprite, and then adding a textField to the display tree of the sprite.
The problem is that there seems to be a lot of extra blank padding surrounding the textField. Although the text fits within the box, the boundaries of the textField extend beyond the visible region of it's containing rectangle. This causes the rectangle's width and height to change also.
The issue is that I want the user to be able to drag the rectangle around the screen. I added an event listener on MOUSE_DOWN to initiate the drag. However, the user can start the drag by clicking on the area surrounding the visible rectangle, rather than only on the rectangle itself. I assume this is because the user will actually be clicking on the extra blank space coming from the the TextField and seeping out over the edges
Any ideas?
I think what you're looking for is the textField.autoSize parameter. It makes the text field bounds shrink to the size of the text (otherwise it has a default height/width regardless of the text it contains)
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
var textField:TextField = new TextField();
textField.autoSize = TextFieldAutoSize.LEFT;
textField.text = "your text"; //set this AFTER autoSize
You can also set the width of the your textField to the width of your rectangle. Or forgo the autosize property and manually set the height/width of your text box to the height/width of the rectangle, though this would truncate any text that doesn't fit.
There is always some padding on the text field. Getting the exact bounds of the actual text can be tricky (though it is possible). An easier way is to just mask your text box.
If your rectangle is drawn with the graphics class, you could do this:
var rectangle:Sprite = new Sprite();
rectangle.graphics.beginFill(0xFF0000);
rectangle.graphics.drawRect(0,0,100,100);
addChild(rectangle);
var myMask:Shape = new Shape();
myMask.graphics.copyFrom(rectangle.graphics);
rectangle.addChild(myMask);
var textField:TextField = new TextField();
textField.width = rectangle.width;
textField.height = rectangle.height;
textField.mask = myMask;
rectangle.addChild(textField);
One other option you could do, is in your event listener, check to see if the target is the text box and exit the function. (just make sure your text field's mouseEnabled property is true (default) if you use this method)
function rectangleClickHandler(e:Event):void {
if(e.target == myTextField) return;
//rest of your code
}
One other thing you could do, and may be simpler than masks etc is just make the text field not receive mouse events. This will stop any mouse events being fired from interaction with the text field, but you'll still be able to process them on your rectangle.
The important item here is the mouseEnabled flag. You leave the mouseChildren enabled on your container, but disable the mouseEnabled of both the container and the textfield.
var tf:TextField = new TextField();
tf.autoSize = TextFieldAutoSize.LEFT;
tf.text = "Testing the text mouse enabled";
var rectangle:Sprite = new Sprite();
rectangle.graphics.beginFill(0xFF0000);
rectangle.graphics.drawRect(0,0,100,100);
var container:Sprite = new Sprite();
container.addChild( rectangle );
container.addChild( tf );
addChild( container );
// IMPORTANT FLAGS HERE
tf.mouseEnabled = false;
container.mouseEnabled = false;
container.mouseChildren = true;
container.addEventListener( MouseEvent.ROLL_OVER, rollOverHandler, false, 0, true );
The event handler will only fire when other children of the container are rolled over, not the text field. Using this method you can selectively active mouse enabled components in a container object.

Can I manipulate when DisplayObjects will render in AS3?

Without using BitmapData.draw(); is it possible to stop the display list from rendering, and perform a render when I see fit (whether that is of the entire display list or individual DisplayObjects).
If not, what is the best method to achieve this (inclusive of the option where I use BitmapData.draw() to render DisplayObjects that haven't been added to the stage)?
You can force a render after a mouse/keyboard event using e.updateAfterEvent(), but you cannot prevent the display list from redrawing to screen. Your best bet would be :
Add all your objects into one holder Sprite, which is off the stage display list.
Add a Bitmap and associated BitmapData to the stage.
Everytime you want to 'force' a render simply draw() the holder onto the BitmapData and it will be rasterized by the exact same renderer that typically operates. Just that you get more control over the rasterizing settings.
Try the following, or see this for more info:
function snapClip( clip:DisplayObject ):BitmapData
{
var bounds:Rectangle = clip.getBounds( clip );
var bitmap:BitmapData = new BitmapData( int( bounds.width + 0.5 ), int( bounds.height + 0.5 ), true, 0 );
bitmap.draw( clip, new Matrix(1,0,0,1,-bounds.x,-bounds.y) );
return bitmap;
}

how to retrieve a part of a bitmapdata which is in a particular color

I have a bitmapdata which contains two colors in it say black and white. Now the black area is transparent and the white area is visible. Now the image should be clickable only on the white areas and not on the black transparent area. How can we do this?
PS: The white areas are not in a regular locations I mean they are in random locations.
//add listener for mouse clicks
stage.addEventListener(MouseEvent.CLICK, eventHandler);
function eventHandler(event:MouseEvent):void
{
//1x1 bitmapData to store snapshot
var bmd:BitmapData = new BitmapData(1, 1);
//matrix object to 'move' stage so that pixel under mouse is effectively at 0,0
var matrix:Matrix = new Matrix();
//'move' stage according to mouse x,y values
matrix.translate(-event.stageX, -event.stageY);
//take snapshot of stage
bmd.draw(stage, matrix);
//get colour from snapshot data
var pixel:uint = bmd.getPixel(0, 0);
//trace result
trace("colour = "+pixel.toString(16));
}
Taken from http://blog.leeburrows.com/2011/06/get-pixel-colour-below-mouse-pointer/
Edit because I have time:
So in your case, instead of:
trace("colour = "+pixel.toString(16));
You would use:
if(pixel.toString(16) == "ffffff") // if clicked pixel is white
{
//do something
}
Sam's answer is great, but since you say you know that the pixel in question is part of a bitmapdata you could skip taking a snapshot of the stage and just check the bitmapdata directly instead. I think it could be as simple as:
stage.addEventListener(MouseEvent.CLICK, eventHandler);
function eventHandler(event:MouseEvent):void
{
if (myBitmapData.getPixel(event.stageX, event.stageY) == 0xffffff)
{
// do something
}
}
Also if you want to take alpha into consideration you will want to use getPixel32() instead of getPixel().