Bug 686417 - Make the tab sidebar hideable in tablet mode [r=mfinkle]
authorMatt Brubeck <mbrubeck@mozilla.com>
Thu, 22 Sep 2011 19:18:40 -0700
changeset 77370 8547992229cc1d3efdcc9be67ddd9c8256cb7aef
parent 77369 99280262eba3dd39a4ff405a35b8afd7c8bc6d19
child 77371 31f2a1d947be4b750f1e5edccaf0e68da3949b92
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewersmfinkle
bugs686417
milestone9.0a1
Bug 686417 - Make the tab sidebar hideable in tablet mode [r=mfinkle]
mobile/chrome/content/TabsPopup.js
mobile/chrome/content/browser.js
mobile/chrome/content/browser.xul
mobile/themes/core/tablet.css
--- a/mobile/chrome/content/TabsPopup.js
+++ b/mobile/chrome/content/TabsPopup.js
@@ -54,22 +54,33 @@ var TabsPopup = {
   },
 
   get button() {
     delete this.button;
     return this.button = document.getElementById("tool-tabs");
   },
 
   hide: function hide() {
+    if (!Util.isPortrait()) {
+      Elements.urlbarState.removeAttribute("tablet_sidebar");
+      ViewableAreaObserver.update();
+      return;
+    }
     this.box.hidden = true;
     BrowserUI.popPopup(this);
     window.removeEventListener("resize", this.resizeHandler, false);
   },
 
   show: function show() {
+    if (!Util.isPortrait()) {
+      Elements.urlbarState.setAttribute("tablet_sidebar", "true");
+      ViewableAreaObserver.update();
+      return;
+    }
+
     while(this.list.firstChild)
       this.list.removeChild(this.list.firstChild);
 
     let tabs = Browser.tabs;
     tabs.forEach(function(aTab) {
       let item = document.createElement("richlistitem");
       item.className = "tab-popup-item";
       if (aTab.active)
@@ -100,20 +111,24 @@ var TabsPopup = {
     this.box.hidden = false;
     this.box.anchorTo(this.button, "after_end");
     BrowserUI.pushPopup(this, [this.box, this.button]);
 
     window.addEventListener("resize", this.resizeHandler.bind(this), false);
   },
 
   toggle: function toggle() {
-    if (this.box.hidden)
+    if (this.visible)
+      this.hide();
+    else
       this.show();
-    else
-      this.hide();
+  },
+
+  get visible() {
+    return Util.isPortrait() ? !this.box.hidden : Elements.urlbarState.hasAttribute("tablet_sidebar");
   },
 
   resizeHandler: function(aEvent) {
     if (!Util.isPortrait())
       this.hide();
   },
 
   closeTab: function(aTab) {
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -544,16 +544,18 @@ var Browser = {
       x.value = ("x" in aOptions ? aOptions.x : x.value);
 
     this.contentScrollboxScroller.scrollTo(x.value, Number.MAX_VALUE);
     this.pageScrollboxScroller.scrollTo(0, Number.MAX_VALUE);
     this.hideTitlebar();
   },
 
   hideSidebars: function scrollSidebarsOffscreen() {
+    if (Util.isTablet()) // Never scroll the sidebar away in tablet mode.
+      return;
     let rect = Elements.browsers.getBoundingClientRect();
     this.controlsScrollboxScroller.scrollBy(Math.round(rect.left), 0);
     this.tryUnfloatToolbar();
   },
 
   /** Workaround to hide the tabstrip if it is partially visible (bug 524469 and bug 626660) */
   hidePartialTabSidebar: function hidePartialSidebars() {
     let [tabsVisibility,,,] = this.computeSidebarVisibility();
@@ -1258,16 +1260,68 @@ var Browser = {
         break;
       case "Browser:BlockedSite":
         this._handleBlockedSite(aMessage);
         break;
       case "Browser:ErrorPage":
         this._handleErrorPage(aMessage);
         break;
     }
+  },
+
+  _grabbedSidebar: false, // true while the user is dragging the sidebar
+  _sidebarOffset: 0, // tracks how far the sidebar has been dragged
+  _slideMultiplier: 1, // set greater than 1 to amplify sidebar drags (makes swiping easier)
+
+  /**
+   * Call this function in landscape tablet mode to begin dragging the tab sidebar.
+   * Hiding the sidebar makes the viewable area grow; showing the sidebar makes it shrink.
+   */
+  grabSidebar: function grabSidebar() {
+    this._grabbedSidebar = true;
+    ViewableAreaObserver.update();
+
+    if (TabsPopup.visible) {
+      this._setSidebarOffset(0);
+      this._slideMultiplier = 3;
+    } else {
+      // If the tab bar is hidden, un-collapse it but scroll it offscreen.
+      document.getElementById("tabs-sidebar").style.visibility = "visible";
+      this._setSidebarOffset(ViewableAreaObserver.sidebarWidth);
+      this._slideMultiplier = 6;
+    }
+  },
+
+  /** Move the tablet sidebar by aX pixels. */
+  slideSidebarBy: function slideSidebarBy(aX) {
+    this._setSidebarOffset(this._sidebarOffset + (aX * this._slideMultiplier));
+  },
+
+  /** Call this when tablet sidebar dragging is finished. */
+  ungrabSidebar: function ungrabSidebar() {
+    if (!this._grabbedSidebar)
+      return;
+
+    this._grabbedSidebar = false;
+    document.getElementById("tabs-sidebar").style.visibility = "";
+
+    let finalOffset = this._sidebarOffset;
+    this._setSidebarOffset(0);
+
+    if (finalOffset > (ViewableAreaObserver.sidebarWidth / 2))
+      TabsPopup.hide();
+    else
+      TabsPopup.show();
+  },
+
+  /** Move the tablet sidebar. */
+  _setSidebarOffset: function _setSidebarOffset(aOffset) {
+    this._sidebarOffset = aOffset;
+    let scrollX = Util.clamp(aOffset, 0, ViewableAreaObserver.sidebarWidth);
+    Browser.controlsScrollboxScroller.scrollTo(scrollX, 0);
   }
 };
 
 
 Browser.MainDragger = function MainDragger() {
   this._horizontalScrollbar = document.getElementById("horizontal-scroller");
   this._verticalScrollbar = document.getElementById("vertical-scroller");
   this._scrollScales = { x: 0, y: 0 };
@@ -1285,32 +1339,50 @@ Browser.MainDragger.prototype = {
     return { x: true, y: true };
   },
 
   dragStart: function dragStart(clientX, clientY, target, scroller) {
     let browser = getBrowser();
     let bcr = browser.getBoundingClientRect();
     this._contentView = browser.getViewAt(clientX - bcr.left, clientY - bcr.top);
     this._stopAtSidebar = 0;
-    this._panToolbars = !Util.isTablet();
+
+    let isTablet = Util.isTablet();
+    this._panToolbars = !isTablet;
+
+    // In landscape portrait mode, swiping from the left margin drags the tab sidebar.
+    this._grabSidebar = isTablet && !Util.isPortrait() && (clientX - bcr.left < 30);
+
+    if (this._grabSidebar)
+      Browser.grabSidebar();
+
     if (this._sidebarTimeout) {
       clearTimeout(this._sidebarTimeout);
       this._sidebarTimeout = null;
     }
   },
 
   dragStop: function dragStop(dx, dy, scroller) {
+    if (this._grabSidebar) {
+      Browser.ungrabSidebar();
+      return;
+    }
     if (this._contentView && this._contentView._updateCacheViewport)
       this._contentView._updateCacheViewport();
     this._contentView = null;
     this.dragMove(Browser.snapSidebars(), 0, scroller);
     Browser.tryUnfloatToolbar();
   },
 
   dragMove: function dragMove(dx, dy, scroller, aIsKinetic) {
+    if (this._grabSidebar) {
+      Browser.slideSidebarBy(dx);
+      return;
+    }
+
     let doffset = new Point(dx, dy);
     let sidebarOffset = null;
 
     if (this._panToolbars) {
       // If the sidebars are showing, we pan them out of the way before panning the content.
       // The panning distance that should be used for the sidebars in is stored in sidebarOffset,
       // and subtracted from doffset.
       sidebarOffset = this._getSidebarOffset(doffset);
@@ -1359,17 +1431,18 @@ Browser.MainDragger.prototype = {
         // Allow a small margin on both sides to prevent adding scrollbars
         // on small viewport approximation
         const ALLOWED_MARGIN = 5;
         const SCROLL_CORNER_SIZE = 8;
         this._scrollScales = {
           x: (width + ALLOWED_MARGIN) < contentWidth ? (width - SCROLL_CORNER_SIZE) / contentWidth : 0,
           y: (height + ALLOWED_MARGIN) < contentHeight ? (height - SCROLL_CORNER_SIZE) / contentHeight : 0
         }
-        this._showScrollbars();
+        if (!this._grabSidebar)
+          this._showScrollbars();
         break;
       }
       case "PanFinished":
         this._hideScrollbars();
 
         // Update the scroll position of the content
         browser._updateCSSViewport();
         break;
@@ -3155,17 +3228,17 @@ function rendererFactory(aBrowser, aCanv
 /* ViewableAreaObserver is an helper object where width/height represents the
  * size of the currently viewable area in pixels. This is use instead of
  * window.innerHeight/innerWidth because some keyboards does not resize the
  * window but floats over it.
  */
 var ViewableAreaObserver = {
   get width() {
     let width = this._width || window.innerWidth;
-    if (Util.isTablet())
+    if (!Browser._grabbedSidebar && Util.isTablet())
       width -= this.sidebarWidth;
     return width;
   },
 
   get height() {
     let height = (this._height || window.innerHeight);
     if (Util.isTablet())
       height -= BrowserUI.toolbarH;
--- a/mobile/chrome/content/browser.xul
+++ b/mobile/chrome/content/browser.xul
@@ -79,17 +79,17 @@
   <script type="application/javascript" src="chrome://browser/content/browser.js"/>
   <script type="application/javascript" src="chrome://browser/content/browser-ui.js"/>
   <script type="application/javascript" src="chrome://browser/content/browser-scripts.js"/>
   <script type="application/javascript" src="chrome://browser/content/Util.js"/>
   <script type="application/javascript" src="chrome://browser/content/input.js"/>
 
   <broadcasterset id="broadcasterset">
     <broadcaster id="bcast_contentShowing" disabled="false"/>
-    <broadcaster id="bcast_urlbarState" mode="view"/>
+    <broadcaster id="bcast_urlbarState" mode="view" tablet_sidebar="true"/>
     <broadcaster id="bcast_uidiscovery"/>
   </broadcasterset>
 
   <observerset id="observerset">
     <observes id="observe_contentShowing" element="bcast_contentShowing" attribute="disabled" onbroadcast="BrowserUI.updateUIFocus();"/>
   </observerset>
 
   <commandset id="mainCommandSet">
--- a/mobile/themes/core/tablet.css
+++ b/mobile/themes/core/tablet.css
@@ -11,17 +11,17 @@
 .button-actionbar:hover:active {
   background-color: #8db8d8;
 }
 %endif
 
 #toolbar-main[tablet] > .button-actionbar {
   visibility: visible;
 }
-#toolbar-main[tablet] > #tool-tabs {
+#toolbar-main[tablet][tablet_sidebar] > #tool-tabs {
   visibility: collapse;
 }
 
 #controls-scrollbox[tablet] > #controls-sidebar {
   visibility: collapse;
 }
 
 #tabs-spacer[tablet]  {
@@ -108,42 +108,28 @@ documenttab[selected="true"] > vbox > st
   text-align: center;
   display: -moz-box;
 }
 
 #newtab-button[tablet] {
   list-style-image: url("images/newtab-default-tablet-hdpi.png");
 }
 
+#controls-scrollbox[tablet]:not([tablet_sidebar]) > #tabs-sidebar {
+  visibility: collapse;
+}
+
 @media (@orientation@: portrait) {
-  #toolbar-main[tablet] > #tool-tabs {
+  #toolbar-main[tablet][tablet_sidebar] > #tool-tabs {
     visibility: visible;
   }
 
   #controls-scrollbox[tablet] > #tabs-sidebar {
-    border: none;
-%ifdef honeycomb
-    top: @touch_button_xlarge@;
-%else
-    top: -moz-calc(@touch_button_xlarge@ + @margin_normal@);
-%endif
     visibility: collapse;
   }
-
-  #controls-scrollbox[tablet] > #tabs-sidebar:-moz-locale-dir(ltr) {
-    left: 0;
-  }
-  #controls-scrollbox[tablet] > #tabs-sidebar:-moz-locale-dir(rtl) {
-    right: 0;
-  }
-
-  #controls-scrollbox[tablet] > #tabs-sidebar[open] {
-    position: fixed;
-    visibility: visible;
-  }
 }
 
 #identity-container[tablet] #identity-popup-container {
   -moz-stack-sizing: ignore;
   max-width: @identity_popup_tablet_width@;
   min-width: @identity_popup_tablet_width@;
 }