mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-09 21:28:43 +08:00
..to mirror space-before-function-paren config we have for JS. Note that there is currently a bug in clang-format, so we have to disable the rule in a couple of places. Bug: chromium:1057042, chromium:1177242 Change-Id: I923570ed22936a67376a69f1db2b8b3d25e27783 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2690528 Reviewed-by: Mathias Bynens <mathias@chromium.org> Commit-Queue: Sigurd Schneider <sigurds@chromium.org>
277 lines
9.0 KiB
TypeScript
277 lines
9.0 KiB
TypeScript
// Copyright 2014 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
/* eslint-disable rulesdir/no_underscored_properties */
|
|
|
|
import * as Common from '../common/common.js';
|
|
import * as i18n from '../i18n/i18n.js';
|
|
import * as Platform from '../platform/platform.js';
|
|
import * as UI from '../ui/ui.js';
|
|
|
|
export const UIStrings = {
|
|
/**
|
|
*@description Tooltip text that appears when hovering over largeicon pan button in Transform Controller of the Layers panel
|
|
*/
|
|
panModeX: 'Pan mode (X)',
|
|
/**
|
|
*@description Tooltip text that appears when hovering over largeicon rotate button in Transform Controller of the Layers panel
|
|
*/
|
|
rotateModeV: 'Rotate mode (V)',
|
|
/**
|
|
*@description Tooltip text that appears when hovering over the largeicon center button in the Transform Controller of the Layers panel
|
|
*/
|
|
resetTransform: 'Reset transform (0)',
|
|
};
|
|
const str_ = i18n.i18n.registerUIStrings('layer_viewer/TransformController.ts', UIStrings);
|
|
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
export class TransformController extends Common.ObjectWrapper.ObjectWrapper {
|
|
_mode!: Modes;
|
|
_scale: number;
|
|
_offsetX: number;
|
|
_offsetY: number;
|
|
_rotateX: number;
|
|
_rotateY: number;
|
|
_oldRotateX: number;
|
|
_oldRotateY: number;
|
|
_originX: number;
|
|
_originY: number;
|
|
element: HTMLElement;
|
|
_minScale: number;
|
|
_maxScale: number;
|
|
_controlPanelToolbar: UI.Toolbar.Toolbar;
|
|
_modeButtons: {[x: string]: UI.Toolbar.ToolbarToggle};
|
|
constructor(element: HTMLElement, disableRotate?: boolean) {
|
|
super();
|
|
this._scale = 1;
|
|
this._offsetX = 0;
|
|
this._offsetY = 0;
|
|
this._rotateX = 0;
|
|
this._rotateY = 0;
|
|
this._oldRotateX = 0;
|
|
this._oldRotateY = 0;
|
|
this._originX = 0;
|
|
this._originY = 0;
|
|
this.element = element;
|
|
this._registerShortcuts();
|
|
UI.UIUtils.installDragHandle(
|
|
element, this._onDragStart.bind(this), this._onDrag.bind(this), this._onDragEnd.bind(this), 'move', null);
|
|
element.addEventListener('wheel', this._onMouseWheel.bind(this), false);
|
|
this._minScale = 0;
|
|
this._maxScale = Infinity;
|
|
|
|
this._controlPanelToolbar = new UI.Toolbar.Toolbar('transform-control-panel');
|
|
|
|
this._modeButtons = {};
|
|
if (!disableRotate) {
|
|
const panModeButton = new UI.Toolbar.ToolbarToggle(i18nString(UIStrings.panModeX), 'largeicon-pan');
|
|
panModeButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, this._setMode.bind(this, Modes.Pan));
|
|
this._modeButtons[Modes.Pan] = panModeButton;
|
|
this._controlPanelToolbar.appendToolbarItem(panModeButton);
|
|
const rotateModeButton = new UI.Toolbar.ToolbarToggle(i18nString(UIStrings.rotateModeV), 'largeicon-rotate');
|
|
rotateModeButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, this._setMode.bind(this, Modes.Rotate));
|
|
this._modeButtons[Modes.Rotate] = rotateModeButton;
|
|
this._controlPanelToolbar.appendToolbarItem(rotateModeButton);
|
|
}
|
|
this._setMode(Modes.Pan);
|
|
|
|
const resetButton = new UI.Toolbar.ToolbarButton(i18nString(UIStrings.resetTransform), 'largeicon-center');
|
|
resetButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, this.resetAndNotify.bind(this, undefined));
|
|
this._controlPanelToolbar.appendToolbarItem(resetButton);
|
|
|
|
this._reset();
|
|
}
|
|
|
|
toolbar(): UI.Toolbar.Toolbar {
|
|
return this._controlPanelToolbar;
|
|
}
|
|
|
|
_registerShortcuts(): void {
|
|
const zoomFactor = 1.1;
|
|
UI.ShortcutRegistry.ShortcutRegistry.instance().addShortcutListener(this.element, {
|
|
// TODO: Remove next line once crbug.com/1177242 is solved.
|
|
// eslint-disable-next-line @typescript-eslint/space-before-function-paren
|
|
'layers.reset-view': async(): Promise<true> => {
|
|
this.resetAndNotify();
|
|
return true;
|
|
},
|
|
// TODO: Remove next line once crbug.com/1177242 is solved.
|
|
// eslint-disable-next-line @typescript-eslint/space-before-function-paren
|
|
'layers.pan-mode': async(): Promise<true> => {
|
|
this._setMode(Modes.Pan);
|
|
return true;
|
|
},
|
|
// TODO: Remove next line once crbug.com/1177242 is solved.
|
|
// eslint-disable-next-line @typescript-eslint/space-before-function-paren
|
|
'layers.rotate-mode': async(): Promise<true> => {
|
|
this._setMode(Modes.Rotate);
|
|
return true;
|
|
},
|
|
'layers.zoom-in': this._onKeyboardZoom.bind(this, zoomFactor),
|
|
'layers.zoom-out': this._onKeyboardZoom.bind(this, 1 / zoomFactor),
|
|
'layers.up': this._onKeyboardPanOrRotate.bind(this, 0, -1),
|
|
'layers.down': this._onKeyboardPanOrRotate.bind(this, 0, 1),
|
|
'layers.left': this._onKeyboardPanOrRotate.bind(this, -1, 0),
|
|
'layers.right': this._onKeyboardPanOrRotate.bind(this, 1, 0),
|
|
});
|
|
}
|
|
|
|
_postChangeEvent(): void {
|
|
this.dispatchEventToListeners(Events.TransformChanged);
|
|
}
|
|
|
|
_reset(): void {
|
|
this._scale = 1;
|
|
this._offsetX = 0;
|
|
this._offsetY = 0;
|
|
this._rotateX = 0;
|
|
this._rotateY = 0;
|
|
}
|
|
|
|
_setMode(mode: Modes): void {
|
|
if (this._mode === mode) {
|
|
return;
|
|
}
|
|
this._mode = mode;
|
|
this._updateModeButtons();
|
|
}
|
|
|
|
_updateModeButtons(): void {
|
|
for (const mode in this._modeButtons) {
|
|
this._modeButtons[mode].setToggled(mode === this._mode);
|
|
}
|
|
}
|
|
|
|
resetAndNotify(event?: Event): void {
|
|
this._reset();
|
|
this._postChangeEvent();
|
|
if (event) {
|
|
event.preventDefault();
|
|
}
|
|
this.element.focus();
|
|
}
|
|
|
|
setScaleConstraints(minScale: number, maxScale: number): void {
|
|
this._minScale = minScale;
|
|
this._maxScale = maxScale;
|
|
this._scale = Platform.NumberUtilities.clamp(this._scale, minScale, maxScale);
|
|
}
|
|
|
|
clampOffsets(minX: number, maxX: number, minY: number, maxY: number): void {
|
|
this._offsetX = Platform.NumberUtilities.clamp(this._offsetX, minX, maxX);
|
|
this._offsetY = Platform.NumberUtilities.clamp(this._offsetY, minY, maxY);
|
|
}
|
|
|
|
scale(): number {
|
|
return this._scale;
|
|
}
|
|
|
|
offsetX(): number {
|
|
return this._offsetX;
|
|
}
|
|
|
|
offsetY(): number {
|
|
return this._offsetY;
|
|
}
|
|
|
|
rotateX(): number {
|
|
return this._rotateX;
|
|
}
|
|
|
|
rotateY(): number {
|
|
return this._rotateY;
|
|
}
|
|
|
|
_onScale(scaleFactor: number, x: number, y: number): void {
|
|
scaleFactor =
|
|
Platform.NumberUtilities.clamp(this._scale * scaleFactor, this._minScale, this._maxScale) / this._scale;
|
|
this._scale *= scaleFactor;
|
|
this._offsetX -= (x - this._offsetX) * (scaleFactor - 1);
|
|
this._offsetY -= (y - this._offsetY) * (scaleFactor - 1);
|
|
this._postChangeEvent();
|
|
}
|
|
|
|
_onPan(offsetX: number, offsetY: number): void {
|
|
this._offsetX += offsetX;
|
|
this._offsetY += offsetY;
|
|
this._postChangeEvent();
|
|
}
|
|
|
|
_onRotate(rotateX: number, rotateY: number): void {
|
|
this._rotateX = rotateX;
|
|
this._rotateY = rotateY;
|
|
this._postChangeEvent();
|
|
}
|
|
|
|
async _onKeyboardZoom(zoomFactor: number): Promise<boolean> {
|
|
this._onScale(zoomFactor, this.element.clientWidth / 2, this.element.clientHeight / 2);
|
|
return true;
|
|
}
|
|
|
|
async _onKeyboardPanOrRotate(xMultiplier: number, yMultiplier: number): Promise<boolean> {
|
|
const panStepInPixels = 6;
|
|
const rotateStepInDegrees = 5;
|
|
|
|
if (this._mode === Modes.Rotate) {
|
|
// Sic! _onRotate treats X and Y as "rotate around X" and "rotate around Y", so swap X/Y multiplers.
|
|
this._onRotate(
|
|
this._rotateX + yMultiplier * rotateStepInDegrees, this._rotateY + xMultiplier * rotateStepInDegrees);
|
|
} else {
|
|
this._onPan(xMultiplier * panStepInPixels, yMultiplier * panStepInPixels);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
_onMouseWheel(event: Event): void {
|
|
/** @const */
|
|
const zoomFactor = 1.1;
|
|
/** @const */
|
|
const wheelZoomSpeed = 1 / 53;
|
|
const mouseEvent = event as WheelEvent;
|
|
const scaleFactor = Math.pow(zoomFactor, -mouseEvent.deltaY * wheelZoomSpeed);
|
|
this._onScale(
|
|
scaleFactor, mouseEvent.clientX - this.element.totalOffsetLeft(),
|
|
mouseEvent.clientY - this.element.totalOffsetTop());
|
|
}
|
|
|
|
_onDrag(event: Event): void {
|
|
const {clientX, clientY} = event as MouseEvent;
|
|
if (this._mode === Modes.Rotate) {
|
|
this._onRotate(
|
|
this._oldRotateX + (this._originY - clientY) / this.element.clientHeight * 180,
|
|
this._oldRotateY - (this._originX - clientX) / this.element.clientWidth * 180);
|
|
} else {
|
|
this._onPan(clientX - this._originX, clientY - this._originY);
|
|
this._originX = clientX;
|
|
this._originY = clientY;
|
|
}
|
|
}
|
|
|
|
_onDragStart(event: MouseEvent): boolean {
|
|
this.element.focus();
|
|
this._originX = event.clientX;
|
|
this._originY = event.clientY;
|
|
this._oldRotateX = this._rotateX;
|
|
this._oldRotateY = this._rotateY;
|
|
return true;
|
|
}
|
|
|
|
_onDragEnd(): void {
|
|
this._originX = 0;
|
|
this._originY = 0;
|
|
this._oldRotateX = 0;
|
|
this._oldRotateY = 0;
|
|
}
|
|
}
|
|
|
|
// TODO(crbug.com/1167717): Make this a const enum again
|
|
// eslint-disable-next-line rulesdir/const_enum
|
|
export enum Events {
|
|
TransformChanged = 'TransformChanged',
|
|
}
|
|
|
|
export const enum Modes {
|
|
Pan = 'Pan',
|
|
Rotate = 'Rotate',
|
|
}
|