2010-04-16 15:09:23 -07:00
|
|
|
// Title: items.js (revision-a)
|
2010-03-29 16:08:48 -07:00
|
|
|
// ##########
|
|
|
|
// An Item is an object that adheres to an interface consisting of these methods:
|
2010-04-01 17:20:59 -07:00
|
|
|
// reloadBounds: function()
|
2010-04-02 17:33:06 -07:00
|
|
|
// getBounds: function(), inherited from Item
|
2010-03-29 17:23:35 -07:00
|
|
|
// setBounds: function(rect, immediately)
|
2010-04-02 17:33:06 -07:00
|
|
|
// setPosition: function(left, top, immediately), inherited from Item
|
|
|
|
// setSize: function(width, height, immediately), inherited from Item
|
|
|
|
// getZ: function(), inherited from Item
|
|
|
|
// setZ: function(value)
|
2010-03-29 16:08:48 -07:00
|
|
|
// close: function()
|
|
|
|
// addOnClose: function(referenceObject, callback)
|
|
|
|
// removeOnClose: function(referenceObject)
|
|
|
|
//
|
2010-03-31 17:24:16 -07:00
|
|
|
// In addition, it must have these properties:
|
|
|
|
// isAnItem, set to true (set by Item)
|
|
|
|
// defaultSize, a Point
|
2010-04-01 17:20:59 -07:00
|
|
|
// bounds, a Rect (set by Item in _init() via reloadBounds())
|
|
|
|
// debug (set by Item)
|
|
|
|
// $debug (set by Item in _init())
|
|
|
|
// container, a DOM element (set by Item in _init())
|
2010-03-31 17:24:16 -07:00
|
|
|
//
|
2010-04-01 17:20:59 -07:00
|
|
|
// Its container must also have a jQuery data named 'item' that points to the item.
|
|
|
|
// This is set by Item in _init().
|
2010-03-29 16:08:48 -07:00
|
|
|
|
2010-04-08 16:36:11 -07:00
|
|
|
|
|
|
|
// Class: Item
|
|
|
|
// Superclass for all visible objects (tabs and groups).
|
2010-03-29 16:08:48 -07:00
|
|
|
window.Item = function() {
|
2010-04-08 16:36:11 -07:00
|
|
|
// Variable: isAnItem
|
|
|
|
// Always true for Items
|
2010-03-29 16:08:48 -07:00
|
|
|
this.isAnItem = true;
|
2010-04-08 16:36:11 -07:00
|
|
|
|
|
|
|
// Variable: bounds
|
|
|
|
// The position and size of this Item, represented as a <Rect>.
|
2010-04-01 17:20:59 -07:00
|
|
|
this.bounds = null;
|
2010-04-08 16:36:11 -07:00
|
|
|
|
|
|
|
// Variable: debug
|
|
|
|
// When set to true, displays a rectangle on the screen that corresponds with bounds.
|
|
|
|
// May be used for additional debugging features in the future.
|
2010-04-01 17:20:59 -07:00
|
|
|
this.debug = false;
|
2010-04-08 16:36:11 -07:00
|
|
|
|
|
|
|
// Variable: $debug
|
|
|
|
// If <debug> is true, this will be the jQuery object for the visible rectangle.
|
2010-04-01 17:20:59 -07:00
|
|
|
this.$debug = null;
|
2010-04-08 16:36:11 -07:00
|
|
|
|
|
|
|
// Variable: container
|
|
|
|
// The outermost DOM element that describes this item on screen.
|
2010-04-01 17:20:59 -07:00
|
|
|
this.container = null;
|
2010-04-16 17:21:03 -07:00
|
|
|
|
|
|
|
// Variable: locked
|
|
|
|
// Affects whether an item can be pushed, closed, renamed, etc
|
|
|
|
this.locked = false;
|
2010-03-29 16:08:48 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
window.Item.prototype = {
|
2010-04-01 17:20:59 -07:00
|
|
|
// ----------
|
2010-04-08 16:36:11 -07:00
|
|
|
// Function: _init
|
|
|
|
// Initializes the object. To be called from the subclass's intialization function.
|
|
|
|
//
|
|
|
|
// Parameters:
|
|
|
|
// container - the outermost DOM element that describes this item onscreen.
|
2010-04-01 17:20:59 -07:00
|
|
|
_init: function(container) {
|
|
|
|
this.container = container;
|
|
|
|
|
|
|
|
if(this.debug) {
|
|
|
|
this.$debug = $('<div />')
|
|
|
|
.css({
|
|
|
|
border: '2px solid green',
|
|
|
|
zIndex: -10,
|
|
|
|
position: 'absolute'
|
|
|
|
})
|
|
|
|
.appendTo($('body'));
|
|
|
|
}
|
|
|
|
|
|
|
|
this.reloadBounds();
|
|
|
|
$(this.container).data('item', this);
|
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
2010-04-08 16:36:11 -07:00
|
|
|
// Function: getBounds
|
|
|
|
// Returns a copy of the Item's bounds as a <Rect>.
|
2010-04-01 17:20:59 -07:00
|
|
|
getBounds: function() {
|
|
|
|
return new Rect(this.bounds);
|
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
2010-04-08 16:36:11 -07:00
|
|
|
// Function: setPosition
|
|
|
|
// Moves the Item to the specified location.
|
|
|
|
//
|
|
|
|
// Parameters:
|
|
|
|
// left - the new left coordinate relative to the window
|
|
|
|
// top - the new top coordinate relative to the window
|
|
|
|
// immediately - if false or omitted, animates to the new position;
|
|
|
|
// otherwise goes there immediately
|
2010-04-01 17:20:59 -07:00
|
|
|
setPosition: function(left, top, immediately) {
|
|
|
|
this.setBounds(new Rect(left, top, this.bounds.width, this.bounds.height), immediately);
|
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
2010-04-08 16:36:11 -07:00
|
|
|
// Function: setSize
|
|
|
|
// Resizes the Item to the specified size.
|
|
|
|
//
|
|
|
|
// Parameters:
|
|
|
|
// width - the new width in pixels
|
|
|
|
// height - the new height in pixels
|
|
|
|
// immediately - if false or omitted, animates to the new size;
|
|
|
|
// otherwise resizes immediately
|
2010-04-01 17:20:59 -07:00
|
|
|
setSize: function(width, height, immediately) {
|
|
|
|
this.setBounds(new Rect(this.bounds.left, this.bounds.top, width, height), immediately);
|
|
|
|
},
|
|
|
|
|
2010-04-02 17:33:06 -07:00
|
|
|
// ----------
|
2010-04-08 16:36:11 -07:00
|
|
|
// Function: getZ
|
|
|
|
// Returns the zIndex of the Item.
|
2010-04-02 17:33:06 -07:00
|
|
|
getZ: function() {
|
|
|
|
return parseInt($(this.container).css('zIndex'));
|
|
|
|
},
|
|
|
|
|
2010-03-29 16:08:48 -07:00
|
|
|
// ----------
|
2010-04-08 16:36:11 -07:00
|
|
|
// Function: pushAway
|
|
|
|
// Pushes all other items away so none overlap this Item.
|
2010-03-29 16:08:48 -07:00
|
|
|
pushAway: function() {
|
|
|
|
var buffer = 10;
|
|
|
|
|
|
|
|
var items = Items.getTopLevelItems();
|
|
|
|
$.each(items, function(index, item) {
|
|
|
|
var data = {};
|
|
|
|
data.bounds = item.getBounds();
|
|
|
|
data.startBounds = new Rect(data.bounds);
|
|
|
|
data.generation = Infinity;
|
|
|
|
item.pushAwayData = data;
|
|
|
|
});
|
|
|
|
|
|
|
|
var itemsToPush = [this];
|
|
|
|
this.pushAwayData.generation = 0;
|
|
|
|
|
|
|
|
var pushOne = function(baseItem) {
|
|
|
|
var baseData = baseItem.pushAwayData;
|
|
|
|
var bb = new Rect(baseData.bounds);
|
|
|
|
bb.inset(-buffer, -buffer);
|
|
|
|
var bbc = bb.center();
|
|
|
|
|
|
|
|
$.each(items, function(index, item) {
|
2010-04-16 17:21:03 -07:00
|
|
|
if(item == baseItem || item.locked)
|
2010-03-29 16:08:48 -07:00
|
|
|
return;
|
|
|
|
|
|
|
|
var data = item.pushAwayData;
|
|
|
|
if(data.generation <= baseData.generation)
|
|
|
|
return;
|
|
|
|
|
|
|
|
var bounds = data.bounds;
|
|
|
|
var box = new Rect(bounds);
|
|
|
|
box.inset(-buffer, -buffer);
|
|
|
|
if(box.intersects(bb)) {
|
|
|
|
var offset = new Point();
|
|
|
|
var center = box.center();
|
|
|
|
if(Math.abs(center.x - bbc.x) < Math.abs(center.y - bbc.y)) {
|
2010-04-16 17:21:03 -07:00
|
|
|
/* offset.x = Math.floor((Math.random() * 10) - 5); */
|
2010-03-29 16:08:48 -07:00
|
|
|
if(center.y > bbc.y)
|
|
|
|
offset.y = bb.bottom - box.top;
|
|
|
|
else
|
|
|
|
offset.y = bb.top - box.bottom;
|
|
|
|
} else {
|
2010-04-16 17:21:03 -07:00
|
|
|
/* offset.y = Math.floor((Math.random() * 10) - 5); */
|
2010-03-29 16:08:48 -07:00
|
|
|
if(center.x > bbc.x)
|
|
|
|
offset.x = bb.right - box.left;
|
|
|
|
else
|
|
|
|
offset.x = bb.left - box.right;
|
|
|
|
}
|
|
|
|
|
|
|
|
bounds.offset(offset);
|
|
|
|
data.generation = baseData.generation + 1;
|
2010-04-16 17:21:03 -07:00
|
|
|
data.pusher = baseItem;
|
2010-03-29 16:08:48 -07:00
|
|
|
itemsToPush.push(item);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
while(itemsToPush.length)
|
|
|
|
pushOne(itemsToPush.shift());
|
|
|
|
|
2010-04-16 17:21:03 -07:00
|
|
|
// ___ Squish!
|
2010-04-20 11:47:54 -07:00
|
|
|
if(true) {
|
2010-04-16 17:21:03 -07:00
|
|
|
var pageBounds = Items.getPageBounds();
|
|
|
|
$.each(items, function(index, item) {
|
|
|
|
var data = item.pushAwayData;
|
|
|
|
if(data.generation == 0 || item.locked)
|
|
|
|
return;
|
|
|
|
|
2010-04-20 11:47:54 -07:00
|
|
|
function apply(item, posStep, posStep2, sizeStep) {
|
2010-04-16 17:21:03 -07:00
|
|
|
var data = item.pushAwayData;
|
|
|
|
if(data.generation == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
var bounds = data.bounds;
|
|
|
|
bounds.width -= sizeStep.x;
|
|
|
|
bounds.height -= sizeStep.y;
|
|
|
|
bounds.left += posStep.x;
|
|
|
|
bounds.top += posStep.y;
|
|
|
|
|
|
|
|
if(sizeStep.y > sizeStep.x) {
|
|
|
|
var newWidth = bounds.height * (TabItems.tabWidth / TabItems.tabHeight);
|
|
|
|
bounds.left += (bounds.width - newWidth) / 2;
|
|
|
|
bounds.width = newWidth;
|
|
|
|
} else {
|
|
|
|
var newHeight = bounds.width * (TabItems.tabHeight / TabItems.tabWidth);
|
|
|
|
bounds.top += (bounds.height - newHeight) / 2;
|
|
|
|
bounds.height = newHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
var pusher = data.pusher;
|
|
|
|
if(pusher)
|
|
|
|
apply(pusher, posStep.plus(posStep2), posStep2, sizeStep);
|
|
|
|
}
|
|
|
|
|
|
|
|
var bounds = data.bounds;
|
|
|
|
var posStep = new Point();
|
|
|
|
var posStep2 = new Point();
|
|
|
|
var sizeStep = new Point();
|
2010-04-20 11:47:54 -07:00
|
|
|
|
|
|
|
if(bounds.left < pageBounds.left) {
|
|
|
|
posStep.x = pageBounds.left - bounds.left;
|
|
|
|
sizeStep.x = posStep.x / data.generation;
|
|
|
|
posStep2.x = -sizeStep.x;
|
|
|
|
} else if(bounds.right > pageBounds.right) {
|
|
|
|
posStep.x = pageBounds.right - bounds.right;
|
|
|
|
sizeStep.x = -posStep.x / data.generation;
|
|
|
|
posStep.x += sizeStep.x;
|
|
|
|
posStep2.x = sizeStep.x;
|
|
|
|
}
|
|
|
|
|
2010-04-16 17:21:03 -07:00
|
|
|
if(bounds.top < pageBounds.top) {
|
|
|
|
posStep.y = pageBounds.top - bounds.top;
|
|
|
|
sizeStep.y = posStep.y / data.generation;
|
|
|
|
posStep2.y = -sizeStep.y;
|
|
|
|
} else if(bounds.bottom > pageBounds.bottom) {
|
|
|
|
posStep.y = pageBounds.bottom - bounds.bottom;
|
|
|
|
sizeStep.y = -posStep.y / data.generation;
|
|
|
|
posStep.y += sizeStep.y;
|
|
|
|
posStep2.y = sizeStep.y;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(posStep.x || posStep.y || sizeStep.x || sizeStep.y)
|
|
|
|
apply(item, posStep, posStep2, sizeStep);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// ___ Apply changes
|
2010-03-29 16:08:48 -07:00
|
|
|
$.each(items, function(index, item) {
|
|
|
|
var data = item.pushAwayData;
|
2010-04-16 17:21:03 -07:00
|
|
|
var bounds = data.bounds;
|
|
|
|
if(!bounds.equals(data.startBounds)) {
|
|
|
|
item.setBounds(bounds);
|
|
|
|
}
|
2010-03-29 16:08:48 -07:00
|
|
|
});
|
2010-04-01 17:20:59 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
// ----------
|
2010-04-08 16:36:11 -07:00
|
|
|
// Function: _updateDebugBounds
|
|
|
|
// Called by a subclass when its bounds change, to update the debugging rectangles on screen.
|
|
|
|
// This functionality is enabled only by the debug property.
|
2010-04-01 17:20:59 -07:00
|
|
|
_updateDebugBounds: function() {
|
|
|
|
if(this.$debug) {
|
|
|
|
this.$debug.css({
|
|
|
|
left: this.bounds.left,
|
|
|
|
top: this.bounds.top,
|
|
|
|
width: this.bounds.width,
|
|
|
|
height: this.bounds.height
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2010-03-29 16:08:48 -07:00
|
|
|
};
|
|
|
|
|
2010-03-29 11:55:13 -07:00
|
|
|
// ##########
|
2010-04-08 16:36:11 -07:00
|
|
|
// Class: Items
|
|
|
|
// Keeps track of all Items.
|
2010-03-29 11:55:13 -07:00
|
|
|
window.Items = {
|
2010-03-29 16:08:48 -07:00
|
|
|
// ----------
|
2010-04-08 16:36:11 -07:00
|
|
|
// Function: item
|
|
|
|
// Given a DOM element representing an Item, returns the Item.
|
2010-03-29 16:08:48 -07:00
|
|
|
item: function(el) {
|
|
|
|
return $(el).data('item');
|
|
|
|
},
|
|
|
|
|
2010-03-29 11:55:13 -07:00
|
|
|
// ----------
|
2010-04-08 16:36:11 -07:00
|
|
|
// Function: getTopLevelItems
|
|
|
|
// Returns an array of all Items not grouped into groups.
|
2010-03-29 11:55:13 -07:00
|
|
|
getTopLevelItems: function() {
|
|
|
|
var items = [];
|
|
|
|
|
2010-03-31 17:24:16 -07:00
|
|
|
$('.tab, .group').each(function() {
|
2010-03-29 11:55:13 -07:00
|
|
|
$this = $(this);
|
|
|
|
if(!$this.data('group'))
|
2010-03-31 17:24:16 -07:00
|
|
|
items.push($this.data('item'));
|
2010-03-29 11:55:13 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
return items;
|
2010-04-14 17:12:06 -07:00
|
|
|
},
|
|
|
|
|
2010-04-16 17:21:03 -07:00
|
|
|
// ----------
|
|
|
|
// Function: getPageBounds
|
|
|
|
// Returns a <Rect> defining the area of the page <Item>s should stay within.
|
|
|
|
getPageBounds: function() {
|
|
|
|
var top = 20;
|
|
|
|
var bottom = TabItems.tabHeight + 10; // MAGIC NUMBER: giving room for the "new tabs" group
|
|
|
|
return new Rect(0, top, window.innerWidth, window.innerHeight - (top + bottom));
|
|
|
|
},
|
|
|
|
|
2010-04-14 17:12:06 -07:00
|
|
|
// ----------
|
|
|
|
// Function: arrange
|
|
|
|
// Arranges the given items in a grid within the given bounds,
|
|
|
|
// maximizing item size but maintaining standard tab aspect ratio for each
|
|
|
|
//
|
|
|
|
// Parameters:
|
|
|
|
// items - an array of <Item>s
|
|
|
|
// bounds - a <Rect> defining the space to arrange within
|
|
|
|
// options - an object with options. If options.animate is false, doesn't animate, otherwise it does.
|
|
|
|
arrange: function(items, bounds, options) {
|
|
|
|
var animate;
|
|
|
|
if(!options || typeof(options.animate) == 'undefined')
|
|
|
|
animate = true;
|
|
|
|
else
|
|
|
|
animate = options.animate;
|
|
|
|
|
|
|
|
if(typeof(options) == 'undefined')
|
|
|
|
options = {};
|
|
|
|
|
|
|
|
var tabAspect = TabItems.tabHeight / TabItems.tabWidth;
|
|
|
|
var count = items.length;
|
|
|
|
if(!count)
|
|
|
|
return;
|
|
|
|
|
|
|
|
var columns = 1;
|
|
|
|
var padding = options.padding || 0;
|
|
|
|
var yScale = 1.1; // to allow for titles
|
|
|
|
var rows;
|
|
|
|
var tabWidth;
|
|
|
|
var tabHeight;
|
|
|
|
var totalHeight;
|
|
|
|
|
|
|
|
function figure() {
|
|
|
|
rows = Math.ceil(count / columns);
|
|
|
|
tabWidth = (bounds.width - (padding * (columns - 1))) / columns;
|
|
|
|
tabHeight = tabWidth * tabAspect;
|
|
|
|
totalHeight = (tabHeight * yScale * rows) + (padding * (rows - 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
figure();
|
|
|
|
|
|
|
|
while(rows > 1 && totalHeight > bounds.height) {
|
|
|
|
columns++;
|
|
|
|
figure();
|
|
|
|
}
|
|
|
|
|
|
|
|
if(rows == 1) {
|
|
|
|
tabWidth = Math.min(bounds.width / count, bounds.height / tabAspect);
|
|
|
|
tabHeight = tabWidth * tabAspect;
|
|
|
|
}
|
|
|
|
|
|
|
|
var box = new Rect(bounds.left, bounds.top, tabWidth, tabHeight);
|
|
|
|
var row = 0;
|
|
|
|
var column = 0;
|
|
|
|
var immediately;
|
|
|
|
|
|
|
|
$.each(items, function(index, item) {
|
|
|
|
/*
|
|
|
|
if(animate == 'sometimes')
|
|
|
|
immediately = (typeof(item.groupData.row) == 'undefined' || item.groupData.row == row);
|
|
|
|
else
|
|
|
|
*/
|
|
|
|
immediately = !animate;
|
|
|
|
|
|
|
|
item.setBounds(box, immediately);
|
|
|
|
/*
|
|
|
|
item.groupData.column = column;
|
|
|
|
item.groupData.row = row;
|
|
|
|
*/
|
|
|
|
|
|
|
|
box.left += box.width + padding;
|
|
|
|
column++;
|
|
|
|
if(column == columns) {
|
|
|
|
box.left = bounds.left;
|
|
|
|
box.top += (box.height * yScale) + padding;
|
|
|
|
column = 0;
|
|
|
|
row++;
|
|
|
|
}
|
|
|
|
});
|
2010-03-29 11:55:13 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|