Adobe AIR ContextMenu - right clicking and roll out - actionscript-3

I am in the process of converting a web-based actionscript project to AIR. There appears to be some subtle differences in the way that context menus are handled between the web and air versions of actionscript, and the (rather large) codebase that I'm converting relies on one of those subtleties for handling context menus.
For the purposes of this question, let's take a very simple AIR application:
package
{
import flash.display.NativeMenu;
import flash.display.NativeMenuItem;
import flash.display.Sprite;
import flash.events.MouseEvent;
public class test extends Sprite
{
private var menu:NativeMenu;
private var menuItem:NativeMenuItem;
public function test()
{
menu = new NativeMenu();
menuItem = new NativeMenuItem("Menu Item");
menu.addItem(menuItem);
var s:Sprite = new Sprite();
s.graphics.clear();
s.graphics.beginFill(0x65ffff, 1);
s.graphics.drawRect(0,0, 100, 100);
s.graphics.endFill();
s.x = 10;
s.y = 10;
this.addChild(s);
s.addEventListener(MouseEvent.RIGHT_CLICK, rightClick);
s.addEventListener(MouseEvent.ROLL_OUT, rollOut);
s.addEventListener(MouseEvent.ROLL_OVER, rollOver);
}
private function rightClick(event:MouseEvent):void {
trace("right click");
menu.display(stage, event.stageX, event.stageY);
}
private function rollOut(event:MouseEvent):void {
trace("roll out");
}
private function rollOver(event:MouseEvent):void {
trace("roll over");
}
}
}
Let's say I run this app and right-click four times on the blue sprite. I would expect to receive roll over and roll out events each time before and after the context menu is displayed. Instead I receive this output in the debugger log:
roll over
right click
roll out
right click
right click
roll over
roll out
right click
The first click works as expected. But the next two clicks do not generate either a roll over or a roll out. And even more strangely, the forth click generates a roll over, then a roll out and afterwards a right click.
The reason that this is a problem is because the legacy code I'm converting, which again was written for web-based actionscript, depends on this roll over and roll out behavior to change the context menu items that will be displayed. It works in the web-based version because these events are triggered in the same order every time the user right-clicks: roll over, right click, roll out. But as we see here, this is not the case with AIR.
I realize that one approach to fixing this would be to change the way the app works, so that it assigns different context menus to different sprites as needed, but I'm looking for a way to maintain the same approach as the legacy system to minimize the number of changes I need to make.
So my question is: Is there a way to have AIR consistently generate roll over and roll out events in the same order, before and after displaying the menu, every time the user right-clicks on the sprite?

Related

How drag a Sprite smoothly on-screen in actionscript

