Update: AS3 Version using TweenLite is available here
In this experiment, you can drag the boxes; when you release them the stack will shuffle to accommodate the dropped box.
I have recently been working on building an application in which I wanted to use Photoshop style ‘layers’ which the user could create, drag onto different depths and delete.
The basic idea was for the user to click and drag ‘layers’, and drop them in between others. This would cause the other layers to reposition to accommodate the new layer and to fill the gap which it had left in it’s old position. Get it?
Perhaps I’m just a bit dim, but I was surprised at how many difficulties I encountered when trying to sort the boxes or ‘layers’. I began taking the approach of calculating whether the surrounding layers were above or below, and moving them up or down accordingly; but with this approach I found that deleting layers or adding new ones caused the system to go a bit crazy.
As usual, it was the simple and elegant approach which eventually yielded the solution. I basically handled all of the movement from a single, straightforward array. The array was shuffled based on a nifty little prototype I wrote for extracting and reinserting elements (essentially shuffling without randomness), from which all movement calculations (i.e. the eventual positions of the shuffling boxes) could be made.
Array.prototype.shuffle = function (x, y)
{
var i:Number = this.slice (x, x + 1);
this.splice (x,1);
this.splice (y,0,i);
};
The shuffle prototype itself could be very useful, as it pulls items out of the array, and by reinserting them; muscles the other elements outwards around it – no complicated calculations needed for determining where the elements go thanks to some really basic build in Flash Actionscript features. Also, by omitting one line of code, the prototype could be used to simply insert items into an array at a particular position (insertAt(value, index) or something along those lines).
I have attached a bare bones example for you, though I assure you, you can do great things with this script. The menu I was working on will probably make an appearance on Soulwire once the program is finished. One example of its usage would be to associate each box with a layer elsewhere; for example a Stack of photos which are depth ‘shuffled’ through the easy manipulation of their affiliated boxes.
Download: Dynamic Stacking Menu (AS2) Download: Dynamic Stacking Menu (AS3)You can download the FLA here above, but here is the basic commented code. It is simple but works quite well.
// Include the Zigo tweening prototypes to make life easier
#include "lmc_tween.as"
// Create the main holder for the boxes
var menu:MovieClip = this.createEmptyMovieClip ("menu", 0);
menu._x = menu._y = 20;
// Set up our Arrays and counters
var boxes:Array = new Array ();
var boxCount:Number = 0;
var boxGap:Number = 37;
/*
The core 'SHUFFLE' function
This pulls out an array item at a given index and inserts it back into the array
at another index. The other array items are consiquently 'pushed' outwards from
the element, so our new array reflects the visual appearance we want for the
shuffled menu. We can now 'sort' our menu based on this modified array...
*/
Array.prototype.shuffle = function (x, y)
{
var i:Number = this.slice (x, x + 1);
this.splice (x,1);
this.splice (y,0,i);
};
// Our sort function
function sort ()
{
var count:Number = boxes.length;
for (var i = 0; i < count; i++)
{
// Check which ID is at this point in the array
var id:Number = boxes[i];
// Find the corrosponding box
var box:MovieClip = eval ("menu.box" + id);
// Calculate the position the box should be at
var y:Number = i * boxGap;
// Alter it's id to it's new position
box.id = i;
// Tween it there (or just use box._y = y)
box.tween ("_y",y,0.8,"easeInOut");
}
}
// Creating new boxes
function addBox ()
{
/*
We use getNextHighestDepth() because of the swapDepths() function used
when the buttons are pressed. This creates new levels and so could
cause problems if we just create boxes at depths based on the length
of the boxes array.
*/
var d:Number = menu.getNextHighestDepth ();
var i:Number = boxCount;
// We could use Push(), but I want the boxes stacked, with newest on top
boxes.unshift (i);
// Attach the box
var box:MovieClip = menu.attachMovie ("box", "box" + i, d);
// Initiate the box
box.initBox (i);
// Increase the counter
boxCount++;
// Sort the boxes
sort ();
}
/*
This is a simple function which can be called to remove boxes. It splices the array and
resorts the stack accordingly.
*/
function deleteLayer (id)
{
var box:MovieClip = eval ("menu.box" + boxes[id]);
box.removeMovieClip ();
boxes.splice (id,1);
sort ();
}
// The box actions
MovieClip.prototype.initBox = function (num)
{
this.id = num;
this.name.text = "Box " + num;
this.onPress = function ()
{
var top:Number = boxCount + 1;
this.swapDepths (top);
this.startDrag ();
};
this.onRelease = this.onReleaseOutside = function ()
{
this.stopDrag ();
// Slide back to the edge
this.tween ("_x",0,0.8,"easeInOut");
/*
Find the original position, and the new position, based on the
location of the draged box in relation to the other boxes.
*/
var x:Number = this.id;
var y:Number = Math.round (this._y / boxGap);
// Call our shuffle function based on these calculations
boxes.shuffle (x,y);
// Sort the boxes based on our shuffled array
sort ();
};
};
// Add some boxes to test it!
for (var i = 0; i < 5; i++)
{
addBox ();
}

