WIP: Introduce dummy scrollbars

Still need tests on all of this
This commit is contained in:
Nathan Sobo
2017-03-20 15:21:05 -07:00
committed by Antonio Scandurra
parent 5a47f179e3
commit 2075f06404
4 changed files with 247 additions and 35 deletions

View File

@@ -696,6 +696,11 @@ class AtomEnvironment extends Model
callback = => @applicationDelegate.didSaveWindowState()
@saveState({isUnloading: true}).catch(callback).then(callback)
didChangeStyles = @didChangeStyles.bind(this)
@disposables.add(@styles.onDidAddStyleElement(didChangeStyles))
@disposables.add(@styles.onDidUpdateStyleElement(didChangeStyles))
@disposables.add(@styles.onDidRemoveStyleElement(didChangeStyles))
@listenForUpdates()
@registerDefaultTargetForKeymaps()
@@ -798,6 +803,10 @@ class AtomEnvironment extends Model
@windowEventHandler?.unsubscribe()
@windowEventHandler = null
didChangeStyles: (styleElement) ->
if styleElement.textContent.indexOf('scrollbar') >= 0
TextEditor.didUpdateScrollbarStyles()
###
Section: Messaging the User
###

View File

@@ -24,6 +24,14 @@ function scaleMouseDragAutoscrollDelta (delta) {
module.exports =
class TextEditorComponent {
static didUpdateScrollbarStyles () {
if (this.attachedComponents) {
this.attachedComponents.forEach((component) => {
component.didUpdateScrollbarStyles()
})
}
}
constructor (props) {
this.props = props
@@ -47,6 +55,8 @@ class TextEditorComponent {
this.horizontalPixelPositionsByScreenLineId = new Map() // Values are maps from column to horiontal pixel positions
this.lineNodesByScreenLineId = new Map()
this.textNodesByScreenLineId = new Map()
this.scrollbarsVisible = true
this.refreshScrollbarStyling = false
this.pendingAutoscroll = null
this.scrollTop = 0
this.scrollLeft = 0
@@ -66,7 +76,7 @@ class TextEditorComponent {
cursors: []
}
if (this.props.model) this.observeModel()
this.observeModel()
resizeDetector.listenTo(this.element, this.didResize.bind(this))
etch.updateSync(this)
@@ -98,26 +108,38 @@ class TextEditorComponent {
this.resolveNextUpdatePromise = null
}
const wasHorizontalScrollbarVisible = this.isHorizontalScrollbarVisible()
this.horizontalPositionsToMeasure.clear()
if (this.pendingAutoscroll) this.autoscrollVertically()
this.populateVisibleRowRange()
const longestLineToMeasure = this.checkForNewLongestLine()
this.queryScreenLinesToRender()
this.queryDecorationsToRender()
this.scrollbarsVisible = !this.refreshScrollbarStyling
etch.updateSync(this)
this.measureHorizontalPositions()
if (longestLineToMeasure) this.measureLongestLineWidth(longestLineToMeasure)
this.updateAbsolutePositionedDecorations()
if (this.pendingAutoscroll) {
this.autoscrollHorizontally()
if (!wasHorizontalScrollbarVisible && this.isHorizontalScrollbarVisible()) {
this.autoscrollVertically()
}
this.pendingAutoscroll = null
}
this.scrollbarsVisible = true
etch.updateSync(this)
if (this.pendingAutoscroll) {
this.autoscrollHorizontally()
this.pendingAutoscroll = null
}
this.currentFrameLineNumberGutterProps = null
if (this.refreshScrollbarStyling) {
this.measureScrollbarDimensions()
this.refreshScrollbarStyling = false
etch.updateSync(this)
}
}
checkIfScrollDimensionsChanged () {
@@ -134,11 +156,12 @@ class TextEditorComponent {
render () {
const {model} = this.props
const style = {}
if (!model.getAutoHeight() && !model.getAutoWidth()) {
style.contain = 'strict'
}
if (this.measurements) {
if (model.getAutoHeight()) {
style.height = this.getContentHeight() + 'px'
@@ -294,7 +317,8 @@ class TextEditorComponent {
className: 'scroll-view',
style
},
this.renderContent()
this.renderContent(),
this.renderDummyScrollbars()
)
}
@@ -478,6 +502,65 @@ class TextEditorComponent {
})
}
renderDummyScrollbars () {
if (this.scrollbarsVisible) {
let scrollHeight, scrollTop, horizontalScrollbarHeight,
scrollWidth, scrollLeft, verticalScrollbarWidth, forceScrollbarVisible
if (this.measurements) {
scrollHeight = this.getScrollHeight()
scrollWidth = this.getScrollWidth()
scrollTop = this.getScrollTop()
scrollLeft = this.getScrollLeft()
horizontalScrollbarHeight =
this.isHorizontalScrollbarVisible()
? this.getHorizontalScrollbarHeight()
: 0
verticalScrollbarWidth =
this.isVerticalScrollbarVisible()
? this.getVerticalScrollbarWidth()
: 0
forceScrollbarVisible = this.refreshScrollbarStyling
} else {
forceScrollbarVisible = true
}
const elements = [
$(DummyScrollbarComponent, {
ref: 'verticalScrollbar',
orientation: 'vertical',
scrollHeight, scrollTop, horizontalScrollbarHeight, forceScrollbarVisible
}),
$(DummyScrollbarComponent, {
ref: 'horizontalScrollbar',
orientation: 'horizontal',
scrollWidth, scrollLeft, verticalScrollbarWidth, forceScrollbarVisible
})
]
// If both scrollbars are visible, push a dummy element to force a "corner"
// to render where the two scrollbars meet at the lower right
if (verticalScrollbarWidth > 0 && horizontalScrollbarHeight > 0) {
elements.push($.div(
{
style: {
position: 'absolute',
height: '20px',
width: '20px',
bottom: 0,
right: 0,
overflow: 'scroll'
}
}
))
}
return elements
} else {
return null
}
}
// This is easier to mock
getPlatform () {
return process.platform
@@ -679,6 +762,10 @@ class TextEditorComponent {
} else {
this.didHide()
}
if (!this.constructor.attachedComponents) {
this.constructor.attachedComponents = new Set()
}
this.constructor.attachedComponents.add(this)
}
}
@@ -686,6 +773,7 @@ class TextEditorComponent {
if (this.attached) {
this.didHide()
this.attached = false
this.constructor.attachedComponents.delete(this)
}
}
@@ -781,6 +869,11 @@ class TextEditorComponent {
}
}
didUpdateScrollbarStyles () {
this.refreshScrollbarStyling = true
this.scheduleUpdate()
}
didTextInput (event) {
event.stopPropagation()
@@ -1175,6 +1268,30 @@ class TextEditorComponent {
this.measureCharacterDimensions()
this.measureGutterDimensions()
this.measureClientContainerDimensions()
this.measureScrollbarDimensions()
}
measureCharacterDimensions () {
this.measurements.lineHeight = this.refs.characterMeasurementLine.getBoundingClientRect().height
this.measurements.baseCharacterWidth = this.refs.normalWidthCharacterSpan.getBoundingClientRect().width
this.measurements.doubleWidthCharacterWidth = this.refs.doubleWidthCharacterSpan.getBoundingClientRect().width
this.measurements.halfWidthCharacterWidth = this.refs.halfWidthCharacterSpan.getBoundingClientRect().width
this.measurements.koreanCharacterWidth = this.refs.koreanCharacterSpan.getBoundingClientRect().widt
this.props.model.setDefaultCharWidth(
this.measurements.baseCharacterWidth,
this.measurements.doubleWidthCharacterWidth,
this.measurements.halfWidthCharacterWidth,
this.measurements.koreanCharacterWidth
)
}
measureGutterDimensions () {
if (this.refs.lineNumberGutter) {
this.measurements.lineNumberGutterWidth = this.refs.lineNumberGutter.offsetWidth
} else {
this.measurements.lineNumberGutterWidth = 0
}
}
measureClientContainerDimensions () {
@@ -1195,19 +1312,9 @@ class TextEditorComponent {
return dimensionsChanged
}
measureCharacterDimensions () {
this.measurements.lineHeight = this.refs.characterMeasurementLine.getBoundingClientRect().height
this.measurements.baseCharacterWidth = this.refs.normalWidthCharacterSpan.getBoundingClientRect().width
this.measurements.doubleWidthCharacterWidth = this.refs.doubleWidthCharacterSpan.getBoundingClientRect().width
this.measurements.halfWidthCharacterWidth = this.refs.halfWidthCharacterSpan.getBoundingClientRect().width
this.measurements.koreanCharacterWidth = this.refs.koreanCharacterSpan.getBoundingClientRect().widt
this.props.model.setDefaultCharWidth(
this.measurements.baseCharacterWidth,
this.measurements.doubleWidthCharacterWidth,
this.measurements.halfWidthCharacterWidth,
this.measurements.koreanCharacterWidth
)
measureScrollbarDimensions () {
this.measurements.verticalScrollbarWidth = this.refs.verticalScrollbar.getRealScrollbarWidth()
this.measurements.horizontalScrollbarHeight = this.refs.horizontalScrollbar.getRealScrollbarHeight()
}
checkForNewLongestLine () {
@@ -1228,14 +1335,6 @@ class TextEditorComponent {
this.longestLineToMeasure = null
}
measureGutterDimensions () {
if (this.refs.lineNumberGutter) {
this.measurements.lineNumberGutterWidth = this.refs.lineNumberGutter.offsetWidth
} else {
this.measurements.lineNumberGutterWidth = 0
}
}
requestHorizontalMeasurement (row, column) {
if (column === 0) return
let columns = this.horizontalPositionsToMeasure.get(row)
@@ -1445,11 +1544,48 @@ class TextEditorComponent {
}
getScrollContainerClientWidth () {
return this.getScrollContainerWidth()
if (this.isVerticalScrollbarVisible()) {
return this.getScrollContainerWidth() - this.getVerticalScrollbarWidth()
} else {
return this.getScrollContainerWidth()
}
}
getScrollContainerClientHeight () {
return this.getScrollContainerHeight()
if (this.isHorizontalScrollbarVisible()) {
return this.getScrollContainerHeight() - this.getHorizontalScrollbarHeight()
} else {
return this.getScrollContainerHeight()
}
}
isVerticalScrollbarVisible () {
return (
this.getContentHeight() > this.getScrollContainerHeight() ||
this.isContentMinimallyOverlappingBothScrollbars()
)
}
isHorizontalScrollbarVisible () {
return (
!this.props.model.isSoftWrapped() &&
(
this.getContentWidth() > this.getScrollContainerWidth() ||
this.isContentMinimallyOverlappingBothScrollbars()
)
)
}
isContentMinimallyOverlappingBothScrollbars () {
const clientHeightWithHorizontalScrollbar =
this.getScrollContainerHeight() - this.getHorizontalScrollbarHeight()
const clientWidthWithVerticalScrollbar =
this.getScrollContainerWidth() - this.getVerticalScrollbarWidth()
return (
this.getContentHeight() > clientHeightWithHorizontalScrollbar &&
this.getContentWidth() > clientWidthWithVerticalScrollbar
)
}
getScrollHeight () {
@@ -1491,6 +1627,14 @@ class TextEditorComponent {
return this.measurements.lineNumberGutterWidth
}
getVerticalScrollbarWidth () {
return this.measurements.verticalScrollbarWidth
}
getHorizontalScrollbarHeight () {
return this.measurements.horizontalScrollbarHeight
}
getRowsPerTile () {
return this.props.rowsPerTile || DEFAULT_ROWS_PER_TILE
}
@@ -1619,6 +1763,64 @@ class TextEditorComponent {
}
}
class DummyScrollbarComponent {
constructor (props) {
this.props = props
etch.initialize(this)
}
update (props) {
this.props = props
etch.updateSync(this)
}
render () {
let scrollTop = 0
let scrollLeft = 0
const outerStyle = {
position: 'absolute',
contain: 'strict',
zIndex: 1
}
const innerStyle = {}
if (this.props.orientation === 'horizontal') {
scrollLeft = this.props.scrollLeft || 0
let right = (this.props.verticalScrollbarWidth || 0)
outerStyle.bottom = 0
outerStyle.left = 0
outerStyle.right = right + 'px'
outerStyle.height = '20px'
outerStyle.overflowY = 'hidden'
outerStyle.overflowX = this.props.forceScrollbarVisible ? 'scroll' : 'auto'
innerStyle.height = '20px'
innerStyle.width = (this.props.scrollWidth || 0) + 'px'
} else {
scrollTop = this.props.scrollTop || 0
let bottom = (this.props.horizontalScrollbarHeight || 0)
outerStyle.right = 0
outerStyle.top = 0
outerStyle.bottom = bottom + 'px'
outerStyle.width = '20px'
outerStyle.overflowX = 'hidden'
outerStyle.overflowY = this.props.forceScrollbarVisible ? 'scroll' : 'auto'
innerStyle.width = '20px'
innerStyle.height = (this.props.scrollHeight || 0) + 'px'
}
return $.div({style: outerStyle, scrollTop, scrollLeft},
$.div({style: innerStyle})
)
}
getRealScrollbarWidth () {
return this.element.offsetWidth - this.element.clientWidth
}
getRealScrollbarHeight () {
return this.element.offsetHeight - this.element.clientHeight
}
}
class LineNumberGutterComponent {
constructor (props) {
this.props = props

View File

@@ -61,6 +61,10 @@ class TextEditor extends Model
@setClipboard: (clipboard) ->
@clipboard = clipboard
@didUpdateScrollbarStyles: ->
TextEditorComponent ?= require './text-editor-component'
TextEditorComponent.didUpdateScrollbarStyles()
serializationVersion: 1
buffer: null
@@ -3561,7 +3565,7 @@ class TextEditor extends Model
@component.element
else
TextEditorComponent ?= require('./text-editor-component')
new TextEditorComponent({model: this})
new TextEditorComponent({model: this, styleManager: atom.styles})
@component.element
# Essential: Retrieves the greyed out placeholder of a mini editor.