Web Inspector: sidebar panels shouldn't be added as subviews unless visible
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / Sidebar.js
index 9b0872c..3768485 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.Sidebar = function(element, side, sidebarPanels, role, label) {
-    WebInspector.Object.call(this);
-
-    console.assert(!side || side === WebInspector.Sidebar.Sides.Left || side === WebInspector.Sidebar.Sides.Right);
-    this._side = side || WebInspector.Sidebar.Sides.Left;
-
-    this._element = element || document.createElement("div");
-    this._element.classList.add(WebInspector.Sidebar.StyleClassName);
-    this._element.classList.add(WebInspector.Sidebar.CollapsedStyleClassName);
-    this._element.classList.add(this._side);
-
-    this._element.setAttribute("role", role || "group");
-    if (label)
-        this._element.setAttribute("aria-label", label);
+WI.Sidebar = class Sidebar extends WI.View
+{
+    constructor(element, side, sidebarPanels, role, label, hasNavigationBar)
+    {
+        super(element);
 
-    this._resizeElement = document.createElement("div");
-    this._resizeElement.classList.add(WebInspector.Sidebar.ResizeElementStyleClassName);
-    this._resizeElement.addEventListener("mousedown", this._resizerMouseDown.bind(this), false);
-    this._resizeElement.addEventListener("dblclick", this._resizerDoubleClicked.bind(this), false);
-    this._element.insertBefore(this._resizeElement, this._element.firstChild);
+        console.assert(!side || side === WI.Sidebar.Sides.Left || side === WI.Sidebar.Sides.Right);
+        this._side = side || WI.Sidebar.Sides.Left;
+        this._collapsed = true;
 
-    this._sidebarPanels = [];
+        this.element.classList.add("sidebar", this._side, WI.Sidebar.CollapsedStyleClassName);
 
-    if (sidebarPanels) {
-        for (var i = 0; i < sidebarPanels.length; ++i)
-            this.addSidebarPanel(sidebarPanels[i]);
-    }
-};
+        this.element.setAttribute("role", role || "group");
+        if (label)
+            this.element.setAttribute("aria-label", label);
 
-WebInspector.Object.addConstructorFunctions(WebInspector.Sidebar);
+        if (hasNavigationBar) {
+            this.element.classList.add("has-navigation-bar");
 
-WebInspector.Sidebar.StyleClassName = "sidebar";
-WebInspector.Sidebar.CollapsedStyleClassName = "collapsed";
-WebInspector.Sidebar.ResizeElementStyleClassName = "resizer";
-WebInspector.Sidebar.AbsoluteMinimumWidth = 200;
+            this._navigationBar = new WI.SidebarNavigationBar(null, null, "tablist");
+            this._navigationBar.addEventListener(WI.NavigationBar.Event.NavigationItemSelected, this._navigationItemSelected, this);
+            this.addSubview(this._navigationBar);
+        }
 
-WebInspector.Sidebar.Sides = {};
-WebInspector.Sidebar.Sides.Right = "right";
-WebInspector.Sidebar.Sides.Left = "left";
+        this._resizer = new WI.Resizer(WI.Resizer.RuleOrientation.Vertical, this);
+        this.element.insertBefore(this._resizer.element, this.element.firstChild);
 
-WebInspector.Sidebar.Event = {
-    SidebarPanelSelected: "sidebar-sidebar-panel-selected",
-    CollapsedStateDidChange: "sidebar-sidebar-collapsed-state-did-change",
-    WidthDidChange: "sidebar-width-did-change",
-};
+        this._sidebarPanels = [];
 
-WebInspector.Sidebar.prototype = {
-    constructor: WebInspector.Sidebar,
+        if (sidebarPanels) {
+            for (let sidebarPanel of sidebarPanels)
+                this.addSidebarPanel(sidebarPanel);
+        }
+    }
 
     // Public
 
-    addSidebarPanel: function(sidebarPanel)
+    addSidebarPanel(sidebarPanel)
     {
-        console.assert(sidebarPanel instanceof WebInspector.SidebarPanel);
-        if (!(sidebarPanel instanceof WebInspector.SidebarPanel))
+        this.insertSidebarPanel(sidebarPanel, this._sidebarPanels.length);
+    }
+
+    insertSidebarPanel(sidebarPanel, index)
+    {
+        console.assert(sidebarPanel instanceof WI.SidebarPanel);
+        if (!(sidebarPanel instanceof WI.SidebarPanel))
             return;
 
         console.assert(!sidebarPanel.parentSidebar);
         if (sidebarPanel.parentSidebar)
             return;
 
-        sidebarPanel._parentSidebar = this;
-
-        this._sidebarPanels.push(sidebarPanel);
-        this._element.appendChild(sidebarPanel.element);
+        console.assert(index >= 0 && index <= this._sidebarPanels.length);
+        this._sidebarPanels.splice(index, 0, sidebarPanel);
 
-        sidebarPanel.added();
-
-        return sidebarPanel;
-    },
+        if (this._navigationBar) {
+            console.assert(sidebarPanel.navigationItem);
+            this._navigationBar.insertNavigationItem(sidebarPanel.navigationItem, index);
+        }
+    }
 
-    removeSidebarPanel: function(sidebarPanelOrIdentifierOrIndex, index)
+    removeSidebarPanel(sidebarPanelOrIdentifierOrIndex)
     {
         var sidebarPanel = this.findSidebarPanel(sidebarPanelOrIdentifierOrIndex);
         if (!sidebarPanel)
             return;
 
-        sidebarPanel.willRemove();
+        if (sidebarPanel.visible) {
+            sidebarPanel.hidden();
+            sidebarPanel.visibilityDidChange();
+        }
 
-        sidebarPanel._parentSidebar = null;
+        sidebarPanel.selected = false;
 
         if (this._selectedSidebarPanel === sidebarPanel) {
             var index = this._sidebarPanels.indexOf(sidebarPanel);
-            this.selectedSidebarPanel = this._sidebarPanels[index - 1] || this._sidebarPanels[index + 1];
+            this.selectedSidebarPanel = this._sidebarPanels[index - 1] || this._sidebarPanels[index + 1] || null;
         }
 
         this._sidebarPanels.remove(sidebarPanel);
-        this._element.removeChild(sidebarPanel.element);
-
-        sidebarPanel.removed();
 
-        return sidebarPanel;
-    },
+        if (this._navigationBar) {
+            console.assert(sidebarPanel.navigationItem);
+            this._navigationBar.removeNavigationItem(sidebarPanel.navigationItem);
+        }
+    }
 
     get selectedSidebarPanel()
     {
         return this._selectedSidebarPanel || null;
-    },
+    }
 
     set selectedSidebarPanel(sidebarPanelOrIdentifierOrIndex)
     {
@@ -129,76 +122,69 @@ WebInspector.Sidebar.prototype = {
             return;
 
         if (this._selectedSidebarPanel) {
-            var wasVisible = this._selectedSidebarPanel.visible;
-
+            this._selectedSidebarPanel.hidden();
+            this._selectedSidebarPanel.visibilityDidChange();
             this._selectedSidebarPanel.selected = false;
-
-            if (wasVisible) {
-                this._selectedSidebarPanel.hidden();
-                this._selectedSidebarPanel.visibilityDidChange();
-            }
+            this.removeSubview(this._selectedSidebarPanel);
         }
 
         this._selectedSidebarPanel = sidebarPanel || null;
 
+        if (this._navigationBar)
+            this._navigationBar.selectedNavigationItem = sidebarPanel ? sidebarPanel.navigationItem : null;
+
         if (this._selectedSidebarPanel) {
+            this.addSubview(this._selectedSidebarPanel);
             this._selectedSidebarPanel.selected = true;
-
-            if (this._selectedSidebarPanel.visible) {
-                this._selectedSidebarPanel.shown();
-                this._selectedSidebarPanel.visibilityDidChange();
-            }
+            this._selectedSidebarPanel.shown();
+            this._selectedSidebarPanel.visibilityDidChange();
         }
 
-        this.dispatchEventToListeners(WebInspector.Sidebar.Event.SidebarPanelSelected);
-    },
+        this.dispatchEventToListeners(WI.Sidebar.Event.SidebarPanelSelected);
+    }
 
     get minimumWidth()
     {
-        return WebInspector.Sidebar.AbsoluteMinimumWidth;
-    },
+        if (this._navigationBar)
+            return Math.max(WI.Sidebar.AbsoluteMinimumWidth, this._navigationBar.minimumWidth);
+        if (this._selectedSidebarPanel)
+            return Math.max(WI.Sidebar.AbsoluteMinimumWidth, this._selectedSidebarPanel.minimumWidth);
+        return WI.Sidebar.AbsoluteMinimumWidth;
+    }
 
     get maximumWidth()
     {
-        // FIXME: This is kind of arbitrary and ideally would be a more complex calculation based on the
-        // available space for the sibling elements.
-        return Math.round(window.innerWidth / 3);
-    },
+        return WI.getMaximumSidebarWidth(this);
+    }
 
     get width()
     {
-        return this._element.offsetWidth;
-    },
+        return this.element.offsetWidth;
+    }
 
     set width(newWidth)
     {
         if (newWidth === this.width)
             return;
 
-        newWidth = Math.max(this.minimumWidth, Math.min(newWidth, this.maximumWidth));
-
-        this._element.style.width = newWidth + "px";
-
-        if (!this.collapsed && this._selectedSidebarPanel)
-            this._selectedSidebarPanel.widthDidChange();
-
-        this.dispatchEventToListeners(WebInspector.Sidebar.Event.WidthDidChange);
-    },
+        this._recalculateWidth(newWidth);
+    }
 
     get collapsed()
     {
-        return this._element.classList.contains(WebInspector.Sidebar.CollapsedStyleClassName);
-    },
+        return this._collapsed;
+    }
 
     set collapsed(flag)
     {
-        if (flag === this.collapsed)
+        if (flag === this._collapsed)
             return;
 
-        if (flag)
-            this._element.classList.add(WebInspector.Sidebar.CollapsedStyleClassName);
-        else
-            this._element.classList.remove(WebInspector.Sidebar.CollapsedStyleClassName);
+        this._collapsed = flag || false;
+        this.element.classList.toggle(WI.Sidebar.CollapsedStyleClassName);
+
+        if (!this._collapsed && this._navigationBar)
+            this._navigationBar.needsLayout();
 
         if (this._selectedSidebarPanel) {
             if (this._selectedSidebarPanel.visible)
@@ -207,35 +193,28 @@ WebInspector.Sidebar.prototype = {
                 this._selectedSidebarPanel.hidden();
 
             this._selectedSidebarPanel.visibilityDidChange();
-
-            this._selectedSidebarPanel.widthDidChange();
         }
 
-        this.dispatchEventToListeners(WebInspector.Sidebar.Event.CollapsedStateDidChange);
-        this.dispatchEventToListeners(WebInspector.Sidebar.Event.WidthDidChange);
-    },
+        this.dispatchEventToListeners(WI.Sidebar.Event.CollapsedStateDidChange);
+        this.dispatchEventToListeners(WI.Sidebar.Event.WidthDidChange);
+    }
 
     get sidebarPanels()
     {
         return this._sidebarPanels;
-    },
-
-    get element()
-    {
-        return this._element;
-    },
+    }
 
     get side()
     {
         return this._side;
-    },
+    }
 
