@xterm/xterm 5.6.0-beta.5 → 5.6.0-beta.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/css/xterm.css +71 -4
- package/lib/xterm.js +1 -1
- package/lib/xterm.js.map +1 -1
- package/lib/xterm.mjs +53 -0
- package/lib/xterm.mjs.map +7 -0
- package/package.json +40 -31
- package/src/browser/AccessibilityManager.ts +53 -25
- package/src/browser/{Terminal.ts → CoreBrowserTerminal.ts} +132 -144
- package/src/browser/Linkifier.ts +15 -13
- package/src/browser/LocalizableStrings.ts +15 -4
- package/src/browser/{Types.d.ts → Types.ts} +67 -15
- package/src/browser/Viewport.ts +142 -364
- package/src/browser/decorations/BufferDecorationRenderer.ts +14 -9
- package/src/browser/decorations/OverviewRulerRenderer.ts +40 -44
- package/src/browser/public/Terminal.ts +25 -19
- package/src/browser/renderer/dom/DomRenderer.ts +14 -16
- package/src/browser/renderer/shared/CharAtlasUtils.ts +4 -0
- package/src/browser/renderer/shared/CustomGlyphs.ts +6 -0
- package/src/browser/renderer/shared/DevicePixelObserver.ts +1 -2
- package/src/browser/renderer/shared/TextureAtlas.ts +3 -3
- package/src/browser/renderer/shared/{Types.d.ts → Types.ts} +4 -4
- package/src/browser/services/CharSizeService.ts +6 -6
- package/src/browser/services/CoreBrowserService.ts +15 -15
- package/src/browser/services/LinkProviderService.ts +2 -2
- package/src/browser/services/RenderService.ts +20 -20
- package/src/browser/services/SelectionService.ts +8 -8
- package/src/browser/services/Services.ts +13 -13
- package/src/browser/services/ThemeService.ts +17 -56
- package/src/browser/shared/Constants.ts +8 -0
- package/src/common/CircularList.ts +5 -5
- package/src/common/CoreTerminal.ts +35 -41
- package/src/common/InputHandler.ts +34 -28
- package/src/common/{Types.d.ts → Types.ts} +11 -17
- package/src/common/buffer/Buffer.ts +5 -1
- package/src/common/buffer/BufferSet.ts +5 -5
- package/src/common/buffer/Marker.ts +4 -4
- package/src/common/buffer/{Types.d.ts → Types.ts} +2 -2
- package/src/common/input/WriteBuffer.ts +3 -3
- package/src/common/parser/EscapeSequenceParser.ts +4 -4
- package/src/common/public/BufferNamespaceApi.ts +3 -3
- package/src/common/services/BufferService.ts +7 -7
- package/src/common/services/CoreMouseService.ts +5 -3
- package/src/common/services/CoreService.ts +6 -6
- package/src/common/services/DecorationService.ts +8 -9
- package/src/common/services/LogService.ts +2 -2
- package/src/common/services/OptionsService.ts +5 -5
- package/src/common/services/Services.ts +24 -17
- package/src/common/services/UnicodeService.ts +2 -2
- package/src/vs/base/browser/browser.ts +141 -0
- package/src/vs/base/browser/canIUse.ts +49 -0
- package/src/vs/base/browser/dom.ts +2369 -0
- package/src/vs/base/browser/fastDomNode.ts +316 -0
- package/src/vs/base/browser/globalPointerMoveMonitor.ts +112 -0
- package/src/vs/base/browser/iframe.ts +135 -0
- package/src/vs/base/browser/keyboardEvent.ts +213 -0
- package/src/vs/base/browser/mouseEvent.ts +229 -0
- package/src/vs/base/browser/touch.ts +372 -0
- package/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +303 -0
- package/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +114 -0
- package/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +720 -0
- package/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +165 -0
- package/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts +114 -0
- package/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +243 -0
- package/src/vs/base/browser/ui/scrollbar/scrollbarVisibilityController.ts +118 -0
- package/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +116 -0
- package/src/vs/base/browser/ui/widget.ts +57 -0
- package/src/vs/base/browser/window.ts +14 -0
- package/src/vs/base/common/arrays.ts +887 -0
- package/src/vs/base/common/arraysFind.ts +202 -0
- package/src/vs/base/common/assert.ts +71 -0
- package/src/vs/base/common/async.ts +1992 -0
- package/src/vs/base/common/cancellation.ts +148 -0
- package/src/vs/base/common/charCode.ts +450 -0
- package/src/vs/base/common/collections.ts +140 -0
- package/src/vs/base/common/decorators.ts +130 -0
- package/src/vs/base/common/equals.ts +146 -0
- package/src/vs/base/common/errors.ts +303 -0
- package/src/vs/base/common/event.ts +1778 -0
- package/src/vs/base/common/functional.ts +32 -0
- package/src/vs/base/common/hash.ts +316 -0
- package/src/vs/base/common/iterator.ts +159 -0
- package/src/vs/base/common/keyCodes.ts +526 -0
- package/src/vs/base/common/keybindings.ts +284 -0
- package/src/vs/base/common/lazy.ts +47 -0
- package/src/vs/base/common/lifecycle.ts +801 -0
- package/src/vs/base/common/linkedList.ts +142 -0
- package/src/vs/base/common/map.ts +202 -0
- package/src/vs/base/common/numbers.ts +98 -0
- package/src/vs/base/common/observable.ts +76 -0
- package/src/vs/base/common/observableInternal/api.ts +31 -0
- package/src/vs/base/common/observableInternal/autorun.ts +281 -0
- package/src/vs/base/common/observableInternal/base.ts +489 -0
- package/src/vs/base/common/observableInternal/debugName.ts +145 -0
- package/src/vs/base/common/observableInternal/derived.ts +428 -0
- package/src/vs/base/common/observableInternal/lazyObservableValue.ts +146 -0
- package/src/vs/base/common/observableInternal/logging.ts +328 -0
- package/src/vs/base/common/observableInternal/promise.ts +209 -0
- package/src/vs/base/common/observableInternal/utils.ts +610 -0
- package/src/vs/base/common/platform.ts +281 -0
- package/src/vs/base/common/scrollable.ts +522 -0
- package/src/vs/base/common/sequence.ts +34 -0
- package/src/vs/base/common/stopwatch.ts +43 -0
- package/src/vs/base/common/strings.ts +557 -0
- package/src/vs/base/common/symbols.ts +9 -0
- package/src/vs/base/common/uint.ts +59 -0
- package/src/vs/patches/nls.ts +90 -0
- package/src/vs/typings/base-common.d.ts +20 -0
- package/src/vs/typings/require.d.ts +42 -0
- package/src/vs/typings/thenable.d.ts +12 -0
- package/src/vs/typings/vscode-globals-nls.d.ts +36 -0
- package/src/vs/typings/vscode-globals-product.d.ts +33 -0
- package/typings/xterm.d.ts +59 -15
- package/src/browser/Lifecycle.ts +0 -33
- package/src/common/EventEmitter.ts +0 -78
- package/src/common/Lifecycle.ts +0 -108
- /package/src/browser/selection/{Types.d.ts → Types.ts} +0 -0
- /package/src/common/parser/{Types.d.ts → Types.ts} +0 -0
|
@@ -0,0 +1,372 @@
|
|
|
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
|
+
import * as DomUtils from 'vs/base/browser/dom';
|
|
7
|
+
import { mainWindow } from 'vs/base/browser/window';
|
|
8
|
+
import * as arrays from 'vs/base/common/arrays';
|
|
9
|
+
import { memoize } from 'vs/base/common/decorators';
|
|
10
|
+
import { Event as EventUtils } from 'vs/base/common/event';
|
|
11
|
+
import { Disposable, IDisposable, markAsSingleton, toDisposable } from 'vs/base/common/lifecycle';
|
|
12
|
+
import { LinkedList } from 'vs/base/common/linkedList';
|
|
13
|
+
|
|
14
|
+
export namespace EventType {
|
|
15
|
+
export const Tap = '-xterm-gesturetap';
|
|
16
|
+
export const Change = '-xterm-gesturechange';
|
|
17
|
+
export const Start = '-xterm-gesturestart';
|
|
18
|
+
export const End = '-xterm-gesturesend';
|
|
19
|
+
export const Contextmenu = '-xterm-gesturecontextmenu';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface TouchData {
|
|
23
|
+
id: number;
|
|
24
|
+
initialTarget: EventTarget;
|
|
25
|
+
initialTimeStamp: number;
|
|
26
|
+
initialPageX: number;
|
|
27
|
+
initialPageY: number;
|
|
28
|
+
rollingTimestamps: number[];
|
|
29
|
+
rollingPageX: number[];
|
|
30
|
+
rollingPageY: number[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface GestureEvent extends MouseEvent {
|
|
34
|
+
initialTarget: EventTarget | undefined;
|
|
35
|
+
translationX: number;
|
|
36
|
+
translationY: number;
|
|
37
|
+
pageX: number;
|
|
38
|
+
pageY: number;
|
|
39
|
+
tapCount: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface Touch {
|
|
43
|
+
identifier: number;
|
|
44
|
+
screenX: number;
|
|
45
|
+
screenY: number;
|
|
46
|
+
clientX: number;
|
|
47
|
+
clientY: number;
|
|
48
|
+
pageX: number;
|
|
49
|
+
pageY: number;
|
|
50
|
+
radiusX: number;
|
|
51
|
+
radiusY: number;
|
|
52
|
+
rotationAngle: number;
|
|
53
|
+
force: number;
|
|
54
|
+
target: Element;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface TouchList {
|
|
58
|
+
[i: number]: Touch;
|
|
59
|
+
length: number;
|
|
60
|
+
item(index: number): Touch;
|
|
61
|
+
identifiedTouch(id: number): Touch;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface TouchEvent extends Event {
|
|
65
|
+
touches: TouchList;
|
|
66
|
+
targetTouches: TouchList;
|
|
67
|
+
changedTouches: TouchList;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class Gesture extends Disposable {
|
|
71
|
+
|
|
72
|
+
private static readonly SCROLL_FRICTION = -0.005;
|
|
73
|
+
private static INSTANCE: Gesture;
|
|
74
|
+
private static readonly HOLD_DELAY = 700;
|
|
75
|
+
|
|
76
|
+
private dispatched = false;
|
|
77
|
+
private readonly targets = new LinkedList<HTMLElement>();
|
|
78
|
+
private readonly ignoreTargets = new LinkedList<HTMLElement>();
|
|
79
|
+
private handle: IDisposable | null;
|
|
80
|
+
|
|
81
|
+
private readonly activeTouches: { [id: number]: TouchData };
|
|
82
|
+
|
|
83
|
+
private _lastSetTapCountTime: number;
|
|
84
|
+
|
|
85
|
+
private static readonly CLEAR_TAP_COUNT_TIME = 400; // ms
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
private constructor() {
|
|
89
|
+
super();
|
|
90
|
+
|
|
91
|
+
this.activeTouches = {};
|
|
92
|
+
this.handle = null;
|
|
93
|
+
this._lastSetTapCountTime = 0;
|
|
94
|
+
|
|
95
|
+
this._register(EventUtils.runAndSubscribe(DomUtils.onDidRegisterWindow, ({ window, disposables }) => {
|
|
96
|
+
disposables.add(DomUtils.addDisposableListener(window.document, 'touchstart', (e: TouchEvent) => this.onTouchStart(e), { passive: false }));
|
|
97
|
+
disposables.add(DomUtils.addDisposableListener(window.document, 'touchend', (e: TouchEvent) => this.onTouchEnd(window, e)));
|
|
98
|
+
disposables.add(DomUtils.addDisposableListener(window.document, 'touchmove', (e: TouchEvent) => this.onTouchMove(e), { passive: false }));
|
|
99
|
+
}, { window: mainWindow, disposables: this._store }));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public static addTarget(element: HTMLElement): IDisposable {
|
|
103
|
+
if (!Gesture.isTouchDevice()) {
|
|
104
|
+
return Disposable.None;
|
|
105
|
+
}
|
|
106
|
+
if (!Gesture.INSTANCE) {
|
|
107
|
+
Gesture.INSTANCE = markAsSingleton(new Gesture());
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const remove = Gesture.INSTANCE.targets.push(element);
|
|
111
|
+
return toDisposable(remove);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public static ignoreTarget(element: HTMLElement): IDisposable {
|
|
115
|
+
if (!Gesture.isTouchDevice()) {
|
|
116
|
+
return Disposable.None;
|
|
117
|
+
}
|
|
118
|
+
if (!Gesture.INSTANCE) {
|
|
119
|
+
Gesture.INSTANCE = markAsSingleton(new Gesture());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const remove = Gesture.INSTANCE.ignoreTargets.push(element);
|
|
123
|
+
return toDisposable(remove);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@memoize
|
|
127
|
+
static isTouchDevice(): boolean {
|
|
128
|
+
// `'ontouchstart' in window` always evaluates to true with typescript's modern typings. This causes `window` to be
|
|
129
|
+
// `never` later in `window.navigator`. That's why we need the explicit `window as Window` cast
|
|
130
|
+
return 'ontouchstart' in mainWindow || navigator.maxTouchPoints > 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public override dispose(): void {
|
|
134
|
+
if (this.handle) {
|
|
135
|
+
this.handle.dispose();
|
|
136
|
+
this.handle = null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
super.dispose();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private onTouchStart(e: TouchEvent): void {
|
|
143
|
+
const timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based.
|
|
144
|
+
|
|
145
|
+
if (this.handle) {
|
|
146
|
+
this.handle.dispose();
|
|
147
|
+
this.handle = null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
for (let i = 0, len = e.targetTouches.length; i < len; i++) {
|
|
151
|
+
const touch = e.targetTouches.item(i);
|
|
152
|
+
|
|
153
|
+
this.activeTouches[touch.identifier] = {
|
|
154
|
+
id: touch.identifier,
|
|
155
|
+
initialTarget: touch.target,
|
|
156
|
+
initialTimeStamp: timestamp,
|
|
157
|
+
initialPageX: touch.pageX,
|
|
158
|
+
initialPageY: touch.pageY,
|
|
159
|
+
rollingTimestamps: [timestamp],
|
|
160
|
+
rollingPageX: [touch.pageX],
|
|
161
|
+
rollingPageY: [touch.pageY]
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const evt = this.newGestureEvent(EventType.Start, touch.target);
|
|
165
|
+
evt.pageX = touch.pageX;
|
|
166
|
+
evt.pageY = touch.pageY;
|
|
167
|
+
this.dispatchEvent(evt);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (this.dispatched) {
|
|
171
|
+
e.preventDefault();
|
|
172
|
+
e.stopPropagation();
|
|
173
|
+
this.dispatched = false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private onTouchEnd(targetWindow: Window, e: TouchEvent): void {
|
|
178
|
+
const timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based.
|
|
179
|
+
|
|
180
|
+
const activeTouchCount = Object.keys(this.activeTouches).length;
|
|
181
|
+
|
|
182
|
+
for (let i = 0, len = e.changedTouches.length; i < len; i++) {
|
|
183
|
+
|
|
184
|
+
const touch = e.changedTouches.item(i);
|
|
185
|
+
|
|
186
|
+
if (!this.activeTouches.hasOwnProperty(String(touch.identifier))) {
|
|
187
|
+
console.warn('move of an UNKNOWN touch', touch);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const data = this.activeTouches[touch.identifier],
|
|
192
|
+
holdTime = Date.now() - data.initialTimeStamp;
|
|
193
|
+
|
|
194
|
+
if (holdTime < Gesture.HOLD_DELAY
|
|
195
|
+
&& Math.abs(data.initialPageX - arrays.tail(data.rollingPageX)!) < 30
|
|
196
|
+
&& Math.abs(data.initialPageY - arrays.tail(data.rollingPageY)!) < 30) {
|
|
197
|
+
|
|
198
|
+
const evt = this.newGestureEvent(EventType.Tap, data.initialTarget);
|
|
199
|
+
evt.pageX = arrays.tail(data.rollingPageX)!;
|
|
200
|
+
evt.pageY = arrays.tail(data.rollingPageY)!;
|
|
201
|
+
this.dispatchEvent(evt);
|
|
202
|
+
|
|
203
|
+
} else if (holdTime >= Gesture.HOLD_DELAY
|
|
204
|
+
&& Math.abs(data.initialPageX - arrays.tail(data.rollingPageX)!) < 30
|
|
205
|
+
&& Math.abs(data.initialPageY - arrays.tail(data.rollingPageY)!) < 30) {
|
|
206
|
+
|
|
207
|
+
const evt = this.newGestureEvent(EventType.Contextmenu, data.initialTarget);
|
|
208
|
+
evt.pageX = arrays.tail(data.rollingPageX)!;
|
|
209
|
+
evt.pageY = arrays.tail(data.rollingPageY)!;
|
|
210
|
+
this.dispatchEvent(evt);
|
|
211
|
+
|
|
212
|
+
} else if (activeTouchCount === 1) {
|
|
213
|
+
const finalX = arrays.tail(data.rollingPageX)!;
|
|
214
|
+
const finalY = arrays.tail(data.rollingPageY)!;
|
|
215
|
+
|
|
216
|
+
const deltaT = arrays.tail(data.rollingTimestamps)! - data.rollingTimestamps[0];
|
|
217
|
+
const deltaX = finalX - data.rollingPageX[0];
|
|
218
|
+
const deltaY = finalY - data.rollingPageY[0];
|
|
219
|
+
|
|
220
|
+
// We need to get all the dispatch targets on the start of the inertia event
|
|
221
|
+
const dispatchTo = [...this.targets].filter(t => data.initialTarget instanceof Node && t.contains(data.initialTarget));
|
|
222
|
+
this.inertia(targetWindow, dispatchTo, timestamp, // time now
|
|
223
|
+
Math.abs(deltaX) / deltaT, // speed
|
|
224
|
+
deltaX > 0 ? 1 : -1, // x direction
|
|
225
|
+
finalX, // x now
|
|
226
|
+
Math.abs(deltaY) / deltaT, // y speed
|
|
227
|
+
deltaY > 0 ? 1 : -1, // y direction
|
|
228
|
+
finalY // y now
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
this.dispatchEvent(this.newGestureEvent(EventType.End, data.initialTarget));
|
|
234
|
+
// forget about this touch
|
|
235
|
+
delete this.activeTouches[touch.identifier];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (this.dispatched) {
|
|
239
|
+
e.preventDefault();
|
|
240
|
+
e.stopPropagation();
|
|
241
|
+
this.dispatched = false;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private newGestureEvent(type: string, initialTarget?: EventTarget): GestureEvent {
|
|
246
|
+
const event = document.createEvent('CustomEvent') as unknown as GestureEvent;
|
|
247
|
+
event.initEvent(type, false, true);
|
|
248
|
+
event.initialTarget = initialTarget;
|
|
249
|
+
event.tapCount = 0;
|
|
250
|
+
return event;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private dispatchEvent(event: GestureEvent): void {
|
|
254
|
+
if (event.type === EventType.Tap) {
|
|
255
|
+
const currentTime = (new Date()).getTime();
|
|
256
|
+
let setTapCount = 0;
|
|
257
|
+
if (currentTime - this._lastSetTapCountTime > Gesture.CLEAR_TAP_COUNT_TIME) {
|
|
258
|
+
setTapCount = 1;
|
|
259
|
+
} else {
|
|
260
|
+
setTapCount = 2;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
this._lastSetTapCountTime = currentTime;
|
|
264
|
+
event.tapCount = setTapCount;
|
|
265
|
+
} else if (event.type === EventType.Change || event.type === EventType.Contextmenu) {
|
|
266
|
+
// tap is canceled by scrolling or context menu
|
|
267
|
+
this._lastSetTapCountTime = 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (event.initialTarget instanceof Node) {
|
|
271
|
+
for (const ignoreTarget of this.ignoreTargets) {
|
|
272
|
+
if (ignoreTarget.contains(event.initialTarget)) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const targets: [number, HTMLElement][] = [];
|
|
278
|
+
for (const target of this.targets) {
|
|
279
|
+
if (target.contains(event.initialTarget)) {
|
|
280
|
+
let depth = 0;
|
|
281
|
+
let now: Node | null = event.initialTarget;
|
|
282
|
+
while (now && now !== target) {
|
|
283
|
+
depth++;
|
|
284
|
+
now = now.parentElement;
|
|
285
|
+
}
|
|
286
|
+
targets.push([depth, target]);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
targets.sort((a, b) => a[0] - b[0]);
|
|
291
|
+
|
|
292
|
+
for (const [_, target] of targets) {
|
|
293
|
+
target.dispatchEvent(event);
|
|
294
|
+
this.dispatched = true;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private inertia(targetWindow: Window, dispatchTo: readonly EventTarget[], t1: number, vX: number, dirX: number, x: number, vY: number, dirY: number, y: number): void {
|
|
300
|
+
this.handle = DomUtils.scheduleAtNextAnimationFrame(targetWindow, () => {
|
|
301
|
+
const now = Date.now();
|
|
302
|
+
|
|
303
|
+
// velocity: old speed + accel_over_time
|
|
304
|
+
const deltaT = now - t1;
|
|
305
|
+
let delta_pos_x = 0, delta_pos_y = 0;
|
|
306
|
+
let stopped = true;
|
|
307
|
+
|
|
308
|
+
vX += Gesture.SCROLL_FRICTION * deltaT;
|
|
309
|
+
vY += Gesture.SCROLL_FRICTION * deltaT;
|
|
310
|
+
|
|
311
|
+
if (vX > 0) {
|
|
312
|
+
stopped = false;
|
|
313
|
+
delta_pos_x = dirX * vX * deltaT;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (vY > 0) {
|
|
317
|
+
stopped = false;
|
|
318
|
+
delta_pos_y = dirY * vY * deltaT;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// dispatch translation event
|
|
322
|
+
const evt = this.newGestureEvent(EventType.Change);
|
|
323
|
+
evt.translationX = delta_pos_x;
|
|
324
|
+
evt.translationY = delta_pos_y;
|
|
325
|
+
dispatchTo.forEach(d => d.dispatchEvent(evt));
|
|
326
|
+
|
|
327
|
+
if (!stopped) {
|
|
328
|
+
this.inertia(targetWindow, dispatchTo, now, vX, dirX, x + delta_pos_x, vY, dirY, y + delta_pos_y);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private onTouchMove(e: TouchEvent): void {
|
|
334
|
+
const timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based.
|
|
335
|
+
|
|
336
|
+
for (let i = 0, len = e.changedTouches.length; i < len; i++) {
|
|
337
|
+
|
|
338
|
+
const touch = e.changedTouches.item(i);
|
|
339
|
+
|
|
340
|
+
if (!this.activeTouches.hasOwnProperty(String(touch.identifier))) {
|
|
341
|
+
console.warn('end of an UNKNOWN touch', touch);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const data = this.activeTouches[touch.identifier];
|
|
346
|
+
|
|
347
|
+
const evt = this.newGestureEvent(EventType.Change, data.initialTarget);
|
|
348
|
+
evt.translationX = touch.pageX - arrays.tail(data.rollingPageX)!;
|
|
349
|
+
evt.translationY = touch.pageY - arrays.tail(data.rollingPageY)!;
|
|
350
|
+
evt.pageX = touch.pageX;
|
|
351
|
+
evt.pageY = touch.pageY;
|
|
352
|
+
this.dispatchEvent(evt);
|
|
353
|
+
|
|
354
|
+
// only keep a few data points, to average the final speed
|
|
355
|
+
if (data.rollingPageX.length > 3) {
|
|
356
|
+
data.rollingPageX.shift();
|
|
357
|
+
data.rollingPageY.shift();
|
|
358
|
+
data.rollingTimestamps.shift();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
data.rollingPageX.push(touch.pageX);
|
|
362
|
+
data.rollingPageY.push(touch.pageY);
|
|
363
|
+
data.rollingTimestamps.push(timestamp);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (this.dispatched) {
|
|
367
|
+
e.preventDefault();
|
|
368
|
+
e.stopPropagation();
|
|
369
|
+
this.dispatched = false;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
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
|
+
import * as dom from 'vs/base/browser/dom';
|
|
7
|
+
import { createFastDomNode, FastDomNode } from 'vs/base/browser/fastDomNode';
|
|
8
|
+
import { GlobalPointerMoveMonitor } from 'vs/base/browser/globalPointerMoveMonitor';
|
|
9
|
+
import { StandardWheelEvent } from 'vs/base/browser/mouseEvent';
|
|
10
|
+
import { ScrollbarArrow, ScrollbarArrowOptions } from 'vs/base/browser/ui/scrollbar/scrollbarArrow';
|
|
11
|
+
import { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState';
|
|
12
|
+
import { ScrollbarVisibilityController } from 'vs/base/browser/ui/scrollbar/scrollbarVisibilityController';
|
|
13
|
+
import { Widget } from 'vs/base/browser/ui/widget';
|
|
14
|
+
import * as platform from 'vs/base/common/platform';
|
|
15
|
+
import { INewScrollPosition, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The orthogonal distance to the slider at which dragging "resets". This implements "snapping"
|
|
19
|
+
*/
|
|
20
|
+
const POINTER_DRAG_RESET_DISTANCE = 140;
|
|
21
|
+
|
|
22
|
+
export interface ISimplifiedPointerEvent {
|
|
23
|
+
buttons: number;
|
|
24
|
+
pageX: number;
|
|
25
|
+
pageY: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ScrollbarHost {
|
|
29
|
+
onMouseWheel(mouseWheelEvent: StandardWheelEvent): void;
|
|
30
|
+
onDragStart(): void;
|
|
31
|
+
onDragEnd(): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface AbstractScrollbarOptions {
|
|
35
|
+
lazyRender: boolean;
|
|
36
|
+
host: ScrollbarHost;
|
|
37
|
+
scrollbarState: ScrollbarState;
|
|
38
|
+
visibility: ScrollbarVisibility;
|
|
39
|
+
extraScrollbarClassName: string;
|
|
40
|
+
scrollable: Scrollable;
|
|
41
|
+
scrollByPage: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export abstract class AbstractScrollbar extends Widget {
|
|
45
|
+
|
|
46
|
+
protected _host: ScrollbarHost;
|
|
47
|
+
protected _scrollable: Scrollable;
|
|
48
|
+
protected _scrollByPage: boolean;
|
|
49
|
+
private _lazyRender: boolean;
|
|
50
|
+
protected _scrollbarState: ScrollbarState;
|
|
51
|
+
protected _visibilityController: ScrollbarVisibilityController;
|
|
52
|
+
private _pointerMoveMonitor: GlobalPointerMoveMonitor;
|
|
53
|
+
|
|
54
|
+
public domNode: FastDomNode<HTMLElement>;
|
|
55
|
+
public slider!: FastDomNode<HTMLElement>;
|
|
56
|
+
|
|
57
|
+
protected _shouldRender: boolean;
|
|
58
|
+
|
|
59
|
+
constructor(opts: AbstractScrollbarOptions) {
|
|
60
|
+
super();
|
|
61
|
+
this._lazyRender = opts.lazyRender;
|
|
62
|
+
this._host = opts.host;
|
|
63
|
+
this._scrollable = opts.scrollable;
|
|
64
|
+
this._scrollByPage = opts.scrollByPage;
|
|
65
|
+
this._scrollbarState = opts.scrollbarState;
|
|
66
|
+
this._visibilityController = this._register(new ScrollbarVisibilityController(opts.visibility, 'visible scrollbar ' + opts.extraScrollbarClassName, 'invisible scrollbar ' + opts.extraScrollbarClassName));
|
|
67
|
+
this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());
|
|
68
|
+
this._pointerMoveMonitor = this._register(new GlobalPointerMoveMonitor());
|
|
69
|
+
this._shouldRender = true;
|
|
70
|
+
this.domNode = createFastDomNode(document.createElement('div'));
|
|
71
|
+
this.domNode.setAttribute('role', 'presentation');
|
|
72
|
+
this.domNode.setAttribute('aria-hidden', 'true');
|
|
73
|
+
|
|
74
|
+
this._visibilityController.setDomNode(this.domNode);
|
|
75
|
+
this.domNode.setPosition('absolute');
|
|
76
|
+
|
|
77
|
+
this._register(dom.addDisposableListener(this.domNode.domNode, dom.EventType.POINTER_DOWN, (e: PointerEvent) => this._domNodePointerDown(e)));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ----------------- creation
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Creates the dom node for an arrow & adds it to the container
|
|
84
|
+
*/
|
|
85
|
+
protected _createArrow(opts: ScrollbarArrowOptions): void {
|
|
86
|
+
const arrow = this._register(new ScrollbarArrow(opts));
|
|
87
|
+
this.domNode.domNode.appendChild(arrow.bgDomNode);
|
|
88
|
+
this.domNode.domNode.appendChild(arrow.domNode);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Creates the slider dom node, adds it to the container & hooks up the events
|
|
93
|
+
*/
|
|
94
|
+
protected _createSlider(top: number, left: number, width: number | undefined, height: number | undefined): void {
|
|
95
|
+
this.slider = createFastDomNode(document.createElement('div'));
|
|
96
|
+
this.slider.setClassName('slider');
|
|
97
|
+
this.slider.setPosition('absolute');
|
|
98
|
+
this.slider.setTop(top);
|
|
99
|
+
this.slider.setLeft(left);
|
|
100
|
+
if (typeof width === 'number') {
|
|
101
|
+
this.slider.setWidth(width);
|
|
102
|
+
}
|
|
103
|
+
if (typeof height === 'number') {
|
|
104
|
+
this.slider.setHeight(height);
|
|
105
|
+
}
|
|
106
|
+
this.slider.setLayerHinting(true);
|
|
107
|
+
this.slider.setContain('strict');
|
|
108
|
+
|
|
109
|
+
this.domNode.domNode.appendChild(this.slider.domNode);
|
|
110
|
+
|
|
111
|
+
this._register(dom.addDisposableListener(
|
|
112
|
+
this.slider.domNode,
|
|
113
|
+
dom.EventType.POINTER_DOWN,
|
|
114
|
+
(e: PointerEvent) => {
|
|
115
|
+
if (e.button === 0) {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
this._sliderPointerDown(e);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
));
|
|
121
|
+
|
|
122
|
+
this.onclick(this.slider.domNode, e => {
|
|
123
|
+
if (e.leftButton) {
|
|
124
|
+
e.stopPropagation();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ----------------- Update state
|
|
130
|
+
|
|
131
|
+
protected _onElementSize(visibleSize: number): boolean {
|
|
132
|
+
if (this._scrollbarState.setVisibleSize(visibleSize)) {
|
|
133
|
+
this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());
|
|
134
|
+
this._shouldRender = true;
|
|
135
|
+
if (!this._lazyRender) {
|
|
136
|
+
this.render();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return this._shouldRender;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
protected _onElementScrollSize(elementScrollSize: number): boolean {
|
|
143
|
+
if (this._scrollbarState.setScrollSize(elementScrollSize)) {
|
|
144
|
+
this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());
|
|
145
|
+
this._shouldRender = true;
|
|
146
|
+
if (!this._lazyRender) {
|
|
147
|
+
this.render();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return this._shouldRender;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
protected _onElementScrollPosition(elementScrollPosition: number): boolean {
|
|
154
|
+
if (this._scrollbarState.setScrollPosition(elementScrollPosition)) {
|
|
155
|
+
this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());
|
|
156
|
+
this._shouldRender = true;
|
|
157
|
+
if (!this._lazyRender) {
|
|
158
|
+
this.render();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return this._shouldRender;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ----------------- rendering
|
|
165
|
+
|
|
166
|
+
public beginReveal(): void {
|
|
167
|
+
this._visibilityController.setShouldBeVisible(true);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public beginHide(): void {
|
|
171
|
+
this._visibilityController.setShouldBeVisible(false);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public render(): void {
|
|
175
|
+
if (!this._shouldRender) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
this._shouldRender = false;
|
|
179
|
+
|
|
180
|
+
this._renderDomNode(this._scrollbarState.getRectangleLargeSize(), this._scrollbarState.getRectangleSmallSize());
|
|
181
|
+
this._updateSlider(this._scrollbarState.getSliderSize(), this._scrollbarState.getArrowSize() + this._scrollbarState.getSliderPosition());
|
|
182
|
+
}
|
|
183
|
+
// ----------------- DOM events
|
|
184
|
+
|
|
185
|
+
private _domNodePointerDown(e: PointerEvent): void {
|
|
186
|
+
if (e.target !== this.domNode.domNode) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
this._onPointerDown(e);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
public delegatePointerDown(e: PointerEvent): void {
|
|
193
|
+
const domTop = this.domNode.domNode.getClientRects()[0].top;
|
|
194
|
+
const sliderStart = domTop + this._scrollbarState.getSliderPosition();
|
|
195
|
+
const sliderStop = domTop + this._scrollbarState.getSliderPosition() + this._scrollbarState.getSliderSize();
|
|
196
|
+
const pointerPos = this._sliderPointerPosition(e);
|
|
197
|
+
if (sliderStart <= pointerPos && pointerPos <= sliderStop) {
|
|
198
|
+
// Act as if it was a pointer down on the slider
|
|
199
|
+
if (e.button === 0) {
|
|
200
|
+
e.preventDefault();
|
|
201
|
+
this._sliderPointerDown(e);
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
// Act as if it was a pointer down on the scrollbar
|
|
205
|
+
this._onPointerDown(e);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private _onPointerDown(e: PointerEvent): void {
|
|
210
|
+
let offsetX: number;
|
|
211
|
+
let offsetY: number;
|
|
212
|
+
if (e.target === this.domNode.domNode && typeof e.offsetX === 'number' && typeof e.offsetY === 'number') {
|
|
213
|
+
offsetX = e.offsetX;
|
|
214
|
+
offsetY = e.offsetY;
|
|
215
|
+
} else {
|
|
216
|
+
const domNodePosition = dom.getDomNodePagePosition(this.domNode.domNode);
|
|
217
|
+
offsetX = e.pageX - domNodePosition.left;
|
|
218
|
+
offsetY = e.pageY - domNodePosition.top;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const offset = this._pointerDownRelativePosition(offsetX, offsetY);
|
|
222
|
+
this._setDesiredScrollPositionNow(
|
|
223
|
+
this._scrollByPage
|
|
224
|
+
? this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(offset)
|
|
225
|
+
: this._scrollbarState.getDesiredScrollPositionFromOffset(offset)
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
if (e.button === 0) {
|
|
229
|
+
// left button
|
|
230
|
+
e.preventDefault();
|
|
231
|
+
this._sliderPointerDown(e);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private _sliderPointerDown(e: PointerEvent): void {
|
|
236
|
+
if (!e.target || !(e.target instanceof Element)) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const initialPointerPosition = this._sliderPointerPosition(e);
|
|
240
|
+
const initialPointerOrthogonalPosition = this._sliderOrthogonalPointerPosition(e);
|
|
241
|
+
const initialScrollbarState = this._scrollbarState.clone();
|
|
242
|
+
this.slider.toggleClassName('active', true);
|
|
243
|
+
|
|
244
|
+
this._pointerMoveMonitor.startMonitoring(
|
|
245
|
+
e.target,
|
|
246
|
+
e.pointerId,
|
|
247
|
+
e.buttons,
|
|
248
|
+
(pointerMoveData: PointerEvent) => {
|
|
249
|
+
const pointerOrthogonalPosition = this._sliderOrthogonalPointerPosition(pointerMoveData);
|
|
250
|
+
const pointerOrthogonalDelta = Math.abs(pointerOrthogonalPosition - initialPointerOrthogonalPosition);
|
|
251
|
+
|
|
252
|
+
if (platform.isWindows && pointerOrthogonalDelta > POINTER_DRAG_RESET_DISTANCE) {
|
|
253
|
+
// The pointer has wondered away from the scrollbar => reset dragging
|
|
254
|
+
this._setDesiredScrollPositionNow(initialScrollbarState.getScrollPosition());
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const pointerPosition = this._sliderPointerPosition(pointerMoveData);
|
|
259
|
+
const pointerDelta = pointerPosition - initialPointerPosition;
|
|
260
|
+
this._setDesiredScrollPositionNow(initialScrollbarState.getDesiredScrollPositionFromDelta(pointerDelta));
|
|
261
|
+
},
|
|
262
|
+
() => {
|
|
263
|
+
this.slider.toggleClassName('active', false);
|
|
264
|
+
this._host.onDragEnd();
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
this._host.onDragStart();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private _setDesiredScrollPositionNow(_desiredScrollPosition: number): void {
|
|
272
|
+
|
|
273
|
+
const desiredScrollPosition: INewScrollPosition = {};
|
|
274
|
+
this.writeScrollPosition(desiredScrollPosition, _desiredScrollPosition);
|
|
275
|
+
|
|
276
|
+
this._scrollable.setScrollPositionNow(desiredScrollPosition);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
public updateScrollbarSize(scrollbarSize: number): void {
|
|
280
|
+
this._updateScrollbarSize(scrollbarSize);
|
|
281
|
+
this._scrollbarState.setScrollbarSize(scrollbarSize);
|
|
282
|
+
this._shouldRender = true;
|
|
283
|
+
if (!this._lazyRender) {
|
|
284
|
+
this.render();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
public isNeeded(): boolean {
|
|
289
|
+
return this._scrollbarState.isNeeded();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ----------------- Overwrite these
|
|
293
|
+
|
|
294
|
+
protected abstract _renderDomNode(largeSize: number, smallSize: number): void;
|
|
295
|
+
protected abstract _updateSlider(sliderSize: number, sliderPosition: number): void;
|
|
296
|
+
|
|
297
|
+
protected abstract _pointerDownRelativePosition(offsetX: number, offsetY: number): number;
|
|
298
|
+
protected abstract _sliderPointerPosition(e: ISimplifiedPointerEvent): number;
|
|
299
|
+
protected abstract _sliderOrthogonalPointerPosition(e: ISimplifiedPointerEvent): number;
|
|
300
|
+
protected abstract _updateScrollbarSize(size: number): void;
|
|
301
|
+
|
|
302
|
+
public abstract writeScrollPosition(target: INewScrollPosition, scrollPosition: number): void;
|
|
303
|
+
}
|