Maintain scroll history

This commit is contained in:
Antonio Scandurra
2015-09-24 16:25:58 +02:00
parent 365a586466
commit 2e6bb53303
4 changed files with 99 additions and 76 deletions

View File

@@ -3605,6 +3605,18 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
describe "when many scroll events are triggered before an animation frame", ->
it "applies them sequentially, as if they were not batched", ->
wrapperNode.style.height = lineHeightInPixels * 7 + "px"
component.measureDimensions()
nextAnimationFrame()
editor.scrollToScreenPosition([8, 0])
editor.scrollToScreenPosition([8, 0], center: true)
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe(4 * lineHeightInPixels)
describe "::screenPositionForPixelPosition(pixelPosition)", ->
it "clips pixel positions above buffer start", ->
expect(component.screenPositionForPixelPosition(top: -Infinity, left: -Infinity)).toEqual [0, 0]

View File

@@ -1893,9 +1893,9 @@ describe "TextEditorPresenter", ->
pixelPosition: {top: 6 * 10 - scrollTop, left: gutterWidth}
}
editor.insertNewline()
presenter.getState() # forces scroll top to be changed
presenter.setScrollTop(scrollTop) # I'm fighting the editor
expectStateUpdate presenter, ->
editor.insertNewline()
presenter.setScrollTop(scrollTop) # I'm fighting the editor
expectValues stateForOverlay(presenter, decoration), {
item: item

View File

@@ -317,7 +317,7 @@ class TextEditorComponent
pendingScrollTop = @pendingScrollTop
@pendingScrollTop = null
@presenter.setScrollTop(pendingScrollTop)
@presenter.commitPendingScrollTopPosition()
@presenter.commitScrollQueue()
onHorizontalScroll: (scrollLeft) =>
return if @updateRequested or scrollLeft is @presenter.getScrollLeft()
@@ -327,7 +327,7 @@ class TextEditorComponent
unless animationFramePending
@requestAnimationFrame =>
@presenter.setScrollLeft(@pendingScrollLeft)
@presenter.commitPendingScrollLeftPosition()
@presenter.commitScrollQueue()
@pendingScrollLeft = null
onMouseWheel: (event) =>
@@ -613,11 +613,11 @@ class TextEditorComponent
if mouseYDelta?
@presenter.setScrollTop(@presenter.getScrollTop() + yDirection * scaleScrollDelta(mouseYDelta))
@presenter.commitPendingScrollTopPosition()
@presenter.commitScrollQueue()
if mouseXDelta?
@presenter.setScrollLeft(@presenter.getScrollLeft() + xDirection * scaleScrollDelta(mouseXDelta))
@presenter.commitPendingScrollLeftPosition()
@presenter.commitScrollQueue()
scaleScrollDelta = (scrollDelta) ->
Math.pow(scrollDelta / 2, 3) / 280

View File

@@ -3,6 +3,23 @@
_ = require 'underscore-plus'
Decoration = require './decoration'
class ScrollQueue
constructor: ->
@queue = []
@committing = false
enqueue: (scrollAction) ->
if @committing
@queue.unshift(scrollAction)
else
@queue.push(scrollAction)
commit: ->
@committing = true
while scrollAction = @queue.shift()
scrollAction()
@committing = false
module.exports =
class TextEditorPresenter
toggleCursorBlinkHandle: null
@@ -22,6 +39,7 @@ class TextEditorPresenter
@measuredVerticalScrollbarWidth = verticalScrollbarWidth
@gutterWidth ?= 0
@tileSize ?= 6
@scrollQueue = new ScrollQueue
@disposables = new CompositeDisposable
@emitter = new Emitter
@@ -816,7 +834,7 @@ class TextEditorPresenter
setScrollTop: (scrollTop) ->
return unless scrollTop?
@pendingScrollTop = scrollTop
@scrollQueue.enqueue => @commitScrollTop(scrollTop)
@shouldUpdateVerticalScrollState = true
@shouldUpdateHiddenInputState = true
@@ -856,7 +874,7 @@ class TextEditorPresenter
setScrollLeft: (scrollLeft) ->
return unless scrollLeft?
@pendingScrollLeft = scrollLeft
@scrollQueue.enqueue => @commitScrollLeft(scrollLeft)
@shouldUpdateHorizontalScrollState = true
@shouldUpdateHiddenInputState = true
@@ -1473,7 +1491,54 @@ class TextEditorPresenter
@emitDidUpdateState()
didChangeScrollPosition: (position) ->
@pendingScrollLogicalPosition = position
@scrollQueue.enqueue =>
{screenRange, options} = position
verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels()
horizontalScrollMarginInPixels = @getHorizontalScrollMarginInPixels()
{top, left} = @pixelRectForScreenRange(new Range(screenRange.start, screenRange.start))
{top: endTop, left: endLeft, height: endHeight} = @pixelRectForScreenRange(new Range(screenRange.end, screenRange.end))
bottom = endTop + endHeight
right = endLeft
top += @getScrollTop()
bottom += @getScrollTop()
left += @getScrollLeft()
right += @getScrollLeft()
if options?.center
desiredScrollCenter = (top + bottom) / 2
unless @getScrollTop() < desiredScrollCenter < @getScrollBottom()
desiredScrollTop = desiredScrollCenter - @getClientHeight() / 2
desiredScrollBottom = desiredScrollCenter + @getClientHeight() / 2
else
desiredScrollTop = top - verticalScrollMarginInPixels
desiredScrollBottom = bottom + verticalScrollMarginInPixels
desiredScrollLeft = left - horizontalScrollMarginInPixels
desiredScrollRight = right + horizontalScrollMarginInPixels
if options?.reversed ? true
if desiredScrollBottom > @getScrollBottom()
@setScrollBottom(desiredScrollBottom)
if desiredScrollTop < @getScrollTop()
@setScrollTop(desiredScrollTop)
if desiredScrollRight > @getScrollRight()
@setScrollRight(desiredScrollRight)
if desiredScrollLeft < @getScrollLeft()
@setScrollLeft(desiredScrollLeft)
else
if desiredScrollTop < @getScrollTop()
@setScrollTop(desiredScrollTop)
if desiredScrollBottom > @getScrollBottom()
@setScrollBottom(desiredScrollBottom)
if desiredScrollLeft < @getScrollLeft()
@setScrollLeft(desiredScrollLeft)
if desiredScrollRight > @getScrollRight()
@setScrollRight(desiredScrollRight)
@shouldUpdateCursorsState = true
@shouldUpdateCustomGutterDecorationState = true
@@ -1500,63 +1565,10 @@ class TextEditorPresenter
getHorizontalScrollbarHeight: ->
@horizontalScrollbarHeight
commitPendingLogicalScrollPosition: ->
return unless @pendingScrollLogicalPosition?
commitScrollLeft: (scrollLeft) ->
return unless scrollLeft?
{screenRange, options} = @pendingScrollLogicalPosition
verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels()
horizontalScrollMarginInPixels = @getHorizontalScrollMarginInPixels()
{top, left} = @pixelRectForScreenRange(new Range(screenRange.start, screenRange.start))
{top: endTop, left: endLeft, height: endHeight} = @pixelRectForScreenRange(new Range(screenRange.end, screenRange.end))
bottom = endTop + endHeight
right = endLeft
top += @scrollTop
bottom += @scrollTop
left += @scrollLeft
right += @scrollLeft
if options?.center
desiredScrollCenter = (top + bottom) / 2
unless @getScrollTop() < desiredScrollCenter < @getScrollBottom()
desiredScrollTop = desiredScrollCenter - @getClientHeight() / 2
desiredScrollBottom = desiredScrollCenter + @getClientHeight() / 2
else
desiredScrollTop = top - verticalScrollMarginInPixels
desiredScrollBottom = bottom + verticalScrollMarginInPixels
desiredScrollLeft = left - horizontalScrollMarginInPixels
desiredScrollRight = right + horizontalScrollMarginInPixels
if options?.reversed ? true
if desiredScrollBottom > @getScrollBottom()
@setScrollBottom(desiredScrollBottom)
if desiredScrollTop < @getScrollTop()
@setScrollTop(desiredScrollTop)
if desiredScrollRight > @getScrollRight()
@setScrollRight(desiredScrollRight)
if desiredScrollLeft < @getScrollLeft()
@setScrollLeft(desiredScrollLeft)
else
if desiredScrollTop < @getScrollTop()
@setScrollTop(desiredScrollTop)
if desiredScrollBottom > @getScrollBottom()
@setScrollBottom(desiredScrollBottom)
if desiredScrollLeft < @getScrollLeft()
@setScrollLeft(desiredScrollLeft)
if desiredScrollRight > @getScrollRight()
@setScrollRight(desiredScrollRight)
@pendingScrollLogicalPosition = null
commitPendingScrollLeftPosition: ->
return unless @pendingScrollLeft?
scrollLeft = @constrainScrollLeft(@pendingScrollLeft)
scrollLeft = @constrainScrollLeft(scrollLeft)
if scrollLeft isnt @scrollLeft and not Number.isNaN(scrollLeft)
@realScrollLeft = scrollLeft
@scrollLeft = Math.round(scrollLeft)
@@ -1567,10 +1579,10 @@ class TextEditorPresenter
@pendingScrollLeft = null
commitPendingScrollTopPosition: ->
return unless @pendingScrollTop?
commitScrollTop: (scrollTop) ->
return unless scrollTop?
scrollTop = @constrainScrollTop(@pendingScrollTop)
scrollTop = @constrainScrollTop(scrollTop)
if scrollTop isnt @scrollTop and not Number.isNaN(scrollTop)
@realScrollTop = scrollTop
@scrollTop = Math.round(scrollTop)
@@ -1580,10 +1592,8 @@ class TextEditorPresenter
@didStartScrolling()
@emitter.emit 'did-change-scroll-top', @scrollTop
@pendingScrollTop = null
restoreScrollPosition: ->
return if @hasRestoredScrollPosition or not @hasPixelPositionRequirements()
return unless @hasPixelPositionRequirements()
@setScrollTop(@scrollRow * @lineHeight) if @scrollRow?
@setScrollLeft(@scrollColumn * @baseCharacterWidth) if @scrollColumn?
@@ -1591,10 +1601,11 @@ class TextEditorPresenter
@hasRestoredScrollPosition = true
updateScrollPosition: ->
@restoreScrollPosition()
@commitPendingLogicalScrollPosition()
@commitPendingScrollLeftPosition()
@commitPendingScrollTopPosition()
@restoreScrollPosition() unless @hasRestoredScrollPosition
@commitScrollQueue()
commitScrollQueue: ->
@scrollQueue.commit()
onDidChangeScrollTop: (callback) ->
@emitter.on 'did-change-scroll-top', callback