From b94576dc0990012e29178400d4345efc72ed0faa Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 4 Feb 2015 14:48:17 -0700 Subject: [PATCH] Pass view measurements to model via presenter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Someday, we won’t need to pass measurements to the model anymore. --- spec/text-editor-presenter-spec.coffee | 30 +++++++++++++++++++-- src/text-editor-component.coffee | 33 +++-------------------- src/text-editor-presenter.coffee | 36 +++++++++++++++++++++++--- 3 files changed, 63 insertions(+), 36 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 92ae1e5d6..af2f320c4 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -137,7 +137,7 @@ describe "TextEditorPresenter", -> expect(presenter.state.horizontalScrollbar.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide it "updates when ::softWrapped changes on the editor", -> - presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) + presenter = buildPresenter(contentFrameWidth: 470, baseCharacterWidth: 10) expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 expectStateUpdate presenter, -> editor.setSoftWrapped(true) expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() @@ -180,6 +180,11 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> editor.getBuffer().insert([6, 0], new Array(100).join('x')) expect(presenter.state.horizontalScrollbar.scrollLeft).toBe scrollLeftBefore + it "never goes negative", -> + presenter = buildPresenter(scrollLeft: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) + expectStateUpdate presenter, -> presenter.setScrollLeft(-300) + expect(presenter.state.horizontalScrollbar.scrollLeft).toBe 0 + describe ".verticalScrollbar", -> describe ".visible", -> it "is true if the scrollHeight exceeds the computed client height", -> @@ -278,6 +283,11 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> editor.getBuffer().insert([9, Infinity], '\n\n\n') expect(presenter.state.verticalScrollbar.scrollTop).toBe scrollTopBefore + it "never goes negative", -> + presenter = buildPresenter(scrollTop: 10, height: 50, horizontalScrollbarHeight: 10) + expectStateUpdate presenter, -> presenter.setScrollTop(-100) + expect(presenter.state.verticalScrollbar.scrollTop).toBe 0 + describe ".content", -> describe ".scrollingVertically", -> it "is true for ::stoppedScrollingDelay milliseconds following a changes to ::scrollTop", -> @@ -354,7 +364,7 @@ describe "TextEditorPresenter", -> expect(presenter.state.content.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide it "updates when ::softWrapped changes on the editor", -> - presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) + presenter = buildPresenter(contentFrameWidth: 470, baseCharacterWidth: 10) expect(presenter.state.content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 expectStateUpdate presenter, -> editor.setSoftWrapped(true) expect(presenter.state.content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() @@ -397,6 +407,12 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> editor.getBuffer().insert([9, Infinity], '\n\n\n') expect(presenter.state.content.scrollTop).toBe scrollTopBefore + it "never goes negative", -> + presenter = buildPresenter(scrollTop: 10, height: 50, horizontalScrollbarHeight: 10) + expectStateUpdate presenter, -> presenter.setScrollTop(-100) + expect(presenter.state.content.scrollTop).toBe 0 + + describe ".scrollLeft", -> it "tracks the value of ::scrollLeft", -> presenter = buildPresenter(scrollLeft: 10, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) expect(presenter.state.content.scrollLeft).toBe 10 @@ -422,6 +438,11 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> editor.getBuffer().insert([6, 0], new Array(100).join('x')) expect(presenter.state.content.scrollLeft).toBe scrollLeftBefore + it "never goes negative", -> + presenter = buildPresenter(scrollLeft: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) + expectStateUpdate presenter, -> presenter.setScrollLeft(-300) + expect(presenter.state.content.scrollLeft).toBe 0 + describe ".indentGuidesVisible", -> it "is initialized based on the editor.showIndentGuide config setting", -> presenter = buildPresenter() @@ -1477,6 +1498,11 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> editor.getBuffer().insert([9, Infinity], '\n\n\n') expect(presenter.state.gutter.scrollTop).toBe scrollTopBefore + it "never goes negative", -> + presenter = buildPresenter(scrollTop: 10, height: 50, horizontalScrollbarHeight: 10) + expectStateUpdate presenter, -> presenter.setScrollTop(-100) + expect(presenter.state.gutter.scrollTop).toBe 0 + describe ".backgroundColor", -> it "is assigned to ::gutterBackgroundColor if present, and to ::backgroundColor otherwise", -> presenter = buildPresenter(backgroundColor: "rgba(255, 0, 0, 0)", gutterBackgroundColor: "rgba(0, 255, 0, 0)") diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 9badc6804..4af44f8ea 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -230,8 +230,6 @@ TextEditorComponent = React.createClass @subscribe editor.observeGrammar(@onGrammarChanged) @subscribe editor.observeCursors(@onCursorAdded) @subscribe editor.observeSelections(@onSelectionAdded) - @subscribe editor.$scrollTop.changes, @onScrollTopChanged - @subscribe editor.$scrollLeft.changes, @onScrollLeftChanged @subscribe editor.$verticalScrollbarWidth.changes, @requestUpdate @subscribe editor.$horizontalScrollbarHeight.changes, @requestUpdate @subscribe editor.$height.changes, @requestUpdate @@ -356,7 +354,6 @@ TextEditorComponent = React.createClass pendingScrollTop = @pendingScrollTop @pendingScrollTop = null @presenter.setScrollTop(pendingScrollTop) - @props.editor.setScrollTop(pendingScrollTop) onHorizontalScroll: (scrollLeft) -> {editor} = @props @@ -367,7 +364,6 @@ TextEditorComponent = React.createClass @pendingScrollLeft = scrollLeft unless animationFramePending @requestAnimationFrame => - @props.editor.setScrollLeft(@pendingScrollLeft) @presenter.setScrollLeft(@pendingScrollLeft) @pendingScrollLeft = null @@ -389,13 +385,13 @@ TextEditorComponent = React.createClass if Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY) # Scrolling horizontally previousScrollLeft = editor.getScrollLeft() - editor.setScrollLeft(previousScrollLeft - Math.round(wheelDeltaX * @scrollSensitivity)) + @presenter.setScrollLeft(previousScrollLeft - Math.round(wheelDeltaX * @scrollSensitivity)) event.preventDefault() unless previousScrollLeft is editor.getScrollLeft() else # Scrolling vertically @presenter.setMouseWheelScreenRow(@screenRowForNode(event.target)) - previousScrollTop = editor.getScrollTop() - editor.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity)) + previousScrollTop = @presenter.scrollTop + @presenter.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity)) event.preventDefault() unless previousScrollTop is editor.getScrollTop() onScrollViewScroll: -> @@ -550,24 +546,6 @@ TextEditorComponent = React.createClass @selectionChanged = true @requestUpdate() - onScrollTopChanged: -> - @presenter.setScrollTop(@props.editor.getScrollTop()) - @requestUpdate() - @onStoppedScrollingAfterDelay ?= debounce(@onStoppedScrolling, 200) - @onStoppedScrollingAfterDelay() - - onScrollLeftChanged: -> - @presenter.setScrollLeft(@props.editor.getScrollLeft()) - @requestUpdate() - - onStoppedScrolling: -> - return unless @isMounted() - - @mouseWheelScreenRow = null - @requestUpdate() - - onStoppedScrollingAfterDelay: null # created lazily - onCursorAdded: (cursor) -> @subscribe cursor.onDidChangePosition @onCursorMoved @@ -675,18 +653,15 @@ TextEditorComponent = React.createClass height = hostElement.offsetHeight if height > 0 @presenter.setHeight(height) - editor.setHeight(height) else @presenter.setAutoHeight(true) @presenter.setHeight(null) - editor.setHeight(null) clientWidth = scrollViewNode.clientWidth paddingLeft = parseInt(getComputedStyle(scrollViewNode).paddingLeft) clientWidth -= paddingLeft if clientWidth > 0 @presenter.setContentFrameWidth(clientWidth) - editor.setWidth(clientWidth) sampleFontStyling: -> oldFontSize = @fontSize @@ -737,9 +712,7 @@ TextEditorComponent = React.createClass width = (cornerNode.offsetWidth - cornerNode.clientWidth) or 15 height = (cornerNode.offsetHeight - cornerNode.clientHeight) or 15 - editor.setVerticalScrollbarWidth(width) @presenter.setVerticalScrollbarWidth(width) - editor.setHorizontalScrollbarHeight(height) @presenter.setHorizontalScrollbarHeight(height) cornerNode.style.display = originalDisplayValue diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index bd99f2c25..468c8afc5 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -18,6 +18,7 @@ class TextEditorPresenter @disposables = new CompositeDisposable @emitter = new Emitter @charWidthsByScope = {} + @transferMeasurementsToModel() @observeModel() @observeConfig() @buildState() @@ -29,6 +30,16 @@ class TextEditorPresenter onDidUpdateState: (callback) -> @emitter.on 'did-update-state', callback + transferMeasurementsToModel: -> + @model.setHeight(@height) if @height? + @model.setWidth(@contentFrameWidth) if @contentFrameWidth? + @model.setLineHeightInPixels(@lineHeight) if @lineHeight? + @model.setDefaultCharWidth(@baseCharacterWidth) if @baseCharacterWidth? + @model.setScrollTop(@scrollTop) if @scrollTop? + @model.setScrollLeft(@scrollLeft) if @scrollLeft? + @model.setVerticalScrollbarWidth(@verticalScrollbarWidth) if @verticalScrollbarWidth? + @model.setHorizontalScrollbarHeight(@horizontalScrollbarHeight) if @horizontalScrollbarHeight? + observeModel: -> @disposables.add @model.onDidChange => @updateHeightState() @@ -49,6 +60,8 @@ class TextEditorPresenter @updateLineNumbersState() @disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this)) @disposables.add @model.onDidAddCursor(@didAddCursor.bind(this)) + @disposables.add @model.onDidChangeScrollTop(@setScrollTop.bind(this)) + @disposables.add @model.onDidChangeScrollLeft(@setScrollLeft.bind(this)) @observeDecoration(decoration) for decoration in @model.getDecorations() @observeCursor(cursor) for cursor in @model.getCursors() @@ -346,16 +359,22 @@ class TextEditorPresenter @contentFrameWidth - @computeVerticalScrollbarWidth() computeScrollTop: -> + @scrollTop = @constrainScrollTop(@scrollTop) + + constrainScrollTop: (scrollTop) -> if @hasRequiredMeasurements() - @scrollTop = Math.min(@scrollTop, @computeScrollHeight() - @computeClientHeight()) + Math.max(0, Math.min(scrollTop, @computeScrollHeight() - @computeClientHeight())) else - @scrollTop + Math.max(0, scrollTop) if scrollTop? computeScrollLeft: -> + @scrollLeft = @constrainScrollLeft(@scrollLeft) + + constrainScrollLeft: (scrollLeft) -> if @hasRequiredMeasurements() - @scrollLeft = Math.min(@scrollLeft, @computeScrollWidth() - @computeClientWidth()) + Math.max(0, Math.min(scrollLeft, @computeScrollWidth() - @computeClientWidth())) else - @scrollLeft + Math.max(0, scrollLeft) if scrollLeft? computeHorizontalScrollbarHeight: -> contentWidth = @computeContentWidth() @@ -423,8 +442,10 @@ class TextEditorPresenter @horizontalScrollbarHeight? setScrollTop: (scrollTop) -> + scrollTop = @constrainScrollTop(scrollTop) unless @scrollTop is scrollTop @scrollTop = scrollTop + @model.setScrollTop(scrollTop) @didStartScrolling() @updateVerticalScrollState() @updateDecorations() @@ -450,19 +471,23 @@ class TextEditorPresenter @emitter.emit 'did-update-state' setScrollLeft: (scrollLeft) -> + scrollLeft = @constrainScrollLeft(scrollLeft) unless @scrollLeft is scrollLeft @scrollLeft = scrollLeft + @model.setScrollLeft(scrollLeft) @updateHorizontalScrollState() setHorizontalScrollbarHeight: (horizontalScrollbarHeight) -> unless @horizontalScrollbarHeight is horizontalScrollbarHeight @horizontalScrollbarHeight = horizontalScrollbarHeight + @model.setHorizontalScrollbarHeight(horizontalScrollbarHeight) @updateScrollbarsState() @updateVerticalScrollState() setVerticalScrollbarWidth: (verticalScrollbarWidth) -> unless @verticalScrollbarWidth is verticalScrollbarWidth @verticalScrollbarWidth = verticalScrollbarWidth + @model.setVerticalScrollbarWidth(verticalScrollbarWidth) @updateScrollbarsState() @updateHorizontalScrollState() @@ -474,6 +499,7 @@ class TextEditorPresenter setHeight: (height) -> unless @height is height @height = height + @model.setHeight(height) @updateVerticalScrollState() @updateScrollbarsState() @updateDecorations() @@ -487,6 +513,7 @@ class TextEditorPresenter setContentFrameWidth: (contentFrameWidth) -> unless @contentFrameWidth is contentFrameWidth @contentFrameWidth = contentFrameWidth + @model.setWidth(contentFrameWidth) @updateVerticalScrollState() @updateHorizontalScrollState() @updateScrollbarsState() @@ -523,6 +550,7 @@ class TextEditorPresenter setBaseCharacterWidth: (baseCharacterWidth) -> unless @baseCharacterWidth is baseCharacterWidth @baseCharacterWidth = baseCharacterWidth + @model.setDefaultCharWidth(baseCharacterWidth) @characterWidthsChanged() getScopedCharWidth: (scopeNames, char) ->