提交 90377a73 编写于 作者: A Alex Dima

* separate scroll dimensions and scroll positions

* have explicit methods for changing the scroll position (delayed/smooth vs now/direct) that bubble up to the editor internals.
* have all other clients of ScrollableElement not use smooth scrolling
上级 5d91fc88
......@@ -124,7 +124,7 @@ export class ListView<T> implements IDisposable {
const scrollHeight = this.getContentHeight();
this.rowsContainer.style.height = `${scrollHeight}px`;
this.scrollableElement.updateState({ scrollHeight });
this.scrollableElement.setScrollDimensions({ scrollHeight });
return deleted.map(i => i.element);
}
......@@ -134,8 +134,8 @@ export class ListView<T> implements IDisposable {
}
get renderHeight(): number {
const scrollState = this.scrollableElement.getScrollState();
return scrollState.height;
const scrollDimensions = this.scrollableElement.getScrollDimensions();
return scrollDimensions.height;
}
element(index: number): T {
......@@ -164,7 +164,7 @@ export class ListView<T> implements IDisposable {
}
layout(height?: number): void {
this.scrollableElement.updateState({
this.scrollableElement.setScrollDimensions({
height: height || DOM.getContentHeight(this._domNode)
});
}
......@@ -221,12 +221,12 @@ export class ListView<T> implements IDisposable {
}
getScrollTop(): number {
const scrollState = this.scrollableElement.getScrollState();
return scrollState.scrollTop;
const scrollPosition = this.scrollableElement.getScrollPosition();
return scrollPosition.scrollTop;
}
setScrollTop(scrollTop: number): void {
this.scrollableElement.updateState({ scrollTop });
this.scrollableElement.setScrollPosition({ scrollTop });
}
get scrollTop(): number {
......
......@@ -13,7 +13,7 @@ import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState';
import { ScrollbarArrow, ScrollbarArrowOptions } from 'vs/base/browser/ui/scrollbar/scrollbarArrow';
import { ScrollbarVisibilityController } from 'vs/base/browser/ui/scrollbar/scrollbarVisibilityController';
import { Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
import { Scrollable, ScrollbarVisibility, INewScrollPosition } from 'vs/base/common/scrollable';
/**
* The orthogonal distance to the slider at which dragging "resets". This implements "snapping"
......@@ -193,7 +193,7 @@ export abstract class AbstractScrollbar extends Widget {
private _onMouseDown(e: IMouseEvent): void {
let domNodePosition = DomUtils.getDomNodePagePosition(this.domNode.domNode);
this.setDesiredScrollPosition(this._scrollbarState.getDesiredScrollPositionFromOffset(this._mouseDownRelativePosition(e, domNodePosition)), 0/* immediate */);
this._setDesiredScrollPositionNow(this._scrollbarState.getDesiredScrollPositionFromOffset(this._mouseDownRelativePosition(e, domNodePosition)));
if (e.leftButton) {
e.preventDefault();
this._sliderMouseDown(e, () => { /*nothing to do*/ });
......@@ -214,13 +214,13 @@ export abstract class AbstractScrollbar extends Widget {
if (Platform.isWindows && mouseOrthogonalDelta > MOUSE_DRAG_RESET_DISTANCE) {
// The mouse has wondered away from the scrollbar => reset dragging
this.setDesiredScrollPosition(initialScrollbarState.getScrollPosition(), 0/* immediate */);
this._setDesiredScrollPositionNow(initialScrollbarState.getScrollPosition());
return;
}
const mousePosition = this._sliderMousePosition(mouseMoveData);
const mouseDelta = mousePosition - initialMousePosition;
this.setDesiredScrollPosition(initialScrollbarState.getDesiredScrollPositionFromDelta(mouseDelta), 0/* immediate */);
this._setDesiredScrollPositionNow(initialScrollbarState.getDesiredScrollPositionFromDelta(mouseDelta));
},
() => {
this.slider.toggleClassName('active', false);
......@@ -232,18 +232,12 @@ export abstract class AbstractScrollbar extends Widget {
this._host.onDragStart();
}
public setDesiredScrollPosition(desiredScrollPosition: number, smoothScrollDuration: number): boolean {
desiredScrollPosition = this.validateScrollPosition(desiredScrollPosition);
private _setDesiredScrollPositionNow(_desiredScrollPosition: number): void {
let oldScrollPosition = this._getScrollPosition();
this._setScrollPosition(desiredScrollPosition, smoothScrollDuration);
let newScrollPosition = this._getScrollPosition();
let desiredScrollPosition: INewScrollPosition = {};
this.writeScrollPosition(desiredScrollPosition, _desiredScrollPosition);
if (oldScrollPosition !== newScrollPosition) {
this._onElementScrollPosition(this._getScrollPosition());
return true;
}
return false;
this._scrollable.setScrollPositionNow(desiredScrollPosition);
}
// ----------------- Overwrite these
......@@ -255,7 +249,5 @@ export abstract class AbstractScrollbar extends Widget {
protected abstract _sliderMousePosition(e: ISimplifiedMouseEvent): number;
protected abstract _sliderOrthogonalMousePosition(e: ISimplifiedMouseEvent): number;
protected abstract _getScrollPosition(): number;
protected abstract _setScrollPosition(elementScrollPosition: number, smoothScrollDuration: number): void;
public abstract validateScrollPosition(desiredScrollPosition: number): number;
public abstract writeScrollPosition(target: INewScrollPosition, scrollPosition: number): void;
}
......@@ -8,7 +8,7 @@ import { AbstractScrollbar, ScrollbarHost, ISimplifiedMouseEvent } from 'vs/base
import { StandardMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { IDomNodePagePosition } from 'vs/base/browser/dom';
import { ScrollableElementResolvedOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
import { Scrollable, ScrollEvent, ScrollbarVisibility } from 'vs/base/common/scrollable';
import { Scrollable, ScrollEvent, ScrollbarVisibility, INewScrollPosition } from 'vs/base/common/scrollable';
import { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState';
import { ARROW_IMG_SIZE } from 'vs/base/browser/ui/scrollbar/scrollbarArrow';
......@@ -89,18 +89,7 @@ export class HorizontalScrollbar extends AbstractScrollbar {
return e.posy;
}
protected _getScrollPosition(): number {
const scrollState = this._scrollable.getState();
return scrollState.scrollLeft;
}
protected _setScrollPosition(scrollPosition: number, smoothScrollDuration: number) {
this._scrollable.updateState({
scrollLeft: scrollPosition
}, smoothScrollDuration);
}
public validateScrollPosition(desiredScrollPosition: number): number {
return this._scrollable.validateScrollLeft(desiredScrollPosition);
public writeScrollPosition(target: INewScrollPosition, scrollPosition: number): void {
target.scrollLeft = scrollPosition;
}
}
......@@ -13,7 +13,7 @@ import { HorizontalScrollbar } from 'vs/base/browser/ui/scrollbar/horizontalScro
import { VerticalScrollbar } from 'vs/base/browser/ui/scrollbar/verticalScrollbar';
import { ScrollableElementCreationOptions, ScrollableElementChangeOptions, ScrollableElementResolvedOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Scrollable, ScrollState, ScrollEvent, INewScrollState, ScrollbarVisibility } from 'vs/base/common/scrollable';
import { Scrollable, ScrollEvent, ScrollbarVisibility, INewScrollDimensions, IScrollDimensions, INewScrollPosition, IScrollPosition } from 'vs/base/common/scrollable';
import { Widget } from 'vs/base/browser/ui/widget';
import { TimeoutTimer } from 'vs/base/common/async';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
......@@ -29,41 +29,36 @@ export interface IOverviewRulerLayoutInfo {
insertBefore: HTMLElement;
}
export class ScrollableElement extends Widget {
export abstract class AbstractScrollableElement extends Widget {
private _options: ScrollableElementResolvedOptions;
private _scrollable: Scrollable;
private _verticalScrollbar: VerticalScrollbar;
private _horizontalScrollbar: HorizontalScrollbar;
private _domNode: HTMLElement;
private readonly _options: ScrollableElementResolvedOptions;
protected readonly _scrollable: Scrollable;
private readonly _verticalScrollbar: VerticalScrollbar;
private readonly _horizontalScrollbar: HorizontalScrollbar;
private readonly _domNode: HTMLElement;
private _leftShadowDomNode: FastDomNode<HTMLElement>;
private _topShadowDomNode: FastDomNode<HTMLElement>;
private _topLeftShadowDomNode: FastDomNode<HTMLElement>;
private readonly _leftShadowDomNode: FastDomNode<HTMLElement>;
private readonly _topShadowDomNode: FastDomNode<HTMLElement>;
private readonly _topLeftShadowDomNode: FastDomNode<HTMLElement>;
private _listenOnDomNode: HTMLElement;
private readonly _listenOnDomNode: HTMLElement;
private _mouseWheelToDispose: IDisposable[];
private _isDragging: boolean;
private _mouseIsOver: boolean;
private _hideTimeout: TimeoutTimer;
private readonly _hideTimeout: TimeoutTimer;
private _shouldRender: boolean;
private _onScroll = this._register(new Emitter<ScrollEvent>());
private readonly _onScroll = this._register(new Emitter<ScrollEvent>());
public onScroll: Event<ScrollEvent> = this._onScroll.event;
constructor(element: HTMLElement, options: ScrollableElementCreationOptions, scrollable?: Scrollable) {
protected constructor(element: HTMLElement, options: ScrollableElementCreationOptions, scrollable?: Scrollable) {
super();
element.style.overflow = 'hidden';
this._options = resolveOptions(options);
if (typeof scrollable === 'undefined') {
this._scrollable = this._register(new Scrollable());
} else {
this._scrollable = scrollable;
}
this._scrollable = scrollable;
this._register(this._scrollable.onScroll((e) => {
this._onDidScroll(e);
......@@ -153,12 +148,12 @@ export class ScrollableElement extends Widget {
this._verticalScrollbar.delegateSliderMouseDown(e, onDragFinished);
}
public updateState(newState: INewScrollState): void {
this._scrollable.updateState(newState, 0/* immediate */);
public getScrollDimensions(): IScrollDimensions {
return this._scrollable.getScrollDimensions();
}
public getScrollState(): ScrollState {
return this._scrollable.getState();
public setScrollDimensions(dimensions: INewScrollDimensions): void {
this._scrollable.setScrollDimensions(dimensions);
}
/**
......@@ -215,8 +210,6 @@ export class ScrollableElement extends Widget {
}
private _onMouseWheel(e: StandardMouseWheelEvent): void {
let desiredScrollTop = -1;
let desiredScrollLeft = -1;
if (e.deltaY || e.deltaX) {
let deltaY = e.deltaY * this._options.mouseWheelScrollSensitivity;
......@@ -244,37 +237,39 @@ export class ScrollableElement extends Widget {
}
}
const scrollState = this._scrollable.getSmoothScrollTargetState();
const futureScrollPosition = this._scrollable.getFutureScrollPosition();
let desiredScrollPosition: INewScrollPosition = {};
if (deltaY) {
let currentScrollTop = scrollState.scrollTop;
desiredScrollTop = this._verticalScrollbar.validateScrollPosition((desiredScrollTop !== -1 ? desiredScrollTop : currentScrollTop) - SCROLL_WHEEL_SENSITIVITY * deltaY);
if (desiredScrollTop === currentScrollTop) {
desiredScrollTop = -1;
}
const desiredScrollTop = futureScrollPosition.scrollTop - SCROLL_WHEEL_SENSITIVITY * deltaY;
this._verticalScrollbar.writeScrollPosition(desiredScrollPosition, desiredScrollTop);
}
if (deltaX) {
let currentScrollLeft = scrollState.scrollLeft;
desiredScrollLeft = this._horizontalScrollbar.validateScrollPosition((desiredScrollLeft !== -1 ? desiredScrollLeft : currentScrollLeft) - SCROLL_WHEEL_SENSITIVITY * deltaX);
if (desiredScrollLeft === currentScrollLeft) {
desiredScrollLeft = -1;
}
const desiredScrollLeft = futureScrollPosition.scrollLeft - SCROLL_WHEEL_SENSITIVITY * deltaX;
this._horizontalScrollbar.writeScrollPosition(desiredScrollPosition, desiredScrollLeft);
}
if (desiredScrollTop !== -1 || desiredScrollLeft !== -1) {
if (desiredScrollTop !== -1) {
// If |∆y| is too small then do not apply smooth scroll animation, because in that case the input source must be a touchpad or something similar.
const applySmoothScroll = this._options.mouseWheelSmoothScroll && Math.abs(deltaY) > SCROLL_WHEEL_SMOOTH_SCROLL_THRESHOLD;
const shouldRender = this._verticalScrollbar.setDesiredScrollPosition(desiredScrollTop, applySmoothScroll ? this._options.mouseWheelSmoothScrollDuration : 0/* immediate */);
this._shouldRender = shouldRender || this._shouldRender;
desiredScrollTop = -1;
}
if (desiredScrollLeft !== -1) {
// If |∆x| is too small then do not apply smooth scroll animation, because in that case the input source must be a touchpad or something similar.
const applySmoothScroll = this._options.mouseWheelSmoothScroll && Math.abs(deltaX) > SCROLL_WHEEL_SMOOTH_SCROLL_THRESHOLD;
const shouldRender = this._horizontalScrollbar.setDesiredScrollPosition(desiredScrollLeft, applySmoothScroll ? this._options.mouseWheelSmoothScrollDuration : 0/* immediate */);
this._shouldRender = shouldRender || this._shouldRender;
desiredScrollLeft = -1;
// 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) {
// TODO@smooth: [MUST] implement better heuristic for distinguishing inertia scrolling
// from physical mouse wheels
const ENABLE_MOUSE_WHEEL_SMOOTH = false;
// If |∆x| and |∆y| are too small then do not apply smooth scroll animation, because in that case the input source must be a touchpad or something similar.
const smoothScrollThresholdReached = (
Math.abs(deltaY) > SCROLL_WHEEL_SMOOTH_SCROLL_THRESHOLD
|| Math.abs(deltaX) > SCROLL_WHEEL_SMOOTH_SCROLL_THRESHOLD
);
if (ENABLE_MOUSE_WHEEL_SMOOTH && this._options.mouseWheelSmoothScroll && smoothScrollThresholdReached) {
this._scrollable.setScrollPositionSmooth(desiredScrollPosition);
} else {
this._scrollable.setScrollPositionNow(desiredScrollPosition);
}
this._shouldRender = true;
}
}
......@@ -322,7 +317,7 @@ export class ScrollableElement extends Widget {
this._verticalScrollbar.render();
if (this._options.useShadows) {
const scrollState = this._scrollable.getState();
const scrollState = this._scrollable.getCurrentScrollPosition();
let enableTop = scrollState.scrollTop > 0;
let enableLeft = scrollState.scrollLeft > 0;
......@@ -374,6 +369,33 @@ export class ScrollableElement extends Widget {
}
}
export class ScrollableElement extends AbstractScrollableElement {
constructor(element: HTMLElement, options: ScrollableElementCreationOptions) {
options = options || {};
options.mouseWheelSmoothScroll = false;
const scrollable = new Scrollable(0);
super(element, options, scrollable);
this._register(scrollable);
}
public setScrollPosition(update: INewScrollPosition): void {
this._scrollable.setScrollPositionNow(update);
}
public getScrollPosition(): IScrollPosition {
return this._scrollable.getCurrentScrollPosition();
}
}
export class SmoothScrollableElement extends AbstractScrollableElement {
constructor(element: HTMLElement, options: ScrollableElementCreationOptions, scrollable: Scrollable) {
super(element, options, scrollable);
}
}
export class DomScrollableElement extends ScrollableElement {
private _element: HTMLElement;
......@@ -394,13 +416,14 @@ export class DomScrollableElement extends ScrollableElement {
public scanDomNode(): void {
// widh, scrollLeft, scrollWidth, height, scrollTop, scrollHeight
this.updateState({
this.setScrollDimensions({
width: this._element.clientWidth,
scrollWidth: this._element.scrollWidth,
scrollLeft: this._element.scrollLeft,
height: this._element.clientHeight,
scrollHeight: this._element.scrollHeight,
scrollHeight: this._element.scrollHeight
});
this.setScrollPosition({
scrollLeft: this._element.scrollLeft,
scrollTop: this._element.scrollTop,
});
}
......@@ -417,7 +440,6 @@ function resolveOptions(opts: ScrollableElementCreationOptions): ScrollableEleme
scrollYToX: (typeof opts.scrollYToX !== 'undefined' ? opts.scrollYToX : false),
mouseWheelScrollSensitivity: (typeof opts.mouseWheelScrollSensitivity !== 'undefined' ? opts.mouseWheelScrollSensitivity : 1),
mouseWheelSmoothScroll: (typeof opts.mouseWheelSmoothScroll !== 'undefined' ? opts.mouseWheelSmoothScroll : true),
mouseWheelSmoothScrollDuration: (typeof opts.mouseWheelSmoothScrollDuration !== 'undefined' ? opts.mouseWheelSmoothScrollDuration : 100),
arrowSize: (typeof opts.arrowSize !== 'undefined' ? opts.arrowSize : 11),
listenOnDomNode: (typeof opts.listenOnDomNode !== 'undefined' ? opts.listenOnDomNode : null),
......
......@@ -31,11 +31,6 @@ export interface ScrollableElementCreationOptions {
* Defaults to true.
*/
mouseWheelSmoothScroll?: boolean;
/**
* Duration in milliseconds for mouse wheel smooth scrolling animation.
* Defaults to 100.
*/
mouseWheelSmoothScrollDuration?: number;
/**
* Flip axes. Treat vertical scrolling like horizontal and vice-versa.
* Defaults to false.
......@@ -125,7 +120,6 @@ export interface ScrollableElementResolvedOptions {
alwaysConsumeMouseWheel: boolean;
mouseWheelScrollSensitivity: number;
mouseWheelSmoothScroll: boolean;
mouseWheelSmoothScrollDuration: number;
arrowSize: number;
listenOnDomNode: HTMLElement;
horizontal: ScrollbarVisibility;
......
......@@ -8,7 +8,7 @@ import { AbstractScrollbar, ScrollbarHost, ISimplifiedMouseEvent } from 'vs/base
import { StandardMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { IDomNodePagePosition } from 'vs/base/browser/dom';
import { ScrollableElementResolvedOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
import { Scrollable, ScrollEvent, ScrollbarVisibility } from 'vs/base/common/scrollable';
import { Scrollable, ScrollEvent, ScrollbarVisibility, INewScrollPosition } from 'vs/base/common/scrollable';
import { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState';
import { ARROW_IMG_SIZE } from 'vs/base/browser/ui/scrollbar/scrollbarArrow';
......@@ -90,18 +90,7 @@ export class VerticalScrollbar extends AbstractScrollbar {
return e.posx;
}
protected _getScrollPosition(): number {
const scrollState = this._scrollable.getState();
return scrollState.scrollTop;
}
protected _setScrollPosition(scrollPosition: number, smoothScrollDuration: number): void {
this._scrollable.updateState({
scrollTop: scrollPosition
}, smoothScrollDuration);
}
public validateScrollPosition(desiredScrollPosition: number): number {
return this._scrollable.validateScrollTop(desiredScrollPosition);
public writeScrollPosition(target: INewScrollPosition, scrollPosition: number): void {
target.scrollTop = scrollPosition;
}
}
......@@ -31,7 +31,7 @@ export interface ScrollEvent {
scrollTopChanged: boolean;
}
export class ScrollState {
export class ScrollState implements IScrollDimensions, IScrollPosition {
_scrollStateBrand: void;
public readonly width: number;
......@@ -95,13 +95,24 @@ export class ScrollState {
);
}
public createUpdated(update: INewScrollState): ScrollState {
public withScrollDimensions(update: INewScrollDimensions): ScrollState {
return new ScrollState(
(typeof update.width !== 'undefined' ? update.width : this.width),
(typeof update.scrollWidth !== 'undefined' ? update.scrollWidth : this.scrollWidth),
(typeof update.scrollLeft !== 'undefined' ? update.scrollLeft : this.scrollLeft),
this.scrollLeft,
(typeof update.height !== 'undefined' ? update.height : this.height),
(typeof update.scrollHeight !== 'undefined' ? update.scrollHeight : this.scrollHeight),
this.scrollTop
);
}
public withScrollPosition(update: INewScrollPosition): ScrollState {
return new ScrollState(
this.width,
this.scrollWidth,
(typeof update.scrollLeft !== 'undefined' ? update.scrollLeft : this.scrollLeft),
this.height,
this.scrollHeight,
(typeof update.scrollTop !== 'undefined' ? update.scrollTop : this.scrollTop)
);
}
......@@ -136,13 +147,25 @@ export class ScrollState {
}
export interface INewScrollState {
export interface IScrollDimensions {
readonly width: number;
readonly scrollWidth: number;
readonly height: number;
readonly scrollHeight: number;
}
export interface INewScrollDimensions {
width?: number;
scrollWidth?: number;
scrollLeft?: number;
height?: number;
scrollHeight?: number;
}
export interface IScrollPosition {
readonly scrollLeft: number;
readonly scrollTop: number;
}
export interface INewScrollPosition {
scrollLeft?: number;
scrollTop?: number;
}
......@@ -150,126 +173,193 @@ export class Scrollable extends Disposable {
_scrollableBrand: void;
private readonly _smoothScrollDuration: number;
private _state: ScrollState;
private _smoothScrolling: boolean;
private _smoothScrollAnimationParams: ISmoothScrollAnimationParams;
private _smoothScrolling: SmoothScrollingOperation;
private _onScroll = this._register(new Emitter<ScrollEvent>());
public onScroll: Event<ScrollEvent> = this._onScroll.event;
constructor() {
constructor(smoothScrollDuration: number) {
super();
this._smoothScrollDuration = smoothScrollDuration;
this._state = new ScrollState(0, 0, 0, 0, 0, 0);
this._smoothScrolling = false;
this._smoothScrollAnimationParams = null;
this._smoothScrolling = null;
}
public dispose(): void {
if (this._smoothScrolling) {
this._smoothScrolling.dispose();
this._smoothScrolling = null;
}
super.dispose();
}
public validateScrollPosition(scrollPosition: INewScrollPosition): IScrollPosition {
return this._state.withScrollPosition(scrollPosition);
}
public getState(): ScrollState {
public getScrollDimensions(): IScrollDimensions {
return this._state;
}
public validateScrollTop(desiredScrollTop: number): number {
desiredScrollTop = Math.round(desiredScrollTop);
desiredScrollTop = Math.max(desiredScrollTop, 0);
desiredScrollTop = Math.min(desiredScrollTop, this._state.scrollHeight - this._state.height);
return desiredScrollTop;
public setScrollDimensions(dimensions: INewScrollDimensions): void {
const newState = this._state.withScrollDimensions(dimensions);
this._setState(newState);
// TODO@smooth: [MUST] validate outstanding animated scroll position request target
// (in case it becomes invalid)
}
public validateScrollLeft(desiredScrollLeft: number): number {
desiredScrollLeft = Math.round(desiredScrollLeft);
desiredScrollLeft = Math.max(desiredScrollLeft, 0);
desiredScrollLeft = Math.min(desiredScrollLeft, this._state.scrollWidth - this._state.width);
return desiredScrollLeft;
/**
* Returns the final scroll position that the instance will have once the smooth scroll animation concludes.
* If no scroll animation is occuring, it will return the current scroll position instead.
*/
public getFutureScrollPosition(): IScrollPosition {
if (this._smoothScrolling) {
return this._smoothScrolling.to;
}
return this._state;
}
/**
* Returns the final scroll state that the instance will have once the smooth scroll animation concludes.
* If no scroll animation is occurring, it will return the actual scroll state instead.
* Returns the current scroll position.
* Note: This result might be an intermediate scroll position, as there might be an ongoing smooth scroll animation.
*/
public getSmoothScrollTargetState(): ScrollState {
return this._smoothScrolling ? this._smoothScrollAnimationParams.newState : this._state;
public getCurrentScrollPosition(): IScrollPosition {
return this._state;
}
public updateState(update: INewScrollState, smoothScrollDuration: number): void {
public setScrollPositionNow(update: INewScrollPosition): void {
// no smooth scrolling requested
const newState = this._state.withScrollPosition(update);
// If smooth scroll duration is not specified, then assume that the invoker intends to do an immediate update.
if (smoothScrollDuration === 0) {
const newState = this._state.createUpdated(update);
// Terminate any outstanding smooth scrolling
if (this._smoothScrolling) {
this._smoothScrolling.dispose();
this._smoothScrolling = null;
}
// If smooth scrolling is in progress, terminate it.
if (this._smoothScrolling) {
this._smoothScrolling = false;
this._smoothScrollAnimationParams = null;
}
this._setState(newState);
}
// Update state immediately if it is different from the previous one.
if (!this._state.equals(newState)) {
this._updateState(newState);
}
public setScrollPositionSmooth(update: INewScrollPosition): void {
if (this._smoothScrollDuration === 0) {
// Smooth scrolling not supported.
return this.setScrollPositionNow(update);
}
// Otherwise update scroll state incrementally.
else {
const targetState = this.getSmoothScrollTargetState();
const newTargetState = targetState.createUpdated(update);
// Proceed only if the new target state differs from the current one.
if (!targetState.equals(newTargetState)) {
// Initialize/update smooth scroll parameters.
this._smoothScrollAnimationParams = {
oldState: this._state,
newState: newTargetState,
startTime: Date.now(),
duration: smoothScrollDuration,
};
// Invoke smooth scrolling functionality in the next frame if it is not already in progress.
if (!this._smoothScrolling) {
this._smoothScrolling = true;
requestAnimationFrame(() => { this._performSmoothScroll(); });
}
if (this._smoothScrolling) {
const oldSmoothScrolling = this._smoothScrolling;
const newSmoothScrolling = oldSmoothScrolling.combine(this._state, update, this._smoothScrollDuration);
if (oldSmoothScrolling.softEquals(newSmoothScrolling)) {
// No change
return;
}
oldSmoothScrolling.dispose();
this._smoothScrolling = newSmoothScrolling;
} else {
this._smoothScrolling = new SmoothScrollingOperation(this._state, update, this._smoothScrollDuration);
}
// Begin smooth scrolling animation
this._smoothScrolling.animationFrameToken = requestAnimationFrame(() => {
this._smoothScrolling.animationFrameToken = -1;
this._performSmoothScrolling();
});
}
private _performSmoothScroll(): void {
if (!this._smoothScrolling) {
// Smooth scrolling has been terminated.
return;
}
private _performSmoothScrolling(): void {
const update = this._smoothScrolling.tick();
const newState = this._state.withScrollPosition(update);
const completion = (Date.now() - this._smoothScrollAnimationParams.startTime) / this._smoothScrollAnimationParams.duration;
const newState = this._smoothScrollAnimationParams.newState;
this._setState(newState);
if (completion < 1) {
const oldState = this._smoothScrollAnimationParams.oldState;
this._updateState(new ScrollState(
newState.width,
newState.scrollWidth,
oldState.scrollLeft + (newState.scrollLeft - oldState.scrollLeft) * completion,
newState.height,
newState.scrollHeight,
oldState.scrollTop + (newState.scrollTop - oldState.scrollTop) * completion
));
requestAnimationFrame(() => { this._performSmoothScroll(); });
}
else {
this._smoothScrolling = false;
this._smoothScrollAnimationParams = null;
this._updateState(newState);
if (!update.isDone) {
// Continue smooth scrolling animation
this._smoothScrolling.animationFrameToken = requestAnimationFrame(() => {
this._smoothScrolling.animationFrameToken = -1;
this._performSmoothScrolling();
});
}
}
private _updateState(newState: ScrollState): void {
private _setState(newState: ScrollState): void {
const oldState = this._state;
if (oldState.equals(newState)) {
// no change
return;
}
this._state = newState;
this._onScroll.fire(this._state.createScrollEvent(oldState));
}
}
interface ISmoothScrollAnimationParams {
oldState: ScrollState;
newState: ScrollState;
startTime: number;
duration: number;
}
\ No newline at end of file
class SmoothScrollingUpdate implements IScrollPosition {
public readonly scrollLeft: number;
public readonly scrollTop: number;
public readonly isDone: boolean;
constructor(scrollLeft: number, scrollTop: number, isDone: boolean) {
this.scrollLeft = scrollLeft;
this.scrollTop = scrollTop;
this.isDone = isDone;
}
}
class SmoothScrollingOperation {
public readonly from: IScrollPosition;
public readonly to: IScrollPosition;
public readonly duration: number;
private readonly _startTime: number;
public animationFrameToken: number;
constructor(from: ScrollState, to: INewScrollPosition, duration: number) {
this.from = from;
this.to = from.withScrollPosition(to);
this.duration = duration;
this._startTime = Date.now();
this.animationFrameToken = -1;
}
public softEquals(other: SmoothScrollingOperation): boolean {
return (
this.to.scrollLeft === other.to.scrollLeft
&& this.to.scrollTop === other.to.scrollTop
);
}
public dispose(): void {
if (this.animationFrameToken !== -1) {
cancelAnimationFrame(this.animationFrameToken);
this.animationFrameToken = -1;
}
}
public tick(): SmoothScrollingUpdate {
const completion = (Date.now() - this._startTime) / this.duration;
if (completion < 1) {
const newScrollLeft = this.from.scrollLeft + (this.to.scrollLeft - this.from.scrollLeft) * completion;
const newScrollTop = this.from.scrollTop + (this.to.scrollTop - this.from.scrollTop) * completion;
return new SmoothScrollingUpdate(newScrollLeft, newScrollTop, false);
}
return new SmoothScrollingUpdate(this.to.scrollLeft, this.to.scrollTop, true);
}
public combine(from: ScrollState, to: INewScrollPosition, duration: number): SmoothScrollingOperation {
// Combine our scrollLeft/scrollTop with incoming scrollLeft/scrollTop
to = {
scrollLeft: (typeof to.scrollLeft === 'undefined' ? this.to.scrollLeft : to.scrollLeft),
scrollTop: (typeof to.scrollTop === 'undefined' ? this.to.scrollTop : to.scrollTop)
};
// TODO@smooth: This is our opportunity to combine animations
return new SmoothScrollingOperation(from, to, duration);
}
}
......@@ -847,27 +847,29 @@ export class TreeView extends HeightMap {
}
public get viewHeight() {
const scrollState = this.scrollableElement.getScrollState();
return scrollState.height;
const scrollDimensions = this.scrollableElement.getScrollDimensions();
return scrollDimensions.height;
}
public set viewHeight(viewHeight: number) {
this.scrollableElement.updateState({
this.scrollableElement.setScrollDimensions({
height: viewHeight,
scrollHeight: this.getTotalHeight()
});
}
public get scrollTop(): number {
const scrollState = this.scrollableElement.getScrollState();
return scrollState.scrollTop;
const scrollPosition = this.scrollableElement.getScrollPosition();
return scrollPosition.scrollTop;
}
public set scrollTop(scrollTop: number) {
this.scrollableElement.updateState({
scrollTop: scrollTop,
this.scrollableElement.setScrollDimensions({
scrollHeight: this.getTotalHeight()
});
this.scrollableElement.setScrollPosition({
scrollTop: scrollTop
});
}
public getScrollPosition(): number {
......
......@@ -423,16 +423,16 @@ class MouseDownOperation extends Disposable {
const mouseColumn = this._getMouseColumn(e);
if (e.posy < editorContent.y) {
let aboveLineNumber = viewLayout.getLineNumberAtVerticalOffset(Math.max(viewLayout.getScrollTop() - (editorContent.y - e.posy), 0));
let aboveLineNumber = viewLayout.getLineNumberAtVerticalOffset(Math.max(viewLayout.getCurrentScrollTop() - (editorContent.y - e.posy), 0));
return new MouseTarget(null, editorBrowser.MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(aboveLineNumber, 1));
}
if (e.posy > editorContent.y + editorContent.height) {
let belowLineNumber = viewLayout.getLineNumberAtVerticalOffset(viewLayout.getScrollTop() + (e.posy - editorContent.y));
let belowLineNumber = viewLayout.getLineNumberAtVerticalOffset(viewLayout.getCurrentScrollTop() + (e.posy - editorContent.y));
return new MouseTarget(null, editorBrowser.MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(belowLineNumber, model.getLineMaxColumn(belowLineNumber)));
}
let possibleLineNumber = viewLayout.getLineNumberAtVerticalOffset(viewLayout.getScrollTop() + (e.posy - editorContent.y));
let possibleLineNumber = viewLayout.getLineNumberAtVerticalOffset(viewLayout.getCurrentScrollTop() + (e.posy - editorContent.y));
if (e.posx < editorContent.x) {
return new MouseTarget(null, editorBrowser.MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(possibleLineNumber, 1));
......
......@@ -327,12 +327,12 @@ class HitTestContext {
return this._viewHelper.getPositionFromDOMInfo(spanNode, offset);
}
public getScrollTop(): number {
return this._context.viewLayout.getScrollTop();
public getCurrentScrollTop(): number {
return this._context.viewLayout.getCurrentScrollTop();
}
public getScrollLeft(): number {
return this._context.viewLayout.getScrollLeft();
public getCurrentScrollLeft(): number {
return this._context.viewLayout.getCurrentScrollLeft();
}
}
......@@ -351,8 +351,8 @@ abstract class BareHitTestRequest {
this.editorPos = editorPos;
this.pos = pos;
this.mouseVerticalOffset = Math.max(0, ctx.getScrollTop() + pos.y - editorPos.y);
this.mouseContentHorizontalOffset = ctx.getScrollLeft() + pos.x - editorPos.x - ctx.layoutInfo.contentLeft;
this.mouseVerticalOffset = Math.max(0, ctx.getCurrentScrollTop() + pos.y - editorPos.y);
this.mouseContentHorizontalOffset = ctx.getCurrentScrollLeft() + pos.x - editorPos.x - ctx.layoutInfo.contentLeft;
this.isInMarginArea = (pos.x - editorPos.x < ctx.layoutInfo.contentLeft);
this.isInContentArea = !this.isInMarginArea;
this.mouseColumn = Math.max(0, MouseTargetFactory._getMouseColumn(this.mouseContentHorizontalOffset, ctx.typicalHalfwidthCharacterWidth));
......@@ -649,7 +649,7 @@ export class MouseTargetFactory {
public getMouseColumn(editorPos: EditorPagePosition, pos: PageCoordinates): number {
let layoutInfo = this._context.configuration.editor.layoutInfo;
let mouseContentHorizontalOffset = this._context.viewLayout.getScrollLeft() + pos.x - editorPos.x - layoutInfo.contentLeft;
let mouseContentHorizontalOffset = this._context.viewLayout.getCurrentScrollLeft() + pos.x - editorPos.x - layoutInfo.contentLeft;
return MouseTargetFactory._getMouseColumn(mouseContentHorizontalOffset, this._context.configuration.editor.fontInfo.typicalHalfwidthCharacterWidth);
}
......
......@@ -99,11 +99,7 @@ class MsPointerHandler extends MouseHandler implements IDisposable {
}
private _onGestureChange(e: IThrottledGestureEvent): void {
const viewLayout = this._context.viewLayout;
viewLayout.setScrollPosition({
scrollLeft: viewLayout.getScrollLeft() - e.translationX,
scrollTop: viewLayout.getScrollTop() - e.translationY,
});
this._context.viewLayout.deltaScrollNow(-e.translationX, -e.translationY);
}
public dispose(): void {
......@@ -181,11 +177,7 @@ class StandardPointerHandler extends MouseHandler implements IDisposable {
}
private _onGestureChange(e: IThrottledGestureEvent): void {
const viewLayout = this._context.viewLayout;
viewLayout.setScrollPosition({
scrollLeft: viewLayout.getScrollLeft() - e.translationX,
scrollTop: viewLayout.getScrollTop() - e.translationY,
});
this._context.viewLayout.deltaScrollNow(-e.translationX, -e.translationY);
}
public dispose(): void {
......@@ -227,11 +219,7 @@ class TouchHandler extends MouseHandler {
}
private onChange(e: GestureEvent): void {
const viewLayout = this._context.viewLayout;
viewLayout.setScrollPosition({
scrollLeft: viewLayout.getScrollLeft() - e.translationX,
scrollTop: viewLayout.getScrollTop() - e.translationY,
});
this._context.viewLayout.deltaScrollNow(-e.translationX, -e.translationY);
}
}
......
......@@ -6,7 +6,7 @@
import * as dom from 'vs/base/browser/dom';
import { ScrollableElementCreationOptions, ScrollableElementChangeOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
import { IOverviewRulerLayoutInfo, ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { IOverviewRulerLayoutInfo, SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { INewScrollPosition } from 'vs/editor/common/editorCommon';
import { ViewPart, PartFingerprint, PartFingerprints } from 'vs/editor/browser/view/viewPart';
import { ViewContext } from 'vs/editor/common/view/viewContext';
......@@ -19,7 +19,7 @@ import { ISimplifiedMouseEvent } from 'vs/base/browser/ui/scrollbar/abstractScro
export class EditorScrollbar extends ViewPart {
private scrollbar: ScrollableElement;
private scrollbar: SmoothScrollableElement;
private scrollbarDomNode: FastDomNode<HTMLElement>;
constructor(
......@@ -52,7 +52,7 @@ export class EditorScrollbar extends ViewPart {
mouseWheelScrollSensitivity: configScrollbarOpts.mouseWheelScrollSensitivity,
};
this.scrollbar = this._register(new ScrollableElement(linesContent.domNode, scrollbarOptions, this._context.viewLayout.scrollable));
this.scrollbar = this._register(new SmoothScrollableElement(linesContent.domNode, scrollbarOptions, this._context.viewLayout.scrollable));
PartFingerprints.write(this.scrollbar.getDomNode(), PartFingerprint.ScrollableElement);
this.scrollbarDomNode = createFastDomNode(this.scrollbar.getDomNode());
......@@ -69,7 +69,7 @@ export class EditorScrollbar extends ViewPart {
if (lookAtScrollTop) {
let deltaTop = domNode.scrollTop;
if (deltaTop) {
newScrollPosition.scrollTop = this._context.viewLayout.getScrollTop() + deltaTop;
newScrollPosition.scrollTop = this._context.viewLayout.getCurrentScrollTop() + deltaTop;
domNode.scrollTop = 0;
}
}
......@@ -77,12 +77,12 @@ export class EditorScrollbar extends ViewPart {
if (lookAtScrollLeft) {
let deltaLeft = domNode.scrollLeft;
if (deltaLeft) {
newScrollPosition.scrollLeft = this._context.viewLayout.getScrollLeft() + deltaLeft;
newScrollPosition.scrollLeft = this._context.viewLayout.getCurrentScrollLeft() + deltaLeft;
domNode.scrollLeft = 0;
}
}
this._context.viewLayout.setScrollPosition(newScrollPosition);
this._context.viewLayout.setScrollPositionNow(newScrollPosition);
};
// I've seen this happen both on the view dom node & on the lines content dom node.
......
......@@ -205,7 +205,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost<ViewLine>,
this._lastCursorRevealRangeHorizontallyEvent = e;
}
this._context.viewLayout.setScrollPosition({ // TODO@Alex: scrolling vertically can be moved to the view model
this._context.viewLayout.setScrollPositionSmooth({ // TODO@Alex: scrolling vertically can be moved to the view model
scrollTop: newScrollTop
});
......@@ -441,6 +441,8 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost<ViewLine>,
// - this must happen after the lines are in the DOM since it might need a line that rendered just now
// - it might change `scrollWidth` and `scrollLeft`
if (this._lastCursorRevealRangeHorizontallyEvent) {
// TODO@smooth: [MUST] the line might not be visible due to our smooth scrolling to it!!!
let revealHorizontalRange = this._lastCursorRevealRangeHorizontallyEvent.range;
this._lastCursorRevealRangeHorizontallyEvent = null;
......@@ -457,16 +459,16 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost<ViewLine>,
}
// set `scrollLeft`
this._context.viewLayout.setScrollPosition({
this._context.viewLayout.setScrollPositionSmooth({
scrollLeft: newScrollLeft.scrollLeft
});
}
// (3) handle scrolling
this._linesContent.setLayerHinting(this._canUseLayerHinting);
const adjustedScrollTop = this._context.viewLayout.getScrollTop() - viewportData.bigNumbersDelta;
const adjustedScrollTop = this._context.viewLayout.getCurrentScrollTop() - viewportData.bigNumbersDelta;
this._linesContent.setTop(-adjustedScrollTop);
this._linesContent.setLeft(-this._context.viewLayout.getScrollLeft());
this._linesContent.setLeft(-this._context.viewLayout.getCurrentScrollLeft());
// Update max line width (not so important, it is just so the horizontal scrollbar doesn't get too small)
this._asyncUpdateLineWidths.schedule();
......
......@@ -528,14 +528,14 @@ export class Minimap extends ViewPart {
if (platform.isWindows && mouseOrthogonalDelta > MOUSE_DRAG_RESET_DISTANCE) {
// The mouse has wondered away from the scrollbar => reset dragging
this._context.viewLayout.setScrollPosition({
this._context.viewLayout.setScrollPositionNow({
scrollTop: initialSliderState.scrollTop
});
return;
}
const mouseDelta = mouseMoveData.posy - initialMousePosition;
this._context.viewLayout.setScrollPosition({
this._context.viewLayout.setScrollPositionNow({
scrollTop: initialSliderState.getDesiredScrollTopFromDelta(mouseDelta)
});
},
......
......@@ -513,7 +513,7 @@ export abstract class CommonCodeEditor extends Disposable implements editorCommo
if (!this.hasView) {
return -1;
}
return this.viewModel.viewLayout.getScrollLeft();
return this.viewModel.viewLayout.getCurrentScrollLeft();
}
public getScrollHeight(): number {
......@@ -526,7 +526,7 @@ export abstract class CommonCodeEditor extends Disposable implements editorCommo
if (!this.hasView) {
return -1;
}
return this.viewModel.viewLayout.getScrollTop();
return this.viewModel.viewLayout.getCurrentScrollTop();
}
public setScrollLeft(newScrollLeft: number): void {
......@@ -536,7 +536,7 @@ export abstract class CommonCodeEditor extends Disposable implements editorCommo
if (typeof newScrollLeft !== 'number') {
throw new Error('Invalid arguments');
}
this.viewModel.viewLayout.setScrollPosition({
this.viewModel.viewLayout.setScrollPositionNow({
scrollLeft: newScrollLeft
});
}
......@@ -547,7 +547,7 @@ export abstract class CommonCodeEditor extends Disposable implements editorCommo
if (typeof newScrollTop !== 'number') {
throw new Error('Invalid arguments');
}
this.viewModel.viewLayout.setScrollPosition({
this.viewModel.viewLayout.setScrollPositionNow({
scrollTop: newScrollTop
});
}
......@@ -555,7 +555,7 @@ export abstract class CommonCodeEditor extends Disposable implements editorCommo
if (!this.hasView) {
return;
}
this.viewModel.viewLayout.setScrollPosition(position);
this.viewModel.viewLayout.setScrollPositionNow(position);
}
public saveViewState(): editorCommon.ICodeEditorViewState {
......
......@@ -1114,7 +1114,7 @@ export namespace CoreNavigationCommands {
noOfLines = args.value;
}
const deltaLines = (args.direction === EditorScroll_.Direction.Up ? -1 : 1) * noOfLines;
return context.getScrollTop() + deltaLines * context.config.lineHeight;
return context.getCurrentScrollTop() + deltaLines * context.config.lineHeight;
}
}
......
......@@ -210,7 +210,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
}
public scrollTo(desiredScrollTop: number): void {
this._viewModel.viewLayout.setScrollPosition({
this._viewModel.viewLayout.setScrollPositionSmooth({
scrollTop: desiredScrollTop
});
}
......
......@@ -304,8 +304,8 @@ export class CursorContext {
return this.viewModel.coordinatesConverter.convertModelRangeToViewRange(modelRange);
}
public getScrollTop(): number {
return this.viewModel.viewLayout.getScrollTop();
public getCurrentScrollTop(): number {
return this.viewModel.viewLayout.getCurrentScrollTop();
}
public getCompletelyVisibleViewRange(): Range {
......
......@@ -5,7 +5,7 @@
'use strict';
import { Disposable } from 'vs/base/common/lifecycle';
import { Scrollable, ScrollState, ScrollEvent, ScrollbarVisibility } from 'vs/base/common/scrollable';
import { Scrollable, ScrollEvent, ScrollbarVisibility, IScrollDimensions } from 'vs/base/common/scrollable';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { LinesLayout } from 'vs/editor/common/viewLayout/linesLayout';
import { IViewLayout, IViewWhitespaceViewportData, Viewport } from 'vs/editor/common/viewModel/viewModel';
......@@ -30,11 +30,12 @@ export class ViewLayout extends Disposable implements IViewLayout {
this._configuration = configuration;
this._linesLayout = new LinesLayout(lineCount, this._configuration.editor.lineHeight);
this.scrollable = this._register(new Scrollable());
this.scrollable.updateState({
// TODO@smooth: [MUST] have an editor option for smooth scrolling
this.scrollable = this._register(new Scrollable(125));
this.scrollable.setScrollDimensions({
width: configuration.editor.layoutInfo.contentWidth,
height: configuration.editor.layoutInfo.contentHeight
}, 0/* immediate */);
});
this.onDidScroll = this.scrollable.onScroll;
this._updateHeight();
......@@ -59,10 +60,10 @@ export class ViewLayout extends Disposable implements IViewLayout {
this._linesLayout.setLineHeight(this._configuration.editor.lineHeight);
}
if (e.layoutInfo) {
this.scrollable.updateState({
this.scrollable.setScrollDimensions({
width: this._configuration.editor.layoutInfo.contentWidth,
height: this._configuration.editor.layoutInfo.contentHeight
}, 0/* immediate */);
});
}
this._updateHeight();
}
......@@ -81,12 +82,12 @@ export class ViewLayout extends Disposable implements IViewLayout {
// ---- end view event handlers
private _getHorizontalScrollbarHeight(scrollState: ScrollState): number {
private _getHorizontalScrollbarHeight(scrollDimensions: IScrollDimensions): number {
if (this._configuration.editor.viewInfo.scrollbar.horizontal === ScrollbarVisibility.Hidden) {
// horizontal scrollbar not visible
return 0;
}
if (scrollState.width >= scrollState.scrollWidth) {
if (scrollDimensions.width >= scrollDimensions.scrollWidth) {
// horizontal scrollbar not visible
return 0;
}
......@@ -94,33 +95,34 @@ export class ViewLayout extends Disposable implements IViewLayout {
}
private _getTotalHeight(): number {
const scrollState = this.scrollable.getState();
const scrollDimensions = this.scrollable.getScrollDimensions();
let result = this._linesLayout.getLinesTotalHeight();
if (this._configuration.editor.viewInfo.scrollBeyondLastLine) {
result += scrollState.height - this._configuration.editor.lineHeight;
result += scrollDimensions.height - this._configuration.editor.lineHeight;
} else {
result += this._getHorizontalScrollbarHeight(scrollState);
result += this._getHorizontalScrollbarHeight(scrollDimensions);
}
return Math.max(scrollState.height, result);
return Math.max(scrollDimensions.height, result);
}
private _updateHeight(): void {
this.scrollable.updateState({
this.scrollable.setScrollDimensions({
scrollHeight: this._getTotalHeight()
}, 0/* immediate */);
});
}
// ---- Layouting logic
public getCurrentViewport(): Viewport {
const scrollState = this.scrollable.getState();
const scrollDimensions = this.scrollable.getScrollDimensions();
const currentScrollPosition = this.scrollable.getCurrentScrollPosition();
return new Viewport(
scrollState.scrollTop,
scrollState.scrollLeft,
scrollState.width,
scrollState.height
currentScrollPosition.scrollTop,
currentScrollPosition.scrollLeft,
scrollDimensions.width,
scrollDimensions.height
);
}
......@@ -134,9 +136,9 @@ export class ViewLayout extends Disposable implements IViewLayout {
public onMaxLineWidthChanged(maxLineWidth: number): void {
let newScrollWidth = this._computeScrollWidth(maxLineWidth, this.getCurrentViewport().width);
this.scrollable.updateState({
this.scrollable.setScrollDimensions({
scrollWidth: newScrollWidth
}, 0/* immediate */);
});
// The height might depend on the fact that there is a horizontal scrollbar or not
this._updateHeight();
......@@ -145,14 +147,14 @@ export class ViewLayout extends Disposable implements IViewLayout {
// ---- view state
public saveState(): editorCommon.IViewState {
const scrollState = this.scrollable.getState();
let scrollTop = scrollState.scrollTop;
const currentScrollPosition = this.scrollable.getFutureScrollPosition();
let scrollTop = currentScrollPosition.scrollTop;
let firstLineNumberInViewport = this._linesLayout.getLineNumberAtOrAfterVerticalOffset(scrollTop);
let whitespaceAboveFirstLine = this._linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(firstLineNumberInViewport);
return {
scrollTop: scrollTop,
scrollTopWithoutViewZones: scrollTop - whitespaceAboveFirstLine,
scrollLeft: scrollState.scrollLeft
scrollLeft: currentScrollPosition.scrollLeft
};
}
......@@ -161,10 +163,10 @@ export class ViewLayout extends Disposable implements IViewLayout {
if (typeof state.scrollTopWithoutViewZones === 'number' && !this._linesLayout.hasWhitespace()) {
restoreScrollTop = state.scrollTopWithoutViewZones;
}
this.scrollable.updateState({
this.scrollable.setScrollPositionNow({
scrollLeft: state.scrollLeft,
scrollTop: restoreScrollTop
}, 0/* immediate */);
});
}
// ---- IVerticalLayoutProvider
......@@ -197,14 +199,14 @@ export class ViewLayout extends Disposable implements IViewLayout {
}
public getLinesViewportDataAtScrollTop(scrollTop: number): IPartialViewLinesViewportData {
// do some minimal validations on scrollTop
const scrollState = this.scrollable.getState();
if (scrollTop + scrollState.height > scrollState.scrollHeight) {
scrollTop = scrollState.scrollHeight - scrollState.height;
const scrollDimensions = this.scrollable.getScrollDimensions();
if (scrollTop + scrollDimensions.height > scrollDimensions.scrollHeight) {
scrollTop = scrollDimensions.scrollHeight - scrollDimensions.height;
}
if (scrollTop < 0) {
scrollTop = 0;
}
return this._linesLayout.getLinesViewportData(scrollTop, scrollTop + scrollState.height);
return this._linesLayout.getLinesViewportData(scrollTop, scrollTop + scrollDimensions.height);
}
public getWhitespaceViewportData(): IViewWhitespaceViewportData[] {
const visibleBox = this.getCurrentViewport();
......@@ -218,37 +220,45 @@ export class ViewLayout extends Disposable implements IViewLayout {
public getScrollWidth(): number {
const scrollState = this.scrollable.getState();
return scrollState.scrollWidth;
}
public getScrollLeft(): number {
const scrollState = this.scrollable.getState();
return scrollState.scrollLeft;
const scrollDimensions = this.scrollable.getScrollDimensions();
return scrollDimensions.scrollWidth;
}
public getScrollHeight(): number {
const scrollState = this.scrollable.getState();
return scrollState.scrollHeight;
}
public getScrollTop(): number {
const scrollState = this.scrollable.getState();
return scrollState.scrollTop;
}
public setScrollPosition(position: editorCommon.INewScrollPosition): void {
const state = this.scrollable.getSmoothScrollTargetState();
const xAbsChange = Math.abs(typeof position.scrollLeft !== 'undefined' ? position.scrollLeft - state.scrollLeft : 0);
const yAbsChange = Math.abs(typeof position.scrollTop !== 'undefined' ? position.scrollTop - state.scrollTop : 0);
if (xAbsChange || yAbsChange) {
// If position change is big enough, then appply smooth scrolling
if (xAbsChange > state.width / 10 ||
yAbsChange > state.height / 10) {
this.scrollable.updateState(position, 125);
}
// Otherwise update scroll position immediately
else {
this.scrollable.updateState(position, 0/* immediate */);
}
}
const scrollDimensions = this.scrollable.getScrollDimensions();
return scrollDimensions.scrollHeight;
}
public getCurrentScrollLeft(): number {
const currentScrollPosition = this.scrollable.getCurrentScrollPosition();
return currentScrollPosition.scrollLeft;
}
public getCurrentScrollTop(): number {
const currentScrollPosition = this.scrollable.getCurrentScrollPosition();
return currentScrollPosition.scrollTop;
}
public getFutureScrollLeft(): number {
const currentScrollPosition = this.scrollable.getFutureScrollPosition();
return currentScrollPosition.scrollLeft;
}
public getFutureScrollTop(): number {
const currentScrollPosition = this.scrollable.getFutureScrollPosition();
return currentScrollPosition.scrollTop;
}
public setScrollPositionNow(position: editorCommon.INewScrollPosition): void {
this.scrollable.setScrollPositionNow(position);
}
public setScrollPositionSmooth(position: editorCommon.INewScrollPosition): void {
this.scrollable.setScrollPositionSmooth(position);
}
public deltaScrollNow(deltaScrollLeft: number, deltaScrollTop: number): void {
const currentScrollPosition = this.scrollable.getCurrentScrollPosition();
this.scrollable.setScrollPositionNow({
scrollLeft: currentScrollPosition.scrollLeft + deltaScrollLeft,
scrollTop: currentScrollPosition.scrollTop + deltaScrollTop
});
}
}
......@@ -44,12 +44,20 @@ export interface IViewLayout {
onMaxLineWidthChanged(width: number): void;
getScrollLeft(): number;
getScrollWidth(): number;
getScrollHeight(): number;
getScrollTop(): number;
getCurrentScrollLeft(): number;
getCurrentScrollTop(): number;
getFutureScrollLeft(): number;
getFutureScrollTop(): number;
getCurrentViewport(): Viewport;
setScrollPosition(position: INewScrollPosition): void;
setScrollPositionNow(position: INewScrollPosition): void;
setScrollPositionSmooth(position: INewScrollPosition): void;
deltaScrollNow(deltaScrollLeft: number, deltaScrollTop: number): void;
getLinesViewportData(): IPartialViewLinesViewportData;
getLinesViewportDataAtScrollTop(scrollTop: number): IPartialViewLinesViewportData;
......
......@@ -125,7 +125,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
this.decorations.onLineMappingChanged();
this.viewLayout.onFlushed(this.getLineCount());
if (this.viewLayout.getScrollTop() !== 0) {
if (this.viewLayout.getCurrentScrollTop() !== 0) {
// Never change the scroll position from 0 to something else...
revealPreviousCenteredModelRange = true;
}
......
......@@ -127,7 +127,7 @@ export class TabsTitleControl extends TitleControl {
// Forward scrolling inside the container to our custom scrollbar
this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.SCROLL, e => {
if (DOM.hasClass(this.tabsContainer, 'scroll')) {
this.scrollbar.updateState({
this.scrollbar.setScrollPosition({
scrollLeft: this.tabsContainer.scrollLeft // during DND the container gets scrolled so we need to update the custom scrollbar
});
}
......@@ -458,7 +458,7 @@ export class TabsTitleControl extends TitleControl {
const totalContainerWidth = this.tabsContainer.scrollWidth;
// Update scrollbar
this.scrollbar.updateState({
this.scrollbar.setScrollDimensions({
width: visibleContainerWidth,
scrollWidth: totalContainerWidth
});
......@@ -478,14 +478,14 @@ export class TabsTitleControl extends TitleControl {
// Tab is overflowing to the right: Scroll minimally until the element is fully visible to the right
// Note: only try to do this if we actually have enough width to give to show the tab fully!
if (activeTabFits && containerScrollPosX + visibleContainerWidth < activeTabPosX + activeTabWidth) {
this.scrollbar.updateState({
this.scrollbar.setScrollPosition({
scrollLeft: containerScrollPosX + ((activeTabPosX + activeTabWidth) /* right corner of tab */ - (containerScrollPosX + visibleContainerWidth) /* right corner of view port */)
});
}
// Tab is overlflowng to the left or does not fit: Scroll it into view to the left
else if (containerScrollPosX > activeTabPosX || !activeTabFits) {
this.scrollbar.updateState({
this.scrollbar.setScrollPosition({
scrollLeft: this.activeTab.offsetLeft
});
}
......@@ -565,7 +565,7 @@ export class TabsTitleControl extends TitleControl {
}
// moving in the tabs container can have an impact on scrolling position, so we need to update the custom scrollbar
this.scrollbar.updateState({
this.scrollbar.setScrollPosition({
scrollLeft: this.tabsContainer.scrollLeft
});
}));
......
......@@ -445,8 +445,8 @@ export class ExtensionEditor extends BaseEditor {
const tree = this.renderDependencies(content, extensionDependencies);
const layout = () => {
scrollableContent.scanDomNode();
const scrollState = scrollableContent.getScrollState();
tree.layout(scrollState.height);
const scrollDimensions = scrollableContent.getScrollDimensions();
tree.layout(scrollDimensions.height);
};
const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
this.contentDisposables.push(toDisposable(removeLayoutParticipant));
......
......@@ -134,11 +134,12 @@ export class WalkThroughPart extends BaseEditor {
}
private updatedScrollPosition() {
const scrollState = this.scrollbar.getScrollState();
const scrollHeight = scrollState.scrollHeight;
const scrollDimensions = this.scrollbar.getScrollDimensions();
const scrollPosition = this.scrollbar.getScrollPosition();
const scrollHeight = scrollDimensions.scrollHeight;
if (scrollHeight && this.input instanceof WalkThroughInput) {
const scrollTop = scrollState.scrollTop;
const height = scrollState.height;
const scrollTop = scrollPosition.scrollTop;
const height = scrollDimensions.height;
this.input.relativeScrollPosition(scrollTop / scrollHeight, (scrollTop + height) / scrollHeight);
}
}
......@@ -163,9 +164,9 @@ export class WalkThroughPart extends BaseEditor {
this.disposables.push(this.addEventListener(this.content, 'focusin', e => {
// Work around scrolling as side-effect of setting focus on the offscreen zone widget (#18929)
if (e.target instanceof HTMLElement && e.target.classList.contains('zone-widget-container')) {
let scrollState = this.scrollbar.getScrollState();
this.content.scrollTop = scrollState.scrollTop;
this.content.scrollLeft = scrollState.scrollLeft;
const scrollPosition = this.scrollbar.getScrollPosition();
this.content.scrollTop = scrollPosition.scrollTop;
this.content.scrollLeft = scrollPosition.scrollLeft;
}
}));
}
......@@ -186,7 +187,7 @@ export class WalkThroughPart extends BaseEditor {
if (scrollTarget && innerContent) {
const targetTop = scrollTarget.getBoundingClientRect().top - 20;
const containerTop = innerContent.getBoundingClientRect().top;
this.scrollbar.updateState({ scrollTop: targetTop - containerTop });
this.scrollbar.setScrollPosition({ scrollTop: targetTop - containerTop });
}
} else {
this.open(URI.parse(node.href));
......@@ -261,13 +262,13 @@ export class WalkThroughPart extends BaseEditor {
}
arrowUp() {
const scrollState = this.scrollbar.getScrollState();
this.scrollbar.updateState({ scrollTop: scrollState.scrollTop - this.getArrowScrollHeight() });
const scrollPosition = this.scrollbar.getScrollPosition();
this.scrollbar.setScrollPosition({ scrollTop: scrollPosition.scrollTop - this.getArrowScrollHeight() });
}
arrowDown() {
const scrollState = this.scrollbar.getScrollState();
this.scrollbar.updateState({ scrollTop: scrollState.scrollTop + this.getArrowScrollHeight() });
const scrollPosition = this.scrollbar.getScrollPosition();
this.scrollbar.setScrollPosition({ scrollTop: scrollPosition.scrollTop + this.getArrowScrollHeight() });
}
private getArrowScrollHeight() {
......@@ -279,13 +280,15 @@ export class WalkThroughPart extends BaseEditor {
}
pageUp() {
const scrollState = this.scrollbar.getScrollState();
this.scrollbar.updateState({ scrollTop: scrollState.scrollTop - scrollState.height });
const scrollDimensions = this.scrollbar.getScrollDimensions();
const scrollPosition = this.scrollbar.getScrollPosition();
this.scrollbar.setScrollPosition({ scrollTop: scrollPosition.scrollTop - scrollDimensions.height });
}
pageDown() {
const scrollState = this.scrollbar.getScrollState();
this.scrollbar.updateState({ scrollTop: scrollState.scrollTop + scrollState.height });
const scrollDimensions = this.scrollbar.getScrollDimensions();
const scrollPosition = this.scrollbar.getScrollPosition();
this.scrollbar.setScrollPosition({ scrollTop: scrollPosition.scrollTop + scrollDimensions.height });
}
setInput(input: WalkThroughInput, options: EditorOptions): TPromise<void> {
......@@ -367,13 +370,14 @@ export class WalkThroughPart extends BaseEditor {
const lineHeight = editor.getConfiguration().lineHeight;
const lineTop = (targetTop + (e.position.lineNumber - 1) * lineHeight) - containerTop;
const lineBottom = lineTop + lineHeight;
const scrollState = this.scrollbar.getScrollState();
const scrollTop = scrollState.scrollTop;
const height = scrollState.height;
const scrollDimensions = this.scrollbar.getScrollDimensions();
const scrollPosition = this.scrollbar.getScrollPosition();
const scrollTop = scrollPosition.scrollTop;
const height = scrollDimensions.height;
if (scrollTop > lineTop) {
this.scrollbar.updateState({ scrollTop: lineTop });
this.scrollbar.setScrollPosition({ scrollTop: lineTop });
} else if (scrollTop < lineBottom - height) {
this.scrollbar.updateState({ scrollTop: lineBottom - height });
this.scrollbar.setScrollPosition({ scrollTop: lineBottom - height });
}
}
}));
......@@ -485,11 +489,11 @@ export class WalkThroughPart extends BaseEditor {
memento[WALK_THROUGH_EDITOR_VIEW_STATE_PREFERENCE_KEY] = editorViewStateMemento;
}
const scrollState = this.scrollbar.getScrollState();
const scrollPosition = this.scrollbar.getScrollPosition();
const editorViewState: IWalkThroughEditorViewState = {
viewState: {
scrollTop: scrollState.scrollTop,
scrollLeft: scrollState.scrollLeft
scrollTop: scrollPosition.scrollTop,
scrollLeft: scrollPosition.scrollLeft
}
};
......@@ -512,7 +516,7 @@ export class WalkThroughPart extends BaseEditor {
if (fileViewState) {
const state: IWalkThroughEditorViewState = fileViewState[this.position];
if (state) {
this.scrollbar.updateState(state.viewState);
this.scrollbar.setScrollPosition(state.viewState);
}
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册