Skip to content

Commit dfc8776

Browse files
committed
First scroll and then update the selection when dragging (microsoft#40890)
1 parent 5b7dc41 commit dfc8776

File tree

6 files changed

+240
-109
lines changed

6 files changed

+240
-109
lines changed

src/vs/editor/browser/controller/mouseHandler.ts

Lines changed: 158 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@
55

66
import * as dom from 'vs/base/browser/dom';
77
import { StandardWheelEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
8-
import { TimeoutTimer } from 'vs/base/common/async';
98
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
109
import * as platform from 'vs/base/common/platform';
1110
import { HitTestContext, MouseTarget, MouseTargetFactory, PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget';
12-
import { IMouseTarget, IMouseTargetViewZoneData, MouseTargetType } from 'vs/editor/browser/editorBrowser';
13-
import { ClientCoordinates, EditorMouseEvent, EditorMouseEventFactory, GlobalEditorPointerMoveMonitor, createEditorPagePosition, createCoordinatesRelativeToEditor } from 'vs/editor/browser/editorDom';
11+
import { IMouseTarget, IMouseTargetOutsideEditor, IMouseTargetViewZoneData, MouseTargetType } from 'vs/editor/browser/editorBrowser';
12+
import { ClientCoordinates, EditorMouseEvent, EditorMouseEventFactory, GlobalEditorPointerMoveMonitor, createEditorPagePosition, createCoordinatesRelativeToEditor, PageCoordinates } from 'vs/editor/browser/editorDom';
1413
import { ViewController } from 'vs/editor/browser/view/viewController';
1514
import { EditorZoom } from 'vs/editor/common/config/editorZoom';
1615
import { Position } from 'vs/editor/common/core/position';
@@ -20,6 +19,7 @@ import { ViewContext } from 'vs/editor/common/viewModel/viewContext';
2019
import * as viewEvents from 'vs/editor/common/viewEvents';
2120
import { ViewEventHandler } from 'vs/editor/common/viewEventHandler';
2221
import { EditorOption } from 'vs/editor/common/config/editorOptions';
22+
import { NavigationCommandRevealType } from 'vs/editor/browser/coreCommands';
2323

2424
export interface IPointerHandlerHelper {
2525
viewDomNode: HTMLElement;
@@ -34,6 +34,11 @@ export interface IPointerHandlerHelper {
3434
*/
3535
getLastRenderData(): PointerHandlerLastRenderData;
3636

37+
/**
38+
* Render right now
39+
*/
40+
renderNow(): void;
41+
3742
shouldSuppressMouseDownOnViewZone(viewZoneId: string): boolean;
3843
shouldSuppressMouseDownOnWidget(widgetId: string): boolean;
3944

@@ -69,6 +74,7 @@ export class MouseHandler extends ViewEventHandler {
6974
this._context,
7075
this.viewController,
7176
this.viewHelper,
77+
this.mouseTargetFactory,
7278
(e, testEventTarget) => this._createMouseTarget(e, testEventTarget),
7379
(e) => this._getMouseColumn(e)
7480
));
@@ -177,10 +183,6 @@ export class MouseHandler extends ViewEventHandler {
177183
public override onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean {
178184
return false;
179185
}
180-
public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
181-
this._mouseDownOperation.onScrollChanged();
182-
return false;
183-
}
184186
// --- end event handlers
185187

186188
public getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget | null {
@@ -313,36 +315,36 @@ export class MouseHandler extends ViewEventHandler {
313315

314316
class MouseDownOperation extends Disposable {
315317

316-
private readonly _context: ViewContext;
317-
private readonly _viewController: ViewController;
318-
private readonly _viewHelper: IPointerHandlerHelper;
319318
private readonly _createMouseTarget: (e: EditorMouseEvent, testEventTarget: boolean) => IMouseTarget;
320319
private readonly _getMouseColumn: (e: EditorMouseEvent) => number;
321320

322321
private readonly _mouseMoveMonitor: GlobalEditorPointerMoveMonitor;
323-
private readonly _onScrollTimeout: TimeoutTimer;
322+
private readonly _topBottomDragScrolling: TopBottomDragScrolling;
324323
private readonly _mouseState: MouseDownState;
325324

326325
private _currentSelection: Selection;
327326
private _isActive: boolean;
328327
private _lastMouseEvent: EditorMouseEvent | null;
329328

330329
constructor(
331-
context: ViewContext,
332-
viewController: ViewController,
333-
viewHelper: IPointerHandlerHelper,
330+
private readonly _context: ViewContext,
331+
private readonly _viewController: ViewController,
332+
private readonly _viewHelper: IPointerHandlerHelper,
333+
private readonly _mouseTargetFactory: MouseTargetFactory,
334334
createMouseTarget: (e: EditorMouseEvent, testEventTarget: boolean) => IMouseTarget,
335335
getMouseColumn: (e: EditorMouseEvent) => number
336336
) {
337337
super();
338-
this._context = context;
339-
this._viewController = viewController;
340-
this._viewHelper = viewHelper;
341338
this._createMouseTarget = createMouseTarget;
342339
this._getMouseColumn = getMouseColumn;
343340

344341
this._mouseMoveMonitor = this._register(new GlobalEditorPointerMoveMonitor(this._viewHelper.viewDomNode));
345-
this._onScrollTimeout = this._register(new TimeoutTimer());
342+
this._topBottomDragScrolling = this._register(new TopBottomDragScrolling(
343+
this._context,
344+
this._viewHelper,
345+
this._mouseTargetFactory,
346+
(position, inSelectionMode, revealType) => this._dispatchMouse(position, inSelectionMode, revealType)
347+
));
346348
this._mouseState = new MouseDownState();
347349

348350
this._currentSelection = new Selection(1, 1, 1, 1);
@@ -374,7 +376,12 @@ class MouseDownOperation extends Disposable {
374376
target: position
375377
});
376378
} else {
377-
this._dispatchMouse(position, true);
379+
if (position.type === MouseTargetType.OUTSIDE_EDITOR) {
380+
this._topBottomDragScrolling.start(position, e);
381+
} else {
382+
this._topBottomDragScrolling.stop();
383+
this._dispatchMouse(position, true, NavigationCommandRevealType.Minimal);
384+
}
378385
}
379386
}
380387

@@ -436,7 +443,7 @@ class MouseDownOperation extends Disposable {
436443
}
437444

438445
this._mouseState.isDragAndDrop = false;
439-
this._dispatchMouse(position, e.shiftKey);
446+
this._dispatchMouse(position, e.shiftKey, NavigationCommandRevealType.Minimal);
440447

441448
if (!this._isActive) {
442449
this._isActive = true;
@@ -452,7 +459,7 @@ class MouseDownOperation extends Disposable {
452459

453460
private _stop(): void {
454461
this._isActive = false;
455-
this._onScrollTimeout.cancel();
462+
this._topBottomDragScrolling.stop();
456463
}
457464

458465
public onHeightChanged(): void {
@@ -463,27 +470,6 @@ class MouseDownOperation extends Disposable {
463470
this._mouseMoveMonitor.stopMonitoring();
464471
}
465472

466-
public onScrollChanged(): void {
467-
if (!this._isActive) {
468-
return;
469-
}
470-
this._onScrollTimeout.setIfNotSet(() => {
471-
if (!this._lastMouseEvent) {
472-
return;
473-
}
474-
const position = this._findMousePosition(this._lastMouseEvent, false);
475-
if (!position) {
476-
// Ignoring because position is unknown
477-
return;
478-
}
479-
if (this._mouseState.isDragAndDrop) {
480-
// Ignoring because users are dragging the text
481-
return;
482-
}
483-
this._dispatchMouse(position, true);
484-
}, 10);
485-
}
486-
487473
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): void {
488474
this._currentSelection = e.selections[0];
489475
}
@@ -578,14 +564,15 @@ class MouseDownOperation extends Disposable {
578564
return null;
579565
}
580566

581-
private _dispatchMouse(position: IMouseTarget, inSelectionMode: boolean): void {
567+
private _dispatchMouse(position: IMouseTarget, inSelectionMode: boolean, revealType: NavigationCommandRevealType): void {
582568
if (!position.position) {
583569
return;
584570
}
585571
this._viewController.dispatchMouse({
586572
position: position.position,
587573
mouseColumn: position.mouseColumn,
588574
startedOnLineNumbers: this._mouseState.startedOnLineNumbers,
575+
revealType,
589576

590577
inSelectionMode: inSelectionMode,
591578
mouseDownCount: this._mouseState.count,
@@ -602,6 +589,134 @@ class MouseDownOperation extends Disposable {
602589
}
603590
}
604591

592+
class TopBottomDragScrolling extends Disposable {
593+
594+
private _operation: TopBottomDragScrollingOperation | null;
595+
596+
constructor(
597+
private readonly _context: ViewContext,
598+
private readonly _viewHelper: IPointerHandlerHelper,
599+
private readonly _mouseTargetFactory: MouseTargetFactory,
600+
private readonly _dispatchMouse: (position: IMouseTarget, inSelectionMode: boolean, revealType: NavigationCommandRevealType) => void,
601+
) {
602+
super();
603+
this._operation = null;
604+
}
605+
606+
public override dispose(): void {
607+
super.dispose();
608+
this.stop();
609+
}
610+
611+
public start(position: IMouseTargetOutsideEditor, mouseEvent: EditorMouseEvent): void {
612+
if (this._operation) {
613+
this._operation.setPosition(position, mouseEvent);
614+
} else {
615+
this._operation = new TopBottomDragScrollingOperation(this._context, this._viewHelper, this._mouseTargetFactory, this._dispatchMouse, position, mouseEvent);
616+
}
617+
}
618+
619+
public stop(): void {
620+
if (this._operation) {
621+
this._operation.dispose();
622+
this._operation = null;
623+
}
624+
}
625+
}
626+
627+
class TopBottomDragScrollingOperation extends Disposable {
628+
629+
private _position: IMouseTargetOutsideEditor;
630+
private _mouseEvent: EditorMouseEvent;
631+
private _lastTime: number;
632+
private _animationFrameDisposable: IDisposable;
633+
634+
constructor(
635+
private readonly _context: ViewContext,
636+
private readonly _viewHelper: IPointerHandlerHelper,
637+
private readonly _mouseTargetFactory: MouseTargetFactory,
638+
private readonly _dispatchMouse: (position: IMouseTarget, inSelectionMode: boolean, revealType: NavigationCommandRevealType) => void,
639+
position: IMouseTargetOutsideEditor,
640+
mouseEvent: EditorMouseEvent
641+
) {
642+
super();
643+
this._position = position;
644+
this._mouseEvent = mouseEvent;
645+
this._lastTime = Date.now();
646+
this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(() => this._execute());
647+
}
648+
649+
public override dispose(): void {
650+
this._animationFrameDisposable.dispose();
651+
}
652+
653+
public setPosition(position: IMouseTargetOutsideEditor, mouseEvent: EditorMouseEvent): void {
654+
this._position = position;
655+
this._mouseEvent = mouseEvent;
656+
}
657+
658+
/**
659+
* update internal state and return elapsed ms since last time
660+
*/
661+
private _tick(): number {
662+
const now = Date.now();
663+
const elapsed = now - this._lastTime;
664+
this._lastTime = now;
665+
return elapsed;
666+
}
667+
668+
/**
669+
* get the number of lines per second to auto-scroll
670+
*/
671+
private _getScrollSpeed(): number {
672+
const lineHeight = this._context.configuration.options.get(EditorOption.lineHeight);
673+
const viewportInLines = this._context.configuration.options.get(EditorOption.layoutInfo).height / lineHeight;
674+
const outsideDistanceInLines = this._position.outsideDistance / lineHeight;
675+
676+
if (outsideDistanceInLines <= 1.5) {
677+
return Math.max(30, viewportInLines * (1 + outsideDistanceInLines));
678+
}
679+
if (outsideDistanceInLines <= 3) {
680+
return Math.max(60, viewportInLines * (2 + outsideDistanceInLines));
681+
}
682+
return Math.max(200, viewportInLines * (7 + outsideDistanceInLines));
683+
}
684+
685+
private _execute(): void {
686+
const lineHeight = this._context.configuration.options.get(EditorOption.lineHeight);
687+
const scrollSpeedInLines = this._getScrollSpeed();
688+
const elapsed = this._tick();
689+
const scrollInPixels = scrollSpeedInLines * (elapsed / 1000) * lineHeight;
690+
const scrollValue = (this._position.outsidePosition === 'above' ? -scrollInPixels : scrollInPixels);
691+
692+
this._context.viewModel.viewLayout.deltaScrollNow(0, scrollValue);
693+
this._viewHelper.renderNow();
694+
695+
const viewportData = this._context.viewLayout.getLinesViewportData();
696+
const edgeLineNumber = (this._position.outsidePosition === 'above' ? viewportData.startLineNumber : viewportData.endLineNumber);
697+
698+
// First, try to find a position that matches the horizontal position of the mouse
699+
let mouseTarget: IMouseTarget;
700+
{
701+
const editorPos = createEditorPagePosition(this._viewHelper.viewDomNode);
702+
const horizontalScrollbarHeight = this._context.configuration.options.get(EditorOption.layoutInfo).horizontalScrollbarHeight;
703+
const pos = new PageCoordinates(this._mouseEvent.pos.x, editorPos.y + editorPos.height - horizontalScrollbarHeight - 0.1);
704+
const relativePos = createCoordinatesRelativeToEditor(this._viewHelper.viewDomNode, editorPos, pos);
705+
mouseTarget = this._mouseTargetFactory.createMouseTarget(this._viewHelper.getLastRenderData(), editorPos, pos, relativePos, null);
706+
}
707+
if (!mouseTarget.position || mouseTarget.position.lineNumber !== edgeLineNumber) {
708+
if (this._position.outsidePosition === 'above') {
709+
mouseTarget = MouseTarget.createOutsideEditor(this._position.mouseColumn, new Position(edgeLineNumber, 1), 'above', this._position.outsideDistance);
710+
} else {
711+
mouseTarget = MouseTarget.createOutsideEditor(this._position.mouseColumn, new Position(edgeLineNumber, this._context.viewModel.getLineMaxColumn(edgeLineNumber)), 'below', this._position.outsideDistance);
712+
}
713+
}
714+
715+
this._dispatchMouse(mouseTarget, true, NavigationCommandRevealType.None);
716+
this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(() => this._execute());
717+
}
718+
}
719+
605720
class MouseDownState {
606721

607722
private static readonly CLEAR_MOUSE_DOWN_COUNT_TIME = 400; // ms

src/vs/editor/browser/controller/pointerHandler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ViewController } from 'vs/editor/browser/view/viewController';
1414
import { ViewContext } from 'vs/editor/common/viewModel/viewContext';
1515
import { BrowserFeatures } from 'vs/base/browser/canIUse';
1616
import { TextAreaSyntethicEvents } from 'vs/editor/browser/controller/textAreaInput';
17+
import { NavigationCommandRevealType } from 'vs/editor/browser/coreCommands';
1718

1819
/**
1920
* Currently only tested on iOS 13/ iPadOS.
@@ -66,6 +67,7 @@ export class PointerEventHandler extends MouseHandler {
6667
position: target.position,
6768
mouseColumn: target.position.column,
6869
startedOnLineNumbers: false,
70+
revealType: NavigationCommandRevealType.Minimal,
6971
mouseDownCount: event.tapCount,
7072
inSelectionMode: false,
7173
altKey: false,
@@ -120,7 +122,7 @@ class TouchHandler extends MouseHandler {
120122
event.initEvent(TextAreaSyntethicEvents.Tap, false, true);
121123
this.viewHelper.dispatchTextAreaEvent(event);
122124

123-
this.viewController.moveTo(target.position);
125+
this.viewController.moveTo(target.position, NavigationCommandRevealType.Minimal);
124126
}
125127
}
126128

0 commit comments

Comments
 (0)