Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/browser/ui/scrollbar/scrollbarState.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
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
/**
7
* The minimal size of the slider (such that it can still be clickable) -- it is artificially enlarged.
8
*/
9
const MINIMUM_SLIDER_SIZE = 20;
10
11
export class ScrollbarState {
12
13
/**
14
* For the vertical scrollbar: the width.
15
* For the horizontal scrollbar: the height.
16
*/
17
private _scrollbarSize: number;
18
19
/**
20
* For the vertical scrollbar: the height of the pair horizontal scrollbar.
21
* For the horizontal scrollbar: the width of the pair vertical scrollbar.
22
*/
23
private _oppositeScrollbarSize: number;
24
25
/**
26
* For the vertical scrollbar: the height of the scrollbar's arrows.
27
* For the horizontal scrollbar: the width of the scrollbar's arrows.
28
*/
29
private readonly _arrowSize: number;
30
31
// --- variables
32
/**
33
* For the vertical scrollbar: the viewport height.
34
* For the horizontal scrollbar: the viewport width.
35
*/
36
private _visibleSize: number;
37
38
/**
39
* For the vertical scrollbar: the scroll height.
40
* For the horizontal scrollbar: the scroll width.
41
*/
42
private _scrollSize: number;
43
44
/**
45
* For the vertical scrollbar: the scroll top.
46
* For the horizontal scrollbar: the scroll left.
47
*/
48
private _scrollPosition: number;
49
50
// --- computed variables
51
52
/**
53
* `visibleSize` - `oppositeScrollbarSize`
54
*/
55
private _computedAvailableSize: number;
56
/**
57
* (`scrollSize` > 0 && `scrollSize` > `visibleSize`)
58
*/
59
private _computedIsNeeded: boolean;
60
61
private _computedSliderSize: number;
62
private _computedSliderRatio: number;
63
private _computedSliderPosition: number;
64
65
constructor(arrowSize: number, scrollbarSize: number, oppositeScrollbarSize: number, visibleSize: number, scrollSize: number, scrollPosition: number) {
66
this._scrollbarSize = Math.round(scrollbarSize);
67
this._oppositeScrollbarSize = Math.round(oppositeScrollbarSize);
68
this._arrowSize = Math.round(arrowSize);
69
70
this._visibleSize = visibleSize;
71
this._scrollSize = scrollSize;
72
this._scrollPosition = scrollPosition;
73
74
this._computedAvailableSize = 0;
75
this._computedIsNeeded = false;
76
this._computedSliderSize = 0;
77
this._computedSliderRatio = 0;
78
this._computedSliderPosition = 0;
79
80
this._refreshComputedValues();
81
}
82
83
public clone(): ScrollbarState {
84
return new ScrollbarState(this._arrowSize, this._scrollbarSize, this._oppositeScrollbarSize, this._visibleSize, this._scrollSize, this._scrollPosition);
85
}
86
87
public setVisibleSize(visibleSize: number): boolean {
88
const iVisibleSize = Math.round(visibleSize);
89
if (this._visibleSize !== iVisibleSize) {
90
this._visibleSize = iVisibleSize;
91
this._refreshComputedValues();
92
return true;
93
}
94
return false;
95
}
96
97
public setScrollSize(scrollSize: number): boolean {
98
const iScrollSize = Math.round(scrollSize);
99
if (this._scrollSize !== iScrollSize) {
100
this._scrollSize = iScrollSize;
101
this._refreshComputedValues();
102
return true;
103
}
104
return false;
105
}
106
107
public setScrollPosition(scrollPosition: number): boolean {
108
const iScrollPosition = Math.round(scrollPosition);
109
if (this._scrollPosition !== iScrollPosition) {
110
this._scrollPosition = iScrollPosition;
111
this._refreshComputedValues();
112
return true;
113
}
114
return false;
115
}
116
117
public setScrollbarSize(scrollbarSize: number): void {
118
this._scrollbarSize = Math.round(scrollbarSize);
119
}
120
121
public setOppositeScrollbarSize(oppositeScrollbarSize: number): void {
122
this._oppositeScrollbarSize = Math.round(oppositeScrollbarSize);
123
}
124
125
private static _computeValues(oppositeScrollbarSize: number, arrowSize: number, visibleSize: number, scrollSize: number, scrollPosition: number) {
126
const computedAvailableSize = Math.max(0, visibleSize - oppositeScrollbarSize);
127
const computedRepresentableSize = Math.max(0, computedAvailableSize - 2 * arrowSize);
128
const computedIsNeeded = (scrollSize > 0 && scrollSize > visibleSize);
129
130
if (!computedIsNeeded) {
131
// There is no need for a slider
132
return {
133
computedAvailableSize: Math.round(computedAvailableSize),
134
computedIsNeeded: computedIsNeeded,
135
computedSliderSize: Math.round(computedRepresentableSize),
136
computedSliderRatio: 0,
137
computedSliderPosition: 0,
138
};
139
}
140
141
// We must artificially increase the size of the slider if needed, since the slider would be too small to grab with the mouse otherwise
142
const computedSliderSize = Math.round(Math.max(MINIMUM_SLIDER_SIZE, Math.floor(visibleSize * computedRepresentableSize / scrollSize)));
143
144
// The slider can move from 0 to `computedRepresentableSize` - `computedSliderSize`
145
// in the same way `scrollPosition` can move from 0 to `scrollSize` - `visibleSize`.
146
const computedSliderRatio = (computedRepresentableSize - computedSliderSize) / (scrollSize - visibleSize);
147
const computedSliderPosition = (scrollPosition * computedSliderRatio);
148
149
return {
150
computedAvailableSize: Math.round(computedAvailableSize),
151
computedIsNeeded: computedIsNeeded,
152
computedSliderSize: Math.round(computedSliderSize),
153
computedSliderRatio: computedSliderRatio,
154
computedSliderPosition: Math.round(computedSliderPosition),
155
};
156
}
157
158
private _refreshComputedValues(): void {
159
const r = ScrollbarState._computeValues(this._oppositeScrollbarSize, this._arrowSize, this._visibleSize, this._scrollSize, this._scrollPosition);
160
this._computedAvailableSize = r.computedAvailableSize;
161
this._computedIsNeeded = r.computedIsNeeded;
162
this._computedSliderSize = r.computedSliderSize;
163
this._computedSliderRatio = r.computedSliderRatio;
164
this._computedSliderPosition = r.computedSliderPosition;
165
}
166
167
public getArrowSize(): number {
168
return this._arrowSize;
169
}
170
171
public getScrollPosition(): number {
172
return this._scrollPosition;
173
}
174
175
public getRectangleLargeSize(): number {
176
return this._computedAvailableSize;
177
}
178
179
public getRectangleSmallSize(): number {
180
return this._scrollbarSize;
181
}
182
183
public isNeeded(): boolean {
184
return this._computedIsNeeded;
185
}
186
187
public getSliderSize(): number {
188
return this._computedSliderSize;
189
}
190
191
public getSliderPosition(): number {
192
return this._computedSliderPosition;
193
}
194
195
/**
196
* Compute a desired `scrollPosition` such that `offset` ends up in the center of the slider.
197
* `offset` is based on the same coordinate system as the `sliderPosition`.
198
*/
199
public getDesiredScrollPositionFromOffset(offset: number): number {
200
if (!this._computedIsNeeded) {
201
// no need for a slider
202
return 0;
203
}
204
205
const desiredSliderPosition = offset - this._arrowSize - this._computedSliderSize / 2;
206
return Math.round(desiredSliderPosition / this._computedSliderRatio);
207
}
208
209
/**
210
* Compute a desired `scrollPosition` from if offset is before or after the slider position.
211
* If offset is before slider, treat as a page up (or left). If after, page down (or right).
212
* `offset` and `_computedSliderPosition` are based on the same coordinate system.
213
* `_visibleSize` corresponds to a "page" of lines in the returned coordinate system.
214
*/
215
public getDesiredScrollPositionFromOffsetPaged(offset: number): number {
216
if (!this._computedIsNeeded) {
217
// no need for a slider
218
return 0;
219
}
220
221
const correctedOffset = offset - this._arrowSize; // compensate if has arrows
222
let desiredScrollPosition = this._scrollPosition;
223
if (correctedOffset < this._computedSliderPosition) {
224
desiredScrollPosition -= this._visibleSize; // page up/left
225
} else {
226
desiredScrollPosition += this._visibleSize; // page down/right
227
}
228
return desiredScrollPosition;
229
}
230
231
/**
232
* Compute a desired `scrollPosition` such that the slider moves by `delta`.
233
*/
234
public getDesiredScrollPositionFromDelta(delta: number): number {
235
if (!this._computedIsNeeded) {
236
// no need for a slider
237
return 0;
238
}
239
240
const desiredSliderPosition = this._computedSliderPosition + delta;
241
return Math.round(desiredSliderPosition / this._computedSliderRatio);
242
}
243
}
244
245