1/*
2 * Copyright 2014 The Chromium Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 */
6
7/**
8 * @constructor
9 * @extends {WebInspector.Object}
10 * @param {!Element} element
11 * @param {boolean=} disableRotate
12 */
13WebInspector.TransformController = function(element, disableRotate)
14{
15    this._shortcuts = {};
16    this.element = element;
17    this._registerShortcuts();
18    element.addEventListener("keydown", this._onKeyDown.bind(this), false);
19    element.addEventListener("keyup", this._onKeyUp.bind(this), false);
20    element.addEventListener("mousemove", this._onMouseMove.bind(this), false);
21    element.addEventListener("mousedown", this._onMouseDown.bind(this), false);
22    element.addEventListener("mouseup", this._onMouseUp.bind(this), false);
23    element.addEventListener("mousewheel", this._onMouseWheel.bind(this), false);
24    this._disableRotate = disableRotate;
25
26    this._controlPanelElement = document.createElement("div");
27    this._controlPanelElement.classList.add("transform-control-panel");
28
29    this._modeButtons = {};
30    if (!disableRotate) {
31        var panModeButton = new WebInspector.StatusBarButton(WebInspector.UIString("Pan mode (X)"), "transform-mode-pan");
32        panModeButton.addEventListener("click", this._setMode.bind(this, WebInspector.TransformController.Modes.Pan));
33        this._modeButtons[WebInspector.TransformController.Modes.Pan] = panModeButton;
34        this._controlPanelElement.appendChild(panModeButton.element);
35        var rotateModeButton = new WebInspector.StatusBarButton(WebInspector.UIString("Rotate mode (V)"), "transform-mode-rotate");
36        rotateModeButton.addEventListener("click", this._setMode.bind(this, WebInspector.TransformController.Modes.Rotate));
37        this._modeButtons[WebInspector.TransformController.Modes.Rotate] = rotateModeButton;
38        this._controlPanelElement.appendChild(rotateModeButton.element);
39    }
40    this._setMode(WebInspector.TransformController.Modes.Pan);
41
42    var resetButton = new WebInspector.StatusBarButton(WebInspector.UIString("Reset transform (0)"), "transform-reset");
43    resetButton.addEventListener("click", this.resetAndNotify.bind(this, undefined));
44    this._controlPanelElement.appendChild(resetButton.element);
45
46    this._reset();
47}
48
49/**
50 * @enum {string}
51 */
52WebInspector.TransformController.Events = {
53    TransformChanged: "TransformChanged"
54}
55
56/**
57 * @enum {string}
58 */
59WebInspector.TransformController.Modes = {
60    Pan: "Pan",
61    Rotate: "Rotate",
62}
63
64WebInspector.TransformController.prototype = {
65    /**
66     * @return {!Element}
67     */
68    controlPanelElement: function()
69    {
70        return this._controlPanelElement;
71    },
72
73    _onKeyDown: function(event)
74    {
75        if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Shift.code) {
76            this._toggleMode();
77            return;
78        }
79
80        var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEventIgnoringModifiers(event);
81        var handler = this._shortcuts[shortcutKey];
82        if (handler && handler(event))
83            event.consume();
84    },
85
86    _onKeyUp: function(event)
87    {
88        if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Shift.code)
89            this._toggleMode();
90    },
91
92    _addShortcuts: function(keys, handler)
93    {
94        for (var i = 0; i < keys.length; ++i)
95            this._shortcuts[keys[i].key] = handler;
96    },
97
98    _registerShortcuts: function()
99    {
100        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.ResetView, this.resetAndNotify.bind(this));
101        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.PanMode, this._setMode.bind(this, WebInspector.TransformController.Modes.Pan));
102        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.RotateMode, this._setMode.bind(this, WebInspector.TransformController.Modes.Rotate));
103        var zoomFactor = 1.1;
104        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.ZoomIn, this._onKeyboardZoom.bind(this, zoomFactor));
105        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.ZoomOut, this._onKeyboardZoom.bind(this, 1 / zoomFactor));
106        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.Up, this._onKeyboardPanOrRotate.bind(this, 0, -1));
107        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.Down, this._onKeyboardPanOrRotate.bind(this, 0, 1));
108        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.Left, this._onKeyboardPanOrRotate.bind(this, -1, 0));
109        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.Right, this._onKeyboardPanOrRotate.bind(this, 1, 0));
110    },
111
112    _postChangeEvent: function()
113    {
114        this.dispatchEventToListeners(WebInspector.TransformController.Events.TransformChanged);
115    },
116
117    _reset: function()
118    {
119        this._scale = 1;
120        this._offsetX = 0;
121        this._offsetY = 0;
122        this._rotateX = 0;
123        this._rotateY = 0;
124    },
125
126    _toggleMode: function()
127    {
128        this._setMode(this._mode === WebInspector.TransformController.Modes.Pan ? WebInspector.TransformController.Modes.Rotate : WebInspector.TransformController.Modes.Pan);
129    },
130
131    /**
132     * @param {!WebInspector.TransformController.Modes} mode
133     */
134    _setMode: function(mode)
135    {
136        if (this._mode === mode)
137            return;
138        this._mode = mode;
139        this._updateModeButtons();
140        this.element.focus();
141    },
142
143    _updateModeButtons: function()
144    {
145        for (var mode in this._modeButtons)
146            this._modeButtons[mode].toggled = (mode === this._mode);
147    },
148
149    /**
150     * @param {!Event=} event
151     */
152    resetAndNotify: function(event)
153    {
154        this._reset();
155        this._postChangeEvent();
156        if (event)
157            event.preventDefault();
158        this.element.focus();
159    },
160
161    /**
162     * @return {number}
163     */
164    scale: function()
165    {
166        return this._scale;
167    },
168
169    /**
170     * @return {number}
171     */
172    offsetX: function()
173    {
174        return this._offsetX;
175    },
176
177    /**
178     * @return {number}
179     */
180    offsetY: function()
181    {
182        return this._offsetY;
183    },
184
185    /**
186     * @return {number}
187     */
188    rotateX: function()
189    {
190        return this._rotateX;
191    },
192
193    /**
194     * @return {number}
195     */
196    rotateY: function()
197    {
198        return this._rotateY;
199    },
200
201    /**
202     * @param {number} scaleFactor
203     * @param {number} x
204     * @param {number} y
205     */
206    _onScale: function(scaleFactor, x, y)
207    {
208        this._scale *= scaleFactor;
209        this._offsetX -= (x - this._offsetX) * (scaleFactor - 1);
210        this._offsetY -= (y - this._offsetY) * (scaleFactor - 1);
211        this._postChangeEvent();
212    },
213
214    /**
215     * @param {number} offsetX
216     * @param {number} offsetY
217     */
218    _onPan: function(offsetX, offsetY)
219    {
220        this._offsetX += offsetX;
221        this._offsetY += offsetY;
222        this._postChangeEvent();
223    },
224
225    /**
226     * @param {number} rotateX
227     * @param {number} rotateY
228     */
229    _onRotate: function(rotateX, rotateY)
230    {
231        this._rotateX = rotateX;
232        this._rotateY = rotateY;
233        this._postChangeEvent();
234    },
235
236    /**
237     * @param {number} zoomFactor
238     */
239    _onKeyboardZoom: function(zoomFactor)
240    {
241        this._onScale(zoomFactor, this.element.clientWidth / 2, this.element.clientHeight / 2);
242    },
243
244    /**
245     * @param {number} xMultiplier
246     * @param {number} yMultiplier
247     */
248    _onKeyboardPanOrRotate: function(xMultiplier, yMultiplier)
249    {
250        var panStepInPixels = 6;
251        var rotateStepInDegrees = 5;
252
253        if (this._mode === WebInspector.TransformController.Modes.Rotate) {
254            // Sic! _onRotate treats X and Y as "rotate around X" and "rotate around Y", so swap X/Y multiplers.
255            this._onRotate(this._rotateX + yMultiplier * rotateStepInDegrees, this._rotateY + xMultiplier * rotateStepInDegrees);
256        } else {
257            this._onPan(xMultiplier * panStepInPixels, yMultiplier * panStepInPixels);
258        }
259    },
260
261    /**
262     * @param {!Event} event
263     */
264    _onMouseWheel: function(event)
265    {
266        /** @const */
267        var zoomFactor = 1.1;
268        /** @const */
269        var mouseWheelZoomSpeed = 1 / 120;
270        var scaleFactor = Math.pow(zoomFactor, event.wheelDeltaY * mouseWheelZoomSpeed);
271        this._onScale(scaleFactor, event.clientX - this.element.totalOffsetLeft(), event.clientY - this.element.totalOffsetTop());
272    },
273
274    /**
275     * @param {!Event} event
276     */
277    _onMouseMove: function(event)
278    {
279        if (event.which !== 1 || typeof this._originX !== "number")
280            return;
281        if (this._mode === WebInspector.TransformController.Modes.Rotate) {
282            this._onRotate(this._oldRotateX + (this._originY - event.clientY) / this.element.clientHeight * 180, this._oldRotateY - (this._originX - event.clientX) / this.element.clientWidth * 180);
283        } else {
284            this._onPan(event.clientX - this._originX, event.clientY - this._originY);
285            this._originX = event.clientX;
286            this._originY = event.clientY;
287        }
288    },
289
290    /**
291     * @param {!Event} event
292     */
293    _setReferencePoint: function(event)
294    {
295        this._originX = event.clientX;
296        this._originY = event.clientY;
297        this._oldRotateX = this._rotateX;
298        this._oldRotateY = this._rotateY;
299    },
300
301    _resetReferencePoint: function()
302    {
303        delete this._originX;
304        delete this._originY;
305        delete this._oldRotateX;
306        delete this._oldRotateY;
307    },
308
309    /**
310     * @param {!Event} event
311     */
312    _onMouseDown: function(event)
313    {
314        if (event.which !== 1)
315            return;
316        this._setReferencePoint(event);
317    },
318
319    /**
320     * @param {!Event} event
321     */
322    _onMouseUp: function(event)
323    {
324        if (event.which !== 1)
325            return;
326        this._resetReferencePoint();
327    },
328
329    __proto__: WebInspector.Object.prototype
330}
331