mirror of
https://github.com/atom/atom.git
synced 2026-02-10 14:45:11 -05:00
WIP: Introduce dummy scrollbars
Still need tests on all of this
This commit is contained in:
committed by
Antonio Scandurra
parent
5a47f179e3
commit
2075f06404
@@ -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
|
||||
###
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user