mirror of
https://github.com/atom/atom.git
synced 2026-01-25 06:48:28 -05:00
Implement autoscroll when mouse is dragged on content
This commit is contained in:
committed by
Antonio Scandurra
parent
5594c9d82f
commit
35ae3fb08f
@@ -901,6 +901,65 @@ describe('TextEditorComponent', () => {
|
||||
didDrag(clientPositionForCharacter(component, 4, 10))
|
||||
expect(editor.getSelectedScreenRange()).toEqual([[2, 0], [5, 0]])
|
||||
})
|
||||
|
||||
it('autoscrolls the content when dragging near the edge of the screen', async () => {
|
||||
const {component, editor} = buildComponent({width: 200, height: 200})
|
||||
const {scroller} = component.refs
|
||||
spyOn(component, 'handleMouseDragUntilMouseUp')
|
||||
|
||||
let previousScrollTop = 0
|
||||
let previousScrollLeft = 0
|
||||
function assertScrolledDownAndRight () {
|
||||
expect(scroller.scrollTop).toBeGreaterThan(previousScrollTop)
|
||||
previousScrollTop = scroller.scrollTop
|
||||
expect(scroller.scrollLeft).toBeGreaterThan(previousScrollLeft)
|
||||
previousScrollLeft = scroller.scrollLeft
|
||||
}
|
||||
|
||||
function assertScrolledUpAndLeft () {
|
||||
expect(scroller.scrollTop).toBeLessThan(previousScrollTop)
|
||||
previousScrollTop = scroller.scrollTop
|
||||
expect(scroller.scrollLeft).toBeLessThan(previousScrollLeft)
|
||||
previousScrollLeft = scroller.scrollLeft
|
||||
}
|
||||
|
||||
component.didMouseDownOnContent({detail: 1, clientX: 100, clientY: 100})
|
||||
const [didDrag, didStopDragging] = component.handleMouseDragUntilMouseUp.argsForCall[0]
|
||||
didDrag({clientX: 199, clientY: 199})
|
||||
assertScrolledDownAndRight()
|
||||
didDrag({clientX: 199, clientY: 199})
|
||||
assertScrolledDownAndRight()
|
||||
didDrag({clientX: 199, clientY: 199})
|
||||
assertScrolledDownAndRight()
|
||||
didDrag({clientX: component.getGutterContainerWidth() + 1, clientY: 1})
|
||||
assertScrolledUpAndLeft()
|
||||
didDrag({clientX: component.getGutterContainerWidth() + 1, clientY: 1})
|
||||
assertScrolledUpAndLeft()
|
||||
didDrag({clientX: component.getGutterContainerWidth() + 1, clientY: 1})
|
||||
assertScrolledUpAndLeft()
|
||||
|
||||
// Don't artificially update scroll measurements beyond the minimum or
|
||||
// maximum possible scroll positions
|
||||
expect(scroller.scrollTop).toBe(0)
|
||||
expect(scroller.scrollLeft).toBe(0)
|
||||
didDrag({clientX: component.getGutterContainerWidth() + 1, clientY: 1})
|
||||
expect(component.measurements.scrollTop).toBe(0)
|
||||
expect(scroller.scrollTop).toBe(0)
|
||||
expect(component.measurements.scrollLeft).toBe(0)
|
||||
expect(scroller.scrollLeft).toBe(0)
|
||||
|
||||
const maxScrollTop = scroller.scrollHeight - scroller.clientHeight
|
||||
const maxScrollLeft = scroller.scrollWidth - scroller.clientWidth
|
||||
scroller.scrollTop = maxScrollTop
|
||||
scroller.scrollLeft = maxScrollLeft
|
||||
await component.getNextUpdatePromise()
|
||||
|
||||
didDrag({clientX: 199, clientY: 199})
|
||||
didDrag({clientX: 199, clientY: 199})
|
||||
didDrag({clientX: 199, clientY: 199})
|
||||
expect(component.measurements.scrollTop).toBe(maxScrollTop)
|
||||
expect(component.measurements.scrollLeft).toBe(maxScrollLeft)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -14,6 +14,11 @@ const DOUBLE_WIDTH_CHARACTER = '我'
|
||||
const HALF_WIDTH_CHARACTER = 'ハ'
|
||||
const KOREAN_CHARACTER = '세'
|
||||
const NBSP_CHARACTER = '\u00a0'
|
||||
const MOUSE_DRAG_AUTOSCROLL_MARGIN = 40
|
||||
|
||||
function scaleMouseDragAutoscrollDelta (delta) {
|
||||
return Math.pow(delta / 3, 3) / 280
|
||||
}
|
||||
|
||||
module.exports =
|
||||
class TextEditorComponent {
|
||||
@@ -789,6 +794,7 @@ class TextEditorComponent {
|
||||
|
||||
this.handleMouseDragUntilMouseUp(
|
||||
(event) => {
|
||||
this.autoscrollOnMouseDrag(event)
|
||||
const screenPosition = this.screenPositionForMouseEvent(event)
|
||||
model.selectToScreenPosition(screenPosition, {suppressSelectionMerge: true, autoscroll: false})
|
||||
this.updateSync()
|
||||
@@ -835,7 +841,59 @@ class TextEditorComponent {
|
||||
window.addEventListener('mouseup', didMouseUp)
|
||||
}
|
||||
|
||||
autoscrollOnMouseDrag ({clientX, clientY}) {
|
||||
let {top, bottom, left, right} = this.refs.scroller.getBoundingClientRect()
|
||||
top += MOUSE_DRAG_AUTOSCROLL_MARGIN
|
||||
bottom -= MOUSE_DRAG_AUTOSCROLL_MARGIN
|
||||
left += this.getGutterContainerWidth() + MOUSE_DRAG_AUTOSCROLL_MARGIN
|
||||
right -= MOUSE_DRAG_AUTOSCROLL_MARGIN
|
||||
|
||||
let yDelta, yDirection
|
||||
if (clientY < top) {
|
||||
yDelta = top - clientY
|
||||
yDirection = -1
|
||||
} else if (clientY > bottom) {
|
||||
yDelta = clientY - bottom
|
||||
yDirection = 1
|
||||
}
|
||||
|
||||
let xDelta, xDirection
|
||||
if (clientX < left) {
|
||||
xDelta = left - clientX
|
||||
xDirection = -1
|
||||
} else if (clientX > right) {
|
||||
xDelta = clientX - right
|
||||
xDirection = 1
|
||||
}
|
||||
|
||||
let scrolled = false
|
||||
if (yDelta != null) {
|
||||
const scaledDelta = scaleMouseDragAutoscrollDelta(yDelta) * yDirection
|
||||
const newScrollTop = this.constrainScrollTop(this.measurements.scrollTop + scaledDelta)
|
||||
if (newScrollTop !== this.measurements.scrollTop) {
|
||||
this.measurements.scrollTop += scaledDelta
|
||||
this.refs.scroller.scrollTop += scaledDelta
|
||||
scrolled = true
|
||||
}
|
||||
}
|
||||
|
||||
if (xDelta != null) {
|
||||
const scaledDelta = scaleMouseDragAutoscrollDelta(xDelta) * xDirection
|
||||
const newScrollLeft = this.constrainScrollLeft(this.measurements.scrollLeft + scaledDelta)
|
||||
if (newScrollLeft !== this.measurements.scrollLeft) {
|
||||
this.measurements.scrollLeft += scaledDelta
|
||||
this.refs.scroller.scrollLeft += scaledDelta
|
||||
scrolled = true
|
||||
}
|
||||
}
|
||||
|
||||
if (scrolled) this.updateSync()
|
||||
}
|
||||
|
||||
screenPositionForMouseEvent ({clientX, clientY}) {
|
||||
const scrollerRect = this.refs.scroller.getBoundingClientRect()
|
||||
clientX = Math.min(scrollerRect.right, Math.max(scrollerRect.left, clientX))
|
||||
clientY = Math.min(scrollerRect.bottom, Math.max(scrollerRect.top, clientY))
|
||||
const linesRect = this.refs.lineTiles.getBoundingClientRect()
|
||||
return this.screenPositionForPixelPosition({
|
||||
top: clientY - linesRect.top,
|
||||
@@ -871,11 +929,11 @@ class TextEditorComponent {
|
||||
}
|
||||
|
||||
if (desiredScrollTop != null) {
|
||||
desiredScrollTop = Math.max(0, Math.min(desiredScrollTop, this.getScrollHeight() - this.getClientHeight()))
|
||||
desiredScrollTop = this.constrainScrollTop(desiredScrollTop)
|
||||
}
|
||||
|
||||
if (desiredScrollBottom != null) {
|
||||
desiredScrollBottom = Math.max(this.getClientHeight(), Math.min(desiredScrollBottom, this.getScrollHeight()))
|
||||
desiredScrollBottom = this.constrainScrollTop(desiredScrollBottom - this.getClientHeight()) + this.getClientHeight()
|
||||
}
|
||||
|
||||
if (!options || options.reversed !== false) {
|
||||
@@ -961,6 +1019,18 @@ class TextEditorComponent {
|
||||
return marginInBaseCharacters * baseCharacterWidth
|
||||
}
|
||||
|
||||
constrainScrollTop (desiredScrollTop) {
|
||||
return Math.max(
|
||||
0, Math.min(desiredScrollTop, this.getScrollHeight() - this.getClientHeight())
|
||||
)
|
||||
}
|
||||
|
||||
constrainScrollLeft (desiredScrollLeft) {
|
||||
return Math.max(
|
||||
0, Math.min(desiredScrollLeft, this.getScrollWidth() - this.getClientWidth())
|
||||
)
|
||||
}
|
||||
|
||||
performInitialMeasurements () {
|
||||
this.measurements = {}
|
||||
this.measureGutterDimensions()
|
||||
|
||||
Reference in New Issue
Block a user