Bug 835984 - Push start-ui tiles away in 3d space when pressing them. r=jwilde
authorSam Foster <sfoster@mozilla.com>
Tue, 09 Jul 2013 18:04:08 -0700
changeset 137940 d65cb3cb55f525e110c1523e5ed5d4cdd2f99cd6
parent 137939 f8257d93273a6c36a4aeb6f3fc6332d6a2a4b5b5
child 137941 e54450e7bb5c93ce58e93caad30857f7603def59
push id24939
push userryanvm@gmail.com
push dateWed, 10 Jul 2013 17:49:39 +0000
treeherdermozilla-central@dde4dcd6fa46 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwilde
bugs835984
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 835984 - Push start-ui tiles away in 3d space when pressing them. r=jwilde
browser/metro/base/content/bindings/grid.xml
browser/metro/modules/CrossSlide.jsm
browser/metro/theme/tiles.css
--- a/browser/metro/base/content/bindings/grid.xml
+++ b/browser/metro/base/content/bindings/grid.xml
@@ -635,18 +635,88 @@
               return;
 
             let event = document.createEvent("Events");
             event.initEvent("selectionchange", true, true);
             this.dispatchEvent(event);
           ]]>
         </body>
       </method>
+
+      <method name="bendItem">
+        <parameter name="aItem"/>
+        <parameter name="aEvent"/>
+        <body><![CDATA[
+          // apply the transform to the contentBox element of the item
+          let bendNode = 'richgriditem' == aItem.nodeName && aItem._contentBox;
+          if (!bendNode)
+            return;
+
+          let event = aEvent;
+          let rect = bendNode.getBoundingClientRect();
+          let angle;
+          let x = (event.clientX - rect.left) / rect.width;
+          let y = (event.clientY - rect.top) / rect.height;
+          let perspective = '450px';
+          // scaling factors for the angle of deflection,
+          //   based on the aspect-ratio of the tile
+          let aspectRatio = rect.width/rect.height;
+          let deflectX = 10 * Math.ceil(1/aspectRatio);
+          let deflectY = 10 * Math.ceil(aspectRatio);
+
+          if (Math.abs(x - .5) < .1 && Math.abs(y - .5) < .1) {
+            bendNode.style.transform = "perspective("+perspective+") translateZ(-10px)";
+          }
+          else if (x > y) {
+            if (1 - y > x) {
+              angle = Math.ceil((.5 - y) * deflectY);
+              bendNode.style.transform = "perspective("+perspective+") rotateX(" + angle + "deg)";
+              bendNode.style.transformOrigin = "center bottom";
+            } else {
+              angle = Math.ceil((x - .5) * deflectX);
+              bendNode.style.transform = "perspective("+perspective+") rotateY(" + angle + "deg)";
+              bendNode.style.transformOrigin = "left center";
+            }
+          } else {
+            if (1 - y < x) {
+              angle = -Math.ceil((y - .5) * deflectY);
+              bendNode.style.transform = "perspective("+perspective+") rotateX(" + angle + "deg)";
+              bendNode.style.transformOrigin = "center top";
+            } else {
+              angle = -Math.ceil((.5 - x) * deflectX);
+              bendNode.style.transform = "perspective("+perspective+") rotateY(" + angle + "deg)";
+              bendNode.style.transformOrigin = "right center";
+            }
+          }
+          // mark when bend effect is applied
+          aItem.setAttribute("bending", true);
+        ]]></body>
+      </method>
+      <method name="unbendItem">
+        <parameter name="aItem"/>
+        <body><![CDATA[
+          // clear the 'bend' transform on the contentBox element of the item
+          let bendNode = 'richgriditem' == aItem.nodeName && aItem._contentBox;
+          if (bendNode && aItem.hasAttribute("bending")) {
+            bendNode.style.removeProperty('transform');
+            bendNode.style.removeProperty('transformOrigin');
+            aItem.removeAttribute("bending");
+          }
+        ]]></body>
+      </method>
     </implementation>
     <handlers>