First of all, my question is basically the same as this one:
How to drag an image to move it smoothly on screen, with ActionScript?
I want my dragged Sprites to keep up with the mouse on-screen, smoothly, without lagging behind.
And I notice that in the old ActionScript 3 Cookbook from way-back-when that they used a similar solution for their DraggableSprite as was used in the above link. Namely, use the stage instance to listen for the MouseMove event and then read from the event.stageX and stageY properties.
I've done that.
But my Sprite still doesn't stay locked with the mouse cursor. It lags behind. I feel like I must be missing something. However, if the solution posted above (ie listen for stage's MouseMove and use event.stageX/Y) is still current and the problem I'm describing should not be occurring, please also let me know. Even though it's not supposed to work, I've tried event.updateAfterEvent() and it also doesn't seem to have any positive effect.
Any help or advice would be greatly appreciated.
Here's a simple example of how I've written the handlers. It should work as-is if pasted into a new project.
I should also add that I'm compiling this as a desktop application using Adobe AIR. Would the run time be a factor???
package {
import flash.display.Sprite;
import flash.events.MouseEvent;
[SWF(width="1280", height="720", frameRate="30")]
public class test_drag extends Sprite {
private var testDragSprite:TestDragSprite;
public function test_drag() {
super();
graphics.clear();
graphics.beginFill(0x0000FF);
graphics.drawRect(0, 0, 1280, 720);
graphics.endFill();
testDragSprite = new TestDragSprite();
addChild(testDragSprite);
testDragSprite.addEventListener(MouseEvent.MOUSE_DOWN, testDragSprite_mouseHandler);
testDragSprite.addEventListener(MouseEvent.MOUSE_UP, testDragSprite_mouseHandler);
}
private function testDragSprite_mouseHandler(e:MouseEvent):void {
switch (e.type) {
case MouseEvent.MOUSE_DOWN: {
stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
break;
}
case MouseEvent.MOUSE_UP: {
stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
break;
}
}
}
private function mouseMoveHandler(e:MouseEvent):void {
//-20 to keep the sprite centered on the mouse
testDragSprite.x = e.stageX - 20;
testDragSprite.y = e.stageY - 20;
//e.updateAfterEvent(); //strange effect, but doesn't solve the problem.
}
}
}
import flash.display.Sprite;
internal class TestDragSprite extends Sprite {
public function TestDragSprite() {
super();
graphics.lineStyle(1, 0xDDDDDD);
graphics.beginFill(0xFF0000);
graphics.drawRoundRect(0, 0, 40, 40, 12);
graphics.endFill();
}
}
There is always going to be a little lag, but:
The first two suggestions will make the most noticeable change to your code/performance.
Enable hardware graphics acceleration; edit your AIR application
descriptor file (xml) and set renderMode to Direct (or GPU). Consult
the Adobe Air help for details.
<!-- The render mode for the app (either auto, cpu, gpu, or direct). Optional. Default auto -->
<renderMode>direct</renderMode>
Use startDrag and endDrag to bypass your manual assignments in your mouseMoveHandler.
Replace :
testDragSprite.addEventListener(MouseEvent.MOUSE_DOWN, testDragSprite_mouseHandler);
testDragSprite.addEventListener(MouseEvent.MOUSE_UP, testDragSprite_mouseHandler);
With :
testDragSprite.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);
testDragSprite.addEventListener(MouseEvent.MOUSE_UP, mouseUp);
Add the new handlers:
private function mouseDown(e:MouseEvent):void {
testDragSprite.startDrag();
}
private function mouseUp(e:MouseEvent):void {
testDragSprite.stopDrag();
}
Increase your frame rate, but watch your CPU usage as your application/game becomes more complicated as you may need to lower it to allow your game logic enough time to complete between frames (otherwise you end up with what is called long frames, you can use the free Adobe Scout profiling tool to watch for these and lots of other things).
Add a frame rate HUD to your display so you can monitor when your actual framerate is lower than the requested framerate (this is for debugging)
If your game is 2d based, consider using the open-source Starling framework as all content is rendered directly by the GPU vs Flash's standard display list objects.
Just try the member function startDrag.
The Feathers UI library includes a drag-and-drop module. The answers above all use the 'old' display list which is not mobile-ready. If you want your code to work cross-platform you need a GPU framework (most popular is Starling on which Feathers is based, as well as most Adobe AIR games and Apps).

Swipe to new stage then swipe back to previous stage

I'm trying to create a cake baking application, with a recipe on a separate stage to the main kitchen. i want the user to be able to swipe from the kitchen to the recipe at any point during the app, swiping to the recipe is easy enough as its never going to change so its just:
stage.addEventListener(TransformGestureEvent.GESTURE_SWIPE, onSwipe2);
function onSwipe2(e: TransformGestureEvent): void {
gotoAndStop(1, 'Recipe');
}
But when trying to go back to the main stage which changes throughout the the program im finding a little trickier to figure out. can anyone help i've tried :
Multitouch.inputMode = MultitouchInputMode.GESTURE;
stage.addEventListener (TransformGestureEvent.GESTURE_SWIPE, fl_SwipeHandler);
function fl_SwipeHandler(event:TransformGestureEvent):void
{
prevScene();
}
But nothing seems to happen

Duplication in AS3