-    findSidebarPanel: function(sidebarPanelOrIdentifierOrIndex)
+    findSidebarPanel(sidebarPanelOrIdentifierOrIndex)
     {
         var sidebarPanel = null;
 
-        if (sidebarPanelOrIdentifierOrIndex instanceof WebInspector.SidebarPanel) {
-            if (this._sidebarPanels.contains(sidebarPanelOrIdentifierOrIndex))
+        if (sidebarPanelOrIdentifierOrIndex instanceof WI.SidebarPanel) {
+            if (this._sidebarPanels.includes(sidebarPanelOrIdentifierOrIndex))
                 sidebarPanel = sidebarPanelOrIdentifierOrIndex;
         } else if (typeof sidebarPanelOrIdentifierOrIndex === "number") {
             sidebarPanel = this._sidebarPanels[sidebarPanelOrIdentifierOrIndex];
@@ -249,71 +228,76 @@ WebInspector.Sidebar.prototype = {
         }
 
         return sidebarPanel;
-    },
+    }
 
-    // Private
+    // Protected
 
-    _navigationItemSelected: function(event)
+    resizerDragStarted(resizer)
     {
-        this.selectedSidebarPanel = event.target.selectedNavigationItem ? event.target.selectedNavigationItem.identifier : null;
-    },
+        this._widthBeforeResize = this.width;
+    }
 
