Skip to content

Commit 7629bf7

Browse files
ttttotemgigarreesalexdima
authored
Horizontal dragging auto-scroll (microsoft#235174)
* move right working * move right working * stop working * start and stop working * smoothen highlighting * scale by horizontal scroll speed by char width * linting * Reduce code duplication * 💄 --------- Co-authored-by: Rhodri Rees <[email protected]> Co-authored-by: 2373655r <[email protected]> Co-authored-by: Alexandru Dima <[email protected]>
1 parent f5f8c4e commit 7629bf7

File tree

2 files changed

+252
-137
lines changed

2 files changed

+252
-137
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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

Comments
 (0)