|
| 1 | +/*--------------------------------------------------------------------------------------------- |
| 2 | + * Copyright (c) Microsoft Corporation. All rights reserved. |
| 3 | + * Licensed under the MIT License. See License.txt in the project root for license information. |
| 4 | + *--------------------------------------------------------------------------------------------*/ |
| 5 | + |
| 6 | +import * as dom from '../../../base/browser/dom.js'; |
| 7 | +import { Disposable, IDisposable } from '../../../base/common/lifecycle.js'; |
| 8 | +import { EditorOption } from '../../common/config/editorOptions.js'; |
| 9 | +import { Position } from '../../common/core/position.js'; |
| 10 | +import { ViewContext } from '../../common/viewModel/viewContext.js'; |
| 11 | +import { NavigationCommandRevealType } from '../coreCommands.js'; |
| 12 | +import { IMouseTarget, IMouseTargetOutsideEditor } from '../editorBrowser.js'; |
| 13 | +import { createCoordinatesRelativeToEditor, createEditorPagePosition, EditorMouseEvent, PageCoordinates } from '../editorDom.js'; |
| 14 | +import { IPointerHandlerHelper } from './mouseHandler.js'; |
| 15 | +import { MouseTarget, MouseTargetFactory } from './mouseTarget.js'; |
| 16 | + |
| 17 | +export abstract class DragScrolling extends Disposable { |
| 18 | + |
| 19 | + private _operation: DragScrollingOperation | null; |
| 20 | + |
| 21 | + constructor( |
| 22 | + protected readonly _context: ViewContext, |
| 23 | + protected readonly _viewHelper: IPointerHandlerHelper, |
| 24 | + protected readonly _mouseTargetFactory: MouseTargetFactory, |
| 25 | + protected readonly _dispatchMouse: (position: IMouseTarget, inSelectionMode: boolean, revealType: NavigationCommandRevealType) => void |
| 26 | + ) { |
| 27 | + super(); |
| 28 | + this._operation = null; |
| 29 | + } |
| 30 | + |
| 31 | + public override dispose(): void { |
| 32 | + super.dispose(); |
| 33 | + this.stop(); |
| 34 | + } |
| 35 | + |
| 36 | + public start(position: IMouseTargetOutsideEditor, mouseEvent: EditorMouseEvent): void { |
| 37 | + if (this._operation) { |
| 38 | + this._operation.setPosition(position, mouseEvent); |
| 39 | + } else { |
| 40 | + this._operation = this._createDragScrollingOperation(position, mouseEvent); |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + public stop(): void { |
| 45 | + if (this._operation) { |
| 46 | + this._operation.dispose(); |
| 47 | + this._operation = null; |
| 48 | + } |
| 49 | + } |
| 50 | + |
| 51 | + protected abstract _createDragScrollingOperation(position: IMouseTargetOutsideEditor, mouseEvent: EditorMouseEvent): DragScrollingOperation; |
| 52 | +} |
| 53 | + |
| 54 | +export abstract class DragScrollingOperation extends Disposable { |
| 55 | + |
| 56 | + protected _position: IMouseTargetOutsideEditor; |
| 57 | + protected _mouseEvent: EditorMouseEvent; |
| 58 | + private _lastTime: number; |
| 59 | + protected _animationFrameDisposable: IDisposable; |
| 60 | + |
| 61 | + constructor( |
| 62 | + protected readonly _context: ViewContext, |
| 63 | + protected readonly _viewHelper: IPointerHandlerHelper, |
| 64 | + protected readonly _mouseTargetFactory: MouseTargetFactory, |
| 65 | + protected readonly _dispatchMouse: (position: IMouseTarget, inSelectionMode: boolean, revealType: NavigationCommandRevealType) => void, |
| 66 | + position: IMouseTargetOutsideEditor, |
| 67 | + mouseEvent: EditorMouseEvent |
| 68 | + ) { |
| 69 | + super(); |
| 70 | + this._position = position; |
| 71 | + this._mouseEvent = mouseEvent; |
| 72 | + this._lastTime = Date.now(); |
| 73 | + this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(dom.getWindow(mouseEvent.browserEvent), () => this._execute()); |
| 74 | + } |
| 75 | + |
| 76 | + public override dispose(): void { |
| 77 | + this._animationFrameDisposable.dispose(); |
| 78 | + super.dispose(); |
| 79 | + } |
| 80 | + |
| 81 | + public setPosition(position: IMouseTargetOutsideEditor, mouseEvent: EditorMouseEvent): void { |
| 82 | + this._position = position; |
| 83 | + this._mouseEvent = mouseEvent; |
| 84 | + } |
| 85 | + |
| 86 | + /** |
| 87 | + * update internal state and return elapsed ms since last time |
| 88 | + */ |
| 89 | + protected _tick(): number { |
| 90 | + const now = Date.now(); |
| 91 | + const elapsed = now - this._lastTime; |
| 92 | + this._lastTime = now; |
| 93 | + return elapsed; |
| 94 | + } |
| 95 | + |
| 96 | + protected abstract _execute(): void; |
| 97 | + |
| 98 | +} |
| 99 | + |
| 100 | +export class TopBottomDragScrolling extends DragScrolling { |
| 101 | + protected _createDragScrollingOperation(position: IMouseTargetOutsideEditor, mouseEvent: EditorMouseEvent): DragScrollingOperation { |
| 102 | + return new TopBottomDragScrollingOperation(this._context, this._viewHelper, this._mouseTargetFactory, this._dispatchMouse, position, mouseEvent); |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +export class TopBottomDragScrollingOperation extends DragScrollingOperation { |
| 107 | + |
| 108 | + /** |
| 109 | + * get the number of lines per second to auto-scroll |
| 110 | + */ |
| 111 | + private _getScrollSpeed(): number { |
| 112 | + const lineHeight = this._context.configuration.options.get(EditorOption.lineHeight); |
| 113 | + const viewportInLines = this._context.configuration.options.get(EditorOption.layoutInfo).height / lineHeight; |
| 114 | + const outsideDistanceInLines = this._position.outsideDistance / lineHeight; |
| 115 | + |
| 116 | + if (outsideDistanceInLines <= 1.5) { |
| 117 | + return Math.max(30, viewportInLines * (1 + outsideDistanceInLines)); |
| 118 | + } |
| 119 | + if (outsideDistanceInLines <= 3) { |
| 120 | + return Math.max(60, viewportInLines * (2 + outsideDistanceInLines)); |
| 121 | + } |
| 122 | + return Math.max(200, viewportInLines * (7 + outsideDistanceInLines)); |
| 123 | + } |
| 124 | + |
| 125 | + protected _execute(): void { |
| 126 | + const lineHeight = this._context.configuration.options.get(EditorOption.lineHeight); |
| 127 | + const scrollSpeedInLines = this._getScrollSpeed(); |
| 128 | + const elapsed = this._tick(); |
| 129 | + const scrollInPixels = scrollSpeedInLines * (elapsed / 1000) * lineHeight; |
| 130 | + const scrollValue = (this._position.outsidePosition === 'above' ? -scrollInPixels : scrollInPixels); |
| 131 | + |
| 132 | + this._context.viewModel.viewLayout.deltaScrollNow(0, scrollValue); |
| 133 | + this._viewHelper.renderNow(); |
| 134 | + |
| 135 | + const viewportData = this._context.viewLayout.getLinesViewportData(); |
| 136 | + const edgeLineNumber = (this._position.outsidePosition === 'above' ? viewportData.startLineNumber : viewportData.endLineNumber); |
| 137 | + |
| 138 | + // First, try to find a position that matches the horizontal position of the mouse |
| 139 | + let mouseTarget: IMouseTarget; |
| 140 | + { |
| 141 | + const editorPos = createEditorPagePosition(this._viewHelper.viewDomNode); |
| 142 | + const horizontalScrollbarHeight = this._context.configuration.options.get(EditorOption.layoutInfo).horizontalScrollbarHeight; |
| 143 | + const pos = new PageCoordinates(this._mouseEvent.pos.x, editorPos.y + editorPos.height - horizontalScrollbarHeight - 0.1); |
| 144 | + const relativePos = createCoordinatesRelativeToEditor(this._viewHelper.viewDomNode, editorPos, pos); |
| 145 | + mouseTarget = this._mouseTargetFactory.createMouseTarget(this._viewHelper.getLastRenderData(), editorPos, pos, relativePos, null); |
| 146 | + } |
| 147 | + if (!mouseTarget.position || mouseTarget.position.lineNumber !== edgeLineNumber) { |
| 148 | + if (this._position.outsidePosition === 'above') { |
| 149 | + mouseTarget = MouseTarget.createOutsideEditor(this._position.mouseColumn, new Position(edgeLineNumber, 1), 'above', this._position.outsideDistance); |
| 150 | + } else { |
| 151 | + mouseTarget = MouseTarget.createOutsideEditor(this._position.mouseColumn, new Position(edgeLineNumber, this._context.viewModel.getLineMaxColumn(edgeLineNumber)), 'below', this._position.outsideDistance); |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + this._dispatchMouse(mouseTarget, true, NavigationCommandRevealType.None); |
| 156 | + this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(dom.getWindow(mouseTarget.element), () => this._execute()); |
| 157 | + } |
| 158 | +} |
| 159 | + |
| 160 | +export class LeftRightDragScrolling extends DragScrolling { |
| 161 | + protected _createDragScrollingOperation(position: IMouseTargetOutsideEditor, mouseEvent: EditorMouseEvent): DragScrollingOperation { |
| 162 | + return new LeftRightDragScrollingOperation(this._context, this._viewHelper, this._mouseTargetFactory, this._dispatchMouse, position, mouseEvent); |
| 163 | + } |
| 164 | +} |
| 165 | + |
| 166 | +export class LeftRightDragScrollingOperation extends DragScrollingOperation { |
| 167 | + |
| 168 | + /** |
| 169 | + * get the number of cols per second to auto-scroll |
| 170 | + */ |
| 171 | + private _getScrollSpeed(): number { |
| 172 | + const charWidth = this._context.configuration.options.get(EditorOption.fontInfo).typicalFullwidthCharacterWidth; |
| 173 | + const viewportInChars = this._context.configuration.options.get(EditorOption.layoutInfo).contentWidth / charWidth; |
| 174 | + const outsideDistanceInChars = this._position.outsideDistance / charWidth; |
| 175 | + if (outsideDistanceInChars <= 1.5) { |
| 176 | + return Math.max(30, viewportInChars * (1 + outsideDistanceInChars)); |
| 177 | + } |
| 178 | + if (outsideDistanceInChars <= 3) { |
| 179 | + return Math.max(60, viewportInChars * (2 + outsideDistanceInChars)); |
| 180 | + } |
| 181 | + return Math.max(200, viewportInChars * (7 + outsideDistanceInChars)); |
| 182 | + } |
| 183 | + |
| 184 | + protected _execute(): void { |
| 185 | + const charWidth = this._context.configuration.options.get(EditorOption.fontInfo).typicalFullwidthCharacterWidth; |
| 186 | + const scrollSpeedInChars = this._getScrollSpeed(); |
| 187 | + const elapsed = this._tick(); |
| 188 | + const scrollInPixels = scrollSpeedInChars * (elapsed / 1000) * charWidth * 0.5; |
| 189 | + const scrollValue = (this._position.outsidePosition === 'left' ? -scrollInPixels : scrollInPixels); |
| 190 | + |
| 191 | + this._context.viewModel.viewLayout.deltaScrollNow(scrollValue, 0); |
| 192 | + this._viewHelper.renderNow(); |
| 193 | + |
| 194 | + if (!this._position.position) { |
| 195 | + return; |
| 196 | + } |
| 197 | + const edgeLineNumber = this._position.position.lineNumber; |
| 198 | + |
| 199 | + // First, try to find a position that matches the horizontal position of the mouse |
| 200 | + let mouseTarget: IMouseTarget; |
| 201 | + { |
| 202 | + const editorPos = createEditorPagePosition(this._viewHelper.viewDomNode); |
| 203 | + const horizontalScrollbarHeight = this._context.configuration.options.get(EditorOption.layoutInfo).horizontalScrollbarHeight; |
| 204 | + const pos = new PageCoordinates(this._mouseEvent.pos.x, editorPos.y + editorPos.height - horizontalScrollbarHeight - 0.1); |
| 205 | + const relativePos = createCoordinatesRelativeToEditor(this._viewHelper.viewDomNode, editorPos, pos); |
| 206 | + mouseTarget = this._mouseTargetFactory.createMouseTarget(this._viewHelper.getLastRenderData(), editorPos, pos, relativePos, null); |
| 207 | + } |
| 208 | + |
| 209 | + if (this._position.outsidePosition === 'left') { |
| 210 | + mouseTarget = MouseTarget.createOutsideEditor(mouseTarget.mouseColumn, new Position(edgeLineNumber, mouseTarget.mouseColumn), 'left', this._position.outsideDistance); |
| 211 | + } else { |
| 212 | + mouseTarget = MouseTarget.createOutsideEditor(mouseTarget.mouseColumn, new Position(edgeLineNumber, mouseTarget.mouseColumn), 'right', this._position.outsideDistance); |
| 213 | + } |
| 214 | + |
| 215 | + this._dispatchMouse(mouseTarget, true, NavigationCommandRevealType.None); |
| 216 | + this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(dom.getWindow(mouseTarget.element), () => this._execute()); |
| 217 | + } |
| 218 | +} |
0 commit comments