I have a horizontal stack of buttons that represent tabs, and each button has a TextField inside of it that is larger than the dimensions of the button so that they overlap. Like this (the dotted line is a TextField and the red box is the area where mousing over does not work due to the overlap):
Each of these buttons is an instance of a TabButton, which has a class definition like this:
package src
{
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.text.TextField;
public class TabButton extends MovieClip
{
// Stage Instances
public var mcHitZone:MovieClip;
public var mcText:TextField;
public function TabButton()
{
super();
addEventListener(Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true);
}
private function onAddedToStage(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
// Disable mouse input on everything except for the hit zone since that's where mouse handling should be done.
mcText.mouseEnabled = false;
mcHitZone.addEventListener(MouseEvent.ROLL_OVER, onMouse, false, 0, true);
}
private function onMouse(e:MouseEvent):void
{
trace("Mouse event: " + name + " " + e.type);
}
}
}
Each TabButton has a mcHitZone that I'm attaching the mouse handler to, which is the black area on the above image. Since the TextField is much larger than the button itself, it needs to be completely ignored for mouse input handling so I'm turning it off with mcText.mouseEnabled = false;, which seems to work when mousing within a single button, but when mousing over the overlap area (the red box) the TextField will block mouse input from the other button.
Is there any way to make this work so that the TextField does not block mouse input on anything that it overlaps with?
I've uploaded the FLA and AS that I've been testing this with here. If you run it, it will trace when you roll over a particular button. If you try to mouse over the red area of the leftmost button then it won't work.
Your button have three movieClips, mcHitZone, mcText, and TabButton itself.
So when you place two buttons close, the up tabButton(not it's Child mcText) will affect the under tabButton.
I advise your tabButton not extends movieClip, just use the mcHitZone as Button's skin, then add it to your stage.
Just as the comment say, you can also make the tabButton's mouseEnabled false. I mixed up mouseEnabled and mouseChildren.
Related
In the game that I am making, you choose a shape, and then on the next screen choose a color. The shape selector works fine and loads one of 6 'shape' movie clips into the next stage of the game. On this stage, I have buttons to control color. Im trying to make the buttons change the color of the movieclip by launching a custom event. This would then be detected by a listener within the class for each movieclip.
So far this is my code:
The screen that contains the color change button:
package {
import flash.display.MovieClip;
import flash.events.MouseEvent;
public class ColorSelector extends MovieClip
{
public function ColorSelector()
{
charcoal.addEventListener (MouseEvent.CLICK, onClickCharcoal );
}
public function onClickCharcoal (mouseEvent:MouseEvent): void
{
dispatchEvent (new ColorEvent (ColorEvent.CHARCOAL) );
trace ("click")
}}
The custom event class:
package
{
import flash.events.Event;
public class ColorEvent extends Event
{
public static const CHARCOAL:String = "charcoal";
public function ColorEvent( type: String )
{
super ( type );
}
}}
The movieclip being acted upon:
package {
import flash.display.MovieClip;
public class Gobbert extends MovieClip {
public function Gobbert()
{
this.addEventListener (ColorEvent.CHARCOAL, makeCharcoal)
}
public function makeCharcoal (colorEvent: ColorEvent) :void
{
this.alpha = .5
}
}
It seems to me like the event is not getting through to the class with the listener. I could really use a fresh pair of eyes to help me figure out whats going on. The program doesn't give me any error, just doesn't do much else either. Thanks in advance!
You are missing the bubbles parameter on the constructor. If omitted it defaults to false. The call to super on the custom event should be:
super(type, bubbles, cancelable);
You will want to pass bubbles in as true via addEventListener function call or hard code inside the custom event constructor.
Also make sure the target (instance of Gobbert) movie clip is on the event bubbling path which means the ColorSelector has to be a child of the display list of Gobbert. If your display list is not set up this way you may want to rethink your approach and have the event propagate from the selector to a common parent and then set the color on Gobbert through that common parent.
This feels like it should be obvious, but I am having trouble handling MouseEnter and MouseOut with a series of movieclips in actionscript.
I have a movieclip that serves as a background. On that background, I am adding an additional movieclip to serve as a button. On that button's MouseEnter, I add an additional movieclip to serve as a hoverstate, and remove the initial button. On MouseOut, I remove the hoverstate button, and readd the original plain button.
90% of the time, it works as you would expect. But the other 10% of the time, on MouseOut, the MouseEnter event triggers and even though your mouse is not on the button anymore, it has the hoverstate on such that you do.
Some code to illustrate, this is my main movieclip that I add first thing:
package {
import flash.display.MovieClip;
public class Menu_Main extends MovieClip {
var backdrop:Backdrop;
public function Menu_Main() {
backdrop = new Backdrop();
addChild(backdrop);
}
}
}
And here is my subsequent movieclip logic, the one that handles my menu button:
package {
import flash.display.MovieClip;
import flash.events.MouseEvent;
public class Backdrop extends MovieClip {
var button:MyMenuButton;
var button_hover:MyMenuButton_Over;
public function InitializeButton()
{
button = new MyMenuButton();
button.addEventListener(MouseEvent.MOUSE_OVER, Button_MouseOver);
addChild(button);
}
function Button_MouseOver(event:MouseEvent):void
{
removeChild(button);
button_hover = new MyMenuButton_Over();
button_hover.addEventListener(MouseEvent.ROLL_OUT, ButtonHover_MouseOut);
addChild(button_hover);
}
function ButtonHover_MouseOut(event:MouseEvent):void
{
removeChild(button_hover);
addChild(button);
}
public function Backdrop() {
InitializeButton();
}
}
}
The code here does not include my attempts to remove EventListeners in opportune places. No matter what combination of adding and removing EventListeners, the result would be the same. I have also tried some combinations of ROLL_OUT and ROLL_OVER instead of the mouse versions. I can't say that I've used them perfectly, but the results were again the same.
Can anyone give some advice as to the proper way to handle this?
Two things: first, I've tried your code, using Flash CS5.5 and FlashDevelop, and see NO problems that you describe. The button performs well for me (on an old P4 machine).
Second, you can accomplish the same performance with a little less code and one fewer MovieClip.
Make a button MC with two frames. Color the button's body differently in fr. 2 than in fr. 1.
Use same Main class. Backdrop class now looks like this:
package
{
import flash.display.MovieClip;
import flash.events.MouseEvent;
public class Backdrop extends MovieClip {
var button:MyMenuButton;
var button_hover:MyMenuButton_Over;
public function Backdrop()
{
InitializeButton();
}
public function InitializeButton()
{
button = new MyMenuButton();
button.stop();
button.addEventListener(MouseEvent.MOUSE_OVER, Button_Over);
addChild(button);
}
function Button_Over(event:MouseEvent):void
{
button.gotoAndStop(2);
button.addEventListener(MouseEvent.MOUSE_OUT, Button_Out);
}
function Button_Out(event:MouseEvent):void
{
button.gotoAndStop(1);
}
}
}
This happens because events in Action Script are not synchronous.
Best practice here is to add a single transparent button, on which you will add all the listeners needed - ROLL_OVER and ROLL_OUT. On each handler - do what you want to do - add or remove children, that doesn't matter, because the actual target which dispatches the events is still there and it's still the same.
Cheers!
We have an effect we like to use where we synchronize a series of slides with a sound. As the sound plays, we show each slide (which is its own frame in a MovieClip), and slowly scale the slide up to provide a little bit of movement. Our sounds tend to be equivalent to one frame on the parent timeline, so we look to see what sound is playing for that frame to calculate how long each slide should be displayed for.
The issue we have is that occasionally we need to "stretch" one of these animations across two or more slides, which means we need to look ahead and calculate the slide length based on the combined length of the sounds during the span of frames where the MovieClip that contains the slide images is displayed.
However, I haven't been able to find a property that tells me how many timeline frames a MovieClip is displayed for (note that this would be different from totalframes, the number of frames that clip contains). Is it just wishful thinking that such a property exists, or can someone point me in the right direction on this?
I'm not sure if I'm understanding the problem correctly but have you tried MovieClip.currentFrame?
Can you create a class like this, which will keep track of the amount of frames it has been present on the DisplayList:
package
{
import flash.display.MovieClip;
import flash.events.Event;
public class ExtMovieClip extends MovieClip
{
// Properties.
private var _lifetime:int = 0;
// Constructor.
public function ExtMovieClip()
{
addEventListener(Event.ADDED_TO_STAGE, _added);
}
// Was added to the DisplayList.
private function _added(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, _added);
addEventListener(Event.ENTER_FRAME, _track);
addEventListener(Event.REMOVED_FROM_STAGE, _removed);
}
// Was removed from the DisplayList.
private function _removed(e:Event):void
{
removeEventListener(Event.REMOVED_FROM_STAGE, _removed);
removeEventListener(Event.ENTER_FRAME, _track);
addEventListener(Event.ADDED_TO_STAGE, _added);
}
// Increment the lifetime of this MovieClip.
public function _track(e:Event):void
{
_lifetime ++;
}
// Retunrns the lifetime of this MovieClip.
public function get lifetime():int
{
return _lifetime;
}
}
}
is it possibile to set the position of the mouse cursor? This is what I would like to do: when the user presses the mouse button over a movieclip, the movieclip starts dragging and on enterframe the cursor is positioned in the middle of the movieclip (don't tell me about lockcenter because I can't use it this way since my movieclip registration point is set at its top left corner). Basically, I would like to be able to force the cursor to reach the center of the movieclip when the user clicks it. Is this possible?
I don't have proof, but I think that you are not allowed to take control of the cursor like that.
An alternative would be to hide the actual mouse cursor, and add a custom cursor instead that you could positioned relative to the real cursor position, or in the center of your drag target if that would be easier. The problem would be that you have no way of knowing the exact appearance of the user's cursor.
In other words you're looking for this functionality: SetCursorPos.
You cannot control the cursor with Flash. You'll have to solve it otherwise - what about setting your movieclip's registration point to the center?!
I don't think that's possible. The mouse coordinates are read only.
However I would suggest any of these instead :
Hide the mouse using Mouse.hide();.
Make your own pointer in the location of the mouse.
Control this pointer as per your wish.
or
When the mouse button is pressed, move the movieclip itself, if
possible.
Expanding on loxxy's response:
Instead of moving the mouse cursor to the center of the object with lockCenter, you can manually move the object to be centered about the mouse cursor when the MouseEvent.MOUSE_DOWN event is fired on the object (just before you call startDrag on the object)
Here's a simple example:
package {
import flash.display.Sprite;
import flash.events.MouseEvent;
public class Main extends Sprite{
public function Main() {
var drag_object:Sprite = new Sprite()
drag_object.graphics.beginFill(0xFF0000, .5);
drag_object.graphics.drawRect(0, 0, 50, 50);
drag_object.graphics.endFill();
drag_object.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
drag_object.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
drag_object.x = 200;
drag_object.y = 300;
addChild(drag_object);
}
private function onMouseDown(e:MouseEvent):void {
var obj:Sprite = e.target as Sprite;
obj.x = e.stageX - (obj.width * .5);
obj.y = e.stageY - (obj.height * .5);
obj.startDrag();
}
private function onMouseUp(e:MouseEvent):void {
var obj:Sprite = e.target as Sprite;
obj.stopDrag();
}
}
}
I've used the GUI to create a rectangle that I turned into a button symbol (SimpleButton). I then edited the button so that it had 4 different button states and a text layer on top. I then created a class definition file for this object so that I could dynamically change the label text (the text layer) when adding instances of this button to the stage.
I was able to create and link a class file (DynamicButton.as) just fine, but when I try to access the text field that I created on the button, I get the error:
"Access of possibly undefined property btnLabel through a reference with static type com.examples:DynamicButton."
when i couldn't get that to work, I decided I'd try adding the TextField directly within the class definition file, using the following code:
package com.examples
{
import flash.display.Sprite;
import flash.display.SimpleButton;
import flash.text.TextField;
public class DynamicButton extends SimpleButton
{
public function DynamicButton(btnText:String="Click Me")
{
placeText();
labelText.text = btnText;
}
//property variables
//create a text box to hold the button label
private var labelText:TextField = new TextField();
//create a displayObject to hold the text
private var labelSprite:Sprite = new Sprite();
private function placeText():void {
labelText.width = this.width;
labelText.height = this.height;
labelText.x = this.x;
labelText.y = this.y;
labelText.visible = true;
labelSprite.addChild(labelText);
this.parent.addChild(labelSprite);
}
}
}
The problem is that I can't seem to add the TextField to the SimpleButton, as it's not a display object. So, I tried adding it to the parent of the simple button (and I figured, I'd just place it exactly above the button). But then I get a "null object reference." error.
So, I have two questions
is there a way to access GUI-created elements from w/i a class definition file?
How would I add the TextField to the button using only AS3 inside of a my class definition file?
OK, it took a few days and some introspection, but it seems that the problem I've having stems from the fact that you cannot add children to a SimpleButton. I changed my class definition to extend a MovieClip, then created a function named "listen()" that I called when constructing the object that added listeners for the "over","down", and "out" mouse states, so as to imitate a simple button. I also had to put a "stop()" command in the constructor, so that each button wouldn't keep cyclying through all the states. The final class definition looks like this:
package com.examples
{
import flash.display.MovieClip;
import flash.events.MouseEvent;
public class DynamicButton extends MovieClip
{
public function DynamicButton(btnText:String="Click Me")
{
stop();
this.btnText_btn.text = btnText;
listen();
}//constructor
private function listen():void {
this.addEventListener(MouseEvent.MOUSE_OVER,function(){
gotoAndStop(2);
}//anon mouseover fcn
);
this.addEventListener(MouseEvent.MOUSE_DOWN,function(){
gotoAndStop(3);
}//anon mousedown fcn
);
this.addEventListener(MouseEvent.MOUSE_OUT,function(){
gotoAndStop(1);
}//anon mouseout fcn
);
}
}//class definition
}//package