From b7d73f63cd730bd8aeb48121ee29d9f8f1c7dc2e Mon Sep 17 00:00:00 2001 From: tisilent Date: Wed, 19 Apr 2023 15:48:12 +0800 Subject: [PATCH] Add smoothScroll to scrollLines --- src/browser/Terminal.ts | 76 +++++++++++++++++++++++++++++++++++++++-- src/browser/Types.d.ts | 10 ++++++ src/browser/Viewport.ts | 8 +---- 3 files changed, 85 insertions(+), 9 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index fe19f9546e..37a4eca136 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, IViewport, ILinkifier2, CharacterJoinerHandler, IBufferRange, IBufferElementProvider } from 'browser/Types'; +import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, IViewport, ILinkifier2, CharacterJoinerHandler, IBufferRange, IBufferElementProvider, ISmoothScrollProgressState } from 'browser/Types'; import { IRenderer } from 'browser/renderer/shared/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; @@ -122,6 +122,13 @@ export class Terminal extends CoreTerminal implements ITerminal { private _compositionHelper: ICompositionHelper | undefined; private _accessibilityManager: AccessibilityManager | undefined; + private _smoothScrollProgressState: ISmoothScrollProgressState = { + startTime: 0, + origin: 0, + target: 0, + progress: 0 + }; + private readonly _onCursorMove = this.register(new EventEmitter()); public readonly onCursorMove = this._onCursorMove.event; private readonly _onKey = this.register(new EventEmitter<{ key: string, domEvent: KeyboardEvent }>()); @@ -869,11 +876,76 @@ export class Terminal extends CoreTerminal implements ITerminal { } } - public scrollLines(disp: number, suppressScrollEvent?: boolean, source = ScrollSource.TERMINAL): void { + private _scrollLines(disp: number, suppressScrollEvent?: boolean, source = ScrollSource.TERMINAL): void { super.scrollLines(disp, suppressScrollEvent, source); this.refresh(0, this.rows - 1); } + public scrollLines(disp: number, suppressScrollEvent?: boolean, source = ScrollSource.TERMINAL): void { + if (source === ScrollSource.VIEWPORT) { + this._scrollLines(disp, suppressScrollEvent, source); + } else { + if (!this.optionsService.rawOptions.smoothScrollDuration) { + this._scrollLines(disp, suppressScrollEvent, source); + } else { + this._smoothScrollProgressState.startTime = Date.now(); + if (this._smoothScrollPercent() < 1) { + this._smoothScrollProgressState.origin = 0; + this._smoothScrollProgressState.target = disp; + this._smoothScrollProgressState.progress = 0; + this._smoothScroll(suppressScrollEvent, source); + } else { + this._clearSmoothScrollState(); + } + } + } + } + + private _smoothScrollPercent(): number { + if (!this.optionsService.rawOptions.smoothScrollDuration || !this._smoothScrollProgressState.startTime) { + return 1; + } + return Math.max(Math.min((Date.now() - this._smoothScrollProgressState.startTime) / this.optionsService.rawOptions.smoothScrollDuration, 1), 0); + } + + private _isSmoothScrollEnd(): boolean { + if (this._smoothScrollProgressState.target < 0) { + if (this._smoothScrollProgressState.progress > this._smoothScrollProgressState.target) { + return false; + } + } else if (this._smoothScrollProgressState.target > 0) { + if (this._smoothScrollProgressState.progress < this._smoothScrollProgressState.target) { + return false; + } + } + return true; + } + + private _smoothScroll(suppressScrollEvent?: boolean, source = ScrollSource.TERMINAL): void { + if (this._smoothScrollProgressState.startTime === 0 || this._isSmoothScrollEnd()) { + return; + } + + const percent = this._smoothScrollPercent(); + const step = Math.round(percent * (this._smoothScrollProgressState.target - this._smoothScrollProgressState.origin)) - this._smoothScrollProgressState.progress; + this._smoothScrollProgressState.progress += step; + this._scrollLines(step, suppressScrollEvent, source); + + if (this._isSmoothScrollEnd()) { + this._clearSmoothScrollState(); + return; + } + + this._coreBrowserService?.window.requestAnimationFrame(() => this._smoothScroll(suppressScrollEvent, source)); + } + + private _clearSmoothScrollState(): void { + this._smoothScrollProgressState.origin = 0; + this._smoothScrollProgressState.target = 0; + this._smoothScrollProgressState.progress = 0; + this._smoothScrollProgressState.startTime = 0; + } + public paste(data: string): void { paste(data, this.textarea!, this.coreService); } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 5f2ed89952..93236e69f3 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -150,6 +150,16 @@ export interface IViewport extends IDisposable { handleTouchMove(ev: TouchEvent): boolean; } +export interface ISmoothScrollState { + startTime: number; + origin: number; + target: number; +} + +export interface ISmoothScrollProgressState extends ISmoothScrollState { + progress: number; +} + export interface ILinkifierEvent { x1: number; y1: number; diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 25757bcac4..b5fa722d12 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -5,7 +5,7 @@ import { Disposable } from 'common/Lifecycle'; import { addDisposableDomListener } from 'browser/Lifecycle'; -import { IColorSet, IViewport, ReadonlyColorSet } from 'browser/Types'; +import { IColorSet, ISmoothScrollState, IViewport, ReadonlyColorSet } from 'browser/Types'; import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { IBuffer } from 'common/buffer/Types'; @@ -13,12 +13,6 @@ import { IRenderDimensions } from 'browser/renderer/shared/Types'; const FALLBACK_SCROLL_BAR_WIDTH = 15; -interface ISmoothScrollState { - startTime: number; - origin: number; - target: number; -} - /** * Represents the viewport of a terminal, the visible area within the larger buffer of output. * Logic for the virtual scroll bar is included in this object.