+ Removed unnecessary margin and padding from tabs and groups (was messing up layout)

+ Now compensating for padding in tabs when laying them out
+ Tab titles are now a single line, cut at the width of the tab (but no ellipsis yet)
+ Groups now have the move cursor
+ Tab resizing now works, and is disabled when a tab is in a group
+ We now have a "new tabs" group that new tabs go into (created as needed)
+ New layout algorithm for tabs inside a group. It's not as space efficient as the last one, but the unused space is at the bottom rather than the right side, and in general I think it behaves more like people expect.
+ Groups now have a minimum width when resizing
+ If you drag the second to last tab out of a group and don't return it to that group, the group now dissolves.
+ When a tab gets below a certain size, its thumbnail and close box are hidden
+ When a tab's title gets below a certain size, it is hidden.
This commit is contained in:
Ian Gilman 2010-04-12 17:20:35 -07:00
parent 3a944f5cd5
commit 6951057a1c
5 changed files with 172 additions and 85 deletions

View File

@ -33,6 +33,7 @@ window.Group = function(listOfEls, options) {
this._children = []; // an array of Items
this._padding = 30;
this.defaultSize = new Point(TabItems.tabWidth * 1.5, TabItems.tabHeight * 1.5);
this.title = '';
var self = this;
@ -199,7 +200,7 @@ window.Group.prototype = $.extend(new Item(), new Subscribable(), {
// ___ Deal with children
if(this._children.length) {
if(css.width || css.height) {
this.arrange({animate: !immediately});
this.arrange({animate: !immediately}); //(immediately ? 'sometimes' : true)});
} else if(css.left || css.top) {
$.each(this._children, function(index, child) {
var box = child.getBounds();
@ -229,6 +230,10 @@ window.Group.prototype = $.extend(new Item(), new Subscribable(), {
// ----------
setZ: function(value) {
$(this.container).css({zIndex: value});
if(this.$debug)
this.$debug.css({zIndex: value + 1});
$.each(this._children, function(index, child) {
child.setZ(value + 1);
});
@ -294,6 +299,7 @@ window.Group.prototype = $.extend(new Item(), new Subscribable(), {
$el.droppable("disable");
item.setZ(this.getZ() + 1);
item.groupData = {};
item.addOnClose(this, function() {
self.remove($el);
@ -360,59 +366,71 @@ window.Group.prototype = $.extend(new Item(), new Subscribable(), {
// ----------
arrange: function(options) {
if( options && options.animate == false )
animate = false;
else
var animate;
if(!options || typeof(options.animate) == 'undefined')
animate = true;
else
animate = options.animate;
if(typeof(options) == 'undefined')
options = {};
var bb = this.getContentBounds();
bb.inset(6, 6);
var tabAspect = TabItems.tabHeight / TabItems.tabWidth;
var count = this._children.length;
if(!count)
return;
var columns = 1;
var padding = 0;
var rows;
var tabWidth;
var tabHeight;
var count = this._children.length;
var bbAspect = bb.width/bb.height;
var tabAspect = 4/3;
function figure() {
rows = Math.ceil(count / columns);
tabWidth = (bb.width / columns) - padding;
tabHeight = tabWidth * tabAspect;
}
function howManyColumns( numRows, count ){ return Math.ceil(count/numRows) }
figure();
var count = this._children.length;
var best = {cols: 0, rows:0, area:0};
for(var numRows=1; numRows<=count; numRows++){
numCols = howManyColumns( numRows, count);
var w = numCols*tabAspect;
var h = numRows;
while(rows > 1 && tabHeight * rows > bb.height) {
columns++;
figure();
}
if(rows == 1) {
tabWidth = Math.min(bb.width / count, bb.height / tabAspect);
tabHeight = tabWidth * tabAspect;
}
var box = new Rect(bb.left, bb.top, tabWidth, tabHeight);
var row = 0;
var column = 0;
var immediately;
$.each(this._children, function(index, child) {
if(animate == 'sometimes')
immediately = (typeof(child.groupData.row) == 'undefined' || child.groupData.row == row);
else
immediately = !animate;
child.setBounds(box, immediately);
child.groupData.column = column;
child.groupData.row = row;
// We are width constrained
if( w/bb.width >= h/bb.height ) var scale = bb.width/w;
// We are height constrained
else var scale = bb.height/h;
var w = w*scale;
var h = h*scale;
if( w*h >= best.area ){
best.numRows = numRows;
best.numCols = numCols;
best.area = w*h;
best.w = w;
best.h = h;
box.left += box.width + padding;
column++;
if(column == columns) {
box.left = bb.left;
box.top += box.height + padding;
column = 0;
row++;
}
}
var padAmount = .15;
var pad = padAmount * (best.w/best.numCols);
var tabW = (best.w-pad)/best.numCols - pad;
var tabH = (best.h-pad)/best.numRows - pad;
var x = pad*2; var y=pad; var numInCol = 0;
for each(var item in this._children){
item.setBounds(new Rect(x + bb.left, y + bb.top, tabW, tabH), !animate);
x += tabW + pad;
numInCol += 1;
if( numInCol >= best.numCols )
[x, numInCol, y] = [pad*2, 0, y+tabH+pad];
}
});
},
// ----------
@ -459,6 +477,8 @@ window.Group.prototype = $.extend(new Item(), new Subscribable(), {
$(this.container).resizable({
handles: "se",
aspectRatio: false,
minWidth: 60,
minHeight: 90,
resize: function(){
self.reloadBounds();
},
@ -479,6 +499,7 @@ var DragInfo = function(element) {
this.el = element;
this.$el = $(this.el);
this.item = Items.item(this.el);
this.parent = this.$el.data('group');
this.$el.data('isDragging', true);
this.item.setZ(9999);
@ -493,6 +514,10 @@ DragInfo.prototype = {
// ----------
stop: function() {
this.$el.data('isDragging', false);
if(this.parent && this.parent != this.$el.data('group') && this.parent._children.length <= 1)
this.parent.remove(this.parent._children[0]);
if(this.item && !this.$el.hasClass('willGroup') && !this.$el.data('group')) {
this.item.setZ(drag.zIndex);
drag.zIndex++;
@ -620,6 +645,24 @@ window.Groups = {
$.each(toRemove, function(index, group) {
group.removeAll();
});
},
// ----------
newTab: function(tabItem) {
var groupTitle = 'New Tabs';
var array = jQuery.grep(this.groups, function(group) {
return group.title == groupTitle;
});
var $el = $(tabItem.container);
if(array.length)
array[0].add($el);
else {
var p = Page.findOpenSpaceFor($el); // TODO shouldn't know about Page
tabItem.setPosition(p.x, p.y, true);
var group = new Group([$el]);
group.title = groupTitle;
}
}
};

View File

@ -3,6 +3,7 @@ window.TabItem = function(container, tab) {
this._init(container);
this.tab = tab;
this.defaultSize = new Point(TabItems.tabWidth, TabItems.tabHeight);
this.setResizable(true);
};
window.TabItem.prototype = $.extend(new Item(), {
@ -14,6 +15,10 @@ window.TabItem.prototype = $.extend(new Item(), {
// ----------
setBounds: function(rect, immediately) {
var $container = $(this.container);
var $title = $('.tab-title', $container);
var $thumb = $('.thumb', $container);
var $close = $('.close', $container);
var css = {};
if(rect.left != this.bounds.left)
@ -23,13 +28,20 @@ window.TabItem.prototype = $.extend(new Item(), {
css.top = rect.top;
if(rect.width != this.bounds.width) {
css.width = rect.width;
var scale = rect.width / TabItems.tabWidth;
var widthExtra = parseInt($container.css('padding-left'))
+ parseInt($container.css('padding-right'));
css.width = rect.width - widthExtra;
var scale = css.width / TabItems.tabWidth;
css.fontSize = TabItems.fontSize * scale;
}
if(rect.height != this.bounds.height)
css.height = rect.height;
if(rect.height != this.bounds.height) {
var heightExtra = parseInt($container.css('padding-top'))
+ parseInt($container.css('padding-bottom'));
css.height = rect.height - heightExtra;
}
if($.isEmptyObject(css))
return;
@ -37,14 +49,40 @@ window.TabItem.prototype = $.extend(new Item(), {
this.bounds.copy(rect);
if(immediately) {
$(this.container).css(css);
$container.css(css);
/*
if(css.fontSize) {
if(css.fontSize < 8)
$title.hide();
else
$title.show();
}
*/
} else {
TabMirror.pausePainting();
$(this.container).animate(css, {complete: function() {
$container.animate(css, {complete: function() {
TabMirror.resumePainting();
}}).dequeue();
}
if(css.fontSize) {
if(css.fontSize < 8)
$title.fadeOut();
else
$title.fadeIn();
}
if(css.width) {
if(css.width < 30) {
$thumb.fadeOut();
$close.fadeOut();
} else {
$thumb.fadeIn();
$close.fadeIn();
}
}
this._updateDebugBounds();
},
@ -66,11 +104,38 @@ window.TabItem.prototype = $.extend(new Item(), {
// ----------
removeOnClose: function(referenceObject) {
this.tab.mirror.removeOnClose(referenceObject);
},
// ----------
setResizable: function(value){
var self = this;
var $resizer = $('.expander', this.container);
if(value) {
$resizer.fadeIn();
$(this.container).resizable({
handles: "se",
aspectRatio: true,
minWidth: TabItems.minTabWidth,
minHeight: TabItems.minTabWidth * (TabItems.tabHeight / TabItems.tabWidth),
resize: function(){
self.reloadBounds();
},
stop: function(){
self.reloadBounds();
self.pushAway();
}
});
} else {
$resizer.fadeOut();
$(this.container).resizable('destroy');
}
}
});
// ##########
window.TabItems = {
minTabWidth: 40,
tabWidth: 160,
tabHeight: 120,
fontSize: 9,
@ -149,19 +214,14 @@ window.TabItems = {
$("<div class='close'></div>").appendTo($div);
$("<div class='expander'></div>").appendTo($div);
function onNewTab(){
var p = Page.findOpenSpaceFor($div); // TODO shouldn't know about Page
$div.css({left: p.x, top: p.y, width:TabItems.tabWidth, height:TabItems.tabHeight});
}
// This code deals with adding a new tab.
if($div.length == 1) onNewTab();
$div.each(function() {
var tab = Tabs.tab(this);
$(this).data('tabItem', new TabItem(this, tab));
});
if($div.length == 1)
Groups.newTab($div.data('tabItem'));
// TODO: Figure out this really weird bug?
// Why is that:
// $div.find("canvas").data("link").tab.url

View File

@ -52,6 +52,8 @@ TabCanvas.prototype = {
var w = $canvas.attr('width');
var h = $canvas.attr('height');
if(!w || !h)
return;
var fromWin = this.tab.contentWindow;
if(fromWin == null) {
@ -123,8 +125,6 @@ function Mirror(tab, manager) {
if( this.tab.url.match("chrome:") )
div.hide();
this.manager._customize(div);
this.needsPaint = 0;
this.canvasSizeForced = false;
this.el = div.get(0);
@ -138,6 +138,9 @@ function Mirror(tab, manager) {
this.tabCanvas.attach();
this.triggerPaint();
}
this.tab.mirror = this;
this.manager._customize(div);
}
Mirror.prototype = $.extend(new Subscribable(), {
@ -259,7 +262,7 @@ TabMirror.prototype = {
},
_createEl: function(tab){
tab.mirror = new Mirror(tab, this);
new Mirror(tab, this); // sets tab.mirror to itself
},
update: function(tab){

View File

@ -147,8 +147,8 @@ window.Subscribable.prototype = {
return element.referenceElement == referenceElement;
});
if(existing.size) {
Utils.assert('should only ever be one', existing.size == 1);
if(existing.length) {
Utils.assert('should only ever be one', existing.length == 1);
existing[0].callback = callback;
} else {
this.onCloseSubscribers.push({

View File

@ -1,9 +1,4 @@
html {
padding-bottom: 20px;
padding-top: 20px;
padding-left: 20px;
padding-right: 20px;
}
body {
@ -22,7 +17,6 @@ body {
.tab {
position: absolute;
margin: 10px;
padding: 4px 6px 6px 4px;
border: 1px solid rgba(230,230,230,1);
background-color: rgba(245,245,245,1);
@ -110,6 +104,8 @@ body {
text-align: center;
text-shadow: rgba(255,255,255,1) 0 1px;
width: 94.5%;
white-space: nowrap;
overflow: hidden;
}
@ -129,8 +125,7 @@ body {
.group {
position: relative;
float: left;
margin: 20px;
padding: 10px 10px 30px 10px;
cursor: move;
border: 1px solid rgba(230,230,230,1);
background-color: rgba(245,245,245,1);
-moz-border-radius: 0.4em;
@ -144,20 +139,6 @@ body {
.group .expander {
}
/*
.group {
cursor: move;
overflow: hidden;
}
.group-content {
border: 1px inset rgba(0,0,0,.3);
background-color: white;
-moz-box-shadow: inset 2px 2px 12px rgba(0,0,0,.15);
}
*/
// -----------------------------------------------------
// Other
// -----------------------------------------------------
@ -198,7 +179,7 @@ input.name{
opacity: .2;
}
.ui-resizable { position: relative;}
.ui-resizable { }
.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }