Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/controller/dragScrolling.ts
3294 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
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
}
219
220