1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5'use strict';
6
7base.requireStylesheet('ui.tool_button');
8base.requireStylesheet('ui.mouse_mode_selector');
9
10base.requireTemplate('ui.mouse_mode_selector');
11
12base.require('base.events');
13base.require('tracing.mouse_mode_constants');
14base.require('ui');
15
16base.exportTo('ui', function() {
17
18  /**
19   * Provides a panel for switching the interaction mode between
20   * panning, selecting and zooming. It handles the user interaction
21   * and dispatches events for the various modes, which are picked up
22   * and used by the Timeline Track View.
23   *
24   * @constructor
25   * @extends {HTMLDivElement}
26   */
27  var MouseModeSelector = ui.define('div');
28
29  MouseModeSelector.prototype = {
30    __proto__: HTMLDivElement.prototype,
31
32    decorate: function(parentEl) {
33
34      this.classList.add('mouse-mode-selector');
35      this.parentEl_ = parentEl;
36
37      var node = base.instantiateTemplate('#mouse-mode-selector-template');
38      this.appendChild(node);
39
40      this.buttonsEl_ = this.querySelector('.buttons');
41      this.dragHandleEl_ = this.querySelector('.drag-handle');
42      this.panScanModeButton_ =
43          this.buttonsEl_.querySelector('.pan-scan-mode-button');
44      this.selectionModeButton_ =
45          this.buttonsEl_.querySelector('.selection-mode-button');
46      this.zoomModeButton_ =
47          this.buttonsEl_.querySelector('.zoom-mode-button');
48
49      this.pos_ = {
50        x: base.Settings.get('mouse_mode_selector.x', window.innerWidth - 50),
51        y: base.Settings.get('mouse_mode_selector.y', 100)
52      };
53
54      this.constrainPositionToWindowBounds_();
55      this.updateStylesFromPosition_();
56
57      this.isDraggingModeSelectorDragHandle_ = false;
58      this.initialRelativeMouseDownPos_ = {x: 0, y: 0};
59
60      this.dragHandleEl_.addEventListener('mousedown',
61          this.onDragHandleMouseDown_.bind(this));
62      document.addEventListener('mousemove',
63          this.onDragHandleMouseMove_.bind(this));
64      document.addEventListener('mouseup',
65          this.onDragHandleMouseUp_.bind(this));
66      window.addEventListener('resize',
67          this.onWindowResize_.bind(this));
68
69      this.buttonsEl_.addEventListener('mouseup', this.onButtonMouseUp_);
70      this.buttonsEl_.addEventListener('mousedown', this.onButtonMouseDown_);
71      this.buttonsEl_.addEventListener('click', this.onButtonPress_.bind(this));
72
73      document.addEventListener('mousemove', this.onMouseMove_.bind(this));
74      document.addEventListener('mouseup', this.onMouseUp_.bind(this));
75      this.parentEl_.addEventListener('mousedown',
76          this.onMouseDown_.bind(this));
77
78      document.addEventListener('keypress', this.onKeyPress_.bind(this));
79      document.addEventListener('keydown', this.onKeyDown_.bind(this));
80      document.addEventListener('keyup', this.onKeyUp_.bind(this));
81
82      this.mode = base.Settings.get('mouse_mode_selector.mouseMode',
83          tracing.mouseModeConstants.MOUSE_MODE_PANSCAN);
84
85      this.isInTemporaryAlternativeMouseMode_ = false;
86      this.isInteracting_ = false;
87    },
88
89    get mode() {
90      return this.currentMode_;
91    },
92
93    set mode(newMode) {
94
95      if (this.currentMode_ === newMode)
96        return;
97
98      var mouseModeConstants = tracing.mouseModeConstants;
99
100      this.currentMode_ = newMode;
101      this.panScanModeButton_.classList.remove('active');
102      this.selectionModeButton_.classList.remove('active');
103      this.zoomModeButton_.classList.remove('active');
104
105      switch (newMode) {
106
107        case mouseModeConstants.MOUSE_MODE_PANSCAN:
108          this.panScanModeButton_.classList.add('active');
109          break;
110
111        case mouseModeConstants.MOUSE_MODE_SELECTION:
112          this.selectionModeButton_.classList.add('active');
113          break;
114
115        case mouseModeConstants.MOUSE_MODE_ZOOM:
116          this.zoomModeButton_.classList.add('active');
117          break;
118
119        default:
120          throw new Error('Unknown selection mode: ' + newMode);
121          break;
122      }
123
124      base.Settings.set('mouse_mode_selector.mouseMode', newMode);
125    },
126
127    getCurrentModeEventNames_: function() {
128
129      var mouseModeConstants = tracing.mouseModeConstants;
130      var modeEventNames = {
131        begin: '',
132        update: '',
133        end: ''
134      };
135
136      switch (this.mode) {
137
138        case mouseModeConstants.MOUSE_MODE_PANSCAN:
139          modeEventNames.begin = 'beginpan';
140          modeEventNames.update = 'updatepan';
141          modeEventNames.end = 'endpan';
142          break;
143
144        case mouseModeConstants.MOUSE_MODE_SELECTION:
145          modeEventNames.begin = 'beginselection';
146          modeEventNames.update = 'updateselection';
147          modeEventNames.end = 'endselection';
148          break;
149
150        case mouseModeConstants.MOUSE_MODE_ZOOM:
151          modeEventNames.begin = 'beginzoom';
152          modeEventNames.update = 'updatezoom';
153          modeEventNames.end = 'endzoom';
154          break;
155
156        default:
157          throw new Error('Unsupported interaction mode');
158          break;
159      }
160
161      return modeEventNames;
162    },
163
164    onMouseDown_: function(e) {
165      var eventNames = this.getCurrentModeEventNames_();
166      var mouseEvent = new base.Event(eventNames.begin, true, true);
167      mouseEvent.data = e;
168      this.dispatchEvent(mouseEvent);
169      this.isInteracting_ = true;
170    },
171
172    onMouseMove_: function(e) {
173      var eventNames = this.getCurrentModeEventNames_();
174      var mouseEvent = new base.Event(eventNames.update, true, true);
175      mouseEvent.data = e;
176      this.dispatchEvent(mouseEvent);
177    },
178
179    onMouseUp_: function(e) {
180      var eventNames = this.getCurrentModeEventNames_();
181      var mouseEvent = new base.Event(eventNames.end, true, true);
182      var userHasReleasedShiftKey = !e.shiftKey;
183      var userHasReleasedCmdOrCtrl = (base.isMac && !e.metaKey) ||
184          (!base.isMac && !e.ctrlKey);
185
186      mouseEvent.data = e;
187      this.dispatchEvent(mouseEvent);
188      this.isInteracting_ = false;
189
190      if (this.isInTemporaryAlternativeMouseMode_ && userHasReleasedShiftKey &&
191          userHasReleasedCmdOrCtrl) {
192        this.mode = tracing.mouseModeConstants.MOUSE_MODE_PANSCAN;
193      }
194    },
195
196    onButtonMouseDown_: function(e) {
197      e.preventDefault();
198      e.stopImmediatePropagation();
199    },
200
201    onButtonMouseUp_: function(e) {
202      e.preventDefault();
203      e.stopImmediatePropagation();
204    },
205
206    onButtonPress_: function(e) {
207
208      var mouseModeConstants = tracing.mouseModeConstants;
209      var newInteractionMode = mouseModeConstants.MOUSE_MODE_PANSCAN;
210
211      switch (e.target) {
212        case this.panScanModeButton_:
213          this.mode = mouseModeConstants.MOUSE_MODE_PANSCAN;
214          break;
215
216        case this.selectionModeButton_:
217          this.mode = mouseModeConstants.MOUSE_MODE_SELECTION;
218          break;
219
220        case this.zoomModeButton_:
221          this.mode = mouseModeConstants.MOUSE_MODE_ZOOM;
222          break;
223
224        default:
225          throw new Error('Unknown mouse mode button pressed');
226          break;
227      }
228
229      e.preventDefault();
230      this.isInTemporaryAlternativeMouseMode_ = false;
231    },
232
233    onKeyPress_: function(e) {
234
235      // Prevent the user from changing modes during an interaction.
236      if (this.isInteracting_)
237        return;
238
239      var mouseModeConstants = tracing.mouseModeConstants;
240
241      switch (e.keyCode) {
242        case 49:   // 1
243          this.mode = mouseModeConstants.MOUSE_MODE_PANSCAN;
244          break;
245        case 50:   // 2
246          this.mode = mouseModeConstants.MOUSE_MODE_SELECTION;
247          break;
248        case 51:   // 3
249          this.mode = mouseModeConstants.MOUSE_MODE_ZOOM;
250          break;
251      }
252    },
253
254    onKeyDown_: function(e) {
255
256      // Prevent the user from changing modes during an interaction.
257      if (this.isInteracting_)
258        return;
259
260      var mouseModeConstants = tracing.mouseModeConstants;
261      var userIsPressingCmdOrCtrl = (base.isMac && e.metaKey) ||
262          (!base.isMac && e.ctrlKey);
263      var userIsPressingShiftKey = e.shiftKey;
264
265      if (this.mode !== mouseModeConstants.MOUSE_MODE_PANSCAN)
266        return;
267
268      if (userIsPressingCmdOrCtrl || userIsPressingShiftKey) {
269
270        this.mode = userIsPressingCmdOrCtrl ?
271            mouseModeConstants.MOUSE_MODE_ZOOM :
272            mouseModeConstants.MOUSE_MODE_SELECTION;
273
274        this.isInTemporaryAlternativeMouseMode_ = true;
275        e.preventDefault();
276      }
277    },
278
279    onKeyUp_: function(e) {
280
281      // Prevent the user from changing modes during an interaction.
282      if (this.isInteracting_)
283        return;
284
285      var mouseModeConstants = tracing.mouseModeConstants;
286      var userHasReleasedCmdOrCtrl = (base.isMac && !e.metaKey) ||
287          (!base.isMac && !e.ctrlKey);
288      var userHasReleasedShiftKey = e.shiftKey;
289
290      if (this.isInTemporaryAlternativeMouseMode_ &&
291          (userHasReleasedCmdOrCtrl || userHasReleasedShiftKey)) {
292        this.mode = mouseModeConstants.MOUSE_MODE_PANSCAN;
293      }
294
295      this.isInTemporaryAlternativeMouseMode_ = false;
296
297    },
298
299    constrainPositionToWindowBounds_: function() {
300      var top = 0;
301      var bottom = window.innerHeight - this.offsetHeight;
302      var left = 0;
303      var right = window.innerWidth - this.offsetWidth;
304
305      this.pos_.x = Math.max(this.pos_.x, left);
306      this.pos_.x = Math.min(this.pos_.x, right);
307
308      this.pos_.y = Math.max(this.pos_.y, top);
309      this.pos_.y = Math.min(this.pos_.y, bottom);
310    },
311
312    updateStylesFromPosition_: function() {
313      this.style.left = this.pos_.x + 'px';
314      this.style.top = this.pos_.y + 'px';
315
316      base.Settings.set('mouse_mode_selector.x', this.pos_.x);
317      base.Settings.set('mouse_mode_selector.y', this.pos_.y);
318    },
319
320    onDragHandleMouseDown_: function(e) {
321      e.preventDefault();
322      e.stopImmediatePropagation();
323
324      this.isDraggingModeSelectorDragHandle_ = true;
325
326      this.initialRelativeMouseDownPos_.x = e.clientX - this.offsetLeft;
327      this.initialRelativeMouseDownPos_.y = e.clientY - this.offsetTop;
328
329    },
330
331    onDragHandleMouseMove_: function(e) {
332      if (!this.isDraggingModeSelectorDragHandle_)
333        return;
334
335      this.pos_.x = (e.clientX - this.initialRelativeMouseDownPos_.x);
336      this.pos_.y = (e.clientY - this.initialRelativeMouseDownPos_.y);
337
338      this.constrainPositionToWindowBounds_();
339      this.updateStylesFromPosition_();
340    },
341
342    onDragHandleMouseUp_: function(e) {
343      this.isDraggingModeSelectorDragHandle_ = false;
344    },
345
346    onWindowResize_: function(e) {
347      this.constrainPositionToWindowBounds_();
348      this.updateStylesFromPosition_();
349    }
350  };
351
352  return {
353    MouseModeSelector: MouseModeSelector
354  };
355});
356