Flex titledborderbox drag and drop disable dragging children of titledborderbox - actionscript-3

So I have this titledborderbox in flex 4 with custom skin, I am working on drag and drop application and I have run into some problems.
When I initiate drag and drop on that component I am able to drag it's children but not the titledborderbox itself, for example: I can grab the label, or button which is inside this box and I can start dragging it but that is not a desired functionality. I should be able to click anywhere on the titledborderbox and drag the whole component with it's children to the new position. I have tried the mouseChildren = false but that disables the events on the children which is not good as some of the children are buttons with which I have to be able to interact anyway. Is there any workaround this problem, how could that be solved?
My titledborderbox containers are in HGroup, I would like to be able to drop the titledborderbox at specific position, for example: if I have 4 titledborderbox containers and I start dragging the last one then I would like to be able to place it at the position 1 in the HGroup container but drag manager always places the dropped item as the last item in the group, is there any solution to that?
I have to use this layout and I can't change to other layout, I can't find any decent example of what I am trying to achieve, is that even possible? Any help appreciated, thank you. Here is my code for dragging:
private function handleStartDrag( evt:MouseEvent ):void
{
// grab the item renderer and relevant data
var dragItem:IUIComponent = evt.target as IUIComponent;
var dragSource:DragSource = new DragSource();
dragSource.addData( dragItem, "item" );
DragManager.doDrag( dragItem, dragSource, evt );
this.buttonMode = true;
}
protected function handleDragEnter( evt:DragEvent ):void
{
if( evt.dragSource.hasFormat( "item" ) )
DragManager.acceptDragDrop( evt.target as IUIComponent );
}
protected function handleDragDrop( evt:DragEvent ):void
{
var dragItem:Object = evt.dragSource.dataForFormat( "item" );
var dragItemOwner:Group = ( dragItem.owner as Group );
dragItemOwner.removeElement( dragItem as IVisualElement );
var targetOwner:Group = ( evt.target as Group );
targetOwner.addElement( dragItem as IVisualElement );
}

Try changing
DragManager.acceptDragDrop( evt.target as IUIComponent );
to
DragManager.acceptDragDrop( evt.currentTarget as IUIComponent );
event.target is the reference to the object with "caused" the event (maybe an image or textfield within you box). event.currentTarget is the reference to the object which binds the event listener (your box).
Another option could be to set the mouseChildren-property of your box to false so that they do not fire MouseEvents. But this is only useful if those children should NEVER fire a MouseEvent.

Related

Actionscript 3 - Passing one variable from one object to another in the same project

