Path: blob/main/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as dom from '../../dom.js';6import { createFastDomNode, FastDomNode } from '../../fastDomNode.js';7import { GlobalPointerMoveMonitor } from '../../globalPointerMoveMonitor.js';8import { StandardWheelEvent } from '../../mouseEvent.js';9import { ScrollbarArrow, ScrollbarArrowOptions } from './scrollbarArrow.js';10import { ScrollbarState } from './scrollbarState.js';11import { ScrollbarVisibilityController } from './scrollbarVisibilityController.js';12import { Widget } from '../widget.js';13import * as platform from '../../../common/platform.js';14import { INewScrollPosition, Scrollable, ScrollbarVisibility } from '../../../common/scrollable.js';1516/**17* The orthogonal distance to the slider at which dragging "resets". This implements "snapping"18*/19const POINTER_DRAG_RESET_DISTANCE = 140;2021export interface ISimplifiedPointerEvent {22buttons: number;23pageX: number;24pageY: number;25}2627export interface ScrollbarHost {28onMouseWheel(mouseWheelEvent: StandardWheelEvent): void;29onDragStart(): void;30onDragEnd(): void;31}3233export interface AbstractScrollbarOptions {34lazyRender: boolean;35host: ScrollbarHost;36scrollbarState: ScrollbarState;37visibility: ScrollbarVisibility;38extraScrollbarClassName: string;39scrollable: Scrollable;40scrollByPage: boolean;41}4243export abstract class AbstractScrollbar extends Widget {4445protected _host: ScrollbarHost;46protected _scrollable: Scrollable;47protected _scrollByPage: boolean;48private _lazyRender: boolean;49protected _scrollbarState: ScrollbarState;50protected _visibilityController: ScrollbarVisibilityController;51private _pointerMoveMonitor: GlobalPointerMoveMonitor;5253public domNode: FastDomNode<HTMLElement>;54public slider!: FastDomNode<HTMLElement>;5556protected _shouldRender: boolean;5758constructor(opts: AbstractScrollbarOptions) {59super();60this._lazyRender = opts.lazyRender;61this._host = opts.host;62this._scrollable = opts.scrollable;63this._scrollByPage = opts.scrollByPage;64this._scrollbarState = opts.scrollbarState;65this._visibilityController = this._register(new ScrollbarVisibilityController(opts.visibility, 'visible scrollbar ' + opts.extraScrollbarClassName, 'invisible scrollbar ' + opts.extraScrollbarClassName));66this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());67this._pointerMoveMonitor = this._register(new GlobalPointerMoveMonitor());68this._shouldRender = true;69this.domNode = createFastDomNode(document.createElement('div'));70this.domNode.setAttribute('role', 'presentation');71this.domNode.setAttribute('aria-hidden', 'true');7273this._visibilityController.setDomNode(this.domNode);74this.domNode.setPosition('absolute');7576this._register(dom.addDisposableListener(this.domNode.domNode, dom.EventType.POINTER_DOWN, (e: PointerEvent) => this._domNodePointerDown(e)));77}7879// ----------------- creation8081/**82* Creates the dom node for an arrow & adds it to the container83*/84protected _createArrow(opts: ScrollbarArrowOptions): void {85const arrow = this._register(new ScrollbarArrow(opts));86this.domNode.domNode.appendChild(arrow.bgDomNode);87this.domNode.domNode.appendChild(arrow.domNode);88}8990/**91* Creates the slider dom node, adds it to the container & hooks up the events92*/93protected _createSlider(top: number, left: number, width: number | undefined, height: number | undefined): void {94this.slider = createFastDomNode(document.createElement('div'));95this.slider.setClassName('slider');96this.slider.setPosition('absolute');97this.slider.setTop(top);98this.slider.setLeft(left);99if (typeof width === 'number') {100this.slider.setWidth(width);101}102if (typeof height === 'number') {103this.slider.setHeight(height);104}105this.slider.setLayerHinting(true);106this.slider.setContain('strict');107108this.domNode.domNode.appendChild(this.slider.domNode);109110this._register(dom.addDisposableListener(111this.slider.domNode,112dom.EventType.POINTER_DOWN,113(e: PointerEvent) => {114if (e.button === 0) {115e.preventDefault();116this._sliderPointerDown(e);117}118}119));120121this.onclick(this.slider.domNode, e => {122if (e.leftButton) {123e.stopPropagation();124}125});126}127128// ----------------- Update state129130protected _onElementSize(visibleSize: number): boolean {131if (this._scrollbarState.setVisibleSize(visibleSize)) {132this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());133this._shouldRender = true;134if (!this._lazyRender) {135this.render();136}137}138return this._shouldRender;139}140141protected _onElementScrollSize(elementScrollSize: number): boolean {142if (this._scrollbarState.setScrollSize(elementScrollSize)) {143this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());144this._shouldRender = true;145if (!this._lazyRender) {146this.render();147}148}149return this._shouldRender;150}151152protected _onElementScrollPosition(elementScrollPosition: number): boolean {153if (this._scrollbarState.setScrollPosition(elementScrollPosition)) {154this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());155this._shouldRender = true;156if (!this._lazyRender) {157this.render();158}159}160return this._shouldRender;161}162163// ----------------- rendering164165public beginReveal(): void {166this._visibilityController.setShouldBeVisible(true);167}168169public beginHide(): void {170this._visibilityController.setShouldBeVisible(false);171}172173public render(): void {174if (!this._shouldRender) {175return;176}177this._shouldRender = false;178179this._renderDomNode(this._scrollbarState.getRectangleLargeSize(), this._scrollbarState.getRectangleSmallSize());180this._updateSlider(this._scrollbarState.getSliderSize(), this._scrollbarState.getArrowSize() + this._scrollbarState.getSliderPosition());181}182// ----------------- DOM events183184private _domNodePointerDown(e: PointerEvent): void {185if (e.target !== this.domNode.domNode) {186return;187}188this._onPointerDown(e);189}190191public delegatePointerDown(e: PointerEvent): void {192const domTop = this.domNode.domNode.getClientRects()[0].top;193const sliderStart = domTop + this._scrollbarState.getSliderPosition();194const sliderStop = domTop + this._scrollbarState.getSliderPosition() + this._scrollbarState.getSliderSize();195const pointerPos = this._sliderPointerPosition(e);196if (sliderStart <= pointerPos && pointerPos <= sliderStop) {197// Act as if it was a pointer down on the slider198if (e.button === 0) {199e.preventDefault();200this._sliderPointerDown(e);201}202} else {203// Act as if it was a pointer down on the scrollbar204this._onPointerDown(e);205}206}207208private _onPointerDown(e: PointerEvent): void {209let offsetX: number;210let offsetY: number;211if (e.target === this.domNode.domNode && typeof e.offsetX === 'number' && typeof e.offsetY === 'number') {212offsetX = e.offsetX;213offsetY = e.offsetY;214} else {215const domNodePosition = dom.getDomNodePagePosition(this.domNode.domNode);216offsetX = e.pageX - domNodePosition.left;217offsetY = e.pageY - domNodePosition.top;218}219220const isMouse = (e.pointerType === 'mouse');221const isLeftClick = (e.button === 0);222223if (isLeftClick || !isMouse) {224const offset = this._pointerDownRelativePosition(offsetX, offsetY);225this._setDesiredScrollPositionNow(226this._scrollByPage227? this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(offset)228: this._scrollbarState.getDesiredScrollPositionFromOffset(offset)229);230}231232if (isLeftClick) {233// left button234e.preventDefault();235this._sliderPointerDown(e);236}237}238239private _sliderPointerDown(e: PointerEvent): void {240if (!e.target || !(e.target instanceof Element)) {241return;242}243const initialPointerPosition = this._sliderPointerPosition(e);244const initialPointerOrthogonalPosition = this._sliderOrthogonalPointerPosition(e);245const initialScrollbarState = this._scrollbarState.clone();246this.slider.toggleClassName('active', true);247248this._pointerMoveMonitor.startMonitoring(249e.target,250e.pointerId,251e.buttons,252(pointerMoveData: PointerEvent) => {253const pointerOrthogonalPosition = this._sliderOrthogonalPointerPosition(pointerMoveData);254const pointerOrthogonalDelta = Math.abs(pointerOrthogonalPosition - initialPointerOrthogonalPosition);255256if (platform.isWindows && pointerOrthogonalDelta > POINTER_DRAG_RESET_DISTANCE) {257// The pointer has wondered away from the scrollbar => reset dragging258this._setDesiredScrollPositionNow(initialScrollbarState.getScrollPosition());259return;260}261262const pointerPosition = this._sliderPointerPosition(pointerMoveData);263const pointerDelta = pointerPosition - initialPointerPosition;264this._setDesiredScrollPositionNow(initialScrollbarState.getDesiredScrollPositionFromDelta(pointerDelta));265},266() => {267this.slider.toggleClassName('active', false);268this._host.onDragEnd();269}270);271272this._host.onDragStart();273}274275private _setDesiredScrollPositionNow(_desiredScrollPosition: number): void {276277const desiredScrollPosition: INewScrollPosition = {};278this.writeScrollPosition(desiredScrollPosition, _desiredScrollPosition);279280this._scrollable.setScrollPositionNow(desiredScrollPosition);281}282283public updateScrollbarSize(scrollbarSize: number): void {284this._updateScrollbarSize(scrollbarSize);285this._scrollbarState.setScrollbarSize(scrollbarSize);286this._shouldRender = true;287if (!this._lazyRender) {288this.render();289}290}291292public isNeeded(): boolean {293return this._scrollbarState.isNeeded();294}295296// ----------------- Overwrite these297298protected abstract _renderDomNode(largeSize: number, smallSize: number): void;299protected abstract _updateSlider(sliderSize: number, sliderPosition: number): void;300301protected abstract _pointerDownRelativePosition(offsetX: number, offsetY: number): number;302protected abstract _sliderPointerPosition(e: ISimplifiedPointerEvent): number;303protected abstract _sliderOrthogonalPointerPosition(e: ISimplifiedPointerEvent): number;304protected abstract _updateScrollbarSize(size: number): void;305306public abstract writeScrollPosition(target: INewScrollPosition, scrollPosition: number): void;307}308309310