feat: 添加vscode编辑器
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as dom from '../../dom.js';
|
||||
import { createFastDomNode } from '../../fastDomNode.js';
|
||||
import { GlobalPointerMoveMonitor } from '../../globalPointerMoveMonitor.js';
|
||||
import { ScrollbarArrow } from './scrollbarArrow.js';
|
||||
import { ScrollbarVisibilityController } from './scrollbarVisibilityController.js';
|
||||
import { Widget } from '../widget.js';
|
||||
import * as platform from '../../../common/platform.js';
|
||||
/**
|
||||
* The orthogonal distance to the slider at which dragging "resets". This implements "snapping"
|
||||
*/
|
||||
const POINTER_DRAG_RESET_DISTANCE = 140;
|
||||
export class AbstractScrollbar extends Widget {
|
||||
constructor(opts) {
|
||||
super();
|
||||
this._lazyRender = opts.lazyRender;
|
||||
this._host = opts.host;
|
||||
this._scrollable = opts.scrollable;
|
||||
this._scrollByPage = opts.scrollByPage;
|
||||
this._scrollbarState = opts.scrollbarState;
|
||||
this._visibilityController = this._register(new ScrollbarVisibilityController(opts.visibility, 'visible scrollbar ' + opts.extraScrollbarClassName, 'invisible scrollbar ' + opts.extraScrollbarClassName));
|
||||
this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());
|
||||
this._pointerMoveMonitor = this._register(new GlobalPointerMoveMonitor());
|
||||
this._shouldRender = true;
|
||||
this.domNode = createFastDomNode(document.createElement('div'));
|
||||
this.domNode.setAttribute('role', 'presentation');
|
||||
this.domNode.setAttribute('aria-hidden', 'true');
|
||||
this._visibilityController.setDomNode(this.domNode);
|
||||
this.domNode.setPosition('absolute');
|
||||
this._register(dom.addDisposableListener(this.domNode.domNode, dom.EventType.POINTER_DOWN, (e) => this._domNodePointerDown(e)));
|
||||
}
|
||||
// ----------------- creation
|
||||
/**
|
||||
* Creates the dom node for an arrow & adds it to the container
|
||||
*/
|
||||
_createArrow(opts) {
|
||||
const arrow = this._register(new ScrollbarArrow(opts));
|
||||
this.domNode.domNode.appendChild(arrow.bgDomNode);
|
||||
this.domNode.domNode.appendChild(arrow.domNode);
|
||||
}
|
||||
/**
|
||||
* Creates the slider dom node, adds it to the container & hooks up the events
|
||||
*/
|
||||
_createSlider(top, left, width, height) {
|
||||
this.slider = createFastDomNode(document.createElement('div'));
|
||||
this.slider.setClassName('slider');
|
||||
this.slider.setPosition('absolute');
|
||||
this.slider.setTop(top);
|
||||
this.slider.setLeft(left);
|
||||
if (typeof width === 'number') {
|
||||
this.slider.setWidth(width);
|
||||
}
|
||||
if (typeof height === 'number') {
|
||||
this.slider.setHeight(height);
|
||||
}
|
||||
this.slider.setLayerHinting(true);
|
||||
this.slider.setContain('strict');
|
||||
this.domNode.domNode.appendChild(this.slider.domNode);
|
||||
this._register(dom.addDisposableListener(this.slider.domNode, dom.EventType.POINTER_DOWN, (e) => {
|
||||
if (e.button === 0) {
|
||||
e.preventDefault();
|
||||
this._sliderPointerDown(e);
|
||||
}
|
||||
}));
|
||||
this.onclick(this.slider.domNode, e => {
|
||||
if (e.leftButton) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
}
|
||||
// ----------------- Update state
|
||||
_onElementSize(visibleSize) {
|
||||
if (this._scrollbarState.setVisibleSize(visibleSize)) {
|
||||
this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());
|
||||
this._shouldRender = true;
|
||||
if (!this._lazyRender) {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
return this._shouldRender;
|
||||
}
|
||||
_onElementScrollSize(elementScrollSize) {
|
||||
if (this._scrollbarState.setScrollSize(elementScrollSize)) {
|
||||
this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());
|
||||
this._shouldRender = true;
|
||||
if (!this._lazyRender) {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
return this._shouldRender;
|
||||
}
|
||||
_onElementScrollPosition(elementScrollPosition) {
|
||||
if (this._scrollbarState.setScrollPosition(elementScrollPosition)) {
|
||||
this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());
|
||||
this._shouldRender = true;
|
||||
if (!this._lazyRender) {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
return this._shouldRender;
|
||||
}
|
||||
// ----------------- rendering
|
||||
beginReveal() {
|
||||
this._visibilityController.setShouldBeVisible(true);
|
||||
}
|
||||
beginHide() {
|
||||
this._visibilityController.setShouldBeVisible(false);
|
||||
}
|
||||
render() {
|
||||
if (!this._shouldRender) {
|
||||
return;
|
||||
}
|
||||
this._shouldRender = false;
|
||||
this._renderDomNode(this._scrollbarState.getRectangleLargeSize(), this._scrollbarState.getRectangleSmallSize());
|
||||
this._updateSlider(this._scrollbarState.getSliderSize(), this._scrollbarState.getArrowSize() + this._scrollbarState.getSliderPosition());
|
||||
}
|
||||
// ----------------- DOM events
|
||||
_domNodePointerDown(e) {
|
||||
if (e.target !== this.domNode.domNode) {
|
||||
return;
|
||||
}
|
||||
this._onPointerDown(e);
|
||||
}
|
||||
delegatePointerDown(e) {
|
||||
const domTop = this.domNode.domNode.getClientRects()[0].top;
|
||||
const sliderStart = domTop + this._scrollbarState.getSliderPosition();
|
||||
const sliderStop = domTop + this._scrollbarState.getSliderPosition() + this._scrollbarState.getSliderSize();
|
||||
const pointerPos = this._sliderPointerPosition(e);
|
||||
if (sliderStart <= pointerPos && pointerPos <= sliderStop) {
|
||||
// Act as if it was a pointer down on the slider
|
||||
if (e.button === 0) {
|
||||
e.preventDefault();
|
||||
this._sliderPointerDown(e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Act as if it was a pointer down on the scrollbar
|
||||
this._onPointerDown(e);
|
||||
}
|
||||
}
|
||||
_onPointerDown(e) {
|
||||
let offsetX;
|
||||
let offsetY;
|
||||
if (e.target === this.domNode.domNode && typeof e.offsetX === 'number' && typeof e.offsetY === 'number') {
|
||||
offsetX = e.offsetX;
|
||||
offsetY = e.offsetY;
|
||||
}
|
||||
else {
|
||||
const domNodePosition = dom.getDomNodePagePosition(this.domNode.domNode);
|
||||
offsetX = e.pageX - domNodePosition.left;
|
||||
offsetY = e.pageY - domNodePosition.top;
|
||||
}
|
||||
const offset = this._pointerDownRelativePosition(offsetX, offsetY);
|
||||
this._setDesiredScrollPositionNow(this._scrollByPage
|
||||
? this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(offset)
|
||||
: this._scrollbarState.getDesiredScrollPositionFromOffset(offset));
|
||||
if (e.button === 0) {
|
||||
// left button
|
||||
e.preventDefault();
|
||||
this._sliderPointerDown(e);
|
||||
}
|
||||
}
|
||||
_sliderPointerDown(e) {
|
||||
if (!e.target || !(e.target instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
const initialPointerPosition = this._sliderPointerPosition(e);
|
||||
const initialPointerOrthogonalPosition = this._sliderOrthogonalPointerPosition(e);
|
||||
const initialScrollbarState = this._scrollbarState.clone();
|
||||
this.slider.toggleClassName('active', true);
|
||||
this._pointerMoveMonitor.startMonitoring(e.target, e.pointerId, e.buttons, (pointerMoveData) => {
|
||||
const pointerOrthogonalPosition = this._sliderOrthogonalPointerPosition(pointerMoveData);
|
||||
const pointerOrthogonalDelta = Math.abs(pointerOrthogonalPosition - initialPointerOrthogonalPosition);
|
||||
if (platform.isWindows && pointerOrthogonalDelta > POINTER_DRAG_RESET_DISTANCE) {
|
||||
// The pointer has wondered away from the scrollbar => reset dragging
|
||||
this._setDesiredScrollPositionNow(initialScrollbarState.getScrollPosition());
|
||||
return;
|
||||
}
|
||||
const pointerPosition = this._sliderPointerPosition(pointerMoveData);
|
||||
const pointerDelta = pointerPosition - initialPointerPosition;
|
||||
this._setDesiredScrollPositionNow(initialScrollbarState.getDesiredScrollPositionFromDelta(pointerDelta));
|
||||
}, () => {
|
||||
this.slider.toggleClassName('active', false);
|
||||
this._host.onDragEnd();
|
||||
});
|
||||
this._host.onDragStart();
|
||||
}
|
||||
_setDesiredScrollPositionNow(_desiredScrollPosition) {
|
||||
const desiredScrollPosition = {};
|
||||
this.writeScrollPosition(desiredScrollPosition, _desiredScrollPosition);
|
||||
this._scrollable.setScrollPositionNow(desiredScrollPosition);
|
||||
}
|
||||
updateScrollbarSize(scrollbarSize) {
|
||||
this._updateScrollbarSize(scrollbarSize);
|
||||
this._scrollbarState.setScrollbarSize(scrollbarSize);
|
||||
this._shouldRender = true;
|
||||
if (!this._lazyRender) {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
isNeeded() {
|
||||
return this._scrollbarState.isNeeded();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { StandardWheelEvent } from '../../mouseEvent.js';
|
||||
import { AbstractScrollbar } from './abstractScrollbar.js';
|
||||
import { ARROW_IMG_SIZE } from './scrollbarArrow.js';
|
||||
import { ScrollbarState } from './scrollbarState.js';
|
||||
import { Codicon } from '../../../common/codicons.js';
|
||||
export class HorizontalScrollbar extends AbstractScrollbar {
|
||||
constructor(scrollable, options, host) {
|
||||
const scrollDimensions = scrollable.getScrollDimensions();
|
||||
const scrollPosition = scrollable.getCurrentScrollPosition();
|
||||
super({
|
||||
lazyRender: options.lazyRender,
|
||||
host: host,
|
||||
scrollbarState: new ScrollbarState((options.horizontalHasArrows ? options.arrowSize : 0), (options.horizontal === 2 /* ScrollbarVisibility.Hidden */ ? 0 : options.horizontalScrollbarSize), (options.vertical === 2 /* ScrollbarVisibility.Hidden */ ? 0 : options.verticalScrollbarSize), scrollDimensions.width, scrollDimensions.scrollWidth, scrollPosition.scrollLeft),
|
||||
visibility: options.horizontal,
|
||||
extraScrollbarClassName: 'horizontal',
|
||||
scrollable: scrollable,
|
||||
scrollByPage: options.scrollByPage
|
||||
});
|
||||
if (options.horizontalHasArrows) {
|
||||
const arrowDelta = (options.arrowSize - ARROW_IMG_SIZE) / 2;
|
||||
const scrollbarDelta = (options.horizontalScrollbarSize - ARROW_IMG_SIZE) / 2;
|
||||
this._createArrow({
|
||||
className: 'scra',
|
||||
icon: Codicon.scrollbarButtonLeft,
|
||||
top: scrollbarDelta,
|
||||
left: arrowDelta,
|
||||
bottom: undefined,
|
||||
right: undefined,
|
||||
bgWidth: options.arrowSize,
|
||||
bgHeight: options.horizontalScrollbarSize,
|
||||
onActivate: () => this._host.onMouseWheel(new StandardWheelEvent(null, 1, 0)),
|
||||
});
|
||||
this._createArrow({
|
||||
className: 'scra',
|
||||
icon: Codicon.scrollbarButtonRight,
|
||||
top: scrollbarDelta,
|
||||
left: undefined,
|
||||
bottom: undefined,
|
||||
right: arrowDelta,
|
||||
bgWidth: options.arrowSize,
|
||||
bgHeight: options.horizontalScrollbarSize,
|
||||
onActivate: () => this._host.onMouseWheel(new StandardWheelEvent(null, -1, 0)),
|
||||
});
|
||||
}
|
||||
this._createSlider(Math.floor((options.horizontalScrollbarSize - options.horizontalSliderSize) / 2), 0, undefined, options.horizontalSliderSize);
|
||||
}
|
||||
_updateSlider(sliderSize, sliderPosition) {
|
||||
this.slider.setWidth(sliderSize);
|
||||
this.slider.setLeft(sliderPosition);
|
||||
}
|
||||
_renderDomNode(largeSize, smallSize) {
|
||||
this.domNode.setWidth(largeSize);
|
||||
this.domNode.setHeight(smallSize);
|
||||
this.domNode.setLeft(0);
|
||||
this.domNode.setBottom(0);
|
||||
}
|
||||
onDidScroll(e) {
|
||||
this._shouldRender = this._onElementScrollSize(e.scrollWidth) || this._shouldRender;
|
||||
this._shouldRender = this._onElementScrollPosition(e.scrollLeft) || this._shouldRender;
|
||||
this._shouldRender = this._onElementSize(e.width) || this._shouldRender;
|
||||
return this._shouldRender;
|
||||
}
|
||||
_pointerDownRelativePosition(offsetX, offsetY) {
|
||||
return offsetX;
|
||||
}
|
||||
_sliderPointerPosition(e) {
|
||||
return e.pageX;
|
||||
}
|
||||
_sliderOrthogonalPointerPosition(e) {
|
||||
return e.pageY;
|
||||
}
|
||||
_updateScrollbarSize(size) {
|
||||
this.slider.setHeight(size);
|
||||
}
|
||||
writeScrollPosition(target, scrollPosition) {
|
||||
target.scrollLeft = scrollPosition;
|
||||
}
|
||||
updateOptions(options) {
|
||||
this.updateScrollbarSize(options.horizontal === 2 /* ScrollbarVisibility.Hidden */ ? 0 : options.horizontalScrollbarSize);
|
||||
this._scrollbarState.setOppositeScrollbarSize(options.vertical === 2 /* ScrollbarVisibility.Hidden */ ? 0 : options.verticalScrollbarSize);
|
||||
this._visibilityController.setVisibility(options.horizontal);
|
||||
this._scrollByPage = options.scrollByPage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Arrows */
|
||||
.monaco-scrollable-element > .scrollbar > .scra {
|
||||
cursor: pointer;
|
||||
font-size: 11px !important;
|
||||
}
|
||||
|
||||
.monaco-scrollable-element > .visible {
|
||||
opacity: 1;
|
||||
|
||||
/* Background rule added for IE9 - to allow clicks on dom node */
|
||||
background:rgba(0,0,0,0);
|
||||
|
||||
transition: opacity 100ms linear;
|
||||
}
|
||||
.monaco-scrollable-element > .invisible {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.monaco-scrollable-element > .invisible.fade {
|
||||
transition: opacity 800ms linear;
|
||||
}
|
||||
|
||||
/* Scrollable Content Inset Shadow */
|
||||
.monaco-scrollable-element > .shadow {
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
.monaco-scrollable-element > .shadow.top {
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 3px;
|
||||
height: 3px;
|
||||
width: 100%;
|
||||
}
|
||||
.monaco-scrollable-element > .shadow.left {
|
||||
display: block;
|
||||
top: 3px;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 3px;
|
||||
}
|
||||
.monaco-scrollable-element > .shadow.top-left-corner {
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 3px;
|
||||
width: 3px;
|
||||
}
|
||||
@@ -0,0 +1,529 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { getZoomFactor } from '../../browser.js';
|
||||
import * as dom from '../../dom.js';
|
||||
import { createFastDomNode } from '../../fastDomNode.js';
|
||||
import { StandardWheelEvent } from '../../mouseEvent.js';
|
||||
import { HorizontalScrollbar } from './horizontalScrollbar.js';
|
||||
import { VerticalScrollbar } from './verticalScrollbar.js';
|
||||
import { Widget } from '../widget.js';
|
||||
import { TimeoutTimer } from '../../../common/async.js';
|
||||
import { Emitter } from '../../../common/event.js';
|
||||
import { dispose } from '../../../common/lifecycle.js';
|
||||
import * as platform from '../../../common/platform.js';
|
||||
import { Scrollable } from '../../../common/scrollable.js';
|
||||
import './media/scrollbars.css';
|
||||
const HIDE_TIMEOUT = 500;
|
||||
const SCROLL_WHEEL_SENSITIVITY = 50;
|
||||
const SCROLL_WHEEL_SMOOTH_SCROLL_ENABLED = true;
|
||||
class MouseWheelClassifierItem {
|
||||
constructor(timestamp, deltaX, deltaY) {
|
||||
this.timestamp = timestamp;
|
||||
this.deltaX = deltaX;
|
||||
this.deltaY = deltaY;
|
||||
this.score = 0;
|
||||
}
|
||||
}
|
||||
export class MouseWheelClassifier {
|
||||
constructor() {
|
||||
this._capacity = 5;
|
||||
this._memory = [];
|
||||
this._front = -1;
|
||||
this._rear = -1;
|
||||
}
|
||||
isPhysicalMouseWheel() {
|
||||
if (this._front === -1 && this._rear === -1) {
|
||||
// no elements
|
||||
return false;
|
||||
}
|
||||
// 0.5 * last + 0.25 * 2nd last + 0.125 * 3rd last + ...
|
||||
let remainingInfluence = 1;
|
||||
let score = 0;
|
||||
let iteration = 1;
|
||||
let index = this._rear;
|
||||
do {
|
||||
const influence = (index === this._front ? remainingInfluence : Math.pow(2, -iteration));
|
||||
remainingInfluence -= influence;
|
||||
score += this._memory[index].score * influence;
|
||||
if (index === this._front) {
|
||||
break;
|
||||
}
|
||||
index = (this._capacity + index - 1) % this._capacity;
|
||||
iteration++;
|
||||
} while (true);
|
||||
return (score <= 0.5);
|
||||
}
|
||||
accept(timestamp, deltaX, deltaY) {
|
||||
const item = new MouseWheelClassifierItem(timestamp, deltaX, deltaY);
|
||||
item.score = this._computeScore(item);
|
||||
if (this._front === -1 && this._rear === -1) {
|
||||
this._memory[0] = item;
|
||||
this._front = 0;
|
||||
this._rear = 0;
|
||||
}
|
||||
else {
|
||||
this._rear = (this._rear + 1) % this._capacity;
|
||||
if (this._rear === this._front) {
|
||||
// Drop oldest
|
||||
this._front = (this._front + 1) % this._capacity;
|
||||
}
|
||||
this._memory[this._rear] = item;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A score between 0 and 1 for `item`.
|
||||
* - a score towards 0 indicates that the source appears to be a physical mouse wheel
|
||||
* - a score towards 1 indicates that the source appears to be a touchpad or magic mouse, etc.
|
||||
*/
|
||||
_computeScore(item) {
|
||||
if (Math.abs(item.deltaX) > 0 && Math.abs(item.deltaY) > 0) {
|
||||
// both axes exercised => definitely not a physical mouse wheel
|
||||
return 1;
|
||||
}
|
||||
let score = 0.5;
|
||||
const prev = (this._front === -1 && this._rear === -1 ? null : this._memory[this._rear]);
|
||||
if (prev) {
|
||||
// const deltaT = item.timestamp - prev.timestamp;
|
||||
// if (deltaT < 1000 / 30) {
|
||||
// // sooner than X times per second => indicator that this is not a physical mouse wheel
|
||||
// score += 0.25;
|
||||
// }
|
||||
// if (item.deltaX === prev.deltaX && item.deltaY === prev.deltaY) {
|
||||
// // equal amplitude => indicator that this is a physical mouse wheel
|
||||
// score -= 0.25;
|
||||
// }
|
||||
}
|
||||
if (!this._isAlmostInt(item.deltaX) || !this._isAlmostInt(item.deltaY)) {
|
||||
// non-integer deltas => indicator that this is not a physical mouse wheel
|
||||
score += 0.25;
|
||||
}
|
||||
return Math.min(Math.max(score, 0), 1);
|
||||
}
|
||||
_isAlmostInt(value) {
|
||||
const delta = Math.abs(Math.round(value) - value);
|
||||
return (delta < 0.01);
|
||||
}
|
||||
}
|
||||
MouseWheelClassifier.INSTANCE = new MouseWheelClassifier();
|
||||
export class AbstractScrollableElement extends Widget {
|
||||
constructor(element, options, scrollable) {
|
||||
super();
|
||||
this._onScroll = this._register(new Emitter());
|
||||
this.onScroll = this._onScroll.event;
|
||||
this._onWillScroll = this._register(new Emitter());
|
||||
element.style.overflow = 'hidden';
|
||||
this._options = resolveOptions(options);
|
||||
this._scrollable = scrollable;
|
||||
this._register(this._scrollable.onScroll((e) => {
|
||||
this._onWillScroll.fire(e);
|
||||
this._onDidScroll(e);
|
||||
this._onScroll.fire(e);
|
||||
}));
|
||||
const scrollbarHost = {
|
||||
onMouseWheel: (mouseWheelEvent) => this._onMouseWheel(mouseWheelEvent),
|
||||
onDragStart: () => this._onDragStart(),
|
||||
onDragEnd: () => this._onDragEnd(),
|
||||
};
|
||||
this._verticalScrollbar = this._register(new VerticalScrollbar(this._scrollable, this._options, scrollbarHost));
|
||||
this._horizontalScrollbar = this._register(new HorizontalScrollbar(this._scrollable, this._options, scrollbarHost));
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.className = 'monaco-scrollable-element ' + this._options.className;
|
||||
this._domNode.setAttribute('role', 'presentation');
|
||||
this._domNode.style.position = 'relative';
|
||||
this._domNode.style.overflow = 'hidden';
|
||||
this._domNode.appendChild(element);
|
||||
this._domNode.appendChild(this._horizontalScrollbar.domNode.domNode);
|
||||
this._domNode.appendChild(this._verticalScrollbar.domNode.domNode);
|
||||
if (this._options.useShadows) {
|
||||
this._leftShadowDomNode = createFastDomNode(document.createElement('div'));
|
||||
this._leftShadowDomNode.setClassName('shadow');
|
||||
this._domNode.appendChild(this._leftShadowDomNode.domNode);
|
||||
this._topShadowDomNode = createFastDomNode(document.createElement('div'));
|
||||
this._topShadowDomNode.setClassName('shadow');
|
||||
this._domNode.appendChild(this._topShadowDomNode.domNode);
|
||||
this._topLeftShadowDomNode = createFastDomNode(document.createElement('div'));
|
||||
this._topLeftShadowDomNode.setClassName('shadow');
|
||||
this._domNode.appendChild(this._topLeftShadowDomNode.domNode);
|
||||
}
|
||||
else {
|
||||
this._leftShadowDomNode = null;
|
||||
this._topShadowDomNode = null;
|
||||
this._topLeftShadowDomNode = null;
|
||||
}
|
||||
this._listenOnDomNode = this._options.listenOnDomNode || this._domNode;
|
||||
this._mouseWheelToDispose = [];
|
||||
this._setListeningToMouseWheel(this._options.handleMouseWheel);
|
||||
this.onmouseover(this._listenOnDomNode, (e) => this._onMouseOver(e));
|
||||
this.onmouseleave(this._listenOnDomNode, (e) => this._onMouseLeave(e));
|
||||
this._hideTimeout = this._register(new TimeoutTimer());
|
||||
this._isDragging = false;
|
||||
this._mouseIsOver = false;
|
||||
this._shouldRender = true;
|
||||
this._revealOnScroll = true;
|
||||
}
|
||||
get options() {
|
||||
return this._options;
|
||||
}
|
||||
dispose() {
|
||||
this._mouseWheelToDispose = dispose(this._mouseWheelToDispose);
|
||||
super.dispose();
|
||||
}
|
||||
/**
|
||||
* Get the generated 'scrollable' dom node
|
||||
*/
|
||||
getDomNode() {
|
||||
return this._domNode;
|
||||
}
|
||||
getOverviewRulerLayoutInfo() {
|
||||
return {
|
||||
parent: this._domNode,
|
||||
insertBefore: this._verticalScrollbar.domNode.domNode,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Delegate a pointer down event to the vertical scrollbar.
|
||||
* This is to help with clicking somewhere else and having the scrollbar react.
|
||||
*/
|
||||
delegateVerticalScrollbarPointerDown(browserEvent) {
|
||||
this._verticalScrollbar.delegatePointerDown(browserEvent);
|
||||
}
|
||||
getScrollDimensions() {
|
||||
return this._scrollable.getScrollDimensions();
|
||||
}
|
||||
setScrollDimensions(dimensions) {
|
||||
this._scrollable.setScrollDimensions(dimensions, false);
|
||||
}
|
||||
/**
|
||||
* Update the class name of the scrollable element.
|
||||
*/
|
||||
updateClassName(newClassName) {
|
||||
this._options.className = newClassName;
|
||||
// Defaults are different on Macs
|
||||
if (platform.isMacintosh) {
|
||||
this._options.className += ' mac';
|
||||
}
|
||||
this._domNode.className = 'monaco-scrollable-element ' + this._options.className;
|
||||
}
|
||||
/**
|
||||
* Update configuration options for the scrollbar.
|
||||
*/
|
||||
updateOptions(newOptions) {
|
||||
if (typeof newOptions.handleMouseWheel !== 'undefined') {
|
||||
this._options.handleMouseWheel = newOptions.handleMouseWheel;
|
||||
this._setListeningToMouseWheel(this._options.handleMouseWheel);
|
||||
}
|
||||
if (typeof newOptions.mouseWheelScrollSensitivity !== 'undefined') {
|
||||
this._options.mouseWheelScrollSensitivity = newOptions.mouseWheelScrollSensitivity;
|
||||
}
|
||||
if (typeof newOptions.fastScrollSensitivity !== 'undefined') {
|
||||
this._options.fastScrollSensitivity = newOptions.fastScrollSensitivity;
|
||||
}
|
||||
if (typeof newOptions.scrollPredominantAxis !== 'undefined') {
|
||||
this._options.scrollPredominantAxis = newOptions.scrollPredominantAxis;
|
||||
}
|
||||
if (typeof newOptions.horizontal !== 'undefined') {
|
||||
this._options.horizontal = newOptions.horizontal;
|
||||
}
|
||||
if (typeof newOptions.vertical !== 'undefined') {
|
||||
this._options.vertical = newOptions.vertical;
|
||||
}
|
||||
if (typeof newOptions.horizontalScrollbarSize !== 'undefined') {
|
||||
this._options.horizontalScrollbarSize = newOptions.horizontalScrollbarSize;
|
||||
}
|
||||
if (typeof newOptions.verticalScrollbarSize !== 'undefined') {
|
||||
this._options.verticalScrollbarSize = newOptions.verticalScrollbarSize;
|
||||
}
|
||||
if (typeof newOptions.scrollByPage !== 'undefined') {
|
||||
this._options.scrollByPage = newOptions.scrollByPage;
|
||||
}
|
||||
this._horizontalScrollbar.updateOptions(this._options);
|
||||
this._verticalScrollbar.updateOptions(this._options);
|
||||
if (!this._options.lazyRender) {
|
||||
this._render();
|
||||
}
|
||||
}
|
||||
// -------------------- mouse wheel scrolling --------------------
|
||||
_setListeningToMouseWheel(shouldListen) {
|
||||
const isListening = (this._mouseWheelToDispose.length > 0);
|
||||
if (isListening === shouldListen) {
|
||||
// No change
|
||||
return;
|
||||
}
|
||||
// Stop listening (if necessary)
|
||||
this._mouseWheelToDispose = dispose(this._mouseWheelToDispose);
|
||||
// Start listening (if necessary)
|
||||
if (shouldListen) {
|
||||
const onMouseWheel = (browserEvent) => {
|
||||
this._onMouseWheel(new StandardWheelEvent(browserEvent));
|
||||
};
|
||||
this._mouseWheelToDispose.push(dom.addDisposableListener(this._listenOnDomNode, dom.EventType.MOUSE_WHEEL, onMouseWheel, { passive: false }));
|
||||
}
|
||||
}
|
||||
_onMouseWheel(e) {
|
||||
const classifier = MouseWheelClassifier.INSTANCE;
|
||||
if (SCROLL_WHEEL_SMOOTH_SCROLL_ENABLED) {
|
||||
const osZoomFactor = window.devicePixelRatio / getZoomFactor();
|
||||
if (platform.isWindows || platform.isLinux) {
|
||||
// On Windows and Linux, the incoming delta events are multiplied with the OS zoom factor.
|
||||
// The OS zoom factor can be reverse engineered by using the device pixel ratio and the configured zoom factor into account.
|
||||
classifier.accept(Date.now(), e.deltaX / osZoomFactor, e.deltaY / osZoomFactor);
|
||||
}
|
||||
else {
|
||||
classifier.accept(Date.now(), e.deltaX, e.deltaY);
|
||||
}
|
||||
}
|
||||
// console.log(`${Date.now()}, ${e.deltaY}, ${e.deltaX}`);
|
||||
let didScroll = false;
|
||||
if (e.deltaY || e.deltaX) {
|
||||
let deltaY = e.deltaY * this._options.mouseWheelScrollSensitivity;
|
||||
let deltaX = e.deltaX * this._options.mouseWheelScrollSensitivity;
|
||||
if (this._options.scrollPredominantAxis) {
|
||||
if (Math.abs(deltaY) >= Math.abs(deltaX)) {
|
||||
deltaX = 0;
|
||||
}
|
||||
else {
|
||||
deltaY = 0;
|
||||
}
|
||||
}
|
||||
if (this._options.flipAxes) {
|
||||
[deltaY, deltaX] = [deltaX, deltaY];
|
||||
}
|
||||
// Convert vertical scrolling to horizontal if shift is held, this
|
||||
// is handled at a higher level on Mac
|
||||
const shiftConvert = !platform.isMacintosh && e.browserEvent && e.browserEvent.shiftKey;
|
||||
if ((this._options.scrollYToX || shiftConvert) && !deltaX) {
|
||||
deltaX = deltaY;
|
||||
deltaY = 0;
|
||||
}
|
||||
if (e.browserEvent && e.browserEvent.altKey) {
|
||||
// fastScrolling
|
||||
deltaX = deltaX * this._options.fastScrollSensitivity;
|
||||
deltaY = deltaY * this._options.fastScrollSensitivity;
|
||||
}
|
||||
const futureScrollPosition = this._scrollable.getFutureScrollPosition();
|
||||
let desiredScrollPosition = {};
|
||||
if (deltaY) {
|
||||
const deltaScrollTop = SCROLL_WHEEL_SENSITIVITY * deltaY;
|
||||
// Here we convert values such as -0.3 to -1 or 0.3 to 1, otherwise low speed scrolling will never scroll
|
||||
const desiredScrollTop = futureScrollPosition.scrollTop - (deltaScrollTop < 0 ? Math.floor(deltaScrollTop) : Math.ceil(deltaScrollTop));
|
||||
this._verticalScrollbar.writeScrollPosition(desiredScrollPosition, desiredScrollTop);
|
||||
}
|
||||
if (deltaX) {
|
||||
const deltaScrollLeft = SCROLL_WHEEL_SENSITIVITY * deltaX;
|
||||
// Here we convert values such as -0.3 to -1 or 0.3 to 1, otherwise low speed scrolling will never scroll
|
||||
const desiredScrollLeft = futureScrollPosition.scrollLeft - (deltaScrollLeft < 0 ? Math.floor(deltaScrollLeft) : Math.ceil(deltaScrollLeft));
|
||||
this._horizontalScrollbar.writeScrollPosition(desiredScrollPosition, desiredScrollLeft);
|
||||
}
|
||||
// Check that we are scrolling towards a location which is valid
|
||||
desiredScrollPosition = this._scrollable.validateScrollPosition(desiredScrollPosition);
|
||||
if (futureScrollPosition.scrollLeft !== desiredScrollPosition.scrollLeft || futureScrollPosition.scrollTop !== desiredScrollPosition.scrollTop) {
|
||||
const canPerformSmoothScroll = (SCROLL_WHEEL_SMOOTH_SCROLL_ENABLED
|
||||
&& this._options.mouseWheelSmoothScroll
|
||||
&& classifier.isPhysicalMouseWheel());
|
||||
if (canPerformSmoothScroll) {
|
||||
this._scrollable.setScrollPositionSmooth(desiredScrollPosition);
|
||||
}
|
||||
else {
|
||||
this._scrollable.setScrollPositionNow(desiredScrollPosition);
|
||||
}
|
||||
didScroll = true;
|
||||
}
|
||||
}
|
||||
let consumeMouseWheel = didScroll;
|
||||
if (!consumeMouseWheel && this._options.alwaysConsumeMouseWheel) {
|
||||
consumeMouseWheel = true;
|
||||
}
|
||||
if (!consumeMouseWheel && this._options.consumeMouseWheelIfScrollbarIsNeeded && (this._verticalScrollbar.isNeeded() || this._horizontalScrollbar.isNeeded())) {
|
||||
consumeMouseWheel = true;
|
||||
}
|
||||
if (consumeMouseWheel) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
_onDidScroll(e) {
|
||||
this._shouldRender = this._horizontalScrollbar.onDidScroll(e) || this._shouldRender;
|
||||
this._shouldRender = this._verticalScrollbar.onDidScroll(e) || this._shouldRender;
|
||||
if (this._options.useShadows) {
|
||||
this._shouldRender = true;
|
||||
}
|
||||
if (this._revealOnScroll) {
|
||||
this._reveal();
|
||||
}
|
||||
if (!this._options.lazyRender) {
|
||||
this._render();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Render / mutate the DOM now.
|
||||
* Should be used together with the ctor option `lazyRender`.
|
||||
*/
|
||||
renderNow() {
|
||||
if (!this._options.lazyRender) {
|
||||
throw new Error('Please use `lazyRender` together with `renderNow`!');
|
||||
}
|
||||
this._render();
|
||||
}
|
||||
_render() {
|
||||
if (!this._shouldRender) {
|
||||
return;
|
||||
}
|
||||
this._shouldRender = false;
|
||||
this._horizontalScrollbar.render();
|
||||
this._verticalScrollbar.render();
|
||||
if (this._options.useShadows) {
|
||||
const scrollState = this._scrollable.getCurrentScrollPosition();
|
||||
const enableTop = scrollState.scrollTop > 0;
|
||||
const enableLeft = scrollState.scrollLeft > 0;
|
||||
const leftClassName = (enableLeft ? ' left' : '');
|
||||
const topClassName = (enableTop ? ' top' : '');
|
||||
const topLeftClassName = (enableLeft || enableTop ? ' top-left-corner' : '');
|
||||
this._leftShadowDomNode.setClassName(`shadow${leftClassName}`);
|
||||
this._topShadowDomNode.setClassName(`shadow${topClassName}`);
|
||||
this._topLeftShadowDomNode.setClassName(`shadow${topLeftClassName}${topClassName}${leftClassName}`);
|
||||
}
|
||||
}
|
||||
// -------------------- fade in / fade out --------------------
|
||||
_onDragStart() {
|
||||
this._isDragging = true;
|
||||
this._reveal();
|
||||
}
|
||||
_onDragEnd() {
|
||||
this._isDragging = false;
|
||||
this._hide();
|
||||
}
|
||||
_onMouseLeave(e) {
|
||||
this._mouseIsOver = false;
|
||||
this._hide();
|
||||
}
|
||||
_onMouseOver(e) {
|
||||
this._mouseIsOver = true;
|
||||
this._reveal();
|
||||
}
|
||||
_reveal() {
|
||||
this._verticalScrollbar.beginReveal();
|
||||
this._horizontalScrollbar.beginReveal();
|
||||
this._scheduleHide();
|
||||
}
|
||||
_hide() {
|
||||
if (!this._mouseIsOver && !this._isDragging) {
|
||||
this._verticalScrollbar.beginHide();
|
||||
this._horizontalScrollbar.beginHide();
|
||||
}
|
||||
}
|
||||
_scheduleHide() {
|
||||
if (!this._mouseIsOver && !this._isDragging) {
|
||||
this._hideTimeout.cancelAndSet(() => this._hide(), HIDE_TIMEOUT);
|
||||
}
|
||||
}
|
||||
}
|
||||
export class ScrollableElement extends AbstractScrollableElement {
|
||||
constructor(element, options) {
|
||||
options = options || {};
|
||||
options.mouseWheelSmoothScroll = false;
|
||||
const scrollable = new Scrollable({
|
||||
forceIntegerValues: true,
|
||||
smoothScrollDuration: 0,
|
||||
scheduleAtNextAnimationFrame: (callback) => dom.scheduleAtNextAnimationFrame(callback)
|
||||
});
|
||||
super(element, options, scrollable);
|
||||
this._register(scrollable);
|
||||
}
|
||||
setScrollPosition(update) {
|
||||
this._scrollable.setScrollPositionNow(update);
|
||||
}
|
||||
}
|
||||
export class SmoothScrollableElement extends AbstractScrollableElement {
|
||||
constructor(element, options, scrollable) {
|
||||
super(element, options, scrollable);
|
||||
}
|
||||
setScrollPosition(update) {
|
||||
if (update.reuseAnimation) {
|
||||
this._scrollable.setScrollPositionSmooth(update, update.reuseAnimation);
|
||||
}
|
||||
else {
|
||||
this._scrollable.setScrollPositionNow(update);
|
||||
}
|
||||
}
|
||||
getScrollPosition() {
|
||||
return this._scrollable.getCurrentScrollPosition();
|
||||
}
|
||||
}
|
||||
export class DomScrollableElement extends AbstractScrollableElement {
|
||||
constructor(element, options) {
|
||||
options = options || {};
|
||||
options.mouseWheelSmoothScroll = false;
|
||||
const scrollable = new Scrollable({
|
||||
forceIntegerValues: false,
|
||||
smoothScrollDuration: 0,
|
||||
scheduleAtNextAnimationFrame: (callback) => dom.scheduleAtNextAnimationFrame(callback)
|
||||
});
|
||||
super(element, options, scrollable);
|
||||
this._register(scrollable);
|
||||
this._element = element;
|
||||
this.onScroll((e) => {
|
||||
if (e.scrollTopChanged) {
|
||||
this._element.scrollTop = e.scrollTop;
|
||||
}
|
||||
if (e.scrollLeftChanged) {
|
||||
this._element.scrollLeft = e.scrollLeft;
|
||||
}
|
||||
});
|
||||
this.scanDomNode();
|
||||
}
|
||||
setScrollPosition(update) {
|
||||
this._scrollable.setScrollPositionNow(update);
|
||||
}
|
||||
getScrollPosition() {
|
||||
return this._scrollable.getCurrentScrollPosition();
|
||||
}
|
||||
scanDomNode() {
|
||||
// width, scrollLeft, scrollWidth, height, scrollTop, scrollHeight
|
||||
this.setScrollDimensions({
|
||||
width: this._element.clientWidth,
|
||||
scrollWidth: this._element.scrollWidth,
|
||||
height: this._element.clientHeight,
|
||||
scrollHeight: this._element.scrollHeight
|
||||
});
|
||||
this.setScrollPosition({
|
||||
scrollLeft: this._element.scrollLeft,
|
||||
scrollTop: this._element.scrollTop,
|
||||
});
|
||||
}
|
||||
}
|
||||
function resolveOptions(opts) {
|
||||
const result = {
|
||||
lazyRender: (typeof opts.lazyRender !== 'undefined' ? opts.lazyRender : false),
|
||||
className: (typeof opts.className !== 'undefined' ? opts.className : ''),
|
||||
useShadows: (typeof opts.useShadows !== 'undefined' ? opts.useShadows : true),
|
||||
handleMouseWheel: (typeof opts.handleMouseWheel !== 'undefined' ? opts.handleMouseWheel : true),
|
||||
flipAxes: (typeof opts.flipAxes !== 'undefined' ? opts.flipAxes : false),
|
||||
consumeMouseWheelIfScrollbarIsNeeded: (typeof opts.consumeMouseWheelIfScrollbarIsNeeded !== 'undefined' ? opts.consumeMouseWheelIfScrollbarIsNeeded : false),
|
||||
alwaysConsumeMouseWheel: (typeof opts.alwaysConsumeMouseWheel !== 'undefined' ? opts.alwaysConsumeMouseWheel : false),
|
||||
scrollYToX: (typeof opts.scrollYToX !== 'undefined' ? opts.scrollYToX : false),
|
||||
mouseWheelScrollSensitivity: (typeof opts.mouseWheelScrollSensitivity !== 'undefined' ? opts.mouseWheelScrollSensitivity : 1),
|
||||
fastScrollSensitivity: (typeof opts.fastScrollSensitivity !== 'undefined' ? opts.fastScrollSensitivity : 5),
|
||||
scrollPredominantAxis: (typeof opts.scrollPredominantAxis !== 'undefined' ? opts.scrollPredominantAxis : true),
|
||||
mouseWheelSmoothScroll: (typeof opts.mouseWheelSmoothScroll !== 'undefined' ? opts.mouseWheelSmoothScroll : true),
|
||||
arrowSize: (typeof opts.arrowSize !== 'undefined' ? opts.arrowSize : 11),
|
||||
listenOnDomNode: (typeof opts.listenOnDomNode !== 'undefined' ? opts.listenOnDomNode : null),
|
||||
horizontal: (typeof opts.horizontal !== 'undefined' ? opts.horizontal : 1 /* ScrollbarVisibility.Auto */),
|
||||
horizontalScrollbarSize: (typeof opts.horizontalScrollbarSize !== 'undefined' ? opts.horizontalScrollbarSize : 10),
|
||||
horizontalSliderSize: (typeof opts.horizontalSliderSize !== 'undefined' ? opts.horizontalSliderSize : 0),
|
||||
horizontalHasArrows: (typeof opts.horizontalHasArrows !== 'undefined' ? opts.horizontalHasArrows : false),
|
||||
vertical: (typeof opts.vertical !== 'undefined' ? opts.vertical : 1 /* ScrollbarVisibility.Auto */),
|
||||
verticalScrollbarSize: (typeof opts.verticalScrollbarSize !== 'undefined' ? opts.verticalScrollbarSize : 10),
|
||||
verticalHasArrows: (typeof opts.verticalHasArrows !== 'undefined' ? opts.verticalHasArrows : false),
|
||||
verticalSliderSize: (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : 0),
|
||||
scrollByPage: (typeof opts.scrollByPage !== 'undefined' ? opts.scrollByPage : false)
|
||||
};
|
||||
result.horizontalSliderSize = (typeof opts.horizontalSliderSize !== 'undefined' ? opts.horizontalSliderSize : result.horizontalScrollbarSize);
|
||||
result.verticalSliderSize = (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : result.verticalScrollbarSize);
|
||||
// Defaults are different on Macs
|
||||
if (platform.isMacintosh) {
|
||||
result.className += ' mac';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
export {};
|
||||
@@ -0,0 +1,74 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { GlobalPointerMoveMonitor } from '../../globalPointerMoveMonitor.js';
|
||||
import { Widget } from '../widget.js';
|
||||
import { IntervalTimer, TimeoutTimer } from '../../../common/async.js';
|
||||
import * as dom from '../../dom.js';
|
||||
/**
|
||||
* The arrow image size.
|
||||
*/
|
||||
export const ARROW_IMG_SIZE = 11;
|
||||
export class ScrollbarArrow extends Widget {
|
||||
constructor(opts) {
|
||||
super();
|
||||
this._onActivate = opts.onActivate;
|
||||
this.bgDomNode = document.createElement('div');
|
||||
this.bgDomNode.className = 'arrow-background';
|
||||
this.bgDomNode.style.position = 'absolute';
|
||||
this.bgDomNode.style.width = opts.bgWidth + 'px';
|
||||
this.bgDomNode.style.height = opts.bgHeight + 'px';
|
||||
if (typeof opts.top !== 'undefined') {
|
||||
this.bgDomNode.style.top = '0px';
|
||||
}
|
||||
if (typeof opts.left !== 'undefined') {
|
||||
this.bgDomNode.style.left = '0px';
|
||||
}
|
||||
if (typeof opts.bottom !== 'undefined') {
|
||||
this.bgDomNode.style.bottom = '0px';
|
||||
}
|
||||
if (typeof opts.right !== 'undefined') {
|
||||
this.bgDomNode.style.right = '0px';
|
||||
}
|
||||
this.domNode = document.createElement('div');
|
||||
this.domNode.className = opts.className;
|
||||
this.domNode.classList.add(...opts.icon.classNamesArray);
|
||||
this.domNode.style.position = 'absolute';
|
||||
this.domNode.style.width = ARROW_IMG_SIZE + 'px';
|
||||
this.domNode.style.height = ARROW_IMG_SIZE + 'px';
|
||||
if (typeof opts.top !== 'undefined') {
|
||||
this.domNode.style.top = opts.top + 'px';
|
||||
}
|
||||
if (typeof opts.left !== 'undefined') {
|
||||
this.domNode.style.left = opts.left + 'px';
|
||||
}
|
||||
if (typeof opts.bottom !== 'undefined') {
|
||||
this.domNode.style.bottom = opts.bottom + 'px';
|
||||
}
|
||||
if (typeof opts.right !== 'undefined') {
|
||||
this.domNode.style.right = opts.right + 'px';
|
||||
}
|
||||
this._pointerMoveMonitor = this._register(new GlobalPointerMoveMonitor());
|
||||
this._register(dom.addStandardDisposableListener(this.bgDomNode, dom.EventType.POINTER_DOWN, (e) => this._arrowPointerDown(e)));
|
||||
this._register(dom.addStandardDisposableListener(this.domNode, dom.EventType.POINTER_DOWN, (e) => this._arrowPointerDown(e)));
|
||||
this._pointerdownRepeatTimer = this._register(new IntervalTimer());
|
||||
this._pointerdownScheduleRepeatTimer = this._register(new TimeoutTimer());
|
||||
}
|
||||
_arrowPointerDown(e) {
|
||||
if (!e.target || !(e.target instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
const scheduleRepeater = () => {
|
||||
this._pointerdownRepeatTimer.cancelAndSet(() => this._onActivate(), 1000 / 24);
|
||||
};
|
||||
this._onActivate();
|
||||
this._pointerdownRepeatTimer.cancel();
|
||||
this._pointerdownScheduleRepeatTimer.cancelAndSet(scheduleRepeater, 200);
|
||||
this._pointerMoveMonitor.startMonitoring(e.target, e.pointerId, e.buttons, (pointerMoveData) => { }, () => {
|
||||
this._pointerdownRepeatTimer.cancel();
|
||||
this._pointerdownScheduleRepeatTimer.cancel();
|
||||
});
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
/**
|
||||
* The minimal size of the slider (such that it can still be clickable) -- it is artificially enlarged.
|
||||
*/
|
||||
const MINIMUM_SLIDER_SIZE = 20;
|
||||
export class ScrollbarState {
|
||||
constructor(arrowSize, scrollbarSize, oppositeScrollbarSize, visibleSize, scrollSize, scrollPosition) {
|
||||
this._scrollbarSize = Math.round(scrollbarSize);
|
||||
this._oppositeScrollbarSize = Math.round(oppositeScrollbarSize);
|
||||
this._arrowSize = Math.round(arrowSize);
|
||||
this._visibleSize = visibleSize;
|
||||
this._scrollSize = scrollSize;
|
||||
this._scrollPosition = scrollPosition;
|
||||
this._computedAvailableSize = 0;
|
||||
this._computedIsNeeded = false;
|
||||
this._computedSliderSize = 0;
|
||||
this._computedSliderRatio = 0;
|
||||
this._computedSliderPosition = 0;
|
||||
this._refreshComputedValues();
|
||||
}
|
||||
clone() {
|
||||
return new ScrollbarState(this._arrowSize, this._scrollbarSize, this._oppositeScrollbarSize, this._visibleSize, this._scrollSize, this._scrollPosition);
|
||||
}
|
||||
setVisibleSize(visibleSize) {
|
||||
const iVisibleSize = Math.round(visibleSize);
|
||||
if (this._visibleSize !== iVisibleSize) {
|
||||
this._visibleSize = iVisibleSize;
|
||||
this._refreshComputedValues();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
setScrollSize(scrollSize) {
|
||||
const iScrollSize = Math.round(scrollSize);
|
||||
if (this._scrollSize !== iScrollSize) {
|
||||
this._scrollSize = iScrollSize;
|
||||
this._refreshComputedValues();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
setScrollPosition(scrollPosition) {
|
||||
const iScrollPosition = Math.round(scrollPosition);
|
||||
if (this._scrollPosition !== iScrollPosition) {
|
||||
this._scrollPosition = iScrollPosition;
|
||||
this._refreshComputedValues();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
setScrollbarSize(scrollbarSize) {
|
||||
this._scrollbarSize = Math.round(scrollbarSize);
|
||||
}
|
||||
setOppositeScrollbarSize(oppositeScrollbarSize) {
|
||||
this._oppositeScrollbarSize = Math.round(oppositeScrollbarSize);
|
||||
}
|
||||
static _computeValues(oppositeScrollbarSize, arrowSize, visibleSize, scrollSize, scrollPosition) {
|
||||
const computedAvailableSize = Math.max(0, visibleSize - oppositeScrollbarSize);
|
||||
const computedRepresentableSize = Math.max(0, computedAvailableSize - 2 * arrowSize);
|
||||
const computedIsNeeded = (scrollSize > 0 && scrollSize > visibleSize);
|
||||
if (!computedIsNeeded) {
|
||||
// There is no need for a slider
|
||||
return {
|
||||
computedAvailableSize: Math.round(computedAvailableSize),
|
||||
computedIsNeeded: computedIsNeeded,
|
||||
computedSliderSize: Math.round(computedRepresentableSize),
|
||||
computedSliderRatio: 0,
|
||||
computedSliderPosition: 0,
|
||||
};
|
||||
}
|
||||
// We must artificially increase the size of the slider if needed, since the slider would be too small to grab with the mouse otherwise
|
||||
const computedSliderSize = Math.round(Math.max(MINIMUM_SLIDER_SIZE, Math.floor(visibleSize * computedRepresentableSize / scrollSize)));
|
||||
// The slider can move from 0 to `computedRepresentableSize` - `computedSliderSize`
|
||||
// in the same way `scrollPosition` can move from 0 to `scrollSize` - `visibleSize`.
|
||||
const computedSliderRatio = (computedRepresentableSize - computedSliderSize) / (scrollSize - visibleSize);
|
||||
const computedSliderPosition = (scrollPosition * computedSliderRatio);
|
||||
return {
|
||||
computedAvailableSize: Math.round(computedAvailableSize),
|
||||
computedIsNeeded: computedIsNeeded,
|
||||
computedSliderSize: Math.round(computedSliderSize),
|
||||
computedSliderRatio: computedSliderRatio,
|
||||
computedSliderPosition: Math.round(computedSliderPosition),
|
||||
};
|
||||
}
|
||||
_refreshComputedValues() {
|
||||
const r = ScrollbarState._computeValues(this._oppositeScrollbarSize, this._arrowSize, this._visibleSize, this._scrollSize, this._scrollPosition);
|
||||
this._computedAvailableSize = r.computedAvailableSize;
|
||||
this._computedIsNeeded = r.computedIsNeeded;
|
||||
this._computedSliderSize = r.computedSliderSize;
|
||||
this._computedSliderRatio = r.computedSliderRatio;
|
||||
this._computedSliderPosition = r.computedSliderPosition;
|
||||
}
|
||||
getArrowSize() {
|
||||
return this._arrowSize;
|
||||
}
|
||||
getScrollPosition() {
|
||||
return this._scrollPosition;
|
||||
}
|
||||
getRectangleLargeSize() {
|
||||
return this._computedAvailableSize;
|
||||
}
|
||||
getRectangleSmallSize() {
|
||||
return this._scrollbarSize;
|
||||
}
|
||||
isNeeded() {
|
||||
return this._computedIsNeeded;
|
||||
}
|
||||
getSliderSize() {
|
||||
return this._computedSliderSize;
|
||||
}
|
||||
getSliderPosition() {
|
||||
return this._computedSliderPosition;
|
||||
}
|
||||
/**
|
||||
* Compute a desired `scrollPosition` such that `offset` ends up in the center of the slider.
|
||||
* `offset` is based on the same coordinate system as the `sliderPosition`.
|
||||
*/
|
||||
getDesiredScrollPositionFromOffset(offset) {
|
||||
if (!this._computedIsNeeded) {
|
||||
// no need for a slider
|
||||
return 0;
|
||||
}
|
||||
const desiredSliderPosition = offset - this._arrowSize - this._computedSliderSize / 2;
|
||||
return Math.round(desiredSliderPosition / this._computedSliderRatio);
|
||||
}
|
||||
/**
|
||||
* Compute a desired `scrollPosition` from if offset is before or after the slider position.
|
||||
* If offset is before slider, treat as a page up (or left). If after, page down (or right).
|
||||
* `offset` and `_computedSliderPosition` are based on the same coordinate system.
|
||||
* `_visibleSize` corresponds to a "page" of lines in the returned coordinate system.
|
||||
*/
|
||||
getDesiredScrollPositionFromOffsetPaged(offset) {
|
||||
if (!this._computedIsNeeded) {
|
||||
// no need for a slider
|
||||
return 0;
|
||||
}
|
||||
const correctedOffset = offset - this._arrowSize; // compensate if has arrows
|
||||
let desiredScrollPosition = this._scrollPosition;
|
||||
if (correctedOffset < this._computedSliderPosition) {
|
||||
desiredScrollPosition -= this._visibleSize; // page up/left
|
||||
}
|
||||
else {
|
||||
desiredScrollPosition += this._visibleSize; // page down/right
|
||||
}
|
||||
return desiredScrollPosition;
|
||||
}
|
||||
/**
|
||||
* Compute a desired `scrollPosition` such that the slider moves by `delta`.
|
||||
*/
|
||||
getDesiredScrollPositionFromDelta(delta) {
|
||||
if (!this._computedIsNeeded) {
|
||||
// no need for a slider
|
||||
return 0;
|
||||
}
|
||||
const desiredSliderPosition = this._computedSliderPosition + delta;
|
||||
return Math.round(desiredSliderPosition / this._computedSliderRatio);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { TimeoutTimer } from '../../../common/async.js';
|
||||
import { Disposable } from '../../../common/lifecycle.js';
|
||||
export class ScrollbarVisibilityController extends Disposable {
|
||||
constructor(visibility, visibleClassName, invisibleClassName) {
|
||||
super();
|
||||
this._visibility = visibility;
|
||||
this._visibleClassName = visibleClassName;
|
||||
this._invisibleClassName = invisibleClassName;
|
||||
this._domNode = null;
|
||||
this._isVisible = false;
|
||||
this._isNeeded = false;
|
||||
this._rawShouldBeVisible = false;
|
||||
this._shouldBeVisible = false;
|
||||
this._revealTimer = this._register(new TimeoutTimer());
|
||||
}
|
||||
setVisibility(visibility) {
|
||||
if (this._visibility !== visibility) {
|
||||
this._visibility = visibility;
|
||||
this._updateShouldBeVisible();
|
||||
}
|
||||
}
|
||||
// ----------------- Hide / Reveal
|
||||
setShouldBeVisible(rawShouldBeVisible) {
|
||||
this._rawShouldBeVisible = rawShouldBeVisible;
|
||||
this._updateShouldBeVisible();
|
||||
}
|
||||
_applyVisibilitySetting() {
|
||||
if (this._visibility === 2 /* ScrollbarVisibility.Hidden */) {
|
||||
return false;
|
||||
}
|
||||
if (this._visibility === 3 /* ScrollbarVisibility.Visible */) {
|
||||
return true;
|
||||
}
|
||||
return this._rawShouldBeVisible;
|
||||
}
|
||||
_updateShouldBeVisible() {
|
||||
const shouldBeVisible = this._applyVisibilitySetting();
|
||||
if (this._shouldBeVisible !== shouldBeVisible) {
|
||||
this._shouldBeVisible = shouldBeVisible;
|
||||
this.ensureVisibility();
|
||||
}
|
||||
}
|
||||
setIsNeeded(isNeeded) {
|
||||
if (this._isNeeded !== isNeeded) {
|
||||
this._isNeeded = isNeeded;
|
||||
this.ensureVisibility();
|
||||
}
|
||||
}
|
||||
setDomNode(domNode) {
|
||||
this._domNode = domNode;
|
||||
this._domNode.setClassName(this._invisibleClassName);
|
||||
// Now that the flags & the dom node are in a consistent state, ensure the Hidden/Visible configuration
|
||||
this.setShouldBeVisible(false);
|
||||
}
|
||||
ensureVisibility() {
|
||||
if (!this._isNeeded) {
|
||||
// Nothing to be rendered
|
||||
this._hide(false);
|
||||
return;
|
||||
}
|
||||
if (this._shouldBeVisible) {
|
||||
this._reveal();
|
||||
}
|
||||
else {
|
||||
this._hide(true);
|
||||
}
|
||||
}
|
||||
_reveal() {
|
||||
if (this._isVisible) {
|
||||
return;
|
||||
}
|
||||
this._isVisible = true;
|
||||
// The CSS animation doesn't play otherwise
|
||||
this._revealTimer.setIfNotSet(() => {
|
||||
var _a;
|
||||
(_a = this._domNode) === null || _a === void 0 ? void 0 : _a.setClassName(this._visibleClassName);
|
||||
}, 0);
|
||||
}
|
||||
_hide(withFadeAway) {
|
||||
var _a;
|
||||
this._revealTimer.cancel();
|
||||
if (!this._isVisible) {
|
||||
return;
|
||||
}
|
||||
this._isVisible = false;
|
||||
(_a = this._domNode) === null || _a === void 0 ? void 0 : _a.setClassName(this._invisibleClassName + (withFadeAway ? ' fade' : ''));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { StandardWheelEvent } from '../../mouseEvent.js';
|
||||
import { AbstractScrollbar } from './abstractScrollbar.js';
|
||||
import { ARROW_IMG_SIZE } from './scrollbarArrow.js';
|
||||
import { ScrollbarState } from './scrollbarState.js';
|
||||
import { Codicon } from '../../../common/codicons.js';
|
||||
export class VerticalScrollbar extends AbstractScrollbar {
|
||||
constructor(scrollable, options, host) {
|
||||
const scrollDimensions = scrollable.getScrollDimensions();
|
||||
const scrollPosition = scrollable.getCurrentScrollPosition();
|
||||
super({
|
||||
lazyRender: options.lazyRender,
|
||||
host: host,
|
||||
scrollbarState: new ScrollbarState((options.verticalHasArrows ? options.arrowSize : 0), (options.vertical === 2 /* ScrollbarVisibility.Hidden */ ? 0 : options.verticalScrollbarSize),
|
||||
// give priority to vertical scroll bar over horizontal and let it scroll all the way to the bottom
|
||||
0, scrollDimensions.height, scrollDimensions.scrollHeight, scrollPosition.scrollTop),
|
||||
visibility: options.vertical,
|
||||
extraScrollbarClassName: 'vertical',
|
||||
scrollable: scrollable,
|
||||
scrollByPage: options.scrollByPage
|
||||
});
|
||||
if (options.verticalHasArrows) {
|
||||
const arrowDelta = (options.arrowSize - ARROW_IMG_SIZE) / 2;
|
||||
const scrollbarDelta = (options.verticalScrollbarSize - ARROW_IMG_SIZE) / 2;
|
||||
this._createArrow({
|
||||
className: 'scra',
|
||||
icon: Codicon.scrollbarButtonUp,
|
||||
top: arrowDelta,
|
||||
left: scrollbarDelta,
|
||||
bottom: undefined,
|
||||
right: undefined,
|
||||
bgWidth: options.verticalScrollbarSize,
|
||||
bgHeight: options.arrowSize,
|
||||
onActivate: () => this._host.onMouseWheel(new StandardWheelEvent(null, 0, 1)),
|
||||
});
|
||||
this._createArrow({
|
||||
className: 'scra',
|
||||
icon: Codicon.scrollbarButtonDown,
|
||||
top: undefined,
|
||||
left: scrollbarDelta,
|
||||
bottom: arrowDelta,
|
||||
right: undefined,
|
||||
bgWidth: options.verticalScrollbarSize,
|
||||
bgHeight: options.arrowSize,
|
||||
onActivate: () => this._host.onMouseWheel(new StandardWheelEvent(null, 0, -1)),
|
||||
});
|
||||
}
|
||||
this._createSlider(0, Math.floor((options.verticalScrollbarSize - options.verticalSliderSize) / 2), options.verticalSliderSize, undefined);
|
||||
}
|
||||
_updateSlider(sliderSize, sliderPosition) {
|
||||
this.slider.setHeight(sliderSize);
|
||||
this.slider.setTop(sliderPosition);
|
||||
}
|
||||
_renderDomNode(largeSize, smallSize) {
|
||||
this.domNode.setWidth(smallSize);
|
||||
this.domNode.setHeight(largeSize);
|
||||
this.domNode.setRight(0);
|
||||
this.domNode.setTop(0);
|
||||
}
|
||||
onDidScroll(e) {
|
||||
this._shouldRender = this._onElementScrollSize(e.scrollHeight) || this._shouldRender;
|
||||
this._shouldRender = this._onElementScrollPosition(e.scrollTop) || this._shouldRender;
|
||||
this._shouldRender = this._onElementSize(e.height) || this._shouldRender;
|
||||
return this._shouldRender;
|
||||
}
|
||||
_pointerDownRelativePosition(offsetX, offsetY) {
|
||||
return offsetY;
|
||||
}
|
||||
_sliderPointerPosition(e) {
|
||||
return e.pageY;
|
||||
}
|
||||
_sliderOrthogonalPointerPosition(e) {
|
||||
return e.pageX;
|
||||
}
|
||||
_updateScrollbarSize(size) {
|
||||
this.slider.setWidth(size);
|
||||
}
|
||||
writeScrollPosition(target, scrollPosition) {
|
||||
target.scrollTop = scrollPosition;
|
||||
}
|
||||
updateOptions(options) {
|
||||
this.updateScrollbarSize(options.vertical === 2 /* ScrollbarVisibility.Hidden */ ? 0 : options.verticalScrollbarSize);
|
||||
// give priority to vertical scroll bar over horizontal and let it scroll all the way to the bottom
|
||||
this._scrollbarState.setOppositeScrollbarSize(0);
|
||||
this._visibilityController.setVisibility(options.vertical);
|
||||
this._scrollByPage = options.scrollByPage;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user