Path: blob/main/src/vs/editor/contrib/inlineCompletions/browser/model/animation.ts
4798 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { getActiveWindow } from '../../../../../base/browser/dom.js';6import { ISettableObservable, observableValue, ITransaction, IReader, observableSignal } from '../../../../../base/common/observable.js';78export class AnimatedValue {9public static const(value: number): AnimatedValue {10return new AnimatedValue(value, value, 0);11}1213public readonly startTimeMs = Date.now();1415constructor(16public readonly startValue: number,17public readonly endValue: number,18public readonly durationMs: number,19private readonly _interpolationFunction: InterpolationFunction = easeOutExpo,20) {21if (startValue === endValue) {22this.durationMs = 0;23}24}2526isFinished(): boolean {27return Date.now() >= this.startTimeMs + this.durationMs;28}2930getValue(): number {31const timePassed = Date.now() - this.startTimeMs;32if (timePassed >= this.durationMs) {33return this.endValue;34}35const value = this._interpolationFunction(timePassed, this.startValue, this.endValue - this.startValue, this.durationMs);36return value;37}38}3940type InterpolationFunction = (passedTime: number, start: number, length: number, totalDuration: number) => number;4142export function easeOutExpo(passedTime: number, start: number, length: number, totalDuration: number): number {43return passedTime === totalDuration44? start + length45: length * (-Math.pow(2, -10 * passedTime / totalDuration) + 1) + start;46}4748export function easeOutCubic(passedTime: number, start: number, length: number, totalDuration: number): number {49return length * ((passedTime = passedTime / totalDuration - 1) * passedTime * passedTime + 1) + start;50}5152export function linear(passedTime: number, start: number, length: number, totalDuration: number): number {53return length * passedTime / totalDuration + start;54}5556export class ObservableAnimatedValue {57public static const(value: number): ObservableAnimatedValue {58return new ObservableAnimatedValue(AnimatedValue.const(value));59}6061private readonly _value: ISettableObservable<AnimatedValue>;6263constructor(64initialValue: AnimatedValue,65) {66this._value = observableValue(this, initialValue);67}6869setAnimation(value: AnimatedValue, tx: ITransaction | undefined): void {70this._value.set(value, tx);71}7273changeAnimation(fn: (prev: AnimatedValue) => AnimatedValue, tx: ITransaction | undefined): void {74const value = fn(this._value.get());75this._value.set(value, tx);76}7778getValue(reader: IReader | undefined): number {79const value = this._value.read(reader);80if (!value.isFinished()) {81AnimationFrameScheduler.instance.invalidateOnNextAnimationFrame(reader);82}83return value.getValue();84}85}8687export class AnimationFrameScheduler {88public static instance = new AnimationFrameScheduler();8990private readonly _counter = observableSignal(this);9192private _isScheduled = false;9394public invalidateOnNextAnimationFrame(reader: IReader | undefined): void {95this._counter.read(reader);96if (!this._isScheduled) {97this._isScheduled = true;98getActiveWindow().requestAnimationFrame(() => {99this._isScheduled = false;100this._update();101});102}103}104105private _update(): void {106this._counter.trigger(undefined);107}108}109110111