mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
388 lines
10 KiB
JavaScript
388 lines
10 KiB
JavaScript
(function(){
|
|
|
|
var numCmp = function(a,b){ return a-b; }
|
|
|
|
function min(list){ return list.slice().sort(numCmp)[0]; }
|
|
function max(list){ return list.slice().sort(numCmp).reverse()[0]; }
|
|
|
|
function isEventOverElement(event, el){
|
|
var hit = {nodeName: null};
|
|
var isOver = false;
|
|
|
|
var hiddenEls = [];
|
|
while(hit.nodeName != "BODY" && hit.nodeName != "HTML"){
|
|
hit = document.elementFromPoint(event.clientX, event.clientY);
|
|
if( hit == el ){
|
|
isOver = true;
|
|
break;
|
|
}
|
|
$(hit).hide();
|
|
hiddenEls.push(hit);
|
|
}
|
|
|
|
var hidden;
|
|
[$(hidden).show() for([,hidden] in Iterator(hiddenEls))];
|
|
return isOver;
|
|
}
|
|
|
|
function Group(){}
|
|
Group.prototype = {
|
|
_children: [],
|
|
_container: null,
|
|
_padding: 30,
|
|
|
|
_getBoundingBox: function(){
|
|
var els = this._children;
|
|
var el;
|
|
var boundingBox = {
|
|
top: min( [$(el).position().top for([,el] in Iterator(els))] ),
|
|
left: min( [$(el).position().left for([,el] in Iterator(els))] ),
|
|
bottom: max( [$(el).position().top for([,el] in Iterator(els))] ) + $(els[0]).height(),
|
|
right: max( [$(el).position().left for([,el] in Iterator(els))] ) + $(els[0]).width(),
|
|
};
|
|
boundingBox.height = boundingBox.bottom - boundingBox.top;
|
|
boundingBox.width = boundingBox.right - boundingBox.left;
|
|
return boundingBox;
|
|
},
|
|
|
|
_getContainerBox: function(){
|
|
var pos = $(this._container).position();
|
|
var w = $(this._container).width();
|
|
var h = $(this._container).height();
|
|
return {
|
|
top: pos.top,
|
|
left: pos.left,
|
|
bottom: pos.top + h,
|
|
right: pos.left + w,
|
|
height: h,
|
|
width: w
|
|
}
|
|
},
|
|
|
|
create: function(listOfEls){
|
|
this._children = $(listOfEls).toArray();
|
|
|
|
var boundingBox = this._getBoundingBox();
|
|
var padding = 30;
|
|
var container = $("<div class='group'/>")
|
|
.css({
|
|
position: "absolute",
|
|
top: boundingBox.top-padding,
|
|
left: boundingBox.left-padding,
|
|
width: boundingBox.width+padding*2,
|
|
height: boundingBox.height+padding*2,
|
|
zIndex: -100,
|
|
opacity: 0,
|
|
})
|
|
.data("group", this)
|
|
.appendTo("body")
|
|
.animate({opacity:1.0}).dequeue();
|
|
|
|
var resizer = $("<div class='resizer'/>")
|
|
.css({
|
|
position: "absolute",
|
|
width: 16, height: 16,
|
|
bottom: 0, right: 0,
|
|
}).appendTo(container);
|
|
|
|
var titlebar = $("<div class='titlebar'><input class='name' value=''/><div class='close'>x</div></div>")
|
|
.appendTo(container)
|
|
|
|
titlebar.css({
|
|
width: container.width(),
|
|
position: "relative",
|
|
top: -(titlebar.height()+2),
|
|
left: -1,
|
|
})
|
|
|
|
// On delay, show the title bar.
|
|
var shouldShow = false;
|
|
container.mouseover(function(){
|
|
shouldShow = true;
|
|
setTimeout(function(){
|
|
if( shouldShow == false ) return;
|
|
container.find("input").focus();
|
|
titlebar
|
|
.css({width: container.width()})
|
|
.animate({ opacity: 1}).dequeue();
|
|
}, 500);
|
|
}).mouseout(function(e){
|
|
shouldShow = false;
|
|
if( isEventOverElement(e, container.get(0) )) return;
|
|
titlebar.animate({opacity:0}).dequeue();
|
|
})
|
|
|
|
this._container = container;
|
|
|
|
this._addHandlers(container);
|
|
this._updateGroup();
|
|
|
|
var els = this._children;
|
|
this._children = [];
|
|
for(var i in els){
|
|
this.add( els[i] );
|
|
}
|
|
},
|
|
|
|
add: function(el){
|
|
if($.inArray(el, this._children) == -1)
|
|
this._children.push( el );
|
|
|
|
$(el).droppable("disable");
|
|
|
|
this._updateGroup();
|
|
this.arrange();
|
|
},
|
|
|
|
remove: function(el){
|
|
$(el).data("toRemove", true);
|
|
this._children = this._children.filter(function(child){
|
|
if( $(child).data("toRemove") == true ){
|
|
$(child).data("group", null);
|
|
scaleTab( $(child), 160/$(child).width());
|
|
$(child).droppable("enable");
|
|
return false;
|
|
}
|
|
else return true;
|
|
})
|
|
$(el).data("toRemove", false);
|
|
|
|
if( this._children.length == 0 ){
|
|
this._container.fadeOut(function() $(this).remove());
|
|
} else {
|
|
this.arrange();
|
|
}
|
|
|
|
},
|
|
|
|
_updateGroup: function(){
|
|
var self = this;
|
|
this._children.forEach(function(el){
|
|
$(el).data("group", self);
|
|
});
|
|
},
|
|
|
|
arrange: function(){
|
|
if( this._children.length < 2 ) return;
|
|
var bb = this._getContainerBox();
|
|
var tab;
|
|
|
|
// TODO: This is an hacky heuristic. Fix this layout algorithm.
|
|
var pad = 10;
|
|
var w = parseInt(Math.sqrt(((bb.height+pad) * (bb.width+pad))/(this._children.length+4)));
|
|
var h = w * (2/3);
|
|
|
|
var x=pad;
|
|
var y=pad;
|
|
for([,tab] in Iterator(this._children)){
|
|
scaleTab( $(tab), w/$(tab).width());
|
|
$(tab).animate({
|
|
top:y+bb.top, left:x+bb.left,
|
|
}, 250);
|
|
|
|
x += w+pad;
|
|
if( x+w+pad > $(this._container).width()){x = pad;y += h+pad;}
|
|
}
|
|
|
|
},
|
|
|
|
_addHandlers: function(container){
|
|
var self = this;
|
|
|
|
$(container).draggable({
|
|
start: function(){
|
|
$(container).data("origPosition", $(container).position());
|
|
self._children.forEach(function(el){
|
|
$(el).data("origPosition", $(el).position());
|
|
});
|
|
},
|
|
drag: function(e, ui){
|
|
var origPos = $(container).data("origPosition");
|
|
dX = ui.offset.left - origPos.left;
|
|
dY = ui.offset.top - origPos.top;
|
|
$(self._children).each(function(){
|
|
$(this).css({
|
|
left: $(this).data("origPosition").left + dX,
|
|
top: $(this).data("origPosition").top + dY
|
|
})
|
|
})
|
|
}
|
|
});
|
|
|
|
|
|
$(container).droppable({
|
|
over: function(){
|
|
$dragged.addClass("willGroup");
|
|
},
|
|
out: function(){
|
|
$dragged.data("group").remove($dragged);
|
|
$dragged.removeClass("willGroup");
|
|
},
|
|
drop: function(){
|
|
$dragged.removeClass("willGroup");
|
|
self.add( $dragged.get(0) )
|
|
},
|
|
accept: ".tab",
|
|
});
|
|
|
|
$(container).resizable({
|
|
handles: "se",
|
|
aspectRatio: true,
|
|
stop: function(){
|
|
self.arrange();
|
|
}
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
var zIndex = 100;
|
|
var $dragged = null;
|
|
var timeout = null;
|
|
|
|
window.Groups = {
|
|
Group: Group,
|
|
|
|
dragOptions: {
|
|
start:function(){
|
|
$dragged = $(this);
|
|
$dragged.data('isDragging', true);
|
|
},
|
|
stop: function(){
|
|
$dragged.data('isDragging', false);
|
|
$(this).css({zIndex: zIndex});
|
|
$dragged = null;
|
|
zIndex += 1;
|
|
},
|
|
zIndex: 999,
|
|
},
|
|
|
|
dropOptions: {
|
|
accept: ".tab",
|
|
tolerance: "pointer",
|
|
greedy: true,
|
|
drop: function(e){
|
|
$target = $(e.target);
|
|
|
|
// Only drop onto the top z-index
|
|
if( $target.css("zIndex") < $dragged.data("topDropZIndex") ) return;
|
|
$dragged.data("topDropZIndex", $target.css("zIndex") );
|
|
$dragged.data("topDrop", $target);
|
|
|
|
// This strange timeout thing solves the problem of when
|
|
// something is dropped onto multiple potential drop targets.
|
|
// We wait a little bit to see get all drops, and then we have saved
|
|
// the top-most one and drop onto that.
|
|
clearTimeout( timeout );
|
|
var dragged = $dragged;
|
|
var target = $target;
|
|
timeout = setTimeout( function(){
|
|
dragged.removeClass("willGroup")
|
|
|
|
dragged.animate({
|
|
top: target.position().top+15,
|
|
left: target.position().left+15,
|
|
}, 100);
|
|
|
|
setTimeout( function(){
|
|
var group = $(target).data("group");
|
|
if( group == null ){
|
|
var group = new Group();
|
|
group.create( [target, dragged] );
|
|
} else {
|
|
group.add( dragged.get(0) )
|
|
}
|
|
|
|
}, 100);
|
|
|
|
|
|
}, 10 );
|
|
|
|
|
|
},
|
|
over: function(e){
|
|
$dragged.addClass("willGroup");
|
|
$dragged.data("topDropZIndex", 0);
|
|
},
|
|
out: function(){
|
|
$dragged.removeClass("willGroup");
|
|
}
|
|
},
|
|
|
|
arrange: function() {
|
|
var $groups = $('.group');
|
|
var count = $groups.length;
|
|
var columns = Math.ceil(Math.sqrt(count));
|
|
var rows = ((columns * columns) - count >= columns ? columns - 1 : columns);
|
|
var padding = 12;
|
|
var startX = padding;
|
|
var startY = 100;
|
|
var totalWidth = window.innerWidth - startX;
|
|
var totalHeight = window.innerHeight - startY;
|
|
var w = (totalWidth / columns) - padding;
|
|
var h = (totalHeight / rows) - padding;
|
|
var x = startX;
|
|
var y = startY;
|
|
|
|
$groups.each(function(i) {
|
|
$(this).css({left: x, top: y, width: w, height: h});
|
|
|
|
$(this).data('group').arrange();
|
|
|
|
x += w + padding;
|
|
if(i % columns == columns - 1) {
|
|
x = startX;
|
|
y += h + padding;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
function scaleTab( el, factor ){
|
|
var $el = $(el);
|
|
|
|
$el.animate({
|
|
width: $el.width()*factor,
|
|
height: $el.height()*factor,
|
|
fontSize: parseInt($el.css("fontSize"))*factor,
|
|
},250).dequeue();
|
|
}
|
|
|
|
|
|
$(".tab").data('isDragging', false)
|
|
.draggable(window.Groups.dragOptions)
|
|
.droppable(window.Groups.dropOptions);
|
|
|
|
$(".tab").click(function(){
|
|
// ZOOM!
|
|
|
|
var ffVersion = parseFloat(navigator.userAgent.match(/\d{8}.*(\d\.\d)/)[1]);
|
|
if( ffVersion < 3.7 ) Utils.error("css-transitions require Firefox 3.7+");
|
|
|
|
var [w,h] = [$(this).width(), $(this).height()];
|
|
var origPos = $(this).position();
|
|
var scale = window.innerWidth/w;
|
|
var fontSize = parseInt($(this).css("font-size"));
|
|
|
|
$(this).addClass("scale-animate").css({
|
|
top: 0, left: 0,
|
|
width:w*scale, height:h*scale,
|
|
fontSize: fontSize*scale
|
|
}).bind("transitionend", function(e){
|
|
// We will get one of this events for every property CSS-animated...
|
|
// I choose one randomly (width) and only do things for that.
|
|
if( e.originalEvent.propertyName != "width" ) return;
|
|
|
|
// Switch tabs, and the re-size and re-position the animated
|
|
// tab image.
|
|
var self = this;
|
|
setTimeout(function(){
|
|
$(self)
|
|
.removeClass("scale-animate")
|
|
.css({top: origPos.top, left: origPos.left, width:w, height:h, fontSize:fontSize});
|
|
}, 500);
|
|
|
|
})
|
|
})
|
|
|
|
})(); |