You can use push of course, but splice can be used to add elements to an array too, and at a specific position…
thanx for the prompt reply, I’ve made a small adjustment to your code to achieve the effect used in this website:
http://www.yourwidgetworld.com/my/
here’s the script, I’ve moved the comment for practicality reason:
import caurina.transitions.Tweener; var menu:MovieClip = this.createEmptyMovieClip ("menu", 0); menu._x = menu._y = 20; var boxCondition:Boolean = false; var orX:Number; var orY:Number; var boxes:Array = new Array (); var boxCount:Number = 0; var boxGap:Number = 45; Array.prototype.shuffle = function (x, y) { var i:Number = this.slice (x, x + 1); this.splice (x, 1); this.splice (y, 0, i); }; function sort () { var count:Number = boxes.length; for (var i = 0; i < count; i++) { var id:Number = boxes[i]; var box:MovieClip = eval ("menu.box" + id); var y:Number = i * boxGap; box.id = i; Tweener.addTween(box, {_y:y, time:0.5, transition:"easeOutElastic"}); } } function addBox () { var d:Number = menu.getNextHighestDepth (); var i:Number = boxCount; boxes.push (i); var box:MovieClip = menu.attachMovie ("box", "box" + i, d); box.initBox (i); boxCount++; sort (); } function deleteLayer (id) { var box:MovieClip = eval ("menu.box" + boxes[id]); box.removeMovieClip (); boxes.splice (id, 1); sort (); } function mathPos(mc, posX, posY){ Tweener.addTween(mc, {_x:posX, _y:posY, time:0.5, transition:"easeOutElastic"}); } MovieClip.prototype.initBox = function (num) { this.boxCondition = false; this.id = num; this.name.text = num; this.orX = 0; this.orY = this.id*boxGap; // this.onPress = function () { var top:Number = boxCount + 1; this.swapDepths (top); this.startDrag (); trace(this.id); }; this.onRelease = this.onReleaseOutside = function () { trace(this+"-"+this.boxCondition); if (this.hitTest(_root.stage_mc)&& this.boxCondition == false){ this.stopDrag(); this.boxCondition = true; Tweener.addTween(this, {_x:this._x, _y:this._y, time:0.5, transition:"easeOutElastic"}); boxes.splice(this.id, 1); var x:Number = this.id; var y:Number = Math.round (this._y / boxGap); sort (); boxes.shuffle (x, y); } else if(this.hitTest(_root.scr_mc)&& this.boxCondition == true){ this.stopDrag (); this.boxCondition = false; Tweener.addTween(this, {_x:0, time:0.5, transition:"easeOutElastic"}); boxes.push(this.id); var x:Number = this.id; var y:Number = Math.round (this._y / boxGap); boxes.shuffle (x, y); sort (); } else { this.stopDrag(); mathPos(this, this.orX, this.orY); } }; }; for (var i = 0; i < 5; i++) { addBox (); }but the modification made the shuffle function somewhat buggy,.. for an example if i try to move the last element of boxes array and then add them back to the populated list, they always add an element.
can you point me in the right direction regarding this?..
I appreciated any help you can give me.
thanx man
hi,.. anybody come up with an addLayer function?.. one which add a layer on runtime?..
Great file!
Is it possible to have the numbers start in a different order? for example: 2.5.3.1.4
Then trace when they are in the following order:12345
Thanks
Sure Jesse. Just shuffle the boxes Array manually after setup.
This is untested but should work:
Awesome – Thanks – works great!
I can trace the order: trace(newBoxes);
However, I’m still confused on how to create an if statement when the boxes are in a specific order, for example: 0,1,2,3
OK. Here’s a simple script which you can call after every sort. It checks each element of the boxes array against a target array. If they all match, it returns true.
Here’s an example of it in use:
function sort () { var count:Number = boxes.length; for (var i = 0; i < count; i++) { var id:Number = boxes[i]; var box:MovieClip = eval ("menu.box" + id); var y:Number = i * boxGap; box.id = i; box.tween ("_y",y,0.8,"easeInOut"); box._y = y; } // Call checkOrder if(checkOrder ()) { trace("Order is correct"); // Do something } } // Target order var target:Array = [0, 1, 2, 3]; function checkOrder ():Boolean { if(boxes.length < 2 ) return false; var correct:Number = 0; for (var i = 0; i < boxes.length; i++) { if (boxes[i].toString () == target[i].toString ()) { correct++; } } return (correct == boxes.length); }Great thanks!
I’ve learnt a lot with this file – thanks for the help.
No problem ;)
Lastly – Can I assign specific text to each box?
In other words how can I point to a specific box?
Instead of: this.name.text = “Hello, I’m box number “+num;
If you look at the line:
var box:MovieClip = menu.attachMovie ("box", "box" + i, d)Its attaching “box” from the library and assigning it an ID of “box” + i (‘i’ being the index of the loop, so the ID will increment each time).
You need to keep the id intact so the sort function can work, but you can reference any clip to use, for example:
var box:MovieClip = menu.attachMovie ("someOtherMovieClip", "box" + i, d)So you could modify the ‘addBox’ method to accept a custom ID (linkage identifier of a clip in the library), and also to return the created box so that you can do custom things with it after instantiation. Then manually add the boxes instead of using the loop.
For example:
function addBox (type:String):MovieClip { var d:Number = menu.getNextHighestDepth(); var i:Number = boxCount; boxes.unshift (i); var box:MovieClip = menu.attachMovie(type,"box" + i,d); box.initBox (i); boxCount++; sort (); return box; } // This will add a box and return a reference to it var myBox:MovieClip = addBox("someLinkageID"); // Then you can do things to the box, like set some text, colour etc myBox.customProperty = "This is a custom Property";