-    _resizerDoubleClicked: function(event)
+    resizerDragging(resizer, positionDelta)
     {
-        this.collapsed = !this.collapsed;
+        if (this._side === WI.Sidebar.Sides.Left)
+            positionDelta *= -1;
 
-        event.preventDefault();
-        event.stopPropagation();
-    },
+        if (WI.resolvedLayoutDirection() === WI.LayoutDirection.RTL)
+            positionDelta *= -1;
 
-    _resizerMouseDown: function(event)
+        var newWidth = positionDelta + this._widthBeforeResize;
+        this.width = newWidth;
+        this.collapsed = newWidth < (this.minimumWidth / 2);
+    }
+
+    resizerDragEnded(resizer)
     {
-        if (event.button !== 0 || event.ctrlKey)
+        if (this._widthBeforeResize === this.width)
             return;
 
-        document.body.style.cursor = "col-resize";
-
-        this._resizerMouseMovedEventListener = this._resizerMouseMoved.bind(this);
-        this._resizerMouseUpEventListener = this._resizerMouseUp.bind(this);
+        if (!this.collapsed && this._navigationBar)
+            this._navigationBar.sizeDidChange();
 
-        // Register these listeners on the document so we can track the mouse if it leaves the resizer.
-        document.addEventListener("mousemove", this._resizerMouseMovedEventListener, false);
-        document.addEventListener("mouseup", this._resizerMouseUpEventListener, false);
+        if (!this.collapsed && this._selectedSidebarPanel)
+            this._selectedSidebarPanel.sizeDidChange();
+    }
 
