Emulating Mouse Event AS3 - actionscript-3

I have a project that involves polling a hardware switch each update and converting its states to a custom events:
ToggleSwitchEvent.BackWardButtonDown
ToggleSwitchEvent.BackWardButtonUp
ToggleSwitchEvent.Click
etc ....
The hardware switch acts similar to a two button mouse. So I would like to be able to "inject" the custom ToggleSwitchEvent events into the Display List and have it act just as a MouseEvent does, bubbling up from the InteractionObject that the click intersects with. This would allow me to addEventListener to the display list just as I do with MouseEvents.
The trouble, as far as I understand it, is that I would need a reference to the leaf nodes of the DisplayList to insert my ToggleSwitchEvent manually. I would like to say that the switch was clicked at a certain position on screen, and have the DisplayList figure out what InteractionObject the click intersects and dispatch my ToggleSwitchEvent instead of a MouseEvent.
Is there a method to emulate the MouseEvent in AS3 but using a custom event?

i do not quite understand ... do you want a custom event to bubble, or to fire MouseEvents on your own?
any event will bubble as expected, if its bubbles property is set to true ...
if you dispatch mouse events manually, they will also bubble, and even their stageX and stageY is calculated appropriately ...
so good luck then ... ;)
edit:
ok, well the display list will not really take care of this for you ... actually, you have two options ... start at the stage, and make your way through it, always keeping under the mouse ... or, start at the object under the mouse, get its path in the tree, and find the right InteractiveObject in its parent chain ... the latter can be done using DisplayObjectContainer::getObjectsUnderPoint ... here's the code ...
public static function dispatchEventUnderMouse(event:Event, stage:Stage, point:Point):Boolean {
//return if stage is disabled
if (!stage.mouseEnabled) return false;
//find the topmost object
var cur:DisplayObject = stage.getObjectsUnderPoint(point).pop();
if (cur == null) cur = stage;
//build the path
var path:Array = [];
while (cur != null) {
path.push(cur);
cur = cur.parent;
}
//traverse the path from the root to find the right InteractiveObject
while (path.length > 0) {
cur = path.pop();
if (cur is InteractiveObject) {
if (!(cur as InteractiveObject).mouseEnabled) {
cur = cur.parent;
break;
}
if (cur is DisplayObjectContainer) {
if (!(cur as DisplayObjectContainer).mouseChildren) break;
}
}
else {
cur = cur.parent;
break;
}
}
return cur.dispatchEvent(event);
}
you might actually want to inject the right coordinates into the event, but i guess you'll figure that out ... ;)

Related

Select "youngest" child (target only) under the mouse in AS3

