+ First pass at a snap-to functionality for groups
authorAza Raskin <aza@mozilla.com>
Sun, 16 May 2010 11:30:45 -0700
changeset 49890 f0366eb62aed35eaec230a5a26405408d9290f8a
parent 49889 00a1aa6cdc41b5ff04074c0a265876cb7616d8fd
child 49891 6151f56c8cf77d022edf2416aa59ec6d290aa30f
child 49892 9ddbe0504565bd5f00fb4340359044d089d8b2ff
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
+ First pass at a snap-to functionality for groups - The jQuery UI's draggable snap-to just wasn't specific enough for our use case + TODO: Need to add the snap ability to resizing groups
browser/base/content/tabcandy/app/groups.js
--- a/browser/base/content/tabcandy/app/groups.js
+++ b/browser/base/content/tabcandy/app/groups.js
@@ -1001,32 +1001,79 @@ var DragInfo = function(element, event) 
     }
   } else
     Page.setActiveTab(this.item);
 };
 
 DragInfo.prototype = {
   // ----------  
   snap: function(event, ui){
-    //window.console.log( event, ui);
-    // Step 1: Find the closest group by edge
+    var me = this.item;
+    function closeTo(a,b){ return Math.abs(a-b) <= 12 }
+        
+    // Snap inline to the tops of other groups.
+    var closestTop = null;
+    var minDist = Infinity;
+    for each(var group in Groups.groups){
+      // Exlcude the current group
+      if( group == me ) continue;      
+      var dist = Math.abs(group.bounds.top - me.bounds.top);
+      if( dist < minDist ){
+        minDist = dist;
+        closestTop = group.bounds.top;
+      }
+    }
     
-    // Step 2: Match to the to
+    if( closeTo(ui.position.top, closestTop) ){
+      ui.position.top = closestTop;
+    }
+      
+    // Snap to the right of other groups by topish left corner
+    var topLeft = new Point( me.bounds.left, ui.position.top + 25 );
+    var other = Groups.findGroupClosestToPoint(topLeft, {exclude:me});     
+    var closestRight = other.bounds.right + 20;
+    if( closeTo(ui.position.left, closestRight) ){
+      ui.position.left = closestRight;
+    }
+
+    // Snap to the bottom of other groups by top leftish corner
+    var topLeft = new Point( me.bounds.left+25, ui.position.top);
+    var other = Groups.findGroupClosestToPoint(topLeft, {exclude:me});     
+    var closestBottom = other.bounds.bottom + 20;
+    if( closeTo(ui.position.top, closestBottom) ){
+      ui.position.top = closestBottom;
+    }
     
-    // Step 3: Profit!
+    // Snap inline to the right of other groups by top right corner
+    var topRight = new Point( me.bounds.right, ui.position.top);
+    var other = Groups.findGroupClosestToPoint(topRight, {exclude:me});     
+    var closestRight = other.bounds.right;
+    if( closeTo(ui.position.left + me.bounds.width, closestRight) ){
+      ui.position.left = closestRight - me.bounds.width;
+    }      
+        
+    // Snap inline to the left of other groups by top left corner
+    var topLeft = new Point( me.bounds.left, ui.position.top);
+    var other = Groups.findGroupClosestToPoint(topLeft, {exclude:me});     
+    var closestLeft = other.bounds.left;
+    if( closeTo(ui.position.left, closestLeft) ){
+      ui.position.left = closestLeft;
+    }  
     
-    // TODO: Refactor these comments. Also, does this routine belong in DragInfo?
+    // Step 4: Profit?
+    return ui;
     
   },
   
   // ----------  
   // Function: drag
   // Called in response to a jQuery-UI draggable "drag" event.
   drag: function(event, ui) {
     if(this.item.isAGroup) {
+      ui = this.snap(event,ui);      
       var bb = this.item.getBounds();
       bb.left = ui.position.left;
       bb.top = ui.position.top;
       this.item.setBounds(bb, true);
     } else
       this.item.reloadBounds();
       
     if(this.parent && this.parent.expanded) {
@@ -1369,16 +1416,65 @@ window.Groups = {
   // Function: getOrphanedTabs
   // Returns an array of all tabs that aren't in a group
   getOrphanedTabs: function(){
     var tabs = TabItems.getItems();
     tabs = tabs.filter(function(tab){
       return tab.parent == null;
     })
     return tabs
+  },
+  
+  // ---------
+  // Function: findGroupClosestToPoint
+  // Given a <Point> returns an object which contains
+  // the group and it's closest side: {group:<Group>, side:"top|left|right|bottom"}
+  //
+  // Paramters
+  //  - <Point>
+  //  - options
+  //   + exclude: <Group> will exclude a group for being matched against
+  findGroupClosestToPoint: function(point, options){
+    minDist = Infinity;
+    closestGroup = null;
+    var onSide = null;
+    for each(var group in this.groups){
+      // Step 0: Exlcude any exluded groups
+      if( options && options.exclude && options.exclude == group ) continue; 
+      
+      // Step 1: Find the closest side
+      var sideDists = [];
+      sideDists.push( [Math.abs(group.bounds.top    - point.y), "top"] );
+      sideDists.push( [Math.abs(group.bounds.bottom - point.y), "bottom"] );      
+      sideDists.push( [Math.abs(group.bounds.left   - point.x), "left"] );
+      sideDists.push( [Math.abs(group.bounds.right  - point.x), "right"] );
+      sideDists.sort(function(a,b){return a[0]-b[0]});
+      var closestSide = sideDists[0][1];
+      
+      // Step 2: Find where one that side is closest to the point.
+      if( closestSide == "top" || closestSide == "bottom" ){
+        var closestPoint = new Point(0, group.bounds[closestSide]);
+        closestPoint.x = Math.max(Math.min(point.x, group.bounds.right), group.bounds.left);
+      } else {
+        var closestPoint = new Point(group.bounds[closestSide], 0);
+        closestPoint.y = Math.max(Math.min(point.y, group.bounds.bottom), group.bounds.top);        
+      }
+      
+      // Step 3: Calculate the distance from the closest point on the rect
+      // to the given point      
+      var dist = closestPoint.distance(point);
+      if( dist < minDist ){
+        closestGroup = group;
+        onSide = closestSide;
+        minDist = dist;
+      }
+      
+    }
+    
+    return closestGroup;
   }
   
 };
 
 // ----------
 Groups.init();
 
 // ##########