+      <!--  item bend effect handlers -->
+      <handler event="mousedown" button="0" phase="capturing" action="this.bendItem(event.target, event)"/>
+      <handler event="touchstart" action="this.bendItem(event.target, event.touches[0])"/>
+      <handler event="mouseup" button="0" action="this.unbendItem(event.target)"/>
+      <handler event="mouseout" button="0" action="this.unbendItem(event.target)"/>
+      <handler event="touchend" action="this.unbendItem(event.target)"/>
+      <!--  /item bend effect handler -->
+
       <handler event="context-action">
         <![CDATA[
           // context-action is an event fired by the appbar typically
           // which directs us to do something to the selected tiles
           switch (event.action) {
             case "clear":
               this.clearSelection();
               break;
@@ -662,22 +732,26 @@
           // MozCrossSliding is swipe gesture across a tile
           // The tile should follow the drag to reinforce the gesture
           // (with inertia/speedbump behavior)
           let state = event.crossSlidingState;
           let thresholds = this._xslideHandler.thresholds;
           let transformValue;
           switch(state) {
             case "cancelled":
+              this.unbendItem(event.target);
+              event.target.removeAttribute('crosssliding');
               // hopefully nothing else is transform-ing the tile
-              event.target.removeAttribute('crosssliding');
               event.target.style.removeProperty('transform');
               break;
             case "dragging":
             case "selecting":
+              // remove bend/depress effect when a cross-slide begins
+              this.unbendItem(event.target);
+
               event.target.setAttribute("crosssliding", true);
               // just track the mouse in the initial phases of the drag gesture
               transformValue = (event.direction=='x') ?
                                       'translateX('+event.delta+'px)' :
                                       'translateY('+event.delta+'px)';
               event.target.style.transform = transformValue;
               break;
             case "selectSpeedBumping":
@@ -722,17 +796,16 @@
 
     <implementation>
       <property name="isBound" readonly="true" onget="return !!this._icon"/>
       <constructor>
         <![CDATA[
             this.refresh();
         ]]>
       </constructor>
-      <property name="_boundNode" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'anon-tile').parentNode;"/>
       <property name="_contentBox" onget="return document.getAnonymousElementByAttribute(this, 'class', 'tile-content');"/>
       <property name="_textbox" onget="return document.getAnonymousElementByAttribute(this, 'class', 'tile-desc');"/>
       <property name="_top" onget="return document.getAnonymousElementByAttribute(this, 'class', 'tile-start-container');"/>
       <property name="_icon" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'anon-tile-icon');"/>
       <property name="_label" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'anon-tile-label');"/>
       <property name="iconSrc"
                 onset="this._icon.src = val; this.setAttribute('iconURI', val);"
                 onget="return this._icon.src;" />
@@ -846,16 +919,17 @@
         <![CDATA[
           // left-click/touch handler
           this.control.handleItemClick(this, event);
           // Stop this from bubbling, when the richgrid container
           // receives click events, we blur the nav bar.
           event.stopPropagation();
         ]]>
       </handler>
+
       <handler event="contextmenu">
         <![CDATA[
           // fires for right-click, long-click and (keyboard) contextmenu input
           // toggle the selected state of tiles in a grid
           this.control.handleItemContextMenu(this, event);
         ]]>
       </handler>
     </handlers>
--- a/browser/metro/modules/CrossSlide.jsm
+++ b/browser/metro/modules/CrossSlide.jsm
@@ -171,61 +171,63 @@ CrossSlideHandler.prototype = {
     };
   },
 
   _onTouchMove: function(aEvent){
     if (!this.drag) {
       return;
     }
 
-    aEvent.stopPropagation();
-
     if (aEvent.touches.length!==1) {
       // cancel if another touch point gets involved
       return this.cancel(aEvent);
     }
 
     let startPt = this.drag.origin;
     let endPt = this.drag.position = pointFromTouchEvent(aEvent);
 
     let scrollAxis = this.drag.scrollAxis,
         crossAxis = this.drag.crossAxis;
 
     // distance from the origin along the axis perpendicular to scrolling
     let crossAxisDistance = Math.abs(endPt[crossAxis] - startPt[crossAxis]);
     // distance along the scrolling axis
     let scrollAxisDistance = Math.abs(endPt[scrollAxis] - startPt[scrollAxis]);
-
     let currState = this.drag.state;
     let newState = this.getCrossSlideState(crossAxisDistance, scrollAxisDistance);
 
-    if (-1 == newState) {
-      // out of bounds, cancel the event always
-      return this.cancel(aEvent);
+    switch (newState) {
+      case -1 :
+        // dodgy input/out of bounds
+        return this.cancel(aEvent);
+      case CrossSlidingState.STARTED :
+        break;
+      case CrossSlidingState.DRAGGING :
+        if (scrollAxisDistance > this.thresholds.SELECTIONSTART) {
+          // looks like a pan/scroll was intended
+          return this.cancel(aEvent);
+        }
+        // else fall-thru'
+      case CrossSlidingState.SELECTING :
+      case CrossSlidingState.SELECT_SPEED_BUMPING :
+      case CrossSlidingState.SPEED_BUMPING :
+        // we're committed to a cross-slide gesture,
+        // so going out of bounds at this point means aborting
+        if (!withinCone(crossAxisDistance, scrollAxisDistance)) {
+          return this.cancel(aEvent);
+        }
+        // we're mid-gesture, consume this event
+        aEvent.stopPropagation();
+        break;
     }
 
-    let isWithinCone = withinCone(crossAxisDistance, scrollAxisDistance);
-
-    if (currState < CrossSlidingState.SELECTING && !isWithinCone) {
-      // ignore, no progress to report
-      return;
+    if (currState !== newState) {
+      this.drag.state = newState;
+      this._fireProgressEvent( CrossSlidingStateNames[newState], aEvent );
     }
-    if (currState >= CrossSlidingState.SELECTING && !isWithinCone) {
-      // we're committed to a cross-slide gesture,
-      // so going out of bounds at this point means aborting
-      return this.cancel(aEvent);
-    }
-
-    if (currState > newState) {
-      // moved backwards, ignoring
-      return;
-    }
-
-    this.drag.state = newState;
-    this._fireProgressEvent( CrossSlidingStateNames[newState], aEvent );
   },
   _onTouchEnd: function(aEvent){
     if (!this.drag)
       return;
 
     if (this.drag.state < CrossSlidingState.SELECTING) {
       return this.cancel(aEvent);
     }
--- a/browser/metro/theme/tiles.css
+++ b/browser/metro/theme/tiles.css
@@ -226,16 +226,20 @@ richgriditem[pinned]:-moz-locale-dir(rtl
   left: 0;
   right: auto;
 }
 
 richgriditem[customColor] {
   color: #f1f1f1;
 }
 
+richgriditem[bending] > .tile-content {
+  transform-origin: center center;
+}
+
 /* Snapped-view variation
    We use the compact, single-column grid treatment for <=320px */
 
 @media (max-width: 330px) {
 
   richgrid > .richgrid-grid {
     -moz-column-width: auto!important; /* let it flow */
     -moz-column-count: auto!important; /* let it flow */