Web Inspector: unify resizer implementations used by DataGrid and Sidebar
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / Sidebar.js
1 /*
2  * Copyright (C) 2013 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.Sidebar = function(element, side, sidebarPanels, role, label) {
27     // FIXME: Convert this to a WebInspector.Object subclass, and call super().
28     // WebInspector.Object.call(this);
29
30     console.assert(!side || side === WebInspector.Sidebar.Sides.Left || side === WebInspector.Sidebar.Sides.Right);
31     this._side = side || WebInspector.Sidebar.Sides.Left;
32
33     this._element = element || document.createElement("div");
34     this._element.classList.add(WebInspector.Sidebar.StyleClassName);
35     this._element.classList.add(WebInspector.Sidebar.CollapsedStyleClassName);
36     this._element.classList.add(this._side);
37
38     this._element.setAttribute("role", role || "group");
39     if (label)
40         this._element.setAttribute("aria-label", label);
41
42     this._resizer = new WebInspector.Resizer(WebInspector.Resizer.RuleOrientation.Vertical, this);
43     this._element.insertBefore(this._resizer.element, this._element.firstChild);
44
45     this._sidebarPanels = [];
46
47     if (sidebarPanels) {
48         for (var i = 0; i < sidebarPanels.length; ++i)
49             this.addSidebarPanel(sidebarPanels[i]);
50     }
51 };
52
53 // FIXME: Move to a WebInspector.Object subclass and we can remove this.
54 WebInspector.Object.deprecatedAddConstructorFunctions(WebInspector.Sidebar);
55
56 WebInspector.Sidebar.StyleClassName = "sidebar";
57 WebInspector.Sidebar.CollapsedStyleClassName = "collapsed";
58 WebInspector.Sidebar.AbsoluteMinimumWidth = 200;
59
60 WebInspector.Sidebar.Sides = {};
61 WebInspector.Sidebar.Sides.Right = "right";
62 WebInspector.Sidebar.Sides.Left = "left";
63
64 WebInspector.Sidebar.Event = {
65     SidebarPanelSelected: "sidebar-sidebar-panel-selected",
66     CollapsedStateDidChange: "sidebar-sidebar-collapsed-state-did-change",
67     WidthDidChange: "sidebar-width-did-change",
68 };
69
70 WebInspector.Sidebar.prototype = {
71     constructor: WebInspector.Sidebar,
72
73     // Public
74
75     addSidebarPanel: function(sidebarPanel)
76     {
77         console.assert(sidebarPanel instanceof WebInspector.SidebarPanel);
78         if (!(sidebarPanel instanceof WebInspector.SidebarPanel))
79             return null;
80
81         console.assert(!sidebarPanel.parentSidebar);
82         if (sidebarPanel.parentSidebar)
83             return null;
84
85         sidebarPanel._parentSidebar = this;
86
87         this._sidebarPanels.push(sidebarPanel);
88         this._element.appendChild(sidebarPanel.element);
89
90         sidebarPanel.added();
91
92         return sidebarPanel;
93     },
94
95     removeSidebarPanel: function(sidebarPanelOrIdentifierOrIndex, index)
96     {
97         var sidebarPanel = this.findSidebarPanel(sidebarPanelOrIdentifierOrIndex);
98         if (!sidebarPanel)
99             return null;
100
101         sidebarPanel.willRemove();
102
103         sidebarPanel._parentSidebar = null;
104
105         if (this._selectedSidebarPanel === sidebarPanel) {
106             var index = this._sidebarPanels.indexOf(sidebarPanel);
107             this.selectedSidebarPanel = this._sidebarPanels[index - 1] || this._sidebarPanels[index + 1] || null;
108         }
109
110         this._sidebarPanels.remove(sidebarPanel);
111         this._element.removeChild(sidebarPanel.element);
112
113         sidebarPanel.removed();
114
115         return sidebarPanel;
116     },
117
118     get selectedSidebarPanel()
119     {
120         return this._selectedSidebarPanel || null;
121     },
122
123     set selectedSidebarPanel(sidebarPanelOrIdentifierOrIndex)
124     {
125         var sidebarPanel = this.findSidebarPanel(sidebarPanelOrIdentifierOrIndex);
126         if (this._selectedSidebarPanel === sidebarPanel)
127             return;
128
129         if (this._selectedSidebarPanel) {
130             var wasVisible = this._selectedSidebarPanel.visible;
131
132             this._selectedSidebarPanel.selected = false;
133
134             if (wasVisible) {
135                 this._selectedSidebarPanel.hidden();
136                 this._selectedSidebarPanel.visibilityDidChange();
137             }
138         }
139
140         this._selectedSidebarPanel = sidebarPanel || null;
141
142         if (this._selectedSidebarPanel) {
143             this._selectedSidebarPanel.selected = true;
144
145             if (this._selectedSidebarPanel.visible) {
146                 this._selectedSidebarPanel.shown();
147                 this._selectedSidebarPanel.visibilityDidChange();
148             }
149         }
150
151         this.dispatchEventToListeners(WebInspector.Sidebar.Event.SidebarPanelSelected);
152     },
153
154     get minimumWidth()
155     {
156         return WebInspector.Sidebar.AbsoluteMinimumWidth;
157     },
158
159     get maximumWidth()
160     {
161         // FIXME: This is kind of arbitrary and ideally would be a more complex calculation based on the
162         // available space for the sibling elements.
163         return Math.round(window.innerWidth / 3);
164     },
165
166     get width()
167     {
168         return this._element.offsetWidth;
169     },
170
171     set width(newWidth)
172     {
173         if (newWidth === this.width)
174             return;
175
176         newWidth = Math.max(this.minimumWidth, Math.min(newWidth, this.maximumWidth));
177
178         this._element.style.width = newWidth + "px";
179
180         if (!this.collapsed && this._selectedSidebarPanel)
181             this._selectedSidebarPanel.widthDidChange();
182
183         this.dispatchEventToListeners(WebInspector.Sidebar.Event.WidthDidChange);
184     },
185
186     get collapsed()
187     {
188         return this._element.classList.contains(WebInspector.Sidebar.CollapsedStyleClassName);
189     },
190
191     set collapsed(flag)
192     {
193         if (flag === this.collapsed)
194             return;
195
196         if (flag)
197             this._element.classList.add(WebInspector.Sidebar.CollapsedStyleClassName);
198         else
199             this._element.classList.remove(WebInspector.Sidebar.CollapsedStyleClassName);
200
201         if (this._selectedSidebarPanel) {
202             if (this._selectedSidebarPanel.visible)
203                 this._selectedSidebarPanel.shown();
204             else
205                 this._selectedSidebarPanel.hidden();
206
207             this._selectedSidebarPanel.visibilityDidChange();
208
209             this._selectedSidebarPanel.widthDidChange();
210         }
211
212         this.dispatchEventToListeners(WebInspector.Sidebar.Event.CollapsedStateDidChange);
213         this.dispatchEventToListeners(WebInspector.Sidebar.Event.WidthDidChange);
214     },
215
216     get sidebarPanels()
217     {
218         return this._sidebarPanels;
219     },
220
221     get element()
222     {
223         return this._element;
224     },
225
226     get side()
227     {
228         return this._side;
229     },
230
231     findSidebarPanel: function(sidebarPanelOrIdentifierOrIndex)
232     {
233         var sidebarPanel = null;
234
235         if (sidebarPanelOrIdentifierOrIndex instanceof WebInspector.SidebarPanel) {
236             if (this._sidebarPanels.contains(sidebarPanelOrIdentifierOrIndex))
237                 sidebarPanel = sidebarPanelOrIdentifierOrIndex;
238         } else if (typeof sidebarPanelOrIdentifierOrIndex === "number") {
239             sidebarPanel = this._sidebarPanels[sidebarPanelOrIdentifierOrIndex];
240         } else if (typeof sidebarPanelOrIdentifierOrIndex === "string") {
241             for (var i = 0; i < this._sidebarPanels.length; ++i) {
242                 if (this._sidebarPanels[i].identifier === sidebarPanelOrIdentifierOrIndex) {
243                     sidebarPanel = this._sidebarPanels[i];
244                     break;
245                 }
246             }
247         }
248
249         return sidebarPanel;
250     },
251
252     // Protected
253
254     resizerDragStarted: function(resizer)
255     {
256         this._widthBeforeResize = this.width;
257     },
258
259     resizerDragging: function(resizer, positionDelta)
260     {
261         if (this._side === WebInspector.Sidebar.Sides.Left)
262             positionDelta *= -1;
263
264         var newWidth = positionDelta + this._widthBeforeResize;
265         this.width = newWidth;
266         this.collapsed = (newWidth < (this.minimumWidth / 2));
267     },
268
269     resizerDragEnded: function(resizer)
270     {
271         delete this._widthBeforeResize;
272     },
273
274     // Private
275
276     _navigationItemSelected: function(event)
277     {
278         this.selectedSidebarPanel = event.target.selectedNavigationItem ? event.target.selectedNavigationItem.identifier : null;
279     }
280 };
281
282 WebInspector.Sidebar.prototype.__proto__ = WebInspector.Object.prototype;