Organize WebInspectorUI/UserInterface into sub-directories.
[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     WebInspector.Object.call(this);
28
29     console.assert(!side || side === WebInspector.Sidebar.Sides.Left || side === WebInspector.Sidebar.Sides.Right);
30     this._side = side || WebInspector.Sidebar.Sides.Left;
31
32     this._element = element || document.createElement("div");
33     this._element.classList.add(WebInspector.Sidebar.StyleClassName);
34     this._element.classList.add(WebInspector.Sidebar.CollapsedStyleClassName);
35     this._element.classList.add(this._side);
36
37     this._element.setAttribute("role", role || "group");
38     if (label)
39         this._element.setAttribute("aria-label", label);
40
41     this._resizeElement = document.createElement("div");
42     this._resizeElement.classList.add(WebInspector.Sidebar.ResizeElementStyleClassName);
43     this._resizeElement.addEventListener("mousedown", this._resizerMouseDown.bind(this), false);
44     this._resizeElement.addEventListener("dblclick", this._resizerDoubleClicked.bind(this), false);
45     this._element.insertBefore(this._resizeElement, this._element.firstChild);
46
47     this._sidebarPanels = [];
48
49     if (sidebarPanels) {
50         for (var i = 0; i < sidebarPanels.length; ++i)
51             this.addSidebarPanel(sidebarPanels[i]);
52     }
53 };
54
55 WebInspector.Object.addConstructorFunctions(WebInspector.Sidebar);
56
57 WebInspector.Sidebar.StyleClassName = "sidebar";
58 WebInspector.Sidebar.CollapsedStyleClassName = "collapsed";
59 WebInspector.Sidebar.ResizeElementStyleClassName = "resizer";
60 WebInspector.Sidebar.AbsoluteMinimumWidth = 200;
61
62 WebInspector.Sidebar.Sides = {};
63 WebInspector.Sidebar.Sides.Right = "right";
64 WebInspector.Sidebar.Sides.Left = "left";
65
66 WebInspector.Sidebar.Event = {
67     SidebarPanelSelected: "sidebar-sidebar-panel-selected",
68     CollapsedStateDidChange: "sidebar-sidebar-collapsed-state-did-change",
69     WidthDidChange: "sidebar-width-did-change",
70 };
71
72 WebInspector.Sidebar.prototype = {
73     constructor: WebInspector.Sidebar,
74
75     // Public
76
77     addSidebarPanel: function(sidebarPanel)
78     {
79         console.assert(sidebarPanel instanceof WebInspector.SidebarPanel);
80         if (!(sidebarPanel instanceof WebInspector.SidebarPanel))
81             return;
82
83         console.assert(!sidebarPanel.parentSidebar);
84         if (sidebarPanel.parentSidebar)
85             return;
86
87         sidebarPanel._parentSidebar = this;
88
89         this._sidebarPanels.push(sidebarPanel);
90         this._element.appendChild(sidebarPanel.element);
91
92         sidebarPanel.added();
93
94         return sidebarPanel;
95     },
96
97     removeSidebarPanel: function(sidebarPanelOrIdentifierOrIndex, index)
98     {
99         var sidebarPanel = this.findSidebarPanel(sidebarPanelOrIdentifierOrIndex);
100         if (!sidebarPanel)
101             return;
102
103         sidebarPanel.willRemove();
104
105         sidebarPanel._parentSidebar = null;
106
107         if (this._selectedSidebarPanel === sidebarPanel) {
108             var index = this._sidebarPanels.indexOf(sidebarPanel);
109             this.selectedSidebarPanel = this._sidebarPanels[index - 1] || this._sidebarPanels[index + 1];
110         }
111
112         this._sidebarPanels.remove(sidebarPanel);
113         this._element.removeChild(sidebarPanel.element);
114
115         sidebarPanel.removed();
116
117         return sidebarPanel;
118     },
119
120     get selectedSidebarPanel()
121     {
122         return this._selectedSidebarPanel || null;
123     },
124
125     set selectedSidebarPanel(sidebarPanelOrIdentifierOrIndex)
126     {
127         var sidebarPanel = this.findSidebarPanel(sidebarPanelOrIdentifierOrIndex);
128         if (this._selectedSidebarPanel === sidebarPanel)
129             return;
130
131         if (this._selectedSidebarPanel) {
132             var wasVisible = this._selectedSidebarPanel.visible;
133
134             this._selectedSidebarPanel.selected = false;
135
136             if (wasVisible) {
137                 this._selectedSidebarPanel.hidden();
138                 this._selectedSidebarPanel.visibilityDidChange();
139             }
140         }
141
142         this._selectedSidebarPanel = sidebarPanel || null;
143
144         if (this._selectedSidebarPanel) {
145             this._selectedSidebarPanel.selected = true;
146
147             if (this._selectedSidebarPanel.visible) {
148                 this._selectedSidebarPanel.shown();
149                 this._selectedSidebarPanel.visibilityDidChange();
150             }
151         }
152
153         this.dispatchEventToListeners(WebInspector.Sidebar.Event.SidebarPanelSelected);
154     },
155
156     get minimumWidth()
157     {
158         return WebInspector.Sidebar.AbsoluteMinimumWidth;
159     },
160
161     get maximumWidth()
162     {
163         // FIXME: This is kind of arbitrary and ideally would be a more complex calculation based on the
164         // available space for the sibling elements.
165         return Math.round(window.innerWidth / 3);
166     },
167
168     get width()
169     {
170         return this._element.offsetWidth;
171     },
172
173     set width(newWidth)
174     {
175         if (newWidth === this.width)
176             return;
177
178         newWidth = Math.max(this.minimumWidth, Math.min(newWidth, this.maximumWidth));
179
180         this._element.style.width = newWidth + "px";
181
182         if (!this.collapsed && this._selectedSidebarPanel)
183             this._selectedSidebarPanel.widthDidChange();
184
185         this.dispatchEventToListeners(WebInspector.Sidebar.Event.WidthDidChange);
186     },
187
188     get collapsed()
189     {
190         return this._element.classList.contains(WebInspector.Sidebar.CollapsedStyleClassName);
191     },
192
193     set collapsed(flag)
194     {
195         if (flag === this.collapsed)
196             return;
197
198         if (flag)
199             this._element.classList.add(WebInspector.Sidebar.CollapsedStyleClassName);
200         else
201             this._element.classList.remove(WebInspector.Sidebar.CollapsedStyleClassName);
202
203         if (this._selectedSidebarPanel) {
204             if (this._selectedSidebarPanel.visible)
205                 this._selectedSidebarPanel.shown();
206             else
207                 this._selectedSidebarPanel.hidden();
208
209             this._selectedSidebarPanel.visibilityDidChange();
210
211             this._selectedSidebarPanel.widthDidChange();
212         }
213
214         this.dispatchEventToListeners(WebInspector.Sidebar.Event.CollapsedStateDidChange);
215         this.dispatchEventToListeners(WebInspector.Sidebar.Event.WidthDidChange);
216     },
217
218     get sidebarPanels()
219     {
220         return this._sidebarPanels;
221     },
222
223     get element()
224     {
225         return this._element;
226     },
227
228     get side()
229     {
230         return this._side;
231     },
232
233     findSidebarPanel: function(sidebarPanelOrIdentifierOrIndex)
234     {
235         var sidebarPanel = null;
236
237         if (sidebarPanelOrIdentifierOrIndex instanceof WebInspector.SidebarPanel) {
238             if (this._sidebarPanels.contains(sidebarPanelOrIdentifierOrIndex))
239                 sidebarPanel = sidebarPanelOrIdentifierOrIndex;
240         } else if (typeof sidebarPanelOrIdentifierOrIndex === "number") {
241             sidebarPanel = this._sidebarPanels[sidebarPanelOrIdentifierOrIndex];
242         } else if (typeof sidebarPanelOrIdentifierOrIndex === "string") {
243             for (var i = 0; i < this._sidebarPanels.length; ++i) {
244                 if (this._sidebarPanels[i].identifier === sidebarPanelOrIdentifierOrIndex) {
245                     sidebarPanel = this._sidebarPanels[i];
246                     break;
247                 }
248             }
249         }
250
251         return sidebarPanel;
252     },
253
254     // Private
255
256     _navigationItemSelected: function(event)
257     {
258         this.selectedSidebarPanel = event.target.selectedNavigationItem ? event.target.selectedNavigationItem.identifier : null;
259     },
260
261     _resizerDoubleClicked: function(event)
262     {
263         this.collapsed = !this.collapsed;
264
265         event.preventDefault();
266         event.stopPropagation();
267     },
268
269     _resizerMouseDown: function(event)
270     {
271         if (event.button !== 0 || event.ctrlKey)
272             return;
273
274         document.body.style.cursor = "col-resize";
275
276         this._resizerMouseMovedEventListener = this._resizerMouseMoved.bind(this);
277         this._resizerMouseUpEventListener = this._resizerMouseUp.bind(this);
278
279         // Register these listeners on the document so we can track the mouse if it leaves the resizer.
280         document.addEventListener("mousemove", this._resizerMouseMovedEventListener, false);
281         document.addEventListener("mouseup", this._resizerMouseUpEventListener, false);
282
283         event.preventDefault();
284         event.stopPropagation();
285     },
286
287     _resizerMouseMoved: function(event)
288     {
289         if (this._side === WebInspector.Sidebar.Sides.Left)
290             var newWidth = event.pageX - this._element.totalOffsetLeft;
291         else
292             var newWidth = this._element.totalOffsetLeft + this._element.offsetWidth - event.pageX;
293
294         this.width = newWidth;
295         this.collapsed = (newWidth < (this.minimumWidth / 2));
296
297         event.preventDefault();
298         event.stopPropagation();
299     },
300
301     _resizerMouseUp: function(event)
302     {
303         if (event.button !== 0 || event.ctrlKey)
304             return;
305
306         document.body.style.removeProperty("cursor");
307
308         document.removeEventListener("mousemove", this._resizerMouseMovedEventListener, false);
309         document.removeEventListener("mouseup", this._resizerMouseUpEventListener, false);
310
311         delete this._resizerMouseMovedEventListener;
312         delete this._resizerMouseUpEventListener;
313
314         event.preventDefault();
315         event.stopPropagation();
316     }
317 };
318
319 WebInspector.Sidebar.prototype.__proto__ = WebInspector.Object.prototype;