Path: blob/main/src/vs/base/browser/ui/scrollbar/scrollbarState.ts
3296 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*--------------------------------------------------------------------------------------------*/45/**6* The minimal size of the slider (such that it can still be clickable) -- it is artificially enlarged.7*/8const MINIMUM_SLIDER_SIZE = 20;910export class ScrollbarState {1112/**13* For the vertical scrollbar: the width.14* For the horizontal scrollbar: the height.15*/16private _scrollbarSize: number;1718/**19* For the vertical scrollbar: the height of the pair horizontal scrollbar.20* For the horizontal scrollbar: the width of the pair vertical scrollbar.21*/22private _oppositeScrollbarSize: number;2324/**25* For the vertical scrollbar: the height of the scrollbar's arrows.26* For the horizontal scrollbar: the width of the scrollbar's arrows.27*/28private readonly _arrowSize: number;2930// --- variables31/**32* For the vertical scrollbar: the viewport height.33* For the horizontal scrollbar: the viewport width.34*/35private _visibleSize: number;3637/**38* For the vertical scrollbar: the scroll height.39* For the horizontal scrollbar: the scroll width.40*/41private _scrollSize: number;4243/**44* For the vertical scrollbar: the scroll top.45* For the horizontal scrollbar: the scroll left.46*/47private _scrollPosition: number;4849// --- computed variables5051/**52* `visibleSize` - `oppositeScrollbarSize`53*/54private _computedAvailableSize: number;55/**56* (`scrollSize` > 0 && `scrollSize` > `visibleSize`)57*/58private _computedIsNeeded: boolean;5960private _computedSliderSize: number;61private _computedSliderRatio: number;62private _computedSliderPosition: number;6364constructor(arrowSize: number, scrollbarSize: number, oppositeScrollbarSize: number, visibleSize: number, scrollSize: number, scrollPosition: number) {65this._scrollbarSize = Math.round(scrollbarSize);66this._oppositeScrollbarSize = Math.round(oppositeScrollbarSize);67this._arrowSize = Math.round(arrowSize);6869this._visibleSize = visibleSize;70this._scrollSize = scrollSize;71this._scrollPosition = scrollPosition;7273this._computedAvailableSize = 0;74this._computedIsNeeded = false;75this._computedSliderSize = 0;76this._computedSliderRatio = 0;77this._computedSliderPosition = 0;7879this._refreshComputedValues();80}8182public clone(): ScrollbarState {83return new ScrollbarState(this._arrowSize, this._scrollbarSize, this._oppositeScrollbarSize, this._visibleSize, this._scrollSize, this._scrollPosition);84}8586public setVisibleSize(visibleSize: number): boolean {87const iVisibleSize = Math.round(visibleSize);88if (this._visibleSize !== iVisibleSize) {89this._visibleSize = iVisibleSize;90this._refreshComputedValues();91return true;92}93return false;94}9596public setScrollSize(scrollSize: number): boolean {97const iScrollSize = Math.round(scrollSize);98if (this._scrollSize !== iScrollSize) {99this._scrollSize = iScrollSize;100this._refreshComputedValues();101return true;102}103return false;104}105106public setScrollPosition(scrollPosition: number): boolean {107const iScrollPosition = Math.round(scrollPosition);108if (this._scrollPosition !== iScrollPosition) {109this._scrollPosition = iScrollPosition;110this._refreshComputedValues();111return true;112}113return false;114}115116public setScrollbarSize(scrollbarSize: number): void {117this._scrollbarSize = Math.round(scrollbarSize);118}119120public setOppositeScrollbarSize(oppositeScrollbarSize: number): void {121this._oppositeScrollbarSize = Math.round(oppositeScrollbarSize);122}123124private static _computeValues(oppositeScrollbarSize: number, arrowSize: number, visibleSize: number, scrollSize: number, scrollPosition: number) {125const computedAvailableSize = Math.max(0, visibleSize - oppositeScrollbarSize);126const computedRepresentableSize = Math.max(0, computedAvailableSize - 2 * arrowSize);127const computedIsNeeded = (scrollSize > 0 && scrollSize > visibleSize);128129if (!computedIsNeeded) {130// There is no need for a slider131return {132computedAvailableSize: Math.round(computedAvailableSize),133computedIsNeeded: computedIsNeeded,134computedSliderSize: Math.round(computedRepresentableSize),135computedSliderRatio: 0,136computedSliderPosition: 0,137};138}139140// We must artificially increase the size of the slider if needed, since the slider would be too small to grab with the mouse otherwise141const computedSliderSize = Math.round(Math.max(MINIMUM_SLIDER_SIZE, Math.floor(visibleSize * computedRepresentableSize / scrollSize)));142143// The slider can move from 0 to `computedRepresentableSize` - `computedSliderSize`144// in the same way `scrollPosition` can move from 0 to `scrollSize` - `visibleSize`.145const computedSliderRatio = (computedRepresentableSize - computedSliderSize) / (scrollSize - visibleSize);146const computedSliderPosition = (scrollPosition * computedSliderRatio);147148return {149computedAvailableSize: Math.round(computedAvailableSize),150computedIsNeeded: computedIsNeeded,151computedSliderSize: Math.round(computedSliderSize),152computedSliderRatio: computedSliderRatio,153computedSliderPosition: Math.round(computedSliderPosition),154};155}156157private _refreshComputedValues(): void {158const r = ScrollbarState._computeValues(this._oppositeScrollbarSize, this._arrowSize, this._visibleSize, this._scrollSize, this._scrollPosition);159this._computedAvailableSize = r.computedAvailableSize;160this._computedIsNeeded = r.computedIsNeeded;161this._computedSliderSize = r.computedSliderSize;162this._computedSliderRatio = r.computedSliderRatio;163this._computedSliderPosition = r.computedSliderPosition;164}165166public getArrowSize(): number {167return this._arrowSize;168}169170public getScrollPosition(): number {171return this._scrollPosition;172}173174public getRectangleLargeSize(): number {175return this._computedAvailableSize;176}177178public getRectangleSmallSize(): number {179return this._scrollbarSize;180}181182public isNeeded(): boolean {183return this._computedIsNeeded;184}185186public getSliderSize(): number {187return this._computedSliderSize;188}189190public getSliderPosition(): number {191return this._computedSliderPosition;192}193194/**195* Compute a desired `scrollPosition` such that `offset` ends up in the center of the slider.196* `offset` is based on the same coordinate system as the `sliderPosition`.197*/198public getDesiredScrollPositionFromOffset(offset: number): number {199if (!this._computedIsNeeded) {200// no need for a slider201return 0;202}203204const desiredSliderPosition = offset - this._arrowSize - this._computedSliderSize / 2;205return Math.round(desiredSliderPosition / this._computedSliderRatio);206}207208/**209* Compute a desired `scrollPosition` from if offset is before or after the slider position.210* If offset is before slider, treat as a page up (or left). If after, page down (or right).211* `offset` and `_computedSliderPosition` are based on the same coordinate system.212* `_visibleSize` corresponds to a "page" of lines in the returned coordinate system.213*/214public getDesiredScrollPositionFromOffsetPaged(offset: number): number {215if (!this._computedIsNeeded) {216// no need for a slider217return 0;218}219220const correctedOffset = offset - this._arrowSize; // compensate if has arrows221let desiredScrollPosition = this._scrollPosition;222if (correctedOffset < this._computedSliderPosition) {223desiredScrollPosition -= this._visibleSize; // page up/left224} else {225desiredScrollPosition += this._visibleSize; // page down/right226}227return desiredScrollPosition;228}229230/**231* Compute a desired `scrollPosition` such that the slider moves by `delta`.232*/233public getDesiredScrollPositionFromDelta(delta: number): number {234if (!this._computedIsNeeded) {235// no need for a slider236return 0;237}238239const desiredSliderPosition = this._computedSliderPosition + delta;240return Math.round(desiredSliderPosition / this._computedSliderRatio);241}242}243244245