Path: blob/main/src/vs/editor/browser/controller/dragScrolling.ts
3294 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as dom from '../../../base/browser/dom.js';6import { Disposable, IDisposable } from '../../../base/common/lifecycle.js';7import { EditorOption } from '../../common/config/editorOptions.js';8import { Position } from '../../common/core/position.js';9import { ViewContext } from '../../common/viewModel/viewContext.js';10import { NavigationCommandRevealType } from '../coreCommands.js';11import { IMouseTarget, IMouseTargetOutsideEditor } from '../editorBrowser.js';12import { createCoordinatesRelativeToEditor, createEditorPagePosition, EditorMouseEvent, PageCoordinates } from '../editorDom.js';13import { IPointerHandlerHelper } from './mouseHandler.js';14import { MouseTarget, MouseTargetFactory } from './mouseTarget.js';1516export abstract class DragScrolling extends Disposable {1718private _operation: DragScrollingOperation | null;1920constructor(21protected readonly _context: ViewContext,22protected readonly _viewHelper: IPointerHandlerHelper,23protected readonly _mouseTargetFactory: MouseTargetFactory,24protected readonly _dispatchMouse: (position: IMouseTarget, inSelectionMode: boolean, revealType: NavigationCommandRevealType) => void25) {26super();27this._operation = null;28}2930public override dispose(): void {31super.dispose();32this.stop();33}3435public start(position: IMouseTargetOutsideEditor, mouseEvent: EditorMouseEvent): void {36if (this._operation) {37this._operation.setPosition(position, mouseEvent);38} else {39this._operation = this._createDragScrollingOperation(position, mouseEvent);40}41}4243public stop(): void {44if (this._operation) {45this._operation.dispose();46this._operation = null;47}48}4950protected abstract _createDragScrollingOperation(position: IMouseTargetOutsideEditor, mouseEvent: EditorMouseEvent): DragScrollingOperation;51}5253export abstract class DragScrollingOperation extends Disposable {5455protected _position: IMouseTargetOutsideEditor;56protected _mouseEvent: EditorMouseEvent;57private _lastTime: number;58protected _animationFrameDisposable: IDisposable;5960constructor(61protected readonly _context: ViewContext,62protected readonly _viewHelper: IPointerHandlerHelper,63protected readonly _mouseTargetFactory: MouseTargetFactory,64protected readonly _dispatchMouse: (position: IMouseTarget, inSelectionMode: boolean, revealType: NavigationCommandRevealType) => void,65position: IMouseTargetOutsideEditor,66mouseEvent: EditorMouseEvent67) {68super();69this._position = position;70this._mouseEvent = mouseEvent;71this._lastTime = Date.now();72this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(dom.getWindow(mouseEvent.browserEvent), () => this._execute());73}7475public override dispose(): void {76this._animationFrameDisposable.dispose();77super.dispose();78}7980public setPosition(position: IMouseTargetOutsideEditor, mouseEvent: EditorMouseEvent): void {81this._position = position;82this._mouseEvent = mouseEvent;83}8485/**86* update internal state and return elapsed ms since last time87*/88protected _tick(): number {89const now = Date.now();90const elapsed = now - this._lastTime;91this._lastTime = now;92return elapsed;93}9495protected abstract _execute(): void;9697}9899export class TopBottomDragScrolling extends DragScrolling {100protected _createDragScrollingOperation(position: IMouseTargetOutsideEditor, mouseEvent: EditorMouseEvent): DragScrollingOperation {101return new TopBottomDragScrollingOperation(this._context, this._viewHelper, this._mouseTargetFactory, this._dispatchMouse, position, mouseEvent);102}103}104105export class TopBottomDragScrollingOperation extends DragScrollingOperation {106107/**108* get the number of lines per second to auto-scroll109*/110private _getScrollSpeed(): number {111const lineHeight = this._context.configuration.options.get(EditorOption.lineHeight);112const viewportInLines = this._context.configuration.options.get(EditorOption.layoutInfo).height / lineHeight;113const outsideDistanceInLines = this._position.outsideDistance / lineHeight;114115if (outsideDistanceInLines <= 1.5) {116return Math.max(30, viewportInLines * (1 + outsideDistanceInLines));117}118if (outsideDistanceInLines <= 3) {119return Math.max(60, viewportInLines * (2 + outsideDistanceInLines));120}121return Math.max(200, viewportInLines * (7 + outsideDistanceInLines));122}123124protected _execute(): void {125const lineHeight = this._context.configuration.options.get(EditorOption.lineHeight);126const scrollSpeedInLines = this._getScrollSpeed();127const elapsed = this._tick();128const scrollInPixels = scrollSpeedInLines * (elapsed / 1000) * lineHeight;129const scrollValue = (this._position.outsidePosition === 'above' ? -scrollInPixels : scrollInPixels);130131this._context.viewModel.viewLayout.deltaScrollNow(0, scrollValue);132this._viewHelper.renderNow();133134const viewportData = this._context.viewLayout.getLinesViewportData();135const edgeLineNumber = (this._position.outsidePosition === 'above' ? viewportData.startLineNumber : viewportData.endLineNumber);136137// First, try to find a position that matches the horizontal position of the mouse138let mouseTarget: IMouseTarget;139{140const editorPos = createEditorPagePosition(this._viewHelper.viewDomNode);141const horizontalScrollbarHeight = this._context.configuration.options.get(EditorOption.layoutInfo).horizontalScrollbarHeight;142const pos = new PageCoordinates(this._mouseEvent.pos.x, editorPos.y + editorPos.height - horizontalScrollbarHeight - 0.1);143const relativePos = createCoordinatesRelativeToEditor(this._viewHelper.viewDomNode, editorPos, pos);144mouseTarget = this._mouseTargetFactory.createMouseTarget(this._viewHelper.getLastRenderData(), editorPos, pos, relativePos, null);145}146if (!mouseTarget.position || mouseTarget.position.lineNumber !== edgeLineNumber) {147if (this._position.outsidePosition === 'above') {148mouseTarget = MouseTarget.createOutsideEditor(this._position.mouseColumn, new Position(edgeLineNumber, 1), 'above', this._position.outsideDistance);149} else {150mouseTarget = MouseTarget.createOutsideEditor(this._position.mouseColumn, new Position(edgeLineNumber, this._context.viewModel.getLineMaxColumn(edgeLineNumber)), 'below', this._position.outsideDistance);151}152}153154this._dispatchMouse(mouseTarget, true, NavigationCommandRevealType.None);155this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(dom.getWindow(mouseTarget.element), () => this._execute());156}157}158159export class LeftRightDragScrolling extends DragScrolling {160protected _createDragScrollingOperation(position: IMouseTargetOutsideEditor, mouseEvent: EditorMouseEvent): DragScrollingOperation {161return new LeftRightDragScrollingOperation(this._context, this._viewHelper, this._mouseTargetFactory, this._dispatchMouse, position, mouseEvent);162}163}164165export class LeftRightDragScrollingOperation extends DragScrollingOperation {166167/**168* get the number of cols per second to auto-scroll169*/170private _getScrollSpeed(): number {171const charWidth = this._context.configuration.options.get(EditorOption.fontInfo).typicalFullwidthCharacterWidth;172const viewportInChars = this._context.configuration.options.get(EditorOption.layoutInfo).contentWidth / charWidth;173const outsideDistanceInChars = this._position.outsideDistance / charWidth;174if (outsideDistanceInChars <= 1.5) {175return Math.max(30, viewportInChars * (1 + outsideDistanceInChars));176}177if (outsideDistanceInChars <= 3) {178return Math.max(60, viewportInChars * (2 + outsideDistanceInChars));179}180return Math.max(200, viewportInChars * (7 + outsideDistanceInChars));181}182183protected _execute(): void {184const charWidth = this._context.configuration.options.get(EditorOption.fontInfo).typicalFullwidthCharacterWidth;185const scrollSpeedInChars = this._getScrollSpeed();186const elapsed = this._tick();187const scrollInPixels = scrollSpeedInChars * (elapsed / 1000) * charWidth * 0.5;188const scrollValue = (this._position.outsidePosition === 'left' ? -scrollInPixels : scrollInPixels);189190this._context.viewModel.viewLayout.deltaScrollNow(scrollValue, 0);191this._viewHelper.renderNow();192193if (!this._position.position) {194return;195}196const edgeLineNumber = this._position.position.lineNumber;197198// First, try to find a position that matches the horizontal position of the mouse199let mouseTarget: IMouseTarget;200{201const editorPos = createEditorPagePosition(this._viewHelper.viewDomNode);202const horizontalScrollbarHeight = this._context.configuration.options.get(EditorOption.layoutInfo).horizontalScrollbarHeight;203const pos = new PageCoordinates(this._mouseEvent.pos.x, editorPos.y + editorPos.height - horizontalScrollbarHeight - 0.1);204const relativePos = createCoordinatesRelativeToEditor(this._viewHelper.viewDomNode, editorPos, pos);205mouseTarget = this._mouseTargetFactory.createMouseTarget(this._viewHelper.getLastRenderData(), editorPos, pos, relativePos, null);206}207208if (this._position.outsidePosition === 'left') {209mouseTarget = MouseTarget.createOutsideEditor(mouseTarget.mouseColumn, new Position(edgeLineNumber, mouseTarget.mouseColumn), 'left', this._position.outsideDistance);210} else {211mouseTarget = MouseTarget.createOutsideEditor(mouseTarget.mouseColumn, new Position(edgeLineNumber, mouseTarget.mouseColumn), 'right', this._position.outsideDistance);212}213214this._dispatchMouse(mouseTarget, true, NavigationCommandRevealType.None);215this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(dom.getWindow(mouseTarget.element), () => this._execute());216}217}218219220