Support smooth scroll

This commit is contained in:
Daniel Imms
2024-07-10 12:32:55 -07:00
parent 2b8ccb5e51
commit 55c627572c
3 changed files with 72 additions and 18 deletions
+28 -3
View File
@@ -71,6 +71,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
public linkifier: ILinkifier2 | undefined;
private _overviewRulerRenderer: OverviewRulerRenderer | undefined;
private _viewport: Viewport | undefined;
public browser: IBrowser = Browser as any;
@@ -505,8 +506,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
this.register(this.onBlur(() => this._renderService!.handleBlur()));
this.register(this.onFocus(() => this._renderService!.handleFocus()));
const viewport = this.register(this._instantiationService.createInstance(Viewport, this.element, this.screenElement));
this.register(viewport.onRequestScrollLines(e => this.scrollLines(e, false)));
this._viewport = this.register(this._instantiationService.createInstance(Viewport, this.element, this.screenElement));
this.register(this._viewport.onRequestScrollLines(e => super.scrollLines(e, false)));
this._selectionService = this.register(this._instantiationService.createInstance(SelectionService,
this.element,
@@ -870,10 +871,34 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
}
public scrollLines(disp: number, suppressScrollEvent?: boolean): void {
super.scrollLines(disp, suppressScrollEvent);
// All scrollLines methods need to go via the viewport in order to support smooth scroll
if (this._viewport) {
this._viewport.scrollLines(disp);
} else {
super.scrollLines(disp, suppressScrollEvent);
}
this.refresh(0, this.rows - 1);
}
public scrollPages(pageCount: number): void {
this.scrollLines(pageCount * (this.rows - 1));
}
public scrollToTop(): void {
this.scrollLines(-this._bufferService.buffer.ydisp);
}
public scrollToBottom(): void {
this.scrollLines(this._bufferService.buffer.ybase - this._bufferService.buffer.ydisp);
}
public scrollToLine(line: number): void {
const scrollAmount = line - this._bufferService.buffer.ydisp;
if (scrollAmount !== 0) {
this.scrollLines(scrollAmount);
}
}
public paste(data: string): void {
paste(data, this.textarea!, this.coreService, this.optionsService);
}
+43 -9
View File
@@ -7,9 +7,10 @@ import { ICoreBrowserService, IRenderService, IThemeService } from 'browser/serv
import { EventEmitter, runAndSubscribe } from 'common/EventEmitter';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IBufferService, IOptionsService } from 'common/services/Services';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import type { ScrollableElementChangeOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
import { ScrollbarVisibility, type ScrollEvent } from 'vs/base/common/scrollable';
import { Scrollable, ScrollbarVisibility, type ScrollEvent } from 'vs/base/common/scrollable';
const enum Constants {
DEFAULT_SCROLL_BAR_WIDTH = 14
@@ -20,7 +21,7 @@ export class Viewport extends Disposable {
protected _onRequestScrollLines = this.register(new EventEmitter<number>());
public readonly onRequestScrollLines = this._onRequestScrollLines.event;
private _scrollableElement: DomScrollableElement;
private _scrollableElement: SmoothScrollableElement;
private _styleElement: HTMLStyleElement;
private _queuedAnimationFrame?: number;
@@ -42,13 +43,23 @@ export class Viewport extends Disposable {
// TODO: Support smooth scroll
this._scrollableElement = this.register(new DomScrollableElement(screenElement, {
const scrollable = this.register(new Scrollable({
forceIntegerValues: false,
smoothScrollDuration: this._optionsService.rawOptions.smoothScrollDuration,
// This is used over `IRenderService.addRefreshCallback` since it can be canceled
scheduleAtNextAnimationFrame: cb => scheduleAtNextAnimationFrame(coreBrowserService.window, cb)
}));
this.register(this._optionsService.onSpecificOptionChange('smoothScrollDuration', () => {
scrollable.setSmoothScrollDuration(this._optionsService.rawOptions.smoothScrollDuration);
}));
this._scrollableElement = this.register(new SmoothScrollableElement(screenElement, {
vertical: ScrollbarVisibility.Auto,
horizontal: ScrollbarVisibility.Hidden,
useShadows: false,
mouseWheelSmoothScroll: true,
...this._getMutableOptions()
}));
}, scrollable));
this.register(this._optionsService.onMultipleOptionChange([
'scrollSensitivity',
'fastScrollSensitivity',
@@ -80,11 +91,29 @@ export class Viewport extends Disposable {
}));
this.register(this._bufferService.onResize(() => this._queueSync()));
this.register(this._bufferService.onScroll(ydisp => this._queueSync(ydisp)));
this.register(this._bufferService.onScroll(() => this._sync()));
this.register(this._scrollableElement.onScroll(e => this._handleScroll(e)));
}
public scrollLines(disp: number): void {
const pos = this._scrollableElement.getScrollPosition();
this._scrollableElement.setScrollPosition({
reuseAnimation: true,
scrollTop: pos.scrollTop + disp * this._renderService.dimensions.css.cell.height
});
}
public scrollToLine(line: number, disableSmoothScroll?: boolean): void {
if (!disableSmoothScroll) {
this._latestYDisp = line;
}
this._scrollableElement.setScrollPosition({
reuseAnimation: !disableSmoothScroll,
scrollTop: line * this._renderService.dimensions.css.cell.height
});
}
private _getMutableOptions(): ScrollableElementChangeOptions {
return {
mouseWheelScrollSensitivity: this._optionsService.rawOptions.scrollSensitivity,
@@ -123,9 +152,13 @@ export class Viewport extends Disposable {
});
this._suppressOnScrollHandler = false;
this._scrollableElement.setScrollPosition({
scrollTop: ydisp * this._renderService.dimensions.css.cell.height
});
// If ydisp has been changed by some other copmonent (input/buffer), then stop animating smooth
// scroll and scroll there immediately.
if (ydisp !== this._latestYDisp) {
this._scrollableElement.setScrollPosition({
scrollTop: ydisp * this._renderService.dimensions.css.cell.height
});
}
this._isSyncing = false;
}
@@ -141,6 +174,7 @@ export class Viewport extends Disposable {
const newRow = Math.round(e.scrollTop / this._renderService.dimensions.css.cell.height);
const diff = newRow - this._bufferService.buffer.ydisp;
if (diff !== 0) {
this._latestYDisp = newRow;
this._onRequestScrollLines.fire(diff);
}
this._isHandlingScroll = false;
+1 -6
View File
@@ -133,15 +133,10 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
this.register(this.coreService.onRequestScrollToBottom(() => this.scrollToBottom()));
this.register(this.coreService.onUserInput(() => this._writeBuffer.handleUserInput()));
this.register(this.optionsService.onMultipleOptionChange(['windowsMode', 'windowsPty'], () => this._handleWindowsPtyOptionChange()));
this.register(this._bufferService.onScroll(event => {
this.register(this._bufferService.onScroll(() => {
this._onScroll.fire({ position: this._bufferService.buffer.ydisp });
this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom);
}));
this.register(this._inputHandler.onScroll(event => {
this._onScroll.fire({ position: this._bufferService.buffer.ydisp });
this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom);
}));
// Setup WriteBuffer
this._writeBuffer = this.register(new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult)));
this.register(forwardEvent(this._writeBuffer.onWriteParsed, this._onWriteParsed));