I'm fairly new at ActionScript 3 but I am working on it and trying to learn by reading and modifying ActionScript source codes.
So far so good, however I stumbled upon one problem which I seemingly can't solve by myself. It should be faily simple for you guys tho.
The situation:
I got one "object" which is clickable and gives a random value which is also being saved in a variable.
I got another "object" which does the same thing but only has a different name.
I want the variable from the first object to be passed to the second one, how can I do it?
One way to pass values relies on using references to objects...
// mc1 and mc2 exist as movieclips on the stage
mc1.addEventListener( MouseEvent.CLICK, onClick );
mc2.addEventListener( MouseEvent.CLICK, onClick );
function onClick( event:MouseEvent ):void
{
// reference clicked movieclip through click target
var mc:MovieClip = event.target as MovieClip;
// if our clip matches one, assign the other clip the value.
if ( mc === mc1 )
{
mc2.value = mc.value;
}
else if ( mc === mc2 )
{
mc1.value = mc.value;
}
}
There's a thousand ways to pass references around, and this is just one.
OK guys let me show you the important parts of the code (cant show it all since I paid for it and I don't know whether the author woudl be OK with me publishing all of it).
I got these two objects there. When I click on one of the objects (which is a DICE), it gives me this code basically
var faceValue:int = 6;
// Add mouse click functionality to roll the die
addEventListener(MouseEvent.CLICK, onClickDie, false, 0, true);
mouseChildren = false;
buttonMode = true;
function onClickDie(e:MouseEvent):void {
removeEventListener(MouseEvent.CLICK, onClickDie);
buttonMode = false;
// Initiate the roll-out sequence
if(faceValue == 6) {
gotoAndPlay("rollout6");
etc...
Then somewhere in the frame where it is SPINNING the dice it is randomizing a number and save it into facevalue
// Calculate a random face value between 1 and 6
faceValue = 1 + Math.floor(Math.random()*6);
// Initiate the roll-in sequence
if(faceValue == 6) {
gotoAndPlay("rollin6");
...etc
Now how can I get the randomized facevalue from the spinning the dice frame to pass it to the other dice?

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.

Difference between e.target and e.currentTarget

I don't understand the difference, they both seem the same but I guess they are not.
Any examples of when to use one or the other would be appreciated.
e.target is what triggers the event dispatcher to trigger and e.currentTarget is what you assigned your listener to.
Ben is completely correct in his answer - so keep what he says in mind. What I'm about to tell you isn't a full explanation, but it's a very easy way to remember how e.target, e.currentTarget work in relation to mouse events and the display list:
e.target = The thing under the mouse (as ben says... the thing that triggers the event).
e.currentTarget = The thing before the dot... (see below)
So if you have 10 buttons inside a clip with an instance name of "btns" and you do:
btns.addEventListener(MouseEvent.MOUSE_OVER, onOver);
// btns = the thing before the dot of an addEventListener call
function onOver(e:MouseEvent):void{
trace(e.target.name, e.currentTarget.name);
}
e.target will be one of the 10 buttons and e.currentTarget will always be the "btns" clip.
It's worth noting that if you changed the MouseEvent to a ROLL_OVER or set the property btns.mouseChildren to false, e.target and e.currentTarget will both always be "btns".
I like visual answers.
When you click #btn, two event handlers get called and they output what you see in the picture.
Demo here: https://jsfiddle.net/ujhe1key/
e.currentTarget is always the element the event is actually bound do. e.target is the element the event originated from, so e.target could be a child of e.currentTarget, or e.target could be === e.currentTarget, depending on how your markup is structured.
target is the element that triggered the event (e.g., the user clicked on)
currenttarget is the element that the event listener is attached to.
It's worth noting that event.target can be useful, for example, for using a single listener to trigger different actions. Let's say you have the typical "menu" sprite with 10 buttons inside, so instead of doing:
menu.button1.addEventListener(MouseEvent.CLICK, doAction1);
menu.button2.addEventListener(MouseEvent.CLICK, doAction2);
etc...
You can simply do:
menu.addEventListener(MouseEvent.CLICK, doAction);
And trigger a different action within doAction(event) depending on the event.target (using it's name property, etc...)
make an example:
var body = document.body,
btn = document.getElementById( 'id' );
body.addEventListener( 'click', function( event ) {
console.log( event.currentTarget === body );
console.log( event.target === btn );
}, false );
when you click 'btn', and 'true' and 'true' will be appeared!
e.currentTarget would always return the component onto which the event listener is added.
On the other hand, e.target can be the component itself or any direct child or grand child or grand-grand-child and so on who received the event. In other words, e.target returns the component which is on top in the Display List hierarchy and must be in the child hierarchy or the component itself.
One use can be when you have several Image in Canvas and you want to drag Images inside the component but Canvas. You can add a listener on Canvas and in that listener you can write the following code to make sure that Canvas wouldn't get dragged.
function dragImageOnly(e:MouseEvent):void
{
if(e.target==e.currentTarget)
{
return;
}
else
{
Image(e.target).startDrag();
}
}
e.target is element, which you f.e. click
e.currentTarget is element with added event listener.
If you click on child element of button, its better to use currentTarget to detect buttons attributes, in CH its sometimes problem to use e.target.
e.currentTarget is element(parent) where event is registered, e.target is node(children) where event is pointing to.

Load External SWF, assign MOUSE_DOWN event, and prevent empty space / sub stage from being clicked

I've been developing in Flash now for over 15 years, and recently started developing games in Flash Actionscript 3. I am having some difficulties, and need some help. I've spent days trying to find a solution, with no luck.
I have a main SWF, which loads sub SWF animations. One object can have 5 different animations / SWFs associated with it. So lets say I have a chicken, chicken01.swf, chicken02.swf, ...
I assign a MOUSE_DOWN event to the first loaded SWF, then based on the tool used while clicking on the object, it will load the other animations. My problem is that each SWF has empty space around it which becomes clickable. I only need the object clickable, and not the empty space, because some of the objects can overlap each other, which makes it hard to click on the object behind another object.
The Sub SWFs / animations are on a single timeline, and I played with Bitmap Tracing to remove the empty space around the imported PNG objects. This works if I reduce the Stage size to behind the Object, but then screws up the size of the loaded SWF due to the Stage size being smaller than the object. So when I assign a width and height to the object, with a smaller stage, the object is huge. If I constraint the stage size to the size of the object, even as a Traced bitmap image, the stage is still clickable. I tried to assign the MOUSE_DOWN event to the object on the sub SWF, from the Main SWF, but this gives errors.
My goal is to load a sub SWF, assign the MOUSE_DOWN event, and only have the object clickable, and not the Stage, or empty space around the object.
Is this possible? I also played around with creating an invisible button, but this makes it difficult to assign to 300 + objects of different shapes and sizes.
Below is some of the code I'm using.
var loadimage = foreground_list[i].imagelocation + foreground_list[i].image;
var loader:SWFLoader = new SWFLoader(loadimage,{container:tn_mc,x:current_tn_x,y:current_tn_y,name:current_name,alpha:1,width:current_tn_w,height:current_tn_h,rotation:0});
loader.load();
tn_mc.buttonMode = true;
tn_mc.addEventListener( MouseEvent.MOUSE_DOWN, tn_down );
tn_mc.addEventListener( MouseEvent.MOUSE_UP, tn_up );
addChild( tn_mc );
function tn_down(e:MouseEvent):void
{
switch (MovieClip(this.root).PointerTool)
{
case "move" :
stage.addEventListener(MouseEvent.MOUSE_UP, stage_up );
e.target.startDrag();
break;
case "play" :
var loader4:SWFLoader = new SWFLoader(foreground_list.imagelocation + foreground_list.playimage,{container:tn_mc,name:e.target.name,x:foreground_list.setx,y:foreground_list.sety,width:foreground_list.setw,height:foreground_list.seth,rotation:0});
tn_mc.removeChildAt(0);
tn_mc.addEventListener( MouseEvent.MOUSE_DOWN, tn_down );
tn_mc.addEventListener( MouseEvent.MOUSE_UP, tn_up );
loader4.load();
loader4.addEventListener(Event.COMPLETE, completeactionHandler);
break;
default :
//Some other animation
break;
}
}
Create a movieclip - a vector shape inside each swf, that is the same shape as your clickable area. Set the alpha to 0% on the vector's fill color. Give it and instance name of something like activeArea, and assign your event listener to that instead of the outer shell moveiclip.
Another approach that might work is to use hitTestObject() on a MOUSE_DOWN event, which would allow you to choose to ignore the transparency.
EDIT
Hard to tell exactly what you are trying to do without seeing it. I didn't actually compile this so I'm not sure if this will work just the way it is, but in theory it should be close. Its a slightly different approach than you are using. I used Loader() instead of SWFLoader, and cleaned up the idea a little bit. As a side note, you should avoid the use of root in as3.
var _swfLoader:Loader;
var loadimage = foreground_list[i].imagelocation + foreground_list[i].image;
var loader:SWFLoader = new SWFLoader(loadimage,{container:tn_mc,x:current_tn_x,y:current_tn_y,name:current_name,alpha:1,width:current_tn_w,height:current_tn_h,rotation:0});
loader.load();
tn_mc.buttonMode = true;
tn_mc.addEventListener( MouseEvent.MOUSE_DOWN, tn_down );
addChild( tn_mc );
function tn_down(e:MouseEvent):void
{
tn_mc.addEventListener( MouseEvent.MOUSE_UP, tn_up );
switch (MovieClip(this.root).PointerTool)
{
case "move" :
stage.addEventListener(MouseEvent.MOUSE_UP, stage_up );
e.target.startDrag();
break;
case "play" :
_swfLoader = new Loader();
var req:URLRequest = new URLRequest(foreground_list.imagelocation + foreground_list.playimage);
_swfLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, animationLoaded);
_swfLoader.load(req);
break;
default :
//Some other animation
break;
}
}
function tn_up(e:MouseEvent):void
{
tn_mc.removeEventListener( MouseEvent.MOUSE_UP, tn_up );
}
function animationLoaded(evt:Event):void
{
_swfLoader.contentLoaderInfo.removeEventListener(Event.COMPLETE, animationLoaded);
tn_mc.removeChildAt(0);
var loadedSwf = evt.target.content;
loadedSwf.x = foreground_list.setx;
loadedSwf.y = foreground_list.sety;
loadedSwf.width = foreground_list.setw;
loadedSwf.height = foreground_list.seth;
loadedSwf.rotation = 0;
loadedSwf.addEventListener(MouseEvent.MOUSE_DOWN, onAnimationStart);
// might wanna add theses to an array to keep track of them and run clean up later on
// now add to some display list
}
function onAnimationStart(evt:MouseEvent):void
{
loadedSwf.addEventListener(MouseEvent.MOUSE_UP, onAnimationStop);
// play your animation or whatever else
evt.target.play();
}
function onAnimationStop(evt:MouseEvent):void
{
loadedSwf.removeEventListener(MouseEvent.MOUSE_UP, onAnimationStop);
// stop your animation or whatever else
evt.target.stop();
}

AS3 Button States

I have 7 buttons on my stage (buttonA, buttonB, etc) all MC's.
I would like to control the RollOver, RollOut & CLick with AS3.
At first I thought I could just tell my button Listener where to go
i.e. gotoAndStop(2) which is RollOver state.
or gotoAndStop(3) which is Click state.
and gotoAndStop(1) which is RollOut state.
But when I "Click" and then rollout, I need the button to stay "clicked" until some other button is "clicked".
can't seem to figure this out. Any help would be appreciated.
it would be quicker and more efficient if you name your buttons numerically. button1, button2, button2.. this will allow you to write for loops to perform functions easily
// loop through the buttons and give them mouse click listeners
for ( var i:int = 1 ; i <= 7; i++ ){
var curButton:MovieClip = getChildByName ("button"+i);
// set click lisitener
curButton.addEventListener ( MouseEvent.CLICK, buttonClickHandler );
// set rollover listener
curButton.addEventListener ( MouseEvent.ROLLOVER, buttonRollOverHandler );
// set rollout listener
curButton.addEventListener ( MouseEvent.ROLLOUT, buttonRollOutHandler );
// set initial state
curButton.gotoAndStop(1);
}
function resetStates (){
for ( var i = 1; i<=7; i++){
var curButton = getChildByName("button"+i);
curButton.gotoAndStop(1);
}
}
function buttonRollOverHandler ( evt:MouseEvent ){
resetStates();
evt.target.gotoAndStop(2);
}
function buttonRollOutHandler ( evt:MouseEvent ){
resetStates();
}
function buttonClickHandler ( evt:MouseEvent ){
resetStates ();
evt.target.gotoAndStop(3);
}
Hmm...
I would keep the roll over and press functionality within the scope of a button element and do a frame two for the permanent clicked state.
[this is quickly written code, but you'll get the idea]
It seems you'll need have some parent "reset" function and individual click handlers for each button.
buttonA.addEventListener(MouseEvent.MOUSE_DOWN, buttonADown);
function resetButtons(){
buttonA.gotoAndStop(1);
buttonB.gotoAndStop(1);
buttonC.gotoAndStop(1);
buttonD.gotoAndStop(1);
buttonE.gotoAndStop(1);
buttonF.gotoAndStop(1);
buttonG.gotoAndStop(1);
}
function buttonADown(e.Mouse_Event):void{
resetButtons(); //RESET ALL PREVIOUSLY CLICKED BUTTONS
buttonA.gotoAndStop(2);
}
// and so on down the line....