Flash Dynamic Stacking Menu

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 ();
}
Posted on 13 Apr 2007
43 Comments
3 Trackbacks

Meta

Dynamic Stacking was posted on April 13th 2007 in the category Code / Flash, Open Source and tagged; , , , , .

You can Leave a comment.

Twitter <follow>

March 18th 2010 - 5:29pm

We've missed you Morris: RT @memotv: This guy is nuts. New trailer for Chris Morris' jihadist comedy Four Lions http://is.gd/aNzki

Discussion

46 Responses to Dynamic Stacking

Leave a Reply

Pingbacks / Trackbacks

  1. 1 year ago Help converting to AS3

    [...] Help converting to AS3 Commenta 27th November , 2008 I found this draggable sortable list thing in AS2 :: http://blog.soulwire.co.uk/flash/act…amic-stacking/ [...]

  2. 1 year ago AS3 Drag and Drop Shuffle Grid Menu Interface

    [...] Dynamic stacking 12% similar Array based menu shuffling [...]

  3. 7 months ago Plasticthinking - unlimited edition » Blog Archive » 好文章推荐 陆续更新

    [...] dynamic stacking soulwire是个超牛的网站(博客),专门介绍一些高端的visual [...]

  1. Jesse 9 months ago

    This works great. However, the boxes are no longer draggable?

    Do I have to change something within: MovieClip.prototype.initBox = function(num) {

    Reply to this comment

  2. Soulwire 9 months ago

    Yep! initBox sets up the mouse listeners. If you Google ‘AS2 Prototypes’ you can read up on how they work.

    Reply to this comment

  3. fizzle 8 months ago

    I apologize in advance for my newbie-ness
    when clicked I would like for the box to move to a certain ‘y’ position, ex. the position of box5, then have box5 move to the clicked box’s ‘y’ position, how can I achieve this?
    i’ve tried different things but to no avail.

    AS3 please. Thanks

    Reply to this comment

  4. twoo 8 months ago

    i just tried the AS3 version of the script. works like a charm.
    is there an easy way to remove boxes in this version aswell?

    Reply to this comment

  5. Soulwire 8 months ago

    Have you tried the ‘deleteLayer’ method? If you pass it a box id it should do the job.

    Reply to this comment

  6. twoo 8 months ago

    hmm, there is no ‘deleteLayer’ method in the class.
    i looked at the method for the AS2 version on this page, but i can´t figure out how to write this in AS3, since i´m a noob and just got startet with AS3 :/

    Reply to this comment

  7. chand 6 months ago

    I want to have unique names on every box, how I can achieve this…?

    Reply to this comment

  8. chand 6 months ago

    I want to have unique names on every box, how I can achieve this…? unique means right now we can’t have such names like one is john, bell, john etc

    Reply to this comment

  9. Soulwire 6 months ago

    Hi Chand,

    You can use an array of names and look up the name for each box based on its ID

    var names:Array = ["John", "Bell", "Steve"];
    // ...
    MovieClip.prototype.initBox = function (num)
    {
    	this.id = num;
    	this.name.text = names[ num];
    	// ...
    

    Reply to this comment

  10. chand 6 months ago

    Thanks so much for your help. Actually I’m not a good programmer but I’m working hard to learn it. For that I take an initiative and for learning I start an opensource project which is already based on opensource codes. One is yours this one for tabs and the other one is opensource Ipagrafika flash flipbook. What I want to do is just that, when we drag a tab and place it onto the new position. It also change the respective page which is linked with it. for example if we have tab1 linked with page1, when we place it to the position5 the page automatically changed to the 5th position. Means now we have the page1 at the location of page5 and also tab1 which is linked with it at position5. So tabs are the simulators to change the pages sorting. I need your help please guide me how I can approach this problem. here I have the sample that till now I have, http://74.213.174.176/wasim/mdbook/sample.html its really very important for my learning please guide me how I can accomplish that and become a part of flash community. Thanks

    Reply to this comment

  11. chand 6 months ago

    Below is your code which I edited slightly to show the path of each image that I have now what I need is I want to write these paths to the xml file, from which I can get the newly adjusted pages in the book when I sort them. And when user reload the book it gets the pages on the new locations that previously set by the user. Please help me in this matter.

     [
    
    var boxes:Array = new Array ();
    var boxCount:Number = 0;
    var boxGap:Number = 85;
    var names:Array = ["123", "232", "4323", "4234", "455", "6567", "5757"];
    var paths:Array = ["../pages/cover.jpg", "../pages/page1_left.png", "../pages/page2_right.png",
    				  "../pages/page3_left.png", "../pages/page4_right.png", "../pages/page5_left.png",
    				  "../pages/page6_right.png", "../pages/page7_left.png", "../pages/page8_right.png",
    				  "../pages/image67.jpg", "../pages/image106.jpg", "../pages/backend.jpg"];
    
    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;
    		trace(box.id);
    		box.tween ("_y", y, 0.8, "easeInOut");
    	}
    }
    
    function addBox ()
    {
    
    	var d:Number = menu.getNextHighestDepth ();
    	var i:Number = boxCount;
    	boxes.unshift (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 ();
    }
    
    MovieClip.prototype.initBox = function (num)
    {
    	this.id = num;
    	this.name.text = names[num];
    	this.path.text = paths[num];
    	trace(this.path[num]);
    
    	this.onPress = function ()
    	{
    		var top:Number = boxCount + 1;
    		this.swapDepths (top);
    		this.startDrag();
    		this._alpha = 70;
    	};
    
    	this.onRelease = this.onReleaseOutside = function ()
    	{
    		this.stopDrag ();
    		this._alpha = 100;
    		this.tween ("_x", 0, 0.8, "easeInOut");
    		var x:Number = this.id;
    		var y:Number = Math.round (this._y / boxGap);
    		boxes.shuffle (x, y);
    		sort ();
    
    		if(this.id == 0){
    		_root.gotoPage(1, false);
    		}
    		if(this.id == 2){
    		_root.gotoPage(3, false);
    		}
    		if(this.id == 3){
    		_root.gotoPage(5, false);
    		}
    		if(this.id == 4){
    		_root.gotoPage(7, false);
    		}
    		if(this.id == 5){
    		_root.gotoPage(9, false);
    		}
    		if(this.id == 6){
    		_root.gotoPage(11, false);
    		}
    	};
    };
    
    for (var i = 0; i < 7; i++)
    {
    	addBox ();
    
    }
    
     ] 

    Reply to this comment

  12. chand 6 months ago

    I want to copy the sorted values in another array, I need hint how I can do that. like we have the names array it remain same but after each sorting I want to have the values in another sortedArray. Please guide me how I can do that.

     [
    var boxes:Array = new Array ();
    var boxCount:Number = 0;
    var boxGap:Number = 50;//85
    
    var names:Array = ["12", "11", "10", "9", "8", "7", "6", "5", "4", "3", "2", "1"];
    var paths:Array = ["../pages/cover.jpg", "../pages/page1_left.png", "../pages/page2_right.png",
    				  "../pages/page3_left.png", "../pages/page4_right.png", "../pages/page5_left.png",
    				  "../pages/page6_right.png", "../pages/page7_left.png", "../pages/page8_right.png",
    				  "../pages/image67.jpg", "../pages/image106.jpg", "../pages/backend.jpg"];
    
    //var sortedPaths:Array = new Array();
    
    //relocate
    /*
    Array.prototype.relocate = function (from, to, putBefore)
    {
        var index = this.splice(from, 1);
        this.splice(to - (putBefore ? 1 : 0), 0, index[0]);
    }
    
    */
    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;
    		var sortedNames:Array = new Array();
    		box.id = i;
    		sortedNames.push(this.name.text);
    		trace(sortedNames);
    		box.tween ("_y", y, 0.8, "easeInOut");
    	}
    }
    
    function addBox ()
    {
    
    	var d:Number = menu.getNextHighestDepth ();
    	var i:Number = boxCount;
    	boxes.unshift (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 ();
    }
    
    MovieClip.prototype.initBox = function (num)
    {
    	this.id = num;
    	this.name.text = names[num];
    	this.path.text = paths[num];
    
    	this.onPress = function ()
    	{
    		var top:Number = boxCount + 1;
    		this.swapDepths (top);
    		this.startDrag();
    		/*
    		this.relocate(paths[num], sortedPaths[num], sortedPaths[num++]);
    		trace(sortedPaths);
    		trace(paths);
    		*/
    		this._alpha = 70;
    	};
    
    	this.onRelease = this.onReleaseOutside = function ()
    	{
    		this.stopDrag ();
    		this._alpha = 100;
    		this.tween ("_x", 0, 0.8, "easeInOut");
    		var x:Number = this.id;
    		var y:Number = Math.round (this._y / boxGap);
    
    //		this.relocate(paths[num], sortedPaths[num], sortedPaths[num++]);
    //		trace(sortedPaths);
    //		trace(paths);
    
    		boxes.shuffle (x, y);
    		sort ();
    
    		if(this.id == 0){
    		_root.gotoPage(1, false);
    		}
    		if(this.id == 2){
    		_root.gotoPage(3, false);
    		}
    		if(this.id == 3){
    		_root.gotoPage(5, false);
    		}
    		if(this.id == 4){
    		_root.gotoPage(7, false);
    		}
    		if(this.id == 5){
    		_root.gotoPage(9, false);
    		}
    		if(this.id == 6){
    		_root.gotoPage(11, false);
    		}
    	};
    };
    
    for (var i = 1; i <= 12; i++)
    {
    	addBox ();
    
    }
     ] 

    Reply to this comment