From b57dffa7a68832257bfa716901331d72e26c3317 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 24 Mar 2016 20:24:01 +0100 Subject: [PATCH] Refactor how the scrollbar gets its dimensions --- scripts/code-perf.sh | 1 + src/vs/base/browser/ui/list/listView.ts | 31 ++++--- .../ui/resourceviewer/resourceViewer.ts | 20 ++-- .../browser/ui/scrollbar/abstractScrollbar.ts | 72 ++++++++------ .../browser/ui/scrollbar/domNodeScrollable.ts | 38 +++++++- .../ui/scrollbar/horizontalScrollbar.ts | 6 +- .../browser/ui/scrollbar/scrollableElement.ts | 13 --- .../ui/scrollbar/scrollableElementImpl.ts | 38 ++------ .../browser/ui/scrollbar/verticalScrollbar.ts | 6 +- src/vs/base/common/scrollable.ts | 93 ++++++++++++++++++- src/vs/base/parts/tree/browser/treeView.ts | 26 ++++-- src/vs/editor/browser/view/viewLayer.ts | 11 ++- .../browser/viewLayout/scrollManager.ts | 6 +- .../browser/viewParts/lines/viewLines.ts | 13 +-- .../common/viewLayout/editorScrollable.ts | 36 +++---- .../contrib/suggest/browser/suggestWidget.ts | 16 +++- .../browser/parts/editor/binaryDiffEditor.ts | 19 ++-- 17 files changed, 283 insertions(+), 162 deletions(-) diff --git a/scripts/code-perf.sh b/scripts/code-perf.sh index 6aa439eaf63..92f83b3a46c 100755 --- a/scripts/code-perf.sh +++ b/scripts/code-perf.sh @@ -30,6 +30,7 @@ function code() { exec ./.build/electron/Electron.app/Contents/MacOS/Electron . "$@" else exec ./.build/electron/electron . "$@" --no-sandbox --js-flags="--trace-hydrogen --trace-phase=Z --trace-deopt --code-comments --hydrogen-track-positions --redirect-code-traces" + # exec ./.build/electron/electron . "$@" --no-sandbox --js-flags="--trace-opt-verbose --trace-hydrogen --trace-phase=Z --trace-deopt --code-comments --hydrogen-track-positions --redirect-code-traces" fi } diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 956ab781061..8ac802d0299 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IScrollable } from 'vs/base/common/scrollable'; +import { IScrollable, ScrollEvent } from 'vs/base/common/scrollable'; import { Emitter } from 'vs/base/common/event'; import { toObject, assign } from 'vs/base/common/objects'; import { IDisposable, disposeAll } from 'vs/base/common/lifecycle'; @@ -16,11 +16,6 @@ import { IDelegate, IRenderer } from './list'; import { RowCache, IRow } from './rowCache'; import { LcsDiff, ISequence } from 'vs/base/common/diff/diff'; -interface IScrollEvent { - vertical: boolean; - horizontal: boolean; -} - interface IItemRange { item: IItem; index: number; @@ -68,7 +63,7 @@ export class ListView implements IScrollable, IDisposable { private rowsContainer: HTMLElement; private scrollableElement: IScrollableElement; - private _onScroll = new Emitter(); + private _onScroll = new Emitter(); private toDispose: IDisposable[]; @@ -94,9 +89,8 @@ export class ListView implements IScrollable, IDisposable { this.rowsContainer.className = 'monaco-list-rows'; this.gesture = new Gesture(this.rowsContainer); - this.scrollableElement = new ScrollableElement(this.rowsContainer, { + this.scrollableElement = new ScrollableElement(this.rowsContainer, this, { forbidTranslate3dUse: true, - scrollable: this, horizontal: 'hidden', vertical: 'auto', useShadows: false, @@ -144,7 +138,7 @@ export class ListView implements IScrollable, IDisposable { this.rowsContainer.style.height = `${ this.rangeMap.size }px`; this.setScrollTop(this.renderTop); - this.scrollableElement.onElementInternalDimensions(); + this._emitScrollEvent(false, false); return deleted.map(i => i.element); } @@ -181,7 +175,7 @@ export class ListView implements IScrollable, IDisposable { this.setRenderHeight(height || DOM.getContentHeight(this._domNode)); this.setScrollTop(this.renderTop); this.scrollableElement.onElementDimensions(); - this.scrollableElement.onElementInternalDimensions(); + this._emitScrollEvent(false, false); } // Render @@ -292,10 +286,21 @@ export class ListView implements IScrollable, IDisposable { this.render(scrollTop, this._renderHeight); this.renderTop = scrollTop; - this._onScroll.fire({ vertical: true, horizontal: false }); + this._emitScrollEvent(true, false); + } + + private _emitScrollEvent(vertical:boolean, horizontal:boolean): void { + this._onScroll.fire(new ScrollEvent( + this.getScrollTop(), + this.getScrollLeft(), + this.getScrollWidth(), + this.getScrollHeight(), + vertical, + horizontal + )); } - addScrollListener(callback: ()=>void): IDisposable { + addScrollListener(callback: (v:ScrollEvent)=>void): IDisposable { return this._onScroll.event(callback); } diff --git a/src/vs/base/browser/ui/resourceviewer/resourceViewer.ts b/src/vs/base/browser/ui/resourceviewer/resourceViewer.ts index 9d7ac61bb28..fee52268e05 100644 --- a/src/vs/base/browser/ui/resourceviewer/resourceViewer.ts +++ b/src/vs/base/browser/ui/resourceviewer/resourceViewer.ts @@ -12,7 +12,7 @@ import URI from 'vs/base/common/uri'; import paths = require('vs/base/common/paths'); import {Builder, $} from 'vs/base/browser/builder'; import DOM = require('vs/base/browser/dom'); -import {IScrollableElement} from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import {DomNodeScrollable} from 'vs/base/browser/ui/scrollbar/domNodeScrollable'; // Known media mimes that we can handle const mapExtToMediaMimes = { @@ -69,7 +69,7 @@ const mapExtToMediaMimes = { */ export class ResourceViewer { - public static show(name: string, resource: URI, container: Builder, scrollbar?: IScrollableElement): void { + public static show(name: string, resource: URI, container: Builder, scrollable?: DomNodeScrollable): void { // Ensure CSS class $(container).addClass('monaco-resource-viewer'); @@ -93,8 +93,8 @@ export class ResourceViewer { .img({ src: resource.toString() + '?' + new Date().getTime() // We really want to avoid the browser from caching this resource, so we add a fake query param that is unique }).on(DOM.EventType.LOAD, () => { - if (scrollbar) { - scrollbar.onElementInternalDimensions(); + if (scrollable) { + scrollable.onContentsDimensions(); } }); } @@ -124,8 +124,8 @@ export class ResourceViewer { text: nls.localize('missingAudioSupport', "Sorry but playback of audio files is not supported."), controls: 'controls' }).on(DOM.EventType.LOAD, () => { - if (scrollbar) { - scrollbar.onElementInternalDimensions(); + if (scrollable) { + scrollable.onContentsDimensions(); } }); } @@ -141,8 +141,8 @@ export class ResourceViewer { text: nls.localize('missingVideoSupport', "Sorry but playback of video files is not supported."), controls: 'controls' }).on(DOM.EventType.LOAD, () => { - if (scrollbar) { - scrollbar.onElementInternalDimensions(); + if (scrollable) { + scrollable.onContentsDimensions(); } }); } @@ -156,8 +156,8 @@ export class ResourceViewer { text: nls.localize('nativeBinaryError', "The file cannot be displayed in the editor because it is either binary, very large or uses an unsupported text encoding.") }); - if (scrollbar) { - scrollbar.onElementInternalDimensions(); + if (scrollable) { + scrollable.onContentsDimensions(); } } } diff --git a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts index 4d131dce885..04d140075f3 100644 --- a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts @@ -83,7 +83,7 @@ export class ScrollbarState { this._computedAvailableSize = 0; this._computedRepresentableSize = 0; - this._computedRatio = 0; + this._computedRatio = 0.1; this._computedIsNeeded = false; this._computedSliderSize = 0; this._computedSliderPosition = 0; @@ -92,9 +92,9 @@ export class ScrollbarState { } public setVisibleSize(visibleSize: number): boolean { - visibleSize = Math.round(visibleSize); - if (this._visibleSize !== visibleSize) { - this._visibleSize = visibleSize; + let iVisibleSize = Math.round(visibleSize); + if (this._visibleSize !== iVisibleSize) { + this._visibleSize = iVisibleSize; this._refreshComputedValues(); return true; } @@ -102,9 +102,9 @@ export class ScrollbarState { } public setScrollSize(scrollSize: number): boolean { - scrollSize = Math.round(scrollSize); - if (this._scrollSize !== scrollSize) { - this._scrollSize = scrollSize; + let iScrollSize = Math.round(scrollSize); + if (this._scrollSize !== iScrollSize) { + this._scrollSize = iScrollSize; this._refreshComputedValues(); return true; } @@ -112,9 +112,9 @@ export class ScrollbarState { } public setScrollPosition(scrollPosition: number): boolean { - scrollPosition = Math.round(scrollPosition); - if (this._scrollPosition !== scrollPosition) { - this._scrollPosition = scrollPosition; + let iScrollPosition = Math.round(scrollPosition); + if (this._scrollPosition !== iScrollPosition) { + this._scrollPosition = iScrollPosition; this._refreshComputedValues(); return true; } @@ -122,39 +122,55 @@ export class ScrollbarState { } private _refreshComputedValues(): void { - this._computedAvailableSize = Math.max(0, this._visibleSize - this._oppositeScrollbarSize); - this._computedRepresentableSize = Math.max(0, this._computedAvailableSize - 2 * this._arrowSize); - this._computedRatio = this._scrollSize > 0 ? (this._computedRepresentableSize / this._scrollSize) : 0; - this._computedIsNeeded = (this._scrollSize > this._visibleSize); - - if (!this._computedIsNeeded) { - this._computedSliderSize = this._computedRepresentableSize; - this._computedSliderPosition = 0; + const oppositeScrollbarSize = this._oppositeScrollbarSize; + const arrowSize = this._arrowSize; + const visibleSize = this._visibleSize; + const scrollSize = this._scrollSize; + const scrollPosition = this._scrollPosition; + + let computedAvailableSize = Math.max(0, visibleSize - oppositeScrollbarSize); + let computedRepresentableSize = Math.max(0, computedAvailableSize - 2 * arrowSize); + let computedRatio = scrollSize > 0 ? (computedRepresentableSize / scrollSize) : 0; + let computedIsNeeded = (scrollSize > visibleSize); + + let computedSliderSize: number; + let computedSliderPosition: number; + + if (!computedIsNeeded) { + computedSliderSize = computedRepresentableSize; + computedSliderPosition = 0; } else { - this._computedSliderSize = Math.floor(this._visibleSize * this._computedRatio); - this._computedSliderPosition = Math.floor(this._scrollPosition * this._computedRatio); + computedSliderSize = Math.floor(visibleSize * computedRatio); + computedSliderPosition = Math.floor(scrollPosition * computedRatio); - if (this._computedSliderSize < MINIMUM_SLIDER_SIZE) { + if (computedSliderSize < MINIMUM_SLIDER_SIZE) { // We must artificially increase the size of the slider, since the slider would be too small otherwise // The effort is to keep the slider centered around the original position, but we must take into // account the cases when the slider is too close to the top or too close to the bottom - let sliderArtificialOffset = (MINIMUM_SLIDER_SIZE - this._computedSliderSize) / 2; - this._computedSliderSize = MINIMUM_SLIDER_SIZE; + let sliderArtificialOffset = (MINIMUM_SLIDER_SIZE - computedSliderSize) / 2; + computedSliderSize = MINIMUM_SLIDER_SIZE; - this._computedSliderPosition -= sliderArtificialOffset; + computedSliderPosition -= sliderArtificialOffset; - if (this._computedSliderPosition + this._computedSliderSize > this._computedRepresentableSize) { + if (computedSliderPosition + computedSliderSize > computedRepresentableSize) { // Slider is too close to the bottom, so we glue it to the bottom - this._computedSliderPosition = this._computedRepresentableSize - this._computedSliderSize; + computedSliderPosition = computedRepresentableSize - computedSliderSize; } - if (this._computedSliderPosition < 0) { + if (computedSliderPosition < 0) { // Slider is too close to the top, so we glue it to the top - this._computedSliderPosition = 0; + computedSliderPosition = 0; } } } + + this._computedAvailableSize = Math.round(computedAvailableSize); + this._computedRepresentableSize = Math.round(computedRepresentableSize); + this._computedRatio = computedRatio; + this._computedIsNeeded = computedIsNeeded; + this._computedSliderSize = Math.round(computedSliderSize); + this._computedSliderPosition = Math.round(computedSliderPosition); } public getArrowSize(): number { diff --git a/src/vs/base/browser/ui/scrollbar/domNodeScrollable.ts b/src/vs/base/browser/ui/scrollbar/domNodeScrollable.ts index 0c9434725d7..a9bf83b2415 100644 --- a/src/vs/base/browser/ui/scrollbar/domNodeScrollable.ts +++ b/src/vs/base/browser/ui/scrollbar/domNodeScrollable.ts @@ -7,20 +7,50 @@ import * as DomUtils from 'vs/base/browser/dom'; import {Gesture} from 'vs/base/browser/touch'; import {Disposable, IDisposable} from 'vs/base/common/lifecycle'; -import {IScrollable} from 'vs/base/common/scrollable'; +import {IScrollable, ScrollEvent} from 'vs/base/common/scrollable'; import {Emitter} from 'vs/base/common/event'; export class DomNodeScrollable extends Disposable implements IScrollable { private _domNode: HTMLElement; private _gestureHandler: Gesture; - private _onScroll = this._register(new Emitter()); + private _onScroll = this._register(new Emitter()); + + private _lastScrollTop:number; + private _lastScrollLeft:number; constructor(domNode: HTMLElement) { super(); this._domNode = domNode; this._gestureHandler = this._register(new Gesture(this._domNode)); - this._register(DomUtils.addDisposableListener(this._domNode, 'scroll', (e) => this._onScroll.fire(void 0))); + + this._lastScrollTop = this.getScrollTop(); + this._lastScrollLeft = this.getScrollLeft(); + + this._register(DomUtils.addDisposableListener(this._domNode, 'scroll', (e) => { + this._emitScrollEvent(); + })); + } + + public onContentsDimensions(): void { + this._emitScrollEvent(); + } + + private _emitScrollEvent(): void { + let vertical = (this._lastScrollTop !== this.getScrollTop()); + this._lastScrollTop = this.getScrollTop(); + + let horizontal = (this._lastScrollLeft !== this.getScrollLeft()); + this._lastScrollLeft = this.getScrollLeft(); + + this._onScroll.fire(new ScrollEvent( + this.getScrollTop(), + this.getScrollLeft(), + this.getScrollWidth(), + this.getScrollHeight(), + vertical, + horizontal + )); } public dispose() { @@ -52,7 +82,7 @@ export class DomNodeScrollable extends Disposable implements IScrollable { this._domNode.scrollTop = scrollTop; } - public addScrollListener(callback: () => void): IDisposable { + public addScrollListener(callback: (v:ScrollEvent) => void): IDisposable { return this._onScroll.event(callback); } } diff --git a/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts b/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts index 87ecc14c7a6..925f490d25a 100644 --- a/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts @@ -9,13 +9,13 @@ import {ARROW_IMG_SIZE, AbstractScrollbar, ScrollbarState, IMouseMoveEventData} import {IMouseEvent, StandardMouseWheelEvent} from 'vs/base/browser/mouseEvent'; import {IDomNodePosition} from 'vs/base/browser/dom'; import {IParent, IScrollableElementOptions, Visibility} from 'vs/base/browser/ui/scrollbar/scrollableElement'; -import {IScrollable} from 'vs/base/common/scrollable'; +import {DelegateScrollable} from 'vs/base/common/scrollable'; export class HorizontalScrollbar extends AbstractScrollbar { - private _scrollable: IScrollable; + private _scrollable: DelegateScrollable; - constructor(scrollable: IScrollable, parent: IParent, options: IScrollableElementOptions) { + constructor(scrollable: DelegateScrollable, parent: IParent, options: IScrollableElementOptions) { let s = new ScrollbarState( (options.horizontalHasArrows ? options.arrowSize : 0), (options.horizontal === Visibility.Hidden ? 0 : options.horizontalScrollbarSize), diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index dbc8c6c24e7..d5a59df1bd0 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {IScrollable} from 'vs/base/common/scrollable'; import {FastDomNode} from 'vs/base/browser/styleMutator'; export interface IScrollableElementCreationOptions { @@ -48,12 +47,6 @@ export interface IScrollableElementCreationOptions { */ arrowSize?: number; - /** - * The scrollable that will react to all the scrolling logic. - * If no scrollable is provided, a dom node scrollable will be created automatically. - */ - scrollable?: IScrollable; - /** * The dom node events should be bound to. * If no listenOnDomNode is provided, the dom node passed to the constructor will be used for event listening. @@ -145,11 +138,6 @@ export interface IScrollableElement { */ onElementDimensions(dimensions?: IDimensions): void; - /** - * Let the scrollable element know that the contained dom node's width / height might have changed. - */ - onElementInternalDimensions(): void; - /** * Dispose. */ @@ -230,7 +218,6 @@ export interface IScrollableElementOptions { flipAxes: boolean; mouseWheelScrollSensitivity: number; arrowSize: number; - scrollable: IScrollable; listenOnDomNode: HTMLElement; horizontal: Visibility; horizontalScrollbarSize: number; diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElementImpl.ts b/src/vs/base/browser/ui/scrollbar/scrollableElementImpl.ts index ee41efa64fe..11a6677df6e 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElementImpl.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElementImpl.ts @@ -9,7 +9,6 @@ import 'vs/css!./media/scrollbars'; import * as DomUtils from 'vs/base/browser/dom'; import * as Platform from 'vs/base/common/platform'; import {StandardMouseWheelEvent, IMouseEvent} from 'vs/base/browser/mouseEvent'; -import {DomNodeScrollable} from 'vs/base/browser/ui/scrollbar/domNodeScrollable'; import {HorizontalScrollbar} from 'vs/base/browser/ui/scrollbar/horizontalScrollbar'; import {VerticalScrollbar} from 'vs/base/browser/ui/scrollbar/verticalScrollbar'; import { @@ -17,7 +16,7 @@ import { IScrollableElement, IScrollableElementCreationOptions, IOverviewRulerLayoutInfo } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import {IDisposable, disposeAll} from 'vs/base/common/lifecycle'; -import {IScrollable} from 'vs/base/common/scrollable'; +import {IScrollable, DelegateScrollable} from 'vs/base/common/scrollable'; import {Widget} from 'vs/base/browser/ui/widget'; import {TimeoutTimer} from 'vs/base/common/async'; @@ -28,7 +27,7 @@ export class ScrollableElement extends Widget implements IScrollableElement { private _originalElement: HTMLElement; private _options: IScrollableElementOptions; - private _scrollable: IScrollable; + private _scrollable: DelegateScrollable; public verticalScrollbarWidth: number; public horizontalScrollbarHeight: number; private _verticalScrollbar: IScrollbar; @@ -43,24 +42,19 @@ export class ScrollableElement extends Widget implements IScrollableElement { private _mouseWheelToDispose: IDisposable[]; private _onElementDimensionsTimeout: TimeoutTimer; - private _onElementInternalDimensionsTimeout: TimeoutTimer; private _isDragging: boolean; private _mouseIsOver: boolean; private _dimensions: IDimensions; private _hideTimeout: TimeoutTimer; - constructor(element: HTMLElement, options: IScrollableElementCreationOptions, dimensions: IDimensions = null) { + constructor(element: HTMLElement, scrollable:IScrollable, options: IScrollableElementCreationOptions, dimensions: IDimensions = null) { super(); this._originalElement = element; this._originalElement.style.overflow = 'hidden'; this._options = this._createOptions(options); - if (this._options.scrollable) { - this._scrollable = this._options.scrollable; - } else { - this._scrollable = this._register(new DomNodeScrollable(this._originalElement)); - } + this._scrollable = this._register(new DelegateScrollable(scrollable, () => this._onScroll())); this.verticalScrollbarWidth = this._options.verticalScrollbarSize; this.horizontalScrollbarHeight = this._options.horizontalScrollbarSize; @@ -95,9 +89,6 @@ export class ScrollableElement extends Widget implements IScrollableElement { this._listenOnDomNode = this._options.listenOnDomNode || this._domNode; - - this._register(this._scrollable.addScrollListener(() => this._onScroll())); - this._mouseWheelToDispose = []; this._setListeningToMouseWheel(this._options.handleMouseWheel); @@ -105,13 +96,13 @@ export class ScrollableElement extends Widget implements IScrollableElement { this.onnonbubblingmouseout(this._listenOnDomNode, (e) => this._onMouseOut(e)); this._onElementDimensionsTimeout = this._register(new TimeoutTimer()); - this._onElementInternalDimensionsTimeout = this._register(new TimeoutTimer()); this._hideTimeout = this._register(new TimeoutTimer()); this._isDragging = false; this._mouseIsOver = false; this.onElementDimensions(dimensions, true); - this.onElementInternalDimensions(true); + this._horizontalScrollbar.onElementScrollSize(this._scrollable.getScrollWidth()); + this._verticalScrollbar.onElementScrollSize(this._scrollable.getScrollHeight()); } public dispose(): void { @@ -155,20 +146,6 @@ export class ScrollableElement extends Widget implements IScrollableElement { this._horizontalScrollbar.onElementSize(this._dimensions.width); } - public onElementInternalDimensions(synchronous: boolean = false): void { - if (synchronous) { - this._actualElementInternalDimensions(); - this._onElementInternalDimensionsTimeout.cancel(); - } else { - this._onElementInternalDimensionsTimeout.setIfNotSet(() => this._actualElementInternalDimensions(), 0); - } - } - - private _actualElementInternalDimensions(): void { - this._horizontalScrollbar.onElementScrollSize(this._scrollable.getScrollWidth()); - this._verticalScrollbar.onElementScrollSize(this._scrollable.getScrollHeight()); - } - public updateClassName(newClassName: string): void { this._options.className = newClassName; // Defaults are different on Macs @@ -282,6 +259,8 @@ export class ScrollableElement extends Widget implements IScrollableElement { let scrollWidth = this._scrollable.getScrollWidth(); let scrollLeft = this._scrollable.getScrollLeft(); + this._horizontalScrollbar.onElementScrollSize(scrollWidth); + this._verticalScrollbar.onElementScrollSize(scrollHeight); this._verticalScrollbar.onElementScrollPosition(scrollTop); this._horizontalScrollbar.onElementScrollPosition(scrollLeft); @@ -378,7 +357,6 @@ export class ScrollableElement extends Widget implements IScrollableElement { mouseWheelScrollSensitivity: ensureValue(options, 'mouseWheelScrollSensitivity', 1), arrowSize: ensureValue(options, 'arrowSize', 11), - scrollable: ensureValue(options, 'scrollable', null), listenOnDomNode: ensureValue(options, 'listenOnDomNode', null), horizontal: visibilityFromString(ensureValue(options, 'horizontal', 'auto')), diff --git a/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts b/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts index 006c1506de3..f383b5692ce 100644 --- a/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts @@ -9,13 +9,13 @@ import {ARROW_IMG_SIZE, AbstractScrollbar, ScrollbarState, IMouseMoveEventData} import {IMouseEvent, StandardMouseWheelEvent} from 'vs/base/browser/mouseEvent'; import {IDomNodePosition} from 'vs/base/browser/dom'; import {IParent, IScrollableElementOptions, Visibility} from 'vs/base/browser/ui/scrollbar/scrollableElement'; -import {IScrollable} from 'vs/base/common/scrollable'; +import {DelegateScrollable} from 'vs/base/common/scrollable'; export class VerticalScrollbar extends AbstractScrollbar { - private _scrollable: IScrollable; + private _scrollable: DelegateScrollable; - constructor(scrollable: IScrollable, parent: IParent, options: IScrollableElementOptions) { + constructor(scrollable: DelegateScrollable, parent: IParent, options: IScrollableElementOptions) { let s = new ScrollbarState( (options.verticalHasArrows ? options.arrowSize : 0), (options.vertical === Visibility.Hidden ? 0 : options.verticalScrollbarSize), diff --git a/src/vs/base/common/scrollable.ts b/src/vs/base/common/scrollable.ts index eae2097fc2c..0722f2af787 100644 --- a/src/vs/base/common/scrollable.ts +++ b/src/vs/base/common/scrollable.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {IDisposable} from 'vs/base/common/lifecycle'; +import {Disposable, IDisposable} from 'vs/base/common/lifecycle'; export interface IScrollable { getScrollHeight():number; @@ -13,5 +13,94 @@ export interface IScrollable { setScrollLeft(scrollLeft:number); getScrollTop():number; setScrollTop(scrollTop:number); - addScrollListener(callback:()=>void): IDisposable; + addScrollListener(callback:(newValues:ScrollEvent)=>void): IDisposable; +} + +export class ScrollEvent { + _scrollEventTrait: void; + + scrollTop: number; + scrollLeft: number; + scrollWidth: number; + scrollHeight: number; + vertical: boolean; + horizontal: boolean; + + constructor(scrollTop:number, scrollLeft:number, scrollWidth:number, scrollHeight:number, vertical:boolean, horizontal:boolean) { + this.scrollTop = Math.round(scrollTop); + this.scrollLeft = Math.round(scrollLeft); + this.scrollWidth = Math.round(scrollWidth); + this.scrollHeight = Math.round(scrollHeight); + this.vertical = Boolean(vertical); + this.horizontal = Boolean(horizontal); + } +} + +export class ScrollableValues { + _scrollableValuesTrait: void; + + scrollTop: number; + scrollLeft: number; + scrollWidth: number; + scrollHeight: number; + + constructor(scrollTop:number, scrollLeft:number, scrollWidth:number, scrollHeight:number) { + this.scrollTop = Math.round(scrollTop); + this.scrollLeft = Math.round(scrollLeft); + this.scrollWidth = Math.round(scrollWidth); + this.scrollHeight = Math.round(scrollHeight); + } + + public equals(other:ScrollEvent): boolean { + return ( + this.scrollTop === other.scrollTop + && this.scrollLeft === other.scrollLeft + && this.scrollWidth === other.scrollWidth + && this.scrollHeight === other.scrollHeight + ); + } +} + +export class DelegateScrollable extends Disposable { + + private _actual:IScrollable; + private _onChange:()=>void; + + private _values: ScrollableValues; + + constructor(actual:IScrollable, onChange:()=>void) { + super(); + this._actual = actual; + this._onChange = onChange; + + this._values = new ScrollableValues(this._actual.getScrollTop(), this._actual.getScrollLeft(), this._actual.getScrollWidth(), this._actual.getScrollHeight()); + this._register(this._actual.addScrollListener((newValues) => this._update(newValues))); + } + + public dispose(): void { + super.dispose(); + } + + private _update(e:ScrollEvent): void { + if (this._values.equals(e)) { + return; + } + + this._values = new ScrollableValues(e.scrollTop, e.scrollLeft, e.scrollWidth, e.scrollHeight); + + this._onChange(); + } + + public getScrollTop():number { return this._values.scrollTop; } + public getScrollLeft():number { return this._values.scrollLeft; } + public getScrollWidth():number { return this._values.scrollWidth; } + public getScrollHeight():number { return this._values.scrollHeight; } + + public setScrollTop(scrollTop:number): void { + this._actual.setScrollTop(scrollTop); + } + + public setScrollLeft(scrollLeft:number): void { + this._actual.setScrollLeft(scrollLeft); + } } diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts index 96b162af22a..ad6527a9238 100644 --- a/src/vs/base/parts/tree/browser/treeView.ts +++ b/src/vs/base/parts/tree/browser/treeView.ts @@ -21,7 +21,7 @@ import ScrollableElementImpl = require('vs/base/browser/ui/scrollbar/scrollableE import { HeightMap } from 'vs/base/parts/tree/browser/treeViewModel'; import _ = require('vs/base/parts/tree/browser/tree'); import { IViewItem } from 'vs/base/parts/tree/browser/treeViewModel'; -import {IScrollable} from 'vs/base/common/scrollable'; +import {IScrollable, ScrollEvent} from 'vs/base/common/scrollable'; import {KeyCode} from 'vs/base/common/keyCodes'; export interface IRow { @@ -489,9 +489,8 @@ export class TreeView extends HeightMap implements IScrollable { this.wrapper = document.createElement('div'); this.wrapper.className = 'monaco-tree-wrapper'; - this.scrollableElement = new ScrollableElementImpl.ScrollableElement(this.wrapper, { + this.scrollableElement = new ScrollableElementImpl.ScrollableElement(this.wrapper, this, { forbidTranslate3dUse: true, - scrollable: this, horizontal: 'hidden', vertical: context.options.verticalScrollMode || 'auto', useShadows: context.options.useShadows, @@ -597,7 +596,7 @@ export class TreeView extends HeightMap implements IScrollable { this.scrollTop = this.onHiddenScrollTop; this.onHiddenScrollTop = null; this.scrollableElement.onElementDimensions(); - this.scrollableElement.onElementInternalDimensions(); + this._emitScrollEvent(false, false); this.setupMSGesture(); } @@ -625,7 +624,7 @@ export class TreeView extends HeightMap implements IScrollable { this.scrollTop = this.scrollTop; // render this.scrollableElement.onElementDimensions(); - this.scrollableElement.onElementInternalDimensions(); + this._emitScrollEvent(false, false); } private render(scrollTop: number, viewHeight: number): void { @@ -750,7 +749,7 @@ export class TreeView extends HeightMap implements IScrollable { } this.scrollTop = scrollTop; - this.scrollableElement.onElementInternalDimensions(); + this._emitScrollEvent(false, false); } public focusNextPage(eventPayload?:any): void { @@ -849,10 +848,21 @@ export class TreeView extends HeightMap implements IScrollable { this.render(scrollTop, this.viewHeight); this._scrollTop = scrollTop; - this.emit('scroll', { vertical: true, horizontal: false }); + this._emitScrollEvent(true, false); } - public addScrollListener(callback:()=>void): Lifecycle.IDisposable { + private _emitScrollEvent(vertical:boolean, horizontal:boolean): void { + this.emit('scroll', new ScrollEvent( + this.getScrollTop(), + this.getScrollLeft(), + this.getScrollWidth(), + this.getScrollHeight(), + vertical, + horizontal + )); + } + + public addScrollListener(callback:(v:ScrollEvent)=>void): Lifecycle.IDisposable { return this.addListener2('scroll', callback); } diff --git a/src/vs/editor/browser/view/viewLayer.ts b/src/vs/editor/browser/view/viewLayer.ts index caa499b9e18..fae22751d0d 100644 --- a/src/vs/editor/browser/view/viewLayer.ts +++ b/src/vs/editor/browser/view/viewLayer.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import * as dom from 'vs/base/browser/dom'; import * as editorCommon from 'vs/editor/common/editorCommon'; import {IViewContext} from 'vs/editor/browser/editorBrowser'; import {ViewPart} from 'vs/editor/browser/view/viewPart'; @@ -47,6 +46,8 @@ export class ViewLayer extends ViewPart { _rendLineNumberStart:number; private _renderer: ViewLayerRenderer; + private _scrollDomNode: HTMLElement; + private _scrollDomNodeIsAbove: boolean; constructor(context:IViewContext) { super(context); @@ -56,6 +57,9 @@ export class ViewLayer extends ViewPart { this._lines = []; this._rendLineNumberStart = 1; + this._scrollDomNode = null; + this._scrollDomNodeIsAbove = false; + this._renderer = new ViewLayerRenderer( () => this._createLine(), () => this._extraDomNodeHTML() @@ -95,7 +99,8 @@ export class ViewLayer extends ViewPart { public onModelFlushed(): boolean { this._lines = []; this._rendLineNumberStart = 1; - dom.clearNode(this.domNode); + this._scrollDomNode = null; + // No need to clear the dom node because a full .innerHTML will occur in ViewLayerRenderer._render return true; } @@ -233,8 +238,6 @@ export class ViewLayer extends ViewPart { // ---- end view event handlers - private _scrollDomNode: HTMLElement = null; - private _scrollDomNodeIsAbove: boolean = false; public _renderLines(linesViewportData:editorCommon.IViewLinesViewportData): void { var ctx: IRendererContext = { diff --git a/src/vs/editor/browser/viewLayout/scrollManager.ts b/src/vs/editor/browser/viewLayout/scrollManager.ts index 829462394ac..ce2e4aa986d 100644 --- a/src/vs/editor/browser/viewLayout/scrollManager.ts +++ b/src/vs/editor/browser/viewLayout/scrollManager.ts @@ -42,7 +42,6 @@ export class ScrollManager implements IDisposable { var configScrollbarOpts = this.configuration.editor.scrollbar; var scrollbarOptions:IScrollableElementCreationOptions = { - scrollable: this.scrollable, listenOnDomNode: viewDomNode, vertical: configScrollbarOpts.vertical, horizontal: configScrollbarOpts.horizontal, @@ -61,15 +60,12 @@ export class ScrollManager implements IDisposable { addPropertyIfPresent(configScrollbarOpts, scrollbarOptions, 'mouseWheelScrollSensitivity'); - this.scrollbar = new ScrollableElement(linesContent, scrollbarOptions, { + this.scrollbar = new ScrollableElement(linesContent, this.scrollable, scrollbarOptions, { width: this.configuration.editor.layoutInfo.contentWidth, height: this.configuration.editor.layoutInfo.contentHeight, }); this.toDispose.push(this.scrollbar); - this.toDispose.push(this.scrollable.addInternalSizeChangeListener(() => { - this.scrollbar.onElementInternalDimensions(); - })); this.toDispose.push(this.configuration.onDidChange((e:IConfigurationChangedEvent) => { this.scrollbar.updateClassName(this.configuration.editor.theme); if (e.scrollbar) { diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index 599b56a1789..0aa6adb78a4 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -85,6 +85,9 @@ export class ViewLines extends ViewLayer { private _lastCursorRevealRangeHorizontallyEvent:editorCommon.IViewRevealRangeEvent; private _lastRenderedData: LastRenderedData; + private _hasVerticalScroll:boolean; + private _hasHorizontalScroll:boolean; + constructor(context:IViewContext, layoutProvider:ILayoutProvider) { super(context); this._lineHeight = this._context.configuration.editor.lineHeight; @@ -139,6 +142,7 @@ export class ViewLines extends ViewLayer { public onLayoutChanged(layoutInfo:editorCommon.IEditorLayoutInfo): boolean { var shouldRender = super.onLayoutChanged(layoutInfo); this._maxLineWidth = 0; + this._lastRenderedData.resetDomNodeClientRectLeft(); return shouldRender; } @@ -180,8 +184,6 @@ export class ViewLines extends ViewLayer { return true; } - private _hasVerticalScroll = false; - private _hasHorizontalScroll = false; public onScrollChanged(e:editorCommon.IScrollEvent): boolean { this._hasVerticalScroll = this._hasVerticalScroll || e.vertical; this._hasHorizontalScroll = this._hasHorizontalScroll || e.horizontal; @@ -366,8 +368,6 @@ export class ViewLines extends ViewLayer { this._context.model.getLineMaxColumn(this._lines.length - 1 + this._rendLineNumberStart) )); - this._lastRenderedData.resetDomNodeClientRectLeft(); - if (this._lastCursorRevealRangeHorizontallyEvent) { var newScrollLeft = this._computeScrollLeftToRevealRange(this._lastCursorRevealRangeHorizontallyEvent.range); this._lastCursorRevealRangeHorizontallyEvent = null; @@ -436,8 +436,9 @@ export class ViewLines extends ViewLayer { // --- width private _ensureMaxLineWidth(lineWidth: number): void { - if (this._maxLineWidth < lineWidth) { - this._maxLineWidth = lineWidth; + let iLineWidth = Math.ceil(lineWidth); + if (this._maxLineWidth < iLineWidth) { + this._maxLineWidth = iLineWidth; this._layoutProvider.onMaxLineWidthChanged(this._maxLineWidth); } } diff --git a/src/vs/editor/common/viewLayout/editorScrollable.ts b/src/vs/editor/common/viewLayout/editorScrollable.ts index 8af7f9204b6..88bbbb86c5c 100644 --- a/src/vs/editor/common/viewLayout/editorScrollable.ts +++ b/src/vs/editor/common/viewLayout/editorScrollable.ts @@ -6,7 +6,7 @@ import {EventEmitter} from 'vs/base/common/eventEmitter'; import {IDisposable} from 'vs/base/common/lifecycle'; -import {IScrollable} from 'vs/base/common/scrollable'; +import {IScrollable, ScrollEvent} from 'vs/base/common/scrollable'; import {IScrollEvent} from 'vs/editor/common/editorCommon'; export class EditorScrollable extends EventEmitter implements IScrollable { @@ -20,8 +20,7 @@ export class EditorScrollable extends EventEmitter implements IScrollable { constructor() { super([ - EditorScrollable._SCROLL_EVENT, - EditorScrollable._INTERNAL_SIZE_CHANGED_EVENT + EditorScrollable._SCROLL_EVENT ]); this.scrollTop = 0; @@ -75,7 +74,7 @@ export class EditorScrollable extends EventEmitter implements IScrollable { // Revalidate this.setScrollLeft(this.scrollLeft); - this._emitInternalSizeEvent(); + this._emitScrollEvent(false, false); } } @@ -140,7 +139,7 @@ export class EditorScrollable extends EventEmitter implements IScrollable { // Revalidate this.setScrollTop(this.scrollTop); - this._emitInternalSizeEvent(); + this._emitScrollEvent(false, false); } } @@ -170,24 +169,17 @@ export class EditorScrollable extends EventEmitter implements IScrollable { static _SCROLL_EVENT = 'scroll'; private _emitScrollEvent(vertical:boolean, horizontal:boolean): void { - var e:IScrollEvent = { - vertical: vertical, - horizontal: horizontal, - scrollTop: this.scrollTop, - scrollLeft: this.scrollLeft - }; + var e:IScrollEvent = new ScrollEvent( + this.getScrollTop(), + this.getScrollLeft(), + this.getScrollWidth(), + this.getScrollHeight(), + vertical, + horizontal + ); this.emit(EditorScrollable._SCROLL_EVENT, e); } - public addScrollListener(listener: (e:IScrollEvent) => void): IDisposable { + public addScrollListener(listener: (e:ScrollEvent) => void): IDisposable { return this.addListener2(EditorScrollable._SCROLL_EVENT, listener); } - - - static _INTERNAL_SIZE_CHANGED_EVENT = 'internalSizeChanged'; - private _emitInternalSizeEvent(): void { - this.emit(EditorScrollable._INTERNAL_SIZE_CHANGED_EVENT); - } - public addInternalSizeChangeListener(listener:()=>void): IDisposable { - return this.addListener2(EditorScrollable._INTERNAL_SIZE_CHANGED_EVENT, listener); - } -} \ No newline at end of file +} diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index 46fac952e99..dbba76589e5 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -27,6 +27,7 @@ import {CONTEXT_SUGGESTION_SUPPORTS_ACCEPT_ON_KEY, SuggestRegistry} from '../com import {CompletionItem, CompletionModel} from './completionModel'; import {ICancelEvent, ISuggestEvent, ITriggerEvent, SuggestModel} from './suggestModel'; import {alert} from 'vs/base/browser/ui/aria/aria'; +import {DomNodeScrollable} from 'vs/base/browser/ui/scrollbar/domNodeScrollable'; interface ISuggestionTemplateData { root: HTMLElement; @@ -180,7 +181,8 @@ class SuggestionDetails { private el: HTMLElement; private title: HTMLElement; private back: HTMLElement; - private scrollable: ScrollableElement; + private scrollable: DomNodeScrollable; + private scrollbar: ScrollableElement; private body: HTMLElement; private type: HTMLElement; private docs: HTMLElement; @@ -193,8 +195,9 @@ class SuggestionDetails { this.back = append(header, $('span.go-back.octicon.octicon-mail-reply')); this.back.title = nls.localize('goback', "Go back"); this.body = $('.body'); - this.scrollable = new ScrollableElement(this.body, {}); - append(this.el, this.scrollable.getDomNode()); + this.scrollable = new DomNodeScrollable(this.body); + this.scrollbar = new ScrollableElement(this.body, this.scrollable, {}); + append(this.el, this.scrollbar.getDomNode()); this.type = append(this.body, $('p.type')); this.docs = append(this.body, $('p.docs')); @@ -227,8 +230,8 @@ class SuggestionDetails { this.widget.toggleDetails(); }; - this.scrollable.onElementDimensions(); - this.scrollable.onElementInternalDimensions(); + this.scrollbar.onElementDimensions(); + this.scrollable.onContentsDimensions(); this.ariaLabel = strings.format('{0}\n{1}\n{2}', item.suggestion.label || '', item.suggestion.typeLabel || '', item.suggestion.documentationLabel || ''); } @@ -254,6 +257,9 @@ class SuggestionDetails { } dispose(): void { + this.scrollbar.dispose(); + this.scrollable.dispose(); + this.el.parentElement.removeChild(this.el); this.el = null; } diff --git a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts index 320352200a5..4d4c2a98a5a 100644 --- a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts @@ -20,6 +20,7 @@ import {BinaryEditorModel} from 'vs/workbench/common/editor/binaryEditorModel'; import {DiffEditorModel} from 'vs/workbench/common/editor/diffEditorModel'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; +import {DomNodeScrollable} from 'vs/base/browser/ui/scrollbar/domNodeScrollable'; /** * An implementation of editor for diffing binary files like images or videos. @@ -31,8 +32,10 @@ export class BinaryResourceDiffEditor extends BaseEditor implements IVerticalSas private static MIN_CONTAINER_WIDTH = 100; private leftBinaryContainer: Builder; + private leftScrollable: DomNodeScrollable; private leftScrollbar: IScrollableElement; private rightBinaryContainer: Builder; + private rightScrollable: DomNodeScrollable; private rightScrollbar: IScrollableElement; private sash: Sash; private dimension: Dimension; @@ -59,7 +62,8 @@ export class BinaryResourceDiffEditor extends BaseEditor implements IVerticalSas this.leftBinaryContainer.tabindex(0); // enable focus support from the editor part (do not remove) // Left Custom Scrollbars - this.leftScrollbar = new ScrollableElement(leftBinaryContainerElement, { horizontal: 'hidden', vertical: 'hidden' }); + this.leftScrollable = new DomNodeScrollable(leftBinaryContainerElement); + this.leftScrollbar = new ScrollableElement(leftBinaryContainerElement, this.leftScrollable, { horizontal: 'hidden', vertical: 'hidden' }); parent.getHTMLElement().appendChild(this.leftScrollbar.getDomNode()); $(this.leftScrollbar.getDomNode()).addClass('binarydiff-left'); @@ -76,7 +80,8 @@ export class BinaryResourceDiffEditor extends BaseEditor implements IVerticalSas this.rightBinaryContainer.tabindex(0); // enable focus support from the editor part (do not remove) // Right Custom Scrollbars - this.rightScrollbar = new ScrollableElement(rightBinaryContainerElement, { horizontal: 'hidden', vertical: 'hidden' }); + this.rightScrollable = new DomNodeScrollable(rightBinaryContainerElement); + this.rightScrollbar = new ScrollableElement(rightBinaryContainerElement, this.rightScrollable, { horizontal: 'hidden', vertical: 'hidden' }); parent.getHTMLElement().appendChild(this.rightScrollbar.getDomNode()); $(this.rightScrollbar.getDomNode()).addClass('binarydiff-right'); } @@ -127,9 +132,9 @@ export class BinaryResourceDiffEditor extends BaseEditor implements IVerticalSas // Pass to ResourceViewer let container = isOriginal ? this.leftBinaryContainer : this.rightBinaryContainer; - let scrollbar = isOriginal ? this.leftScrollbar : this.rightScrollbar; + let scrollable = isOriginal ? this.leftScrollable : this.rightScrollable; - ResourceViewer.show(name, resource, container, scrollbar); + ResourceViewer.show(name, resource, container, scrollable); } public clearInput(): void { @@ -165,12 +170,12 @@ export class BinaryResourceDiffEditor extends BaseEditor implements IVerticalSas // Size left container this.leftBinaryContainer.size(this.leftContainerWidth, this.dimension.height); this.leftScrollbar.onElementDimensions(); - this.leftScrollbar.onElementInternalDimensions(); + this.leftScrollable.onContentsDimensions(); // Size right container this.rightBinaryContainer.size(this.dimension.width - this.leftContainerWidth, this.dimension.height); this.rightScrollbar.onElementDimensions(); - this.rightScrollbar.onElementInternalDimensions(); + this.rightScrollable.onContentsDimensions(); } private onSashDragStart(): void { @@ -217,7 +222,9 @@ export class BinaryResourceDiffEditor extends BaseEditor implements IVerticalSas // Dispose Scrollbar this.leftScrollbar.dispose(); + this.leftScrollable.dispose(); this.rightScrollbar.dispose(); + this.rightScrollable.dispose(); // Destroy Container this.leftBinaryContainer.destroy(); -- GitLab