-        event.preventDefault();
-        event.stopPropagation();
-    },
+    // Private
 
-    _resizerMouseMoved: function(event)
+    _recalculateWidth(newWidth = this.width)
     {
-        if (this._side === WebInspector.Sidebar.Sides.Left)
-            var newWidth = event.pageX - this._element.totalOffsetLeft;
-        else
-            var newWidth = this._element.totalOffsetLeft + this._element.offsetWidth - event.pageX;
-
-        this.width = newWidth;
-        this.collapsed = (newWidth < (this.minimumWidth / 2));
-
-        event.preventDefault();
-        event.stopPropagation();
-    },
+        // Need to add 1 because of the 1px border-right.
+        newWidth = Math.ceil(Number.constrain(newWidth, this.minimumWidth + 1, this.maximumWidth));
+        this.element.style.width = `${newWidth}px`;
 
-    _resizerMouseUp: function(event)
-    {
-        if (event.button !== 0 || event.ctrlKey)
+        if (this.collapsed)
             return;
 
-        document.body.style.removeProperty("cursor");
+        if (this._navigationBar)
+            this._navigationBar.updateLayoutIfNeeded(WI.View.LayoutReason.Resize);
 
-        document.removeEventListener("mousemove", this._resizerMouseMovedEventListener, false);
-        document.removeEventListener("mouseup", this._resizerMouseUpEventListener, false);
+        if (this._selectedSidebarPanel)
+            this._selectedSidebarPanel.updateLayoutIfNeeded(WI.View.LayoutReason.Resize);
 
-        delete this._resizerMouseMovedEventListener;
-        delete this._resizerMouseUpEventListener;
+        this.dispatchEventToListeners(WI.Sidebar.Event.WidthDidChange, {newWidth});
+    }
 
-        event.preventDefault();
-        event.stopPropagation();
+    _navigationItemSelected(event)
+    {
+        this.selectedSidebarPanel = event.target.selectedNavigationItem ? event.target.selectedNavigationItem.identifier : null;
     }
 };
 
-WebInspector.Sidebar.prototype.__proto__ = WebInspector.Object.prototype;
+WI.Sidebar.CollapsedStyleClassName = "collapsed";
+WI.Sidebar.AbsoluteMinimumWidth = 200;
+
+WI.Sidebar.Sides = {
+    Right: "right",
+    Left: "left"
+};
+
+WI.Sidebar.Event = {
+    SidebarPanelSelected: "sidebar-panel-selected",
+    CollapsedStateDidChange: "sidebar-collapsed-state-did-change",
+    WidthDidChange: "sidebar-width-did-change",
+};