tldr:
how does one tell a mouse event to only fire for the youngest child under the mouse, and not its parent?
I'm just making a solitaire game to try to better learn OOP. I have a Card class and a CardHome class. Each Card contains a CardHome which can hold a card, and so on. Thus, moving a card will move all the cards contained in it. The problem is that my mouse event is detecting the parent of all the cards. So what I think I need is a way to say "run this mouse event only for the youngest child of the target". Any way to do that?
private function pressCard(me:MouseEvent):void{
var c:Object = me.target as Card;
// place card in center of container x wise
c.x = 0;
// and down 5
c.y = 5;
// bring drag container up to front z order
addChild(c as Card);
// begin dragging drag container
c.startDrag(true);
// hide mouse
Mouse.hide();
// add listener to this card for mouse up
c.addEventListener(MouseEvent.MOUSE_UP,dropCard);
}
private function dropCard(me:MouseEvent):void{
var c:Object = me.target as Card;
// release drag container
c.stopDrag();
// remove mouse up listener from card
c.removeEventListener(MouseEvent.MOUSE_UP,dropCard);
Mouse.show();
// check for colision with a card that has a matching holder
for (var i:int = 0; i < deckArray.length; i++){
if (c.hitTestObject(deckArray[i]) && c != deckArray[i]){
trace("hit",deckArray[i]._containerOwned._occupied,deckArray[i]._flippable);
if (deckArray[i]._number == c._number + 1 && deckArray[i]._flippable == false && deckArray[i]._suit != c._suit && deckArray[i]._containerOwned._occupied == false){
c._containedIn.parent._containerOwned._occupied = false;
makeFlippable(c._containedIn.parent);
deckArray[i]._containerOwned.addChild(c as Card);
c._containedIn = c.parent;
c.x = 0;
c.y = 0;
break;
}
}
}
// add selected card back to home spot
c._containedIn.addChild(c);
c.x = 0;
c.y = 0;
}
private function mOver(me:MouseEvent):void{
trace(me.target._number);
trace(getHighestZ());
}
private function getHighestZ():Card{
var highestZ:int = 0;
var c:Card;
for (var i:int = 0; i < deckArray.length; i++){
if (deckArray[i].hitTestPoint(stage.mouseX,stage.mouseY) && deckArray[i].getChildIndex() > highestZ){
c = deckArray[i];
highestZ = deckArray[i].getChildIndex();
}
}
return c;
}
Each card is a child of a container in the card above it. I want to be able to select a card and drag it (and its children). The problem is that when I click card B, it selects card E. So what I need is to select the youngest child in the mouse event, and NOT its parent. Any way to control that?
I know I can do mouseChildren true or false. That's fine. It has to be set to true for all of the cards so that I can select them independent of their parent card. Do I need to do something with bubbling my event? That's something I've never understood.
Note: I realize there are problems with mouse mouse over function. Fixing that will be trivial and is kind of a placeholder for now.
tldr:
use a combination of useCapture set to true and the stopPropagation method. This allows a user to click an element and have the eventHandler only called for the target, not any of the elements between the target and the stage!
So the key here is to access the as3 event flow. When a mouse event gets triggered it propagates from the stage up through all objects that can receive that type of mouse event. In this case, the cards parents. Then it bubbles back down to the stage. So what I needed was to set the capture setting to true which switches the phase in which the event handler is used to the bubbling phase (this causes the first element to actually call the function to be the actual target) and then, instead of letting the event propagate back down to the stage, call the stopPropagation method. Looks something like this:
c.addEventListener(MouseEvent.MOUSE_OVER, mOver, true);
The true here says to fire the function on each element on the way back to the stage from the target. Then during the mOver function I do:
stopPropagation();
trace(c._Number);
// and any other code to do on this target
Which keeps the event from flowing back down through the other cards under the mouse.
Now instead of an output like
5
3
9
when the mouse is over the 9 card (which is over a 3 which is over 5) I now get simply
9
and then when I mouse over the 3 I get
3
So now the mouse is calling a function for the target only. Surprisingly non intuitive but allows for some serious flexibility and control!
from Adobe on Event Flow

AS3: how do i stop two of the same function from playing at once?

