123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import * as dom from '../../../base/browser/dom.js';
- import { StandardWheelEvent } from '../../../base/browser/mouseEvent.js';
- import { TimeoutTimer } from '../../../base/common/async.js';
- import { Disposable } from '../../../base/common/lifecycle.js';
- import * as platform from '../../../base/common/platform.js';
- import { HitTestContext, MouseTarget, MouseTargetFactory } from './mouseTarget.js';
- import { ClientCoordinates, EditorMouseEventFactory, GlobalEditorMouseMoveMonitor, createEditorPagePosition } from '../editorDom.js';
- import { EditorZoom } from '../../common/config/editorZoom.js';
- import { Position } from '../../common/core/position.js';
- import { Selection } from '../../common/core/selection.js';
- import { ViewEventHandler } from '../../common/viewModel/viewEventHandler.js';
- /**
- * Merges mouse events when mouse move events are throttled
- */
- export function createMouseMoveEventMerger(mouseTargetFactory) {
- return function (lastEvent, currentEvent) {
- let targetIsWidget = false;
- if (mouseTargetFactory) {
- targetIsWidget = mouseTargetFactory.mouseTargetIsWidget(currentEvent);
- }
- if (!targetIsWidget) {
- currentEvent.preventDefault();
- }
- return currentEvent;
- };
- }
- export class MouseHandler extends ViewEventHandler {
- constructor(context, viewController, viewHelper) {
- super();
- this._context = context;
- this.viewController = viewController;
- this.viewHelper = viewHelper;
- this.mouseTargetFactory = new MouseTargetFactory(this._context, viewHelper);
- this._mouseDownOperation = this._register(new MouseDownOperation(this._context, this.viewController, this.viewHelper, (e, testEventTarget) => this._createMouseTarget(e, testEventTarget), (e) => this._getMouseColumn(e)));
- this.lastMouseLeaveTime = -1;
- this._height = this._context.configuration.options.get(130 /* layoutInfo */).height;
- const mouseEvents = new EditorMouseEventFactory(this.viewHelper.viewDomNode);
- this._register(mouseEvents.onContextMenu(this.viewHelper.viewDomNode, (e) => this._onContextMenu(e, true)));
- this._register(mouseEvents.onMouseMoveThrottled(this.viewHelper.viewDomNode, (e) => this._onMouseMove(e), createMouseMoveEventMerger(this.mouseTargetFactory), MouseHandler.MOUSE_MOVE_MINIMUM_TIME));
- this._register(mouseEvents.onMouseUp(this.viewHelper.viewDomNode, (e) => this._onMouseUp(e)));
- this._register(mouseEvents.onMouseLeave(this.viewHelper.viewDomNode, (e) => this._onMouseLeave(e)));
- this._register(mouseEvents.onMouseDown(this.viewHelper.viewDomNode, (e) => this._onMouseDown(e)));
- const onMouseWheel = (browserEvent) => {
- this.viewController.emitMouseWheel(browserEvent);
- if (!this._context.configuration.options.get(67 /* mouseWheelZoom */)) {
- return;
- }
- const e = new StandardWheelEvent(browserEvent);
- const doMouseWheelZoom = (platform.isMacintosh
- // on macOS we support cmd + two fingers scroll (`metaKey` set)
- // and also the two fingers pinch gesture (`ctrKey` set)
- ? ((browserEvent.metaKey || browserEvent.ctrlKey) && !browserEvent.shiftKey && !browserEvent.altKey)
- : (browserEvent.ctrlKey && !browserEvent.metaKey && !browserEvent.shiftKey && !browserEvent.altKey));
- if (doMouseWheelZoom) {
- const zoomLevel = EditorZoom.getZoomLevel();
- const delta = e.deltaY > 0 ? 1 : -1;
- EditorZoom.setZoomLevel(zoomLevel + delta);
- e.preventDefault();
- e.stopPropagation();
- }
- };
- this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, dom.EventType.MOUSE_WHEEL, onMouseWheel, { capture: true, passive: false }));
- this._context.addEventHandler(this);
- }
- dispose() {
- this._context.removeEventHandler(this);
- super.dispose();
- }
- // --- begin event handlers
- onConfigurationChanged(e) {
- if (e.hasChanged(130 /* layoutInfo */)) {
- // layout change
- const height = this._context.configuration.options.get(130 /* layoutInfo */).height;
- if (this._height !== height) {
- this._height = height;
- this._mouseDownOperation.onHeightChanged();
- }
- }
- return false;
- }
- onCursorStateChanged(e) {
- this._mouseDownOperation.onCursorStateChanged(e);
- return false;
- }
- onFocusChanged(e) {
- return false;
- }
- onScrollChanged(e) {
- this._mouseDownOperation.onScrollChanged();
- return false;
- }
- // --- end event handlers
- getTargetAtClientPoint(clientX, clientY) {
- const clientPos = new ClientCoordinates(clientX, clientY);
- const pos = clientPos.toPageCoordinates();
- const editorPos = createEditorPagePosition(this.viewHelper.viewDomNode);
- if (pos.y < editorPos.y || pos.y > editorPos.y + editorPos.height || pos.x < editorPos.x || pos.x > editorPos.x + editorPos.width) {
- return null;
- }
- return this.mouseTargetFactory.createMouseTarget(this.viewHelper.getLastRenderData(), editorPos, pos, null);
- }
- _createMouseTarget(e, testEventTarget) {
- let target = e.target;
- if (!this.viewHelper.viewDomNode.contains(target)) {
- const shadowRoot = dom.getShadowRoot(this.viewHelper.viewDomNode);
- if (shadowRoot) {
- target = shadowRoot.elementsFromPoint(e.posx, e.posy).find((el) => this.viewHelper.viewDomNode.contains(el));
- }
- }
- return this.mouseTargetFactory.createMouseTarget(this.viewHelper.getLastRenderData(), e.editorPos, e.pos, testEventTarget ? target : null);
- }
- _getMouseColumn(e) {
- return this.mouseTargetFactory.getMouseColumn(e.editorPos, e.pos);
- }
- _onContextMenu(e, testEventTarget) {
- this.viewController.emitContextMenu({
- event: e,
- target: this._createMouseTarget(e, testEventTarget)
- });
- }
- _onMouseMove(e) {
- if (this._mouseDownOperation.isActive()) {
- // In selection/drag operation
- return;
- }
- const actualMouseMoveTime = e.timestamp;
- if (actualMouseMoveTime < this.lastMouseLeaveTime) {
- // Due to throttling, this event occurred before the mouse left the editor, therefore ignore it.
- return;
- }
- this.viewController.emitMouseMove({
- event: e,
- target: this._createMouseTarget(e, true)
- });
- }
- _onMouseLeave(e) {
- this.lastMouseLeaveTime = (new Date()).getTime();
- this.viewController.emitMouseLeave({
- event: e,
- target: null
- });
- }
- _onMouseUp(e) {
- this.viewController.emitMouseUp({
- event: e,
- target: this._createMouseTarget(e, true)
- });
- }
- _onMouseDown(e) {
- const t = this._createMouseTarget(e, true);
- const targetIsContent = (t.type === 6 /* CONTENT_TEXT */ || t.type === 7 /* CONTENT_EMPTY */);
- const targetIsGutter = (t.type === 2 /* GUTTER_GLYPH_MARGIN */ || t.type === 3 /* GUTTER_LINE_NUMBERS */ || t.type === 4 /* GUTTER_LINE_DECORATIONS */);
- const targetIsLineNumbers = (t.type === 3 /* GUTTER_LINE_NUMBERS */);
- const selectOnLineNumbers = this._context.configuration.options.get(97 /* selectOnLineNumbers */);
- const targetIsViewZone = (t.type === 8 /* CONTENT_VIEW_ZONE */ || t.type === 5 /* GUTTER_VIEW_ZONE */);
- const targetIsWidget = (t.type === 9 /* CONTENT_WIDGET */);
- let shouldHandle = e.leftButton || e.middleButton;
- if (platform.isMacintosh && e.leftButton && e.ctrlKey) {
- shouldHandle = false;
- }
- const focus = () => {
- e.preventDefault();
- this.viewHelper.focusTextArea();
- };
- if (shouldHandle && (targetIsContent || (targetIsLineNumbers && selectOnLineNumbers))) {
- focus();
- this._mouseDownOperation.start(t.type, e);
- }
- else if (targetIsGutter) {
- // Do not steal focus
- e.preventDefault();
- }
- else if (targetIsViewZone) {
- const viewZoneData = t.detail;
- if (this.viewHelper.shouldSuppressMouseDownOnViewZone(viewZoneData.viewZoneId)) {
- focus();
- this._mouseDownOperation.start(t.type, e);
- e.preventDefault();
- }
- }
- else if (targetIsWidget && this.viewHelper.shouldSuppressMouseDownOnWidget(t.detail)) {
- focus();
- e.preventDefault();
- }
- this.viewController.emitMouseDown({
- event: e,
- target: t
- });
- }
- }
- MouseHandler.MOUSE_MOVE_MINIMUM_TIME = 100; // ms
- class MouseDownOperation extends Disposable {
- constructor(context, viewController, viewHelper, createMouseTarget, getMouseColumn) {
- super();
- this._context = context;
- this._viewController = viewController;
- this._viewHelper = viewHelper;
- this._createMouseTarget = createMouseTarget;
- this._getMouseColumn = getMouseColumn;
- this._mouseMoveMonitor = this._register(new GlobalEditorMouseMoveMonitor(this._viewHelper.viewDomNode));
- this._onScrollTimeout = this._register(new TimeoutTimer());
- this._mouseState = new MouseDownState();
- this._currentSelection = new Selection(1, 1, 1, 1);
- this._isActive = false;
- this._lastMouseEvent = null;
- }
- dispose() {
- super.dispose();
- }
- isActive() {
- return this._isActive;
- }
- _onMouseDownThenMove(e) {
- this._lastMouseEvent = e;
- this._mouseState.setModifiers(e);
- const position = this._findMousePosition(e, true);
- if (!position) {
- // Ignoring because position is unknown
- return;
- }
- if (this._mouseState.isDragAndDrop) {
- this._viewController.emitMouseDrag({
- event: e,
- target: position
- });
- }
- else {
- this._dispatchMouse(position, true);
- }
- }
- start(targetType, e) {
- this._lastMouseEvent = e;
- this._mouseState.setStartedOnLineNumbers(targetType === 3 /* GUTTER_LINE_NUMBERS */);
- this._mouseState.setStartButtons(e);
- this._mouseState.setModifiers(e);
- const position = this._findMousePosition(e, true);
- if (!position || !position.position) {
- // Ignoring because position is unknown
- return;
- }
- this._mouseState.trySetCount(e.detail, position.position);
- // Overwrite the detail of the MouseEvent, as it will be sent out in an event and contributions might rely on it.
- e.detail = this._mouseState.count;
- const options = this._context.configuration.options;
- if (!options.get(80 /* readOnly */)
- && options.get(31 /* dragAndDrop */)
- && !options.get(18 /* columnSelection */)
- && !this._mouseState.altKey // we don't support multiple mouse
- && e.detail < 2 // only single click on a selection can work
- && !this._isActive // the mouse is not down yet
- && !this._currentSelection.isEmpty() // we don't drag single cursor
- && (position.type === 6 /* CONTENT_TEXT */) // single click on text
- && position.position && this._currentSelection.containsPosition(position.position) // single click on a selection
- ) {
- this._mouseState.isDragAndDrop = true;
- this._isActive = true;
- this._mouseMoveMonitor.startMonitoring(e.target, e.buttons, createMouseMoveEventMerger(null), (e) => this._onMouseDownThenMove(e), (browserEvent) => {
- const position = this._findMousePosition(this._lastMouseEvent, true);
- if (browserEvent && browserEvent instanceof KeyboardEvent) {
- // cancel
- this._viewController.emitMouseDropCanceled();
- }
- else {
- this._viewController.emitMouseDrop({
- event: this._lastMouseEvent,
- target: (position ? this._createMouseTarget(this._lastMouseEvent, true) : null) // Ignoring because position is unknown, e.g., Content View Zone
- });
- }
- this._stop();
- });
- return;
- }
- this._mouseState.isDragAndDrop = false;
- this._dispatchMouse(position, e.shiftKey);
- if (!this._isActive) {
- this._isActive = true;
- this._mouseMoveMonitor.startMonitoring(e.target, e.buttons, createMouseMoveEventMerger(null), (e) => this._onMouseDownThenMove(e), () => this._stop());
- }
- }
- _stop() {
- this._isActive = false;
- this._onScrollTimeout.cancel();
- }
- onHeightChanged() {
- this._mouseMoveMonitor.stopMonitoring();
- }
- onScrollChanged() {
- if (!this._isActive) {
- return;
- }
- this._onScrollTimeout.setIfNotSet(() => {
- if (!this._lastMouseEvent) {
- return;
- }
- const position = this._findMousePosition(this._lastMouseEvent, false);
- if (!position) {
- // Ignoring because position is unknown
- return;
- }
- if (this._mouseState.isDragAndDrop) {
- // Ignoring because users are dragging the text
- return;
- }
- this._dispatchMouse(position, true);
- }, 10);
- }
- onCursorStateChanged(e) {
- this._currentSelection = e.selections[0];
- }
- _getPositionOutsideEditor(e) {
- const editorContent = e.editorPos;
- const model = this._context.model;
- const viewLayout = this._context.viewLayout;
- const mouseColumn = this._getMouseColumn(e);
- if (e.posy < editorContent.y) {
- const verticalOffset = Math.max(viewLayout.getCurrentScrollTop() - (editorContent.y - e.posy), 0);
- const viewZoneData = HitTestContext.getZoneAtCoord(this._context, verticalOffset);
- if (viewZoneData) {
- const newPosition = this._helpPositionJumpOverViewZone(viewZoneData);
- if (newPosition) {
- return new MouseTarget(null, 13 /* OUTSIDE_EDITOR */, mouseColumn, newPosition);
- }
- }
- const aboveLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset);
- return new MouseTarget(null, 13 /* OUTSIDE_EDITOR */, mouseColumn, new Position(aboveLineNumber, 1));
- }
- if (e.posy > editorContent.y + editorContent.height) {
- const verticalOffset = viewLayout.getCurrentScrollTop() + (e.posy - editorContent.y);
- const viewZoneData = HitTestContext.getZoneAtCoord(this._context, verticalOffset);
- if (viewZoneData) {
- const newPosition = this._helpPositionJumpOverViewZone(viewZoneData);
- if (newPosition) {
- return new MouseTarget(null, 13 /* OUTSIDE_EDITOR */, mouseColumn, newPosition);
- }
- }
- const belowLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset);
- return new MouseTarget(null, 13 /* OUTSIDE_EDITOR */, mouseColumn, new Position(belowLineNumber, model.getLineMaxColumn(belowLineNumber)));
- }
- const possibleLineNumber = viewLayout.getLineNumberAtVerticalOffset(viewLayout.getCurrentScrollTop() + (e.posy - editorContent.y));
- if (e.posx < editorContent.x) {
- return new MouseTarget(null, 13 /* OUTSIDE_EDITOR */, mouseColumn, new Position(possibleLineNumber, 1));
- }
- if (e.posx > editorContent.x + editorContent.width) {
- return new MouseTarget(null, 13 /* OUTSIDE_EDITOR */, mouseColumn, new Position(possibleLineNumber, model.getLineMaxColumn(possibleLineNumber)));
- }
- return null;
- }
- _findMousePosition(e, testEventTarget) {
- const positionOutsideEditor = this._getPositionOutsideEditor(e);
- if (positionOutsideEditor) {
- return positionOutsideEditor;
- }
- const t = this._createMouseTarget(e, testEventTarget);
- const hintedPosition = t.position;
- if (!hintedPosition) {
- return null;
- }
- if (t.type === 8 /* CONTENT_VIEW_ZONE */ || t.type === 5 /* GUTTER_VIEW_ZONE */) {
- const newPosition = this._helpPositionJumpOverViewZone(t.detail);
- if (newPosition) {
- return new MouseTarget(t.element, t.type, t.mouseColumn, newPosition, null, t.detail);
- }
- }
- return t;
- }
- _helpPositionJumpOverViewZone(viewZoneData) {
- // Force position on view zones to go above or below depending on where selection started from
- const selectionStart = new Position(this._currentSelection.selectionStartLineNumber, this._currentSelection.selectionStartColumn);
- const positionBefore = viewZoneData.positionBefore;
- const positionAfter = viewZoneData.positionAfter;
- if (positionBefore && positionAfter) {
- if (positionBefore.isBefore(selectionStart)) {
- return positionBefore;
- }
- else {
- return positionAfter;
- }
- }
- return null;
- }
- _dispatchMouse(position, inSelectionMode) {
- if (!position.position) {
- return;
- }
- this._viewController.dispatchMouse({
- position: position.position,
- mouseColumn: position.mouseColumn,
- startedOnLineNumbers: this._mouseState.startedOnLineNumbers,
- inSelectionMode: inSelectionMode,
- mouseDownCount: this._mouseState.count,
- altKey: this._mouseState.altKey,
- ctrlKey: this._mouseState.ctrlKey,
- metaKey: this._mouseState.metaKey,
- shiftKey: this._mouseState.shiftKey,
- leftButton: this._mouseState.leftButton,
- middleButton: this._mouseState.middleButton,
- });
- }
- }
- class MouseDownState {
- constructor() {
- this._altKey = false;
- this._ctrlKey = false;
- this._metaKey = false;
- this._shiftKey = false;
- this._leftButton = false;
- this._middleButton = false;
- this._startedOnLineNumbers = false;
- this._lastMouseDownPosition = null;
- this._lastMouseDownPositionEqualCount = 0;
- this._lastMouseDownCount = 0;
- this._lastSetMouseDownCountTime = 0;
- this.isDragAndDrop = false;
- }
- get altKey() { return this._altKey; }
- get ctrlKey() { return this._ctrlKey; }
- get metaKey() { return this._metaKey; }
- get shiftKey() { return this._shiftKey; }
- get leftButton() { return this._leftButton; }
- get middleButton() { return this._middleButton; }
- get startedOnLineNumbers() { return this._startedOnLineNumbers; }
- get count() {
- return this._lastMouseDownCount;
- }
- setModifiers(source) {
- this._altKey = source.altKey;
- this._ctrlKey = source.ctrlKey;
- this._metaKey = source.metaKey;
- this._shiftKey = source.shiftKey;
- }
- setStartButtons(source) {
- this._leftButton = source.leftButton;
- this._middleButton = source.middleButton;
- }
- setStartedOnLineNumbers(startedOnLineNumbers) {
- this._startedOnLineNumbers = startedOnLineNumbers;
- }
- trySetCount(setMouseDownCount, newMouseDownPosition) {
- // a. Invalidate multiple clicking if too much time has passed (will be hit by IE because the detail field of mouse events contains garbage in IE10)
- const currentTime = (new Date()).getTime();
- if (currentTime - this._lastSetMouseDownCountTime > MouseDownState.CLEAR_MOUSE_DOWN_COUNT_TIME) {
- setMouseDownCount = 1;
- }
- this._lastSetMouseDownCountTime = currentTime;
- // b. Ensure that we don't jump from single click to triple click in one go (will be hit by IE because the detail field of mouse events contains garbage in IE10)
- if (setMouseDownCount > this._lastMouseDownCount + 1) {
- setMouseDownCount = this._lastMouseDownCount + 1;
- }
- // c. Invalidate multiple clicking if the logical position is different
- if (this._lastMouseDownPosition && this._lastMouseDownPosition.equals(newMouseDownPosition)) {
- this._lastMouseDownPositionEqualCount++;
- }
- else {
- this._lastMouseDownPositionEqualCount = 1;
- }
- this._lastMouseDownPosition = newMouseDownPosition;
- // Finally set the lastMouseDownCount
- this._lastMouseDownCount = Math.min(setMouseDownCount, this._lastMouseDownPositionEqualCount);
- }
- }
- MouseDownState.CLEAR_MOUSE_DOWN_COUNT_TIME = 400; // ms
|