Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/controller/dragScrolling.ts
5240 views
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
const cannotScrollAnymore = (this._position.outsidePosition === 'above' ? viewportData.startLineNumber === 1 : viewportData.endLineNumber === this._context.viewModel.getLineCount());
138
139
// First, try to find a position that matches the horizontal position of the mouse
140
let mouseTarget: IMouseTarget;
141
{
142
const editorPos = createEditorPagePosition(this._viewHelper.viewDomNode);
143
const horizontalScrollbarHeight = this._context.configuration.options.get(EditorOption.layoutInfo).horizontalScrollbarHeight;
144
const pos = new PageCoordinates(this._mouseEvent.pos.x, editorPos.y + editorPos.height - horizontalScrollbarHeight - 0.1);
145
const relativePos = createCoordinatesRelativeToEditor(this._viewHelper.viewDomNode, editorPos, pos);
146
mouseTarget = this._mouseTargetFactory.createMouseTarget(this._viewHelper.getLastRenderData(), editorPos, pos, relativePos, null);
147
}
148
if (!mouseTarget.position || mouseTarget.position.lineNumber !== edgeLineNumber || cannotScrollAnymore) {
149
if (this._position.outsidePosition === 'above') {
150
mouseTarget = MouseTarget.createOutsideEditor(this._position.mouseColumn, new Position(edgeLineNumber, 1), 'above', this._position.outsideDistance);
151
} else {
152
mouseTarget = MouseTarget.createOutsideEditor(this._position.mouseColumn, new Position(edgeLineNumber, this._context.viewModel.getLineMaxColumn(edgeLineNumber)), 'below', this._position.outsideDistance);
153
}
154
}
155
156
this._dispatchMouse(mouseTarget, true, NavigationCommandRevealType.None);
157
this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(dom.getWindow(mouseTarget.element), () => this._execute());
158
}
159
}
160
161
export class LeftRightDragScrolling extends DragScrolling {
162
protected _createDragScrollingOperation(position: IMouseTargetOutsideEditor, mouseEvent: EditorMouseEvent): DragScrollingOperation {
163
return new LeftRightDragScrollingOperation(this._context, this._viewHelper, this._mouseTargetFactory, this._dispatchMouse, position, mouseEvent);
164
}
165
}
166
167
export class LeftRightDragScrollingOperation extends DragScrollingOperation {
168
169
/**
170
* get the number of cols per second to auto-scroll
171
*/
172
private _getScrollSpeed(): number {
173
const charWidth = this._context.configuration.options.get(EditorOption.fontInfo).typicalFullwidthCharacterWidth;
174
const viewportInChars = this._context.configuration.options.get(EditorOption.layoutInfo).contentWidth / charWidth;
175
const outsideDistanceInChars = this._position.outsideDistance / charWidth;
176
if (outsideDistanceInChars <= 1.5) {
177
return Math.max(30, viewportInChars * (1 + outsideDistanceInChars));
178
}
179
if (outsideDistanceInChars <= 3) {
180
return Math.max(60, viewportInChars * (2 + outsideDistanceInChars));
181
}
182
return Math.max(200, viewportInChars * (7 + outsideDistanceInChars));
183
}
184
185
protected _execute(): void {
186
const charWidth = this._context.configuration.options.get(EditorOption.fontInfo).typicalFullwidthCharacterWidth;
187
const scrollSpeedInChars = this._getScrollSpeed();
188
const elapsed = this._tick();
189
const scrollInPixels = scrollSpeedInChars * (elapsed / 1000) * charWidth * 0.5;
190
const scrollValue = (this._position.outsidePosition === 'left' ? -scrollInPixels : scrollInPixels);
191
192
this._context.viewModel.viewLayout.deltaScrollNow(scrollValue, 0);
193
this._viewHelper.renderNow();
194
195
if (!this._position.position) {
196
return;
197
}
198
const edgeLineNumber = this._position.position.lineNumber;
199
200
// First, try to find a position that matches the horizontal position of the mouse
201
let mouseTarget: IMouseTarget;
202
{
203
const editorPos = createEditorPagePosition(this._viewHelper.viewDomNode);
204
const horizontalScrollbarHeight = this._context.configuration.options.get(EditorOption.layoutInfo).horizontalScrollbarHeight;
205
const pos = new PageCoordinates(this._mouseEvent.pos.x, editorPos.y + editorPos.height - horizontalScrollbarHeight - 0.1);
206
const relativePos = createCoordinatesRelativeToEditor(this._viewHelper.viewDomNode, editorPos, pos);
207
mouseTarget = this._mouseTargetFactory.createMouseTarget(this._viewHelper.getLastRenderData(), editorPos, pos, relativePos, null);
208
}
209
210
if (this._position.outsidePosition === 'left') {
211
mouseTarget = MouseTarget.createOutsideEditor(mouseTarget.mouseColumn, new Position(edgeLineNumber, mouseTarget.mouseColumn), 'left', this._position.outsideDistance);
212
} else {
213
mouseTarget = MouseTarget.createOutsideEditor(mouseTarget.mouseColumn, new Position(edgeLineNumber, mouseTarget.mouseColumn), 'right', this._position.outsideDistance);
214
}
215
216
this._dispatchMouse(mouseTarget, true, NavigationCommandRevealType.None);
217
this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(dom.getWindow(mouseTarget.element), () => this._execute());
218
}
219
}
220
221