i am using AS3 to create a function that will automatically play a movieclip all the way through and then remove it. my project is going to have a lot of animated cutscenes, so id like to be able to call this function, use the cutscene id like as a parameter, and then move on to the next. the problem is, im trying to use the function multiple times in a row to play clips sequentially, but they're all playing at the same time. is there a fix, or a better way to do this altogether?
playClip(new a_walk); //find a way to make these stop playing at the same time
playClip(new a_door);
//a_walk and a_door are the AS linkage class names for the movieclips im referring to
function playClip (clip:MovieClip):void {
addChildAt(clip, 0);
clip.mask = myMask;
clip.x=412.4;
clip.y=244.5;
clip.addEventListener(Event.ENTER_FRAME, checkframes);
function checkframes(event:Event) {
if (clip.currentFrame == clip.totalFrames) {
//trace("wow! youre an idiot!");
if (clip.parent) {
clip.parent.removeChild(clip);
trace (100);
return;
}
}
}
}
Sounds like you want a mechanism to play a queue of MovieClips? If so, here is a way you can accomplish this:
//create an array with the clips you want to play (in order), in my example here, the items can be a MovieClip derived Class, or a MovieClip instance
var playQueue:Array = [a_walk, a_door];
//create a var to store the currently playing clip
var currentClip:MovieClip;
playNext(); //call this when you want the queue of clips to start playing
function playNext():void {
//if there was an item previously playing (currentClip has a value), stop it and remove it/dispose of it
if(currentClip){
currentClip.stop(); //stop it from playing
currentClip.addFrameScript(currentClip.totalFrames-1, null); //remove the frame script that was added
currentClip.parent.removeChild(currentClip); //remove it from the display
currentClip = null;
}
//check if there's anything left to play
if(playQueue.length < 1) return;
var nextItem:* = playQueue.shift(); //shift retrieves and removes the first item in the array;
if(nextItem is Class){
//if it's a class, instantiate it
currentClip = new nextItem();
}else{
currentClip = MovieClip(nextItem);
}
//initialize the movie clip
addChildAt(currentClip, 0);
currentClip.gotoAndPlay(1);
//this is just what you were doing before:
currentClip.mask = myMask;
currentClip.x=412.4;
currentClip.y=244.5;
//add a command on the last frame of the movie clip to play the next item in the queue
currentClip.addFrameScript(currentClip.totalFrames-1, playNext);
//addFrameScript is 0 based, so 0 would refer to the first frame. This is why we subtract 1 to get the last frame
}
I should note, that addFrameScript is an undocumented function. It serves as a nice shortcut so you don't have to have an ENTER_FRAME listener checking currentFrame vs. totalFrames. Being undocumented however, one can not count on it's continued existence in future versions of the Flash/AIR runtimes (though it's been around for a long long time)
note
This answer is a work in progress. I'm waiting on a response from the OP.
// playClip(new a_door); don't call this yet, or they will just both play.
var clipData:CustomClass = new CustomClass(); // add an instance of a custom class to hold the value of the movie
//clip being played (so that you can use this value later in the event handler.)
// it will also hold a value of the next clip
clipData._currentClip = a_walk;
clipData._nextClip = a_door;
playClip(new a_walk);
function playClip (clip:MovieClip):void {
addChildAt(clip, 0);
clip.mask = myMask;
clip.x=412.4;
clip.y=244.5;
clip.addEventListener(Event.ENTER_FRAME, checkframes);
}
function checkframes(event:Event) {
if (clipData._currentClip.currentFrame == clipData._currentClip.totalFrames) {
//trace("wow! youre an idiot!");
if (clipData._currentClip.parent) {
playClip(clipData._nextClip);
clipData._currentClip.parent.removeChild(clipData._currentClip);
clipData._currentClip = clipData._nextClip; // moves the clips down
//clipData._nextClip = here we have
//a problem. Do these clips play in a set
//order, first to last? Or do the play out of
//order jumping back and forth? If so, how
//are you planning on controlling which clip
//plays next?
trace (100);
return;
}
}
}
I haven't checked this in Flash yet to see if it works, but I noticed that you are defining a function inside another function, which I don't think is good practice, so this might clean things up for you. Give it a try and let us know.
I'll try to fix my code above when I get a chance. In the meantime, you answered my question about playing the clips in order, so a simple solution would be to put all the clips in an array and then play them by playClip(clipArray[i]) and then when the clip ends and gets removed, do i++ and call the same function playClip(clipArray[i]) which will play the next clip in the array.

Turn image array images to buttons AS3

I'm creating an app with a search function. I display the images by loading from the array the one's which match the search criteria. All the images are loaded from the library. I want to be able to click on an image as though it were a button. Once I click I want to goto frame 3 and change a variable integer to say which image was clicked on so that I can display the information about the photo in frame 3. Can I do this using an event listener say
imagesArray[i].addEventListener(MouseEvent.CLICK, imageClick);
function imageClick(event:MouseEvent):void
{
gotoAndStop(3);
current = i;
}
or similar,
Thanks
Yes, but it won't be as easy. First, Bitmaps do not process events, so you can't assign a listener directly to a Bitmap object. Next, there is no "i" available in such a construction, you have to determine that "i" by yourself. To do that, you parse event.target property, which is the object that's been clicked. You wrap each Bitmap object into a separate Sprite object, assign listeners to these sprites, then you parse event.target to get the relevant object reference out of it, grab the index via indexOf() call, and assign it to global current variable.
for (i=0;i<imageArray.length;i++) {
var sp:Sprite=new Sprite();
sp.addChild(imageArray[i]);
// position "sp" correctly here
addChild(sp);
sp.addEventListener(MouseEvent.CLICK, imageClick);
}
function imageClick(e:Event):void {
var content=e.target.getChildAt(0); // the object that was wrapped
var i:int=imageArray.indexOf(content);
if (i==-1) return; // OW, out of array
current=i;
gotoAndStop(3);
}
I'm not so sure that setting "current" to "i" would work as the function wouldn't be running at the same time as the for loop.
I'm assuming that because you're using the [i] that you've added the event listeners in a for loop?
What you could do is:
for (var i:int = 0; i < (howeverManyTimesToRun); i++)
{
imagesArray[i].addEventListener(MouseEvent.CLICK, imageClick);
imagesArray[i].myIndex = i;
}
And then in your function (outside of the for loop)
function imageClick (e:MouseEvent) {
gotoAndStop(3);
current = e.currentTarget.myIndex;
}
Also in your for loop you might want to add
imagesArray[i].buttonMode = true;
To change the mouse cursor to a hand when their mouse goes over your image.

ActionScript 3, handling mouse event cases

I'm learning ActionScript 3 and at the moment, following a tutorial here http://www.senocular.com/flash/tutorials/as3withmxmlc/ . It describes a simple application which put a ball on the stage and lets the user drag it around. But it has bugs, especially because it doesn't handle the case that the user drags the pointer off the stage. This has gotten me thinking about a better way to handle cases. For starters, I'm considering how to handle MOUSE_UP events. I'd like to write something like this:
public class Test extends Sprite
{
public function Test(stage:Stage)
{
mySprite = SomeSpriteClass()
stage.addEventListener(MouseEvent.MOUSE_UP, handleStageMouseUp);
mySprite.addEventListener(MouseEvent.MOUSE_UP, handleSpriteMouseUp);
}
private function handleStageMouseUp(event:MouseEvent):void {
// how do I determine here if there was also a MOUSE_UP on the Sprite?
// Perhaps I could check the click coordinates of 'event' to see if
// they are superimposed on the Sprite.
}
private function handleSpriteMouseUp(event:MouseEvent):void {
// I don't need this method if I can handle all cases in the
// method above.
}
}
This issue that arises is that with the event model ActionScript3 uses, I don't know how to look for cases that involve combination of events. Alternatively as I wrote in the comment above in handleStageMouseUp() I could check to see if a mouse event occurs over 'mySprite' (how would I do that?)
What I'd really like to do is be able to combine all my case logic in something like this:
private function handleAllCases(...):void {
if (..mouse up on stage but not sprite..) {
.. handle case ..;
} else if ( .. mouse up on both stage and sprite .. ) {
.. handle case .. ;
}
}
Is there a way to do this, or perhaps a better way to think about it?
You check Event.target and Event.currentTarget. target is the object that originally dispatched the event and currentTarget is the latest object to dispatch it (generally the object your listener is attached to).
Quick overview of that. Assume obj2 is wrapped by obj1 which is on the stage. Quick hierarchy:
-Stage
--obj1
---obj2
If you were to trigger an event (that bubbles, that is important as it allows an event to go through its parents until it reaches an event handler. I believe all MouseEvents do this ) in obj2 and had a listener attached to Stage, obj2 would be the target and Stage would be the currentTarget.
So how does this work for you? Well, you just need to check what target and currentTarget are to determine where the event started and where it currently is at.
Plug this into Flash and click on the object and release in various locations and take a look at your console (I did test this):
import flash.display.*;
var mySprite:Sprite = new Sprite();
mySprite.graphics.beginFill(0x000000);
mySprite.graphics.drawRect(0,0,100,100);
mySprite.graphics.endFill();
addChild( mySprite);
stage.addEventListener(MouseEvent.MOUSE_UP, handleStageMouseUp);
function handleStageMouseUp(e:MouseEvent):void {
trace( "handleStageMouseUp - target == stage: " + (e.target == this.stage) );
trace( "handleStageMouseUp - target == mySprite: " + (e.target == mySprite) );
trace( "handleStageMouseUp - currentTarget == stage: " + (e.currentTarget == this.stage) );
trace( "handleStageMouseUp - currentTarget == mySprite: " + (e.currentTarget == mySprite) );
trace( "--------" );
}
For the most part, in your stageUp handler, you can check if e.target == mySprite to determine if it also happened to mySprite.
There is a caveat here, though. If your Sprite has children, one of those children will be the target. However, if you set mySprite.mouseChildren = false, it will not register mouse events on children which means it will behave as it does in the above example.
Hopefully that helps

Bubble click event in swf

I try to add a MovieClip to an existent SWF on the fly - inject an small code who do something like:
this.obj = new MovieClip(); // it is inside an object
obj.name = 'FLOOR';
obj.graphics.beginFill(0xFFFFFF, 0);
obj.graphics.drawRect(0,0,self.width, self.height);
obj.graphics.endFill();
obj.buttonMode = true;
self.addChildAt( floorLayerMC , 0); /* self is an reference for the this keyword, reference for the entire swf */
My question is: this SWF has many elements like images and textfields, and some of this elements has no event handler for click. I Need to find a way to "redirect" all of the events to my "FLOOR" element, using something like bubbling the event.
Of course, I can add the FLOOR in top of any elements BUT I have some elements with click handler. I can't ignore all of the elements. So my problem is:
if I click over an MovieClip with click handler, perform the original action.
if I click over an MovieClip without click handler, perform the FLOOR action.
I can't add a event handler in all of the elements.
Any Idea?
Listen for a click on the container movieclip's own stage (the movieclip that contains the FLOOR). In the handler method for the click event, do a hit test using hitTestPoint with the mouseX and MouseY of the container movieclip, and if the mouse is over any clickable objects, ignore the stage click. Store all the objects that are clickable in an array to do that test.
This code is untested but it would go something like this:
var exemptArray:Array = [ btn_mc1, btn_mc2, btn_mc3 ];
containerMC.stage.addEventListener(MouseEvent.CLICK, onClickMyMC);
function onClickMyMC( event:Event ):void
{
for(var i:int = 0; i < exemptArray.length; i++)
{
if( exemptArray[i].hitTestPoint(containerMC.mouseX, containerMC.mouseY) )
{
// do nothing, ignore the stage click ( and let the object with the click respond )
break;
}
else
{
// respond to the stage click
}
}
}
To build the exemptArray without knowing what objects are clickable ahead of time:
( untested but should be close enough to give you an idea ).
var exemptArray:Array = buildExemptArray();
function buildExemptArray():Array
{
var arr:Array = [];
for(var j:int = 0; j < containerMC.numChildren; j++)
{
if( containerMC.getChildAt(i).hasEventListener(MouseEvent.CLICK) )
{
arr.push( containerMC.getChildAt(i) );
}
}
return arr:
}
EDIT TO ANSWER QUESTION IN COMMENTS:
this.addEventListener(MouseEvent.CLICK, onClick) will add a click event to the whole object, children included.
this.stage.addEventListener(MouseEvent.CLICK, onClick) will add a click only to the movieclip's stage, not its children as well.
In as3 all movieclips have a stage property. If you wrote on the main timeline this.stage.addEventListener(MouseEvent.CLICK, onClick); that would be adding a stage click to the whole swf. But, if you wrote something like myMC.stage.addEventListener(MouseEvent.CLICK, onClick); it would only add a click to that movieclip's stage (myMC's stage). Since stage is below the display list, you can capture a click there in any movieclip. If you don't have access to all the objects that have mouse events ahead of time, you could loop through all the container's children and check if they have a mouseEvent with .hasEventListener(MouseEvent.CLICK);, create your exemptArray from that, then use the same logic above to ignore items in the exemptArray.