scrollable.ts ×32

Frontier kind: Code frontier

unlabeled · c_dc6f6cfdae87

1053 tests · 4491 LOC · 22 files · introduces 0 tests · 205 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
32 ranges205 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
624 ranges4491 lines · 22 files · Browse complete extent
All tests (intent)
1053 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 205 introduced LOC across 32 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/base/common/scrollable.ts 205 introduced LOC · 32 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- scrollable.ts
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 { Emitter, Event } from './event.js';
7 > import { Disposable, IDisposable } from './lifecycle.js';
8 >
9 > export const enum ScrollbarVisibility {
10 > Auto = 1,
11 > Hidden = 2,
12 > Visible = 3
13 > }
14 >
15 > export interface ScrollEvent {
16 > inSmoothScrolling: boolean;
17 >
18 > oldWidth: number;
19 > oldScrollWidth: number;
20 > oldScrollLeft: number;
21 >
22 > width: number;
23 > scrollWidth: number;
24 > scrollLeft: number;
25 >
26 > oldHeight: number;
27 > oldScrollHeight: number;
28 > oldScrollTop: number;
29 >
30 > height: number;
31 > scrollHeight: number;
32 > scrollTop: number;
33 >
34 > widthChanged: boolean;
35 > scrollWidthChanged: boolean;
36 > scrollLeftChanged: boolean;
37 >
38 > heightChanged: boolean;
39 > scrollHeightChanged: boolean;
40 > scrollTopChanged: boolean;
41 > }
42 >
43 > export class ScrollState implements IScrollDimensions, IScrollPosition {
44 > _scrollStateBrand: void = undefined;
45 >
46 > public readonly rawScrollLeft: number;
47 > public readonly rawScrollTop: number;
48 >
49 > public readonly width: number;
50 > public readonly scrollWidth: number;
51 > public readonly scrollLeft: number;
52 > public readonly height: number;
53 > public readonly scrollHeight: number;
54 > public readonly scrollTop: number;
55 >
56 > constructor(
57 private readonly _forceIntegerValues: boolean,
58 width: number,
102 this.scrollTop = scrollTop;
103 }
105 > public equals(other: ScrollState): boolean {
106 return (
107 this.rawScrollLeft === other.rawScrollLeft
115 );
116 }
118 > public withScrollDimensions(update: INewScrollDimensions, useRawScrollPositions: boolean): ScrollState {
119 return new ScrollState(
120 this._forceIntegerValues,
127 );
128 }
130 > public withScrollPosition(update: INewScrollPosition): ScrollState {
131 return new ScrollState(
132 this._forceIntegerValues,
139 );
140 }
142 > public createScrollEvent(previous: ScrollState, inSmoothScrolling: boolean): ScrollEvent {
143 const widthChanged = (this.width !== previous.width);
144 const scrollWidthChanged = (this.scrollWidth !== previous.scrollWidth);
176 };
177 }
179 > }
180 >
181 > export interface IScrollDimensions {
182 > readonly width: number;
183 > readonly scrollWidth: number;
184 > readonly height: number;
185 > readonly scrollHeight: number;
186 > }
187 > export interface INewScrollDimensions {
188 > width?: number;
189 > scrollWidth?: number;
190 > height?: number;
191 > scrollHeight?: number;
192 > }
193 >
194 > export interface IScrollPosition {
195 > readonly scrollLeft: number;
196 > readonly scrollTop: number;
197 > }
198 > export interface ISmoothScrollPosition {
199 > readonly scrollLeft: number;
200 > readonly scrollTop: number;
201 >
202 > readonly width: number;
203 > readonly height: number;
204 > }
205 > export interface INewScrollPosition {
206 > scrollLeft?: number;
207 > scrollTop?: number;
208 > }
209 >
210 > export interface IScrollableOptions {
211 > /**
212 > * Define if the scroll values should always be integers.
213 > */
214 > forceIntegerValues: boolean;
215 > /**
216 > * Set the duration (ms) used for smooth scroll animations.
217 > */
218 > smoothScrollDuration: number;
219 > /**
220 > * A function to schedule an update at the next frame (used for smooth scroll animations).
221 > */
222 > scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable;
223 > }
224 >
225 > export class Scrollable extends Disposable {
226 >
227 > _scrollableBrand: void = undefined;
228 >
229 > private _smoothScrollDuration: number;
230 > private readonly _scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable;
231 > private _state: ScrollState;
232 > private _smoothScrolling: SmoothScrollingOperation | null;
233 >
234 > private _onScroll = this._register(new Emitter<ScrollEvent>());
235 > public readonly onScroll: Event<ScrollEvent> = this._onScroll.event;
236 >
237 > constructor(options: IScrollableOptions) {
238 super();
239
243 this._smoothScrolling = null;
244 }
246 > public override dispose(): void {
247 if (this._smoothScrolling) {
248 this._smoothScrolling.dispose();
251 super.dispose();
252 }
254 > public setSmoothScrollDuration(smoothScrollDuration: number): void {
255 this._smoothScrollDuration = smoothScrollDuration;
256 }
258 > public validateScrollPosition(scrollPosition: INewScrollPosition): IScrollPosition {
259 return this._state.withScrollPosition(scrollPosition);
260 }
262 > public getScrollDimensions(): IScrollDimensions {
263 return this._state;
264 }
266 > public setScrollDimensions(dimensions: INewScrollDimensions, useRawScrollPositions: boolean): void {
267 const newState = this._state.withScrollDimensions(dimensions, useRawScrollPositions);
268 this._setState(newState, Boolean(this._smoothScrolling));
271 this._smoothScrolling?.acceptScrollDimensions(this._state);
272 }
274 > /**
275 > * Returns the final scroll position that the instance will have once the smooth scroll animation concludes.
276 > * If no scroll animation is occurring, it will return the current scroll position instead.
277 > */
278 > public getFutureScrollPosition(): IScrollPosition {
279 if (this._smoothScrolling) {
280 return this._smoothScrolling.to;
282 return this._state;
283 }
285 > /**
286 > * Returns the current scroll position.
287 > * Note: This result might be an intermediate scroll position, as there might be an ongoing smooth scroll animation.
288 > */
289 > public getCurrentScrollPosition(): IScrollPosition {
290 return this._state;
291 }
293 > public setScrollPositionNow(update: INewScrollPosition): void {
294 // no smooth scrolling requested
295 const newState = this._state.withScrollPosition(update);
303 this._setState(newState, false);
304 }
306 > public setScrollPositionSmooth(update: INewScrollPosition, reuseAnimation?: boolean): void {
307 if (this._smoothScrollDuration === 0) {
308 // Smooth scrolling not supported.
348 });
349 }
351 > public hasPendingScrollAnimation(): boolean {
352 return Boolean(this._smoothScrolling);
353 }
355 > private _performSmoothScrolling(): void {
356 if (!this._smoothScrolling) {
357 return;
383 });
384 }
386 > private _setState(newState: ScrollState, inSmoothScrolling: boolean): void {
387 const oldState = this._state;
388 if (oldState.equals(newState)) {
393 this._onScroll.fire(this._state.createScrollEvent(oldState, inSmoothScrolling));
394 }
395 > } scrollable.ts
396 >
397 > export class SmoothScrollingUpdate {
398 >
399 > public readonly scrollLeft: number;
400 > public readonly scrollTop: number;
401 > public readonly isDone: boolean;
402 >
403 > constructor(scrollLeft: number, scrollTop: number, isDone: boolean) {
404 this.scrollLeft = scrollLeft;
405 this.scrollTop = scrollTop;
406 this.isDone = isDone;
407 }
409 > }
410 >
411 > interface IAnimation {
412 > (completion: number): number;
413 > }
414 >
415 function createEaseOutCubic(from: number, to: number): IAnimation {
416 const delta = to - from;
419 };
420 }
422 function createComposed(a: IAnimation, b: IAnimation, cut: number): IAnimation {
423 return function (completion: number): number {
428 };
429 }
431 > export class SmoothScrollingOperation {
432 >
433 > public readonly from: ISmoothScrollPosition;
434 > public to: ISmoothScrollPosition;
435 > public readonly duration: number;
436 > public readonly startTime: number;
437 > public animationFrameDisposable: IDisposable | null;
438 >
439 > private scrollLeft!: IAnimation;
440 > private scrollTop!: IAnimation;
441 >
442 > constructor(from: ISmoothScrollPosition, to: ISmoothScrollPosition, startTime: number, duration: number) {
443 this.from = from;
444 this.to = to;
450 this._initAnimations();
451 }
453 > private _initAnimations(): void {
454 this.scrollLeft = this._initAnimation(this.from.scrollLeft, this.to.scrollLeft, this.to.width);
455 this.scrollTop = this._initAnimation(this.from.scrollTop, this.to.scrollTop, this.to.height);
456 }
458 > private _initAnimation(from: number, to: number, viewportSize: number): IAnimation {
459 const delta = Math.abs(from - to);
460 if (delta > 2.5 * viewportSize) {
472 return createEaseOutCubic(from, to);
473 }
475 > public dispose(): void {
476 if (this.animationFrameDisposable !== null) {
477 this.animationFrameDisposable.dispose();
479 }
480 }
482 > public acceptScrollDimensions(state: ScrollState): void {
483 this.to = state.withScrollPosition(this.to);
484 this._initAnimations();
485 }
487 > public tick(): SmoothScrollingUpdate {
488 return this._tick(Date.now());
489 }
491 > protected _tick(now: number): SmoothScrollingUpdate {
492 const completion = (now - this.startTime) / this.duration;
493
500 return new SmoothScrollingUpdate(this.to.scrollLeft, this.to.scrollTop, true);
501 }
503 > public combine(from: ISmoothScrollPosition, to: ISmoothScrollPosition, duration: number): SmoothScrollingOperation {
504 return SmoothScrollingOperation.start(from, to, duration);
505 }
507 > public static start(from: ISmoothScrollPosition, to: ISmoothScrollPosition, duration: number): SmoothScrollingOperation {
508 // +10 / -10 : pretend the animation already started for a quicker response to a scroll request
509 duration = duration + 10;
512 return new SmoothScrollingOperation(from, to, startTime, duration);
513 }
514 > } scrollable.ts
515 >
516 function easeInCubic(t: number) {
517 return Math.pow(t, 3);
518 }
520 function easeOutCubic(t: number) {
521 return 1 - easeInCubic(1 - t);