4

Given stage coordinates (x,y), I want to make my flash app behave just as if the user clicked at position (x,y). That is, something like

function simulateClick(x:Number, y:Number):void{
    var e:MouseEvent = new MouseEvent(MouseEvent.CLICK, true, false, x, y)
    stage.dispatchEvent(e);
}

I've found a bunch of pages talking about this kind of thing, and they all give solutions similar to the above. However, this isn't equivalent to the user clicking at (x,y). There are two problems:

The first is that e.stageX and e.stageY are both 0. I can't set them directly. The documentation says they are calculated when e.localX and e.localY are set, but this isn't happening when I set e.localX before dispatchEvent, nor in the event listener.

I could rewrite all of my event listeners with something like this:

var p:Point = e.target.localToGlobal(new Point(e.localX, e.localY));

Is this the only option?

The second problem is that my event listeners are registered with children of stage, not stage itself. So I need to find out what target to call dispatchEvent on. Clearly Flash is capable of determining what the target should be, ie which object owns the topmost pixel visible at position (x,y), because it does so when the user actually clicks. Is there an easy way to get at this information, or should I just write my own recursive function to do the same thing? I'm using DisplayObjectContainer.getObjectsUnderPoint at the moment, but it's not quite right.

I'm writing in FlashDevelop, if that makes any difference.

stewbasic
  • 831
  • 7
  • 21
  • getObjectsUnderPoint() only returns the first InteractiveObject, but then just the shapes. This is either an omission in documentation, or a bug. – FilippoG Oct 30 '15 at 18:57

3 Answers3

1

e.stageX/Y is populated correctly for me... also getObjectsUnderPoint() seems to work fine. I'm assuming that the x/y values passed to simulateClick are global coordinates?

edit: as pointed out in the comments, the mouse event must be dispatched on InteractiveObject instances... modified the code accordingly.

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.InteractiveObject;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.geom.Point;

public function simulateClick(x:Number, y:Number):void
{
    var objects:Array = stage.getObjectsUnderPoint(new Point(x, y));

    var target:DisplayObject;
    while(target = objects.pop())
    {
        if(target is InteractiveObject)
        {
            break;
        }
    }

    if(target !== null)
    {
        var local:Point = target.globalToLocal(new Point(x, y));

        var e:MouseEvent = new MouseEvent(MouseEvent.CLICK, true, false, local.x, local.y);
        target.dispatchEvent(e);
    }
}

public function addedToStage():void
{
    var parent:Sprite = new Sprite();
    stage.addChild(parent);

    var child:Sprite = new Sprite();
    child.name = 'child 1';
    child.graphics.beginFill(0xff0000, 1);
    child.graphics.drawRect(0, 0, 200, 200);
    child.graphics.endFill();

    var child2:Sprite = new Sprite();
    child2.name = 'child 2';
    child2.graphics.beginFill(0xff00ff, 1);
    child2.graphics.drawRect(0, 0, 100, 100);
    child2.graphics.endFill();
    child2.x = 150;
    child2.y = 150;

    var bmpData:BitmapData = new BitmapData(80, 80, false, 0x00ff00);
    var bmp:Bitmap = new Bitmap(bmpData);
    bmp.name = 'bitmap';

    child2.addChild(bmp);

    parent.addChild(child);
    parent.addChild(child2);

    child2.addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void
    {
        trace('target: ' + e.target.name);
        trace('localX: ' + e.localX);
        trace('localY: ' + e.localY);
        trace('stageX: ' + e.stageX);
        trace('stageY: ' + e.stageY);
    });

    simulateClick(190, 190);
}

Output:

target: child 2
localX: 40
localY: 40
stageX: 190
stageY: 190
ndm
  • 59,784
  • 9
  • 71
  • 110
  • 1
    Thanks for the response. After investigating further it seems that my problem with e.stageX only occurs when the target is a Bitmap rather than a Sprite (maybe because Bitmap is not a subclass of InteractiveObject?). I will try following your example but climbing up in search of an InteractiveObject if the target is not one. – stewbasic Oct 02 '12 at 01:03
  • Yep, that seems to be the problem, Non-InteractiveObjects aren't clickable. Popping through the list, searching for an InteractiveObject instance seems to do the trick. I've updated the code example. – ndm Oct 02 '12 at 01:27
0

For question 1: After you create the MouseEvent (assigning it a local x,y) you should be able to directly reference e.stageX and set it to what you want prior to dispatching the event. It's just a property of the MouseEvent instance. For #2, currentTarget is always the thing that is topmost under the mouse, while target is the thing that is dispatching the event -- assuming the event is genuinely being dispatched by mouse interaction. In your case, you can set the target to be whatever object you have dispatching the event, and set the currentTarget arbitrarily. The question really is whether this is the most efficient way to deal with what's under the mouse right now; and the answer is, probably not. You'd be a lot better off using a MOUSE_OVER event to keep tabs on what the mouse is over right now, store that as a variable you can use when you want to call this, and don't try to iterate the whole display chain all the time (because Flash natively does that much faster than you can do it in a loop). If you put a mouseOver on the stage, and just check the currentTarget, you'll be getting whatever the topmost item is under the mouse on every frame where it changes.

You should be aware that (to prevent some obvious nasty scripts), certain actions cannot be triggered by mouse events that are generated dynamically by actionscript. These include opening a file reference and going fullscreen.

joshstrike
  • 1,753
  • 11
  • 15
0

I have faced this issue too, gave me a bit of a headache. In my situation I was creating the event, performing a bit of complex computations, but I couldn't retrieve global coordinates even though I had already set local coordinates.

Actually the solution was quite obvious in my case... Global coordinates are populated only AFTER the event is dispatched, otherwise how can the event know how to translate local to global?

This is another pitfall, on top of not checking for the object used to dispatch event being an InteractiveObject.

I post this because someone else may face this issue due to both pitfalls. A quick answer easy to read.

FilippoG
  • 546
  • 3
  • 16