So, I've been wondering about this for a while, because if it doesn't work for this new game I'm going to manually have to create hundreds of different movieclips. So, here is what I want to know:
Say if I was developing a game about "Ice Cream" Where you have to create different scoops for your customers.To get an ice cream cone you click the cone and one generates, you can drag it. How do I do this? What I would usually do is create a hundred ice cream cones, make them all invisible, and when it's clicked make the first one visible and if it's clicked again, see if the first one is visible and make the second one visible and so forth. I obviously know they are is an easier way.. but I don't know what that is.
I heard about Duplicating Movie Clips but I read somewhere that it was removed in AS3.
It's possible and quite easy in fact.
1) Click an object and get the class name.
2) Create an new instance of that class and put it in the display list.
3) Move that new instance on every frame to the mouse coordinates until you detect the MouseEvent.MOUSE_UP event.
You will have to tweak the code to fit your project, but this it how it goes. I haven't tested it, but it should work.
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;
var duplicate;
var className;
original.addEventListener(MouseEvent.MOUSE_DOWN,duplicateMe);
public function duplicateMe(event):void {
className:Class = Class(getDefinitionByName(getQualifiedClassName(event.target)));
duplicate = new className;
addChild(duplicate);
duplicate.addEventListener(MouseEvent.MOUSE_UP,endDrag);
duplicate.addEventListener(MouseEvent.ENTER_FRAME,update);
}
public function update(event):void {
event.target.x = mouseX;
event.target.y = mouseY;
}
public function endDrag(event):void {
event.target.removeEventListener(MouseEvent.ENTER_FRAME,update);
}
The movieclip type is like the blue print of a movieclip. One can create as many of those movieclips as one needs. For example the movieclip type cat can have an instance with the name Simba. So:
var Simba:Cat = new Cat();
In flash you can simply select the right export to actionscript option to export the type.

as3 Air - AddChild works but is slooooooooooow

I am creating an app where when a button is pressed a very large picture is added to the stage (it is larger than the screen but can be dragged around by the user)
When the button is pressed the picture (well, movieClip) does come up and it able to be dragged fine, buttons inside it work.
The problem though is that there is a pause of about 6 seconds between the button press and the image appearing. I am using one .fla file for publishing and compiling (let's just call it Main.fla for now), and another one to hold all the graphics. The graphics are then added with this embed command:
[Embed (source = "assets/graphic.swf", symbol = "Content")]
private var Content:Class;
private var _content:*;
I have these lines where all the variables are declared (in between the class definition and the constructor function) I was under the impression that embedding it like this was equivalent to loading it at compile time. Is this true? What could be causing this lag when the button is pressed?
If I can't cut out the lag, another idea I had was to make some spinning circle or something to tell the user, "hey, don't worry. It's loading!"
If the slowness is at addChild you can add the asset to the stage much earlier and set it's visiblility to false, then when the button is clicked set it back to true. Obviously this is a small hack but might be sufficient for what you are doing.
var asset:MovieClip;
private function init():void
{
asset = new Content();
assset.visible = false;
addChild(asset);
button.addEventListener(MouseEvent.CLICK, onMouseClick);
}
private function onMouseClick(e:MouseEvent):void
{
asset.visible = true;
}
Embedding your SWF is probably not what is causing the delay.. or rather it would not likely be better if you imported the SWF into your FLA. Is this on a device? Chances are you would either have to devise a different way of loading your asset, or be satisfied with a loading animation.
If the main K size is coming from a large image, you could consider loading it in segments, starting with the part that is initially visible.

ActionScript / AIR - One Button Limit (Exclusive Touch) For Mobile Devices?

Two years ago, when I was developing an application for the iPhone, I used the following built-in system method on all of my buttons:
[button setExclusiveTouch:YES];
Essentially, if you had many buttons on screen, this method insured that the application wouldn't be permitted do crazy things when several button events firing at the same time as any new button press would cancel all others.
problematic: ButtonA and ButtonB are available. Each button has a mouse up event which fire a specific animated (tweened) reorganization/layout of the UI. If both button's events are fired at the same time, their events will likely conflict, causing a strange new layout, perhaps a runtime error.
solution: Application buttons cancel any current pending mouse up events when said button enters mouse down.
private function mouseDownEventHandler(evt:MouseEvent):void
{
//if other buttons are currently in a mouse down state ready to fire
//a mouse up event, cancel them all here.
}
It's simple to manually handle this if there are only a few buttons on stage, but managing buttons becomes more and more complicated / bug-prone if there are several / many buttons available.
Is there a convenience method available in AIR specifically for this functionality?
I'm not aware of such thing.
I guess your best bet would be creating your own Button class where you handle mouse down, set a static flag and prevent reaction if that flag has been already set up by other instance of the same class.
In pseudo-code:
class MyButton
{
private static var pressed : Boolean = false;
function mouseDown(evt : MouseEvent)
{
if(!pressed)
{
pressed = true;
// Do your thing
}
}
}
Just remember to set pressed to false on mouse up and you should be good to go.
HTH,
J