未验证 提交 eec97b82 编写于 作者: A Alex Dima

Use the future scroll position to compute the scroll top and reuse the current...

Use the future scroll position to compute the scroll top and reuse the current smooth scrolling animation (#104144, #107704, #104284)
Co-authored-by: NJoao Moreno <joao.moreno@microsoft.com>
上级 5df492ff
...@@ -809,14 +809,14 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -809,14 +809,14 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
return scrollPosition.scrollTop; return scrollPosition.scrollTop;
} }
setScrollTop(scrollTop: number): void { setScrollTop(scrollTop: number, reuseAnimation?: boolean): void {
if (this.scrollableElementUpdateDisposable) { if (this.scrollableElementUpdateDisposable) {
this.scrollableElementUpdateDisposable.dispose(); this.scrollableElementUpdateDisposable.dispose();
this.scrollableElementUpdateDisposable = null; this.scrollableElementUpdateDisposable = null;
this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight }); this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight });
} }
this.scrollableElement.setScrollPosition({ scrollTop }); this.scrollableElement.setScrollPosition({ scrollTop, reuseAnimation });
} }
getScrollLeft(): number { getScrollLeft(): number {
...@@ -895,9 +895,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -895,9 +895,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.render(previousRenderRange, e.scrollTop, e.height, e.scrollLeft, e.scrollWidth); this.render(previousRenderRange, e.scrollTop, e.height, e.scrollLeft, e.scrollWidth);
if (this.supportDynamicHeights) { if (this.supportDynamicHeights) {
// Don't update scrollTop from within an scroll event this._rerender(e.scrollTop, e.height, e.inSmoothScrolling);
// so we don't break smooth scrolling. #104144
this._rerender(e.scrollTop, e.height, false);
} }
} catch (err) { } catch (err) {
console.error('Got bad scroll event:', e); console.error('Got bad scroll event:', e);
...@@ -1167,7 +1165,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -1167,7 +1165,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
* Given a stable rendered state, checks every rendered element whether it needs * Given a stable rendered state, checks every rendered element whether it needs
* to be probed for dynamic height. Adjusts scroll height and top if necessary. * to be probed for dynamic height. Adjusts scroll height and top if necessary.
*/ */
private _rerender(renderTop: number, renderHeight: number, updateScrollTop: boolean = true): void { private _rerender(renderTop: number, renderHeight: number, inSmoothScrolling?: boolean): void {
const previousRenderRange = this.getRenderRange(renderTop, renderHeight); const previousRenderRange = this.getRenderRange(renderTop, renderHeight);
// Let's remember the second element's position, this helps in scrolling up // Let's remember the second element's position, this helps in scrolling up
...@@ -1233,8 +1231,15 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -1233,8 +1231,15 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
} }
} }
if (updateScrollTop && typeof anchorElementIndex === 'number') { if (typeof anchorElementIndex === 'number') {
this.scrollTop = this.elementTop(anchorElementIndex) - anchorElementTopDelta!; // To compute a destination scroll top, we need to take into account the current smooth scrolling
// animation, and then reuse it with a new target (to avoid prolonging the scroll)
// See https://github.com/microsoft/vscode/issues/104144
// See https://github.com/microsoft/vscode/pull/104284
// See https://github.com/microsoft/vscode/issues/107704
const deltaScrollTop = this.scrollable.getFutureScrollPosition().scrollTop - renderTop;
const newScrollTop = this.elementTop(anchorElementIndex) - anchorElementTopDelta! + deltaScrollTop;
this.setScrollTop(newScrollTop, inSmoothScrolling);
} }
this._onDidChangeContentHeight.fire(this.contentHeight); this._onDidChangeContentHeight.fire(this.contentHeight);
......
...@@ -549,8 +549,12 @@ export class SmoothScrollableElement extends AbstractScrollableElement { ...@@ -549,8 +549,12 @@ export class SmoothScrollableElement extends AbstractScrollableElement {
super(element, options, scrollable); super(element, options, scrollable);
} }
public setScrollPosition(update: INewScrollPosition): void { public setScrollPosition(update: INewScrollPosition & { reuseAnimation?: boolean }): void {
this._scrollable.setScrollPositionNow(update); if (update.reuseAnimation) {
this._scrollable.setScrollPositionSmooth(update, update.reuseAnimation);
} else {
this._scrollable.setScrollPositionNow(update);
}
} }
public getScrollPosition(): IScrollPosition { public getScrollPosition(): IScrollPosition {
......
...@@ -13,6 +13,8 @@ export const enum ScrollbarVisibility { ...@@ -13,6 +13,8 @@ export const enum ScrollbarVisibility {
} }
export interface ScrollEvent { export interface ScrollEvent {
inSmoothScrolling: boolean;
oldWidth: number; oldWidth: number;
oldScrollWidth: number; oldScrollWidth: number;
oldScrollLeft: number; oldScrollLeft: number;
...@@ -132,7 +134,7 @@ export class ScrollState implements IScrollDimensions, IScrollPosition { ...@@ -132,7 +134,7 @@ export class ScrollState implements IScrollDimensions, IScrollPosition {
); );
} }
public createScrollEvent(previous: ScrollState): ScrollEvent { public createScrollEvent(previous: ScrollState, inSmoothScrolling: boolean): ScrollEvent {
const widthChanged = (this.width !== previous.width); const widthChanged = (this.width !== previous.width);
const scrollWidthChanged = (this.scrollWidth !== previous.scrollWidth); const scrollWidthChanged = (this.scrollWidth !== previous.scrollWidth);
const scrollLeftChanged = (this.scrollLeft !== previous.scrollLeft); const scrollLeftChanged = (this.scrollLeft !== previous.scrollLeft);
...@@ -142,6 +144,7 @@ export class ScrollState implements IScrollDimensions, IScrollPosition { ...@@ -142,6 +144,7 @@ export class ScrollState implements IScrollDimensions, IScrollPosition {
const scrollTopChanged = (this.scrollTop !== previous.scrollTop); const scrollTopChanged = (this.scrollTop !== previous.scrollTop);
return { return {
inSmoothScrolling: inSmoothScrolling,
oldWidth: previous.width, oldWidth: previous.width,
oldScrollWidth: previous.scrollWidth, oldScrollWidth: previous.scrollWidth,
oldScrollLeft: previous.scrollLeft, oldScrollLeft: previous.scrollLeft,
...@@ -242,7 +245,7 @@ export class Scrollable extends Disposable { ...@@ -242,7 +245,7 @@ export class Scrollable extends Disposable {
public setScrollDimensions(dimensions: INewScrollDimensions, useRawScrollPositions: boolean): void { public setScrollDimensions(dimensions: INewScrollDimensions, useRawScrollPositions: boolean): void {
const newState = this._state.withScrollDimensions(dimensions, useRawScrollPositions); const newState = this._state.withScrollDimensions(dimensions, useRawScrollPositions);
this._setState(newState); this._setState(newState, Boolean(this._smoothScrolling));
// Validate outstanding animated scroll position target // Validate outstanding animated scroll position target
if (this._smoothScrolling) { if (this._smoothScrolling) {
...@@ -279,10 +282,10 @@ export class Scrollable extends Disposable { ...@@ -279,10 +282,10 @@ export class Scrollable extends Disposable {
this._smoothScrolling = null; this._smoothScrolling = null;
} }
this._setState(newState); this._setState(newState, false);
} }
public setScrollPositionSmooth(update: INewScrollPosition): void { public setScrollPositionSmooth(update: INewScrollPosition, reuseAnimation?: boolean): void {
if (this._smoothScrollDuration === 0) { if (this._smoothScrollDuration === 0) {
// Smooth scrolling not supported. // Smooth scrolling not supported.
return this.setScrollPositionNow(update); return this.setScrollPositionNow(update);
...@@ -302,8 +305,12 @@ export class Scrollable extends Disposable { ...@@ -302,8 +305,12 @@ export class Scrollable extends Disposable {
// No need to interrupt or extend the current animation since we're going to the same place // No need to interrupt or extend the current animation since we're going to the same place
return; return;
} }
let newSmoothScrolling: SmoothScrollingOperation;
const newSmoothScrolling = this._smoothScrolling.combine(this._state, validTarget, this._smoothScrollDuration); if (reuseAnimation) {
newSmoothScrolling = new SmoothScrollingOperation(this._smoothScrolling.from, validTarget, this._smoothScrolling.startTime, this._smoothScrolling.duration);
} else {
newSmoothScrolling = this._smoothScrolling.combine(this._state, validTarget, this._smoothScrollDuration);
}
this._smoothScrolling.dispose(); this._smoothScrolling.dispose();
this._smoothScrolling = newSmoothScrolling; this._smoothScrolling = newSmoothScrolling;
} else { } else {
...@@ -330,7 +337,7 @@ export class Scrollable extends Disposable { ...@@ -330,7 +337,7 @@ export class Scrollable extends Disposable {
const update = this._smoothScrolling.tick(); const update = this._smoothScrolling.tick();
const newState = this._state.withScrollPosition(update); const newState = this._state.withScrollPosition(update);
this._setState(newState); this._setState(newState, true);
if (!this._smoothScrolling) { if (!this._smoothScrolling) {
// Looks like someone canceled the smooth scrolling // Looks like someone canceled the smooth scrolling
...@@ -354,14 +361,14 @@ export class Scrollable extends Disposable { ...@@ -354,14 +361,14 @@ export class Scrollable extends Disposable {
}); });
} }
private _setState(newState: ScrollState): void { private _setState(newState: ScrollState, inSmoothScrolling: boolean): void {
const oldState = this._state; const oldState = this._state;
if (oldState.equals(newState)) { if (oldState.equals(newState)) {
// no change // no change
return; return;
} }
this._state = newState; this._state = newState;
this._onScroll.fire(this._state.createScrollEvent(oldState)); this._onScroll.fire(this._state.createScrollEvent(oldState, inSmoothScrolling));
} }
} }
...@@ -404,17 +411,17 @@ export class SmoothScrollingOperation { ...@@ -404,17 +411,17 @@ export class SmoothScrollingOperation {
public readonly from: ISmoothScrollPosition; public readonly from: ISmoothScrollPosition;
public to: ISmoothScrollPosition; public to: ISmoothScrollPosition;
public readonly duration: number; public readonly duration: number;
private readonly _startTime: number; public readonly startTime: number;
public animationFrameDisposable: IDisposable | null; public animationFrameDisposable: IDisposable | null;
private scrollLeft!: IAnimation; private scrollLeft!: IAnimation;
private scrollTop!: IAnimation; private scrollTop!: IAnimation;
protected constructor(from: ISmoothScrollPosition, to: ISmoothScrollPosition, startTime: number, duration: number) { constructor(from: ISmoothScrollPosition, to: ISmoothScrollPosition, startTime: number, duration: number) {
this.from = from; this.from = from;
this.to = to; this.to = to;
this.duration = duration; this.duration = duration;
this._startTime = startTime; this.startTime = startTime;
this.animationFrameDisposable = null; this.animationFrameDisposable = null;
...@@ -460,7 +467,7 @@ export class SmoothScrollingOperation { ...@@ -460,7 +467,7 @@ export class SmoothScrollingOperation {
} }
protected _tick(now: number): SmoothScrollingUpdate { protected _tick(now: number): SmoothScrollingUpdate {
const completion = (now - this._startTime) / this.duration; const completion = (now - this.startTime) / this.duration;
if (completion < 1) { if (completion < 1) {
const newScrollLeft = this.scrollLeft(completion); const newScrollLeft = this.scrollLeft(completion);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册