Skip to content

Commit

Permalink
Refactor scrolling hierarchy
Browse files Browse the repository at this point in the history
This commit puts the scrollview as a sibling of the EditView.

Work in progress, there are a number of rough edges, but it's possible
to get an idea of the performance improvements.

Rough edges include:
* Scroll offset isn't applied to mouse events
* Line numbers are missing (gutter view is removed)
* Weirdness around size negotiation when scrollers pop in and out
* Scroll notification isn't sent to core on resize
* No horizontal scrolling
  • Loading branch information
raphlinus committed Dec 22, 2017
1 parent 6457825 commit dc4840e
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 46 deletions.
22 changes: 15 additions & 7 deletions XiEditor/EditView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ func colorToArgb(_ color: NSColor) -> UInt32 {
}

class EditView: NSView, NSTextInputClient, TextPlaneDelegate {
var scrollOrigin: NSPoint {
didSet {
needsDisplay = true
}
}

var dataSource: EditViewDataSource!

var lastDragLineCol: (Int, Int)?
Expand Down Expand Up @@ -132,13 +138,14 @@ class EditView: NSView, NSTextInputClient, TextPlaneDelegate {
required init?(coder: NSCoder) {
_selectedRange = NSMakeRange(NSNotFound, 0)
_markedRange = NSMakeRange(NSNotFound, 0)
scrollOrigin = NSPoint()
super.init(coder: coder)

wantsLayer = true
wantsBestResolutionOpenGLSurface = true
let glLayer = TextPlaneLayer()
glLayer.textDelegate = self
glLayer.frame = CGRect(x: 0, y: 0, width: 800, height: 600)
//glLayer.frame = CGRect(x: 0, y: 0, width: 800, height: 600)
layer = glLayer
}

Expand All @@ -161,10 +168,11 @@ class EditView: NSView, NSTextInputClient, TextPlaneDelegate {
*/

let topPad = dataSource.textMetrics.linespace - dataSource.textMetrics.ascent
let first = max(0, Int((floor(dirtyRect.origin.y - topPad) / dataSource.textMetrics.linespace)))
let firstVisible = max(0, Int((floor(dirtyRect.origin.y - topPad) / dataSource.textMetrics.linespace)))
let lastVisible = Int(ceil((dirtyRect.origin.y + dirtyRect.size.height - topPad) / dataSource.textMetrics.linespace))

let totalLines = dataSource.lines.height
let first = min(totalLines, firstVisible)
let last = min(totalLines, lastVisible)
let lines = dataSource.lines.blockingGet(lines: first..<last)

Expand Down Expand Up @@ -528,8 +536,8 @@ class EditView: NSView, NSTextInputClient, TextPlaneDelegate {
if dataSource.document.coreViewIdentifier == nil { return }
let linespace = dataSource.textMetrics.linespace
let topPad = linespace - dataSource.textMetrics.ascent
let first = max(0, Int((floor(dirtyRect.origin.y - topPad) / linespace)))
let lastVisible = Int(ceil((dirtyRect.origin.y + dirtyRect.size.height - topPad) / linespace))
let first = max(0, Int((floor(dirtyRect.origin.y - topPad + scrollOrigin.y) / linespace)))
let lastVisible = Int(ceil((dirtyRect.origin.y + dirtyRect.size.height - topPad + scrollOrigin.y) / linespace))

let totalLines = dataSource.lines.height
let last = min(totalLines, lastVisible)
Expand All @@ -554,14 +562,14 @@ class EditView: NSView, NSTextInputClient, TextPlaneDelegate {
dataSource.styleMap.applyStyles(builder: builder, styles: line.styles)
let textLine = builder.build(fontCache: renderer.fontCache)
textLines.append(textLine)
let y0 = topPad + dataSource.textMetrics.linespace * CGFloat(lineIx)
let y0 = topPad + dataSource.textMetrics.linespace * CGFloat(lineIx) - scrollOrigin.y
renderer.drawLineBg(line: textLine, x0: GLfloat(x0), yRange: GLfloat(y0)..<GLfloat(y0 + linespace), selColor: selArgb)
}

// second pass: draw text
for lineIx in first..<last {
if let textLine = textLines[lineIx - first] {
let y = topPad + dataSource.textMetrics.ascent + linespace * CGFloat(lineIx)
let y = topPad + dataSource.textMetrics.ascent + linespace * CGFloat(lineIx) - scrollOrigin.y
renderer.drawLine(line: textLine, x0: GLfloat(x0), y0: GLfloat(y))
}
}
Expand All @@ -575,7 +583,7 @@ class EditView: NSView, NSTextInputClient, TextPlaneDelegate {
for cursor in line.cursor {
let utf16Ix = utf8_offset_to_utf16(line.text, cursor)
let x = textLine.offsetForIndex(utf16Ix: utf16Ix)
let y0 = topPad + dataSource.textMetrics.linespace * CGFloat(lineIx)
let y0 = topPad + dataSource.textMetrics.linespace * CGFloat(lineIx) - scrollOrigin.y
let cursorWidth: GLfloat = 1.0
renderer.drawSolidRect(x: GLfloat(x0 + x) - 0.5 * cursorWidth, y: GLfloat(y0), width: cursorWidth, height: GLfloat(linespace), argb: cursorArgb)
}
Expand Down
10 changes: 6 additions & 4 deletions XiEditor/EditViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class EditViewController: NSViewController, EditViewDataSource, FindDelegate, Sc
override func viewDidLoad() {
super.viewDidLoad()
editView.dataSource = self
gutterView.dataSource = self
//gutterView.dataSource = self
scrollView.contentView.documentCursor = NSCursor.iBeam;
scrollView.automaticallyAdjustsContentInsets = false
(scrollView.contentView as? XiClipView)?.delegate = self
Expand All @@ -129,7 +129,7 @@ class EditViewController: NSViewController, EditViewDataSource, FindDelegate, Sc
func updateGutterWidth() {
let gutterColumns = "\(lineCount)".count
let chWidth = NSString(string: "9").size(withAttributes: textMetrics.attributes).width
gutterViewWidth.constant = chWidth * max(2, CGFloat(gutterColumns)) + 2 * gutterView.xPadding
//gutterViewWidth.constant = chWidth * max(2, CGFloat(gutterColumns)) + 2 * gutterView.xPadding
}

@objc func frameDidChangeNotification(_ notification: Notification) {
Expand All @@ -140,6 +140,8 @@ class EditViewController: NSViewController, EditViewDataSource, FindDelegate, Sc
/// Can be called manually with the current visible origin in order to ensure the line cache
/// is up to date.
func willScroll(to newOrigin: NSPoint) {
editView.scrollOrigin = newOrigin
// TODO: this calculation doesn't take into account toppad; do in EditView in DRY fashion
let first = Int(floor(newOrigin.y / textMetrics.linespace))
let height = Int(ceil((scrollView.contentView.bounds.size.height) / textMetrics.linespace)) + 1
let last = first + height
Expand All @@ -156,7 +158,7 @@ class EditViewController: NSViewController, EditViewDataSource, FindDelegate, Sc
updateEditViewHeight()
willScroll(to: scrollView.contentView.bounds.origin)
editView.needsDisplay = true
editView.needsDisplay = true
editView.needsDisplay = true // TODO: probably meant to be gutterview, but that's going away
}

fileprivate func updateEditViewHeight() {
Expand All @@ -181,7 +183,7 @@ class EditViewController: NSViewController, EditViewDataSource, FindDelegate, Sc
self?.updateEditViewHeight()
self?.editView.showBlinkingCursor = self?.editView.isFrontmostView ?? false
self?.editView.partialInvalidate(invalid: inval)
self?.gutterView.needsDisplay = true
//self?.gutterView.needsDisplay = true
}

}
Expand Down
57 changes: 22 additions & 35 deletions XiEditor/Main.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -32,70 +32,57 @@
<scene sceneID="LEm-46-qP4">
<objects>
<viewController id="K7o-qN-T9e" customClass="EditViewController" customModule="XiEditor" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="3ye-5b-Ab2">
<view key="view" wantsLayer="YES" id="3ye-5b-Ab2">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="Qwv-ci-fVA" customClass="XiScrollView" customModule="XiEditor" customModuleProvider="target">
<customView wantsLayer="YES" translatesAutoresizingMaskIntoConstraints="NO" id="q1P-kd-995" customClass="EditView" customModule="XiEditor" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<clipView key="contentView" id="1wJ-s5-kVO" customClass="XiClipView" customModule="XiEditor" customModuleProvider="target">
</customView>
<scrollView wantsLayer="YES" borderType="none" autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="Qwv-ci-fVA">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<clipView key="contentView" wantsLayer="YES" drawsBackground="NO" id="1wJ-s5-kVO" customClass="XiClipView" customModule="XiEditor" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="rTT-RO-I3T" customClass="EditContainerView" customModule="XiEditor" customModuleProvider="target">
<customView wantsLayer="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rTT-RO-I3T" customClass="EditContainerView" customModule="XiEditor" customModuleProvider="target">
<rect key="frame" x="0.0" y="2" width="480" height="268"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="3Js-0o-jqZ" customClass="GutterView" customModule="XiEditor" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="20" height="268"/>
<constraints>
<constraint firstAttribute="width" constant="20" id="Smj-nI-f5r"/>
</constraints>
</customView>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="q1P-kd-995" customClass="EditView" customModule="XiEditor" customModuleProvider="target">
<rect key="frame" x="20" y="0.0" width="460" height="268"/>
</customView>
</subviews>
<constraints>
<constraint firstItem="q1P-kd-995" firstAttribute="leading" secondItem="3Js-0o-jqZ" secondAttribute="trailing" id="LH9-Tc-TcJ"/>
<constraint firstAttribute="height" priority="900" constant="268" id="Saf-eA-IEJ"/>
<constraint firstAttribute="bottom" secondItem="q1P-kd-995" secondAttribute="bottom" id="anp-0j-jCG"/>
<constraint firstAttribute="trailing" secondItem="q1P-kd-995" secondAttribute="trailing" id="dC4-sm-7Xf"/>
<constraint firstItem="3Js-0o-jqZ" firstAttribute="leading" secondItem="rTT-RO-I3T" secondAttribute="leading" id="f7L-vN-ABi"/>
<constraint firstItem="q1P-kd-995" firstAttribute="top" secondItem="rTT-RO-I3T" secondAttribute="top" id="gJj-bF-tAE"/>
<constraint firstAttribute="bottom" secondItem="3Js-0o-jqZ" secondAttribute="bottom" id="kjU-1n-2Y5"/>
<constraint firstItem="3Js-0o-jqZ" firstAttribute="top" secondItem="rTT-RO-I3T" secondAttribute="top" id="ycq-Wi-XAC"/>
<constraint firstAttribute="height" priority="900" constant="268" id="dVF-N3-gwQ"/>
</constraints>
</customView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="rTT-RO-I3T" secondAttribute="trailing" id="Q9O-Xi-2fY"/>
<constraint firstItem="rTT-RO-I3T" firstAttribute="leading" secondItem="1wJ-s5-kVO" secondAttribute="leading" id="uaQ-li-gxJ"/>
<constraint firstItem="rTT-RO-I3T" firstAttribute="top" secondItem="1wJ-s5-kVO" secondAttribute="top" id="x4R-Ox-BgW"/>
<constraint firstAttribute="trailing" secondItem="rTT-RO-I3T" secondAttribute="trailing" id="6lr-25-Z5i"/>
<constraint firstItem="rTT-RO-I3T" firstAttribute="leading" secondItem="1wJ-s5-kVO" secondAttribute="leading" id="AaV-pl-YnC"/>
<constraint firstItem="rTT-RO-I3T" firstAttribute="top" secondItem="1wJ-s5-kVO" secondAttribute="top" id="BTR-DU-CLt"/>
</constraints>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="hEG-a6-BYd">
<rect key="frame" x="1" y="253" width="478" height="16"/>
<rect key="frame" x="0.0" y="255" width="465" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="O02-Lc-5gg">
<rect key="frame" x="465" y="1" width="16" height="268"/>
<rect key="frame" x="465" y="0.0" width="15" height="255"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
<constraints>
<constraint firstItem="Qwv-ci-fVA" firstAttribute="leading" secondItem="3ye-5b-Ab2" secondAttribute="leading" id="0TL-x0-ZpR"/>
<constraint firstAttribute="bottom" secondItem="Qwv-ci-fVA" secondAttribute="bottom" id="2oP-pv-WbK"/>
<constraint firstAttribute="trailing" secondItem="Qwv-ci-fVA" secondAttribute="trailing" id="YVP-Fu-Kqv"/>
<constraint firstItem="Qwv-ci-fVA" firstAttribute="top" secondItem="3ye-5b-Ab2" secondAttribute="top" id="hcS-Ds-4hI"/>
<constraint firstAttribute="trailing" secondItem="q1P-kd-995" secondAttribute="trailing" id="5IG-vx-2rb"/>
<constraint firstItem="Qwv-ci-fVA" firstAttribute="leading" secondItem="q1P-kd-995" secondAttribute="leading" id="Pyq-kX-0wa"/>
<constraint firstItem="Qwv-ci-fVA" firstAttribute="top" secondItem="q1P-kd-995" secondAttribute="top" id="Qwh-xz-kUZ"/>
<constraint firstItem="Qwv-ci-fVA" firstAttribute="bottom" secondItem="q1P-kd-995" secondAttribute="bottom" id="Sdy-8S-2NP"/>
<constraint firstItem="q1P-kd-995" firstAttribute="top" secondItem="3ye-5b-Ab2" secondAttribute="top" id="gHb-9s-5Ki"/>
<constraint firstItem="q1P-kd-995" firstAttribute="leading" secondItem="3ye-5b-Ab2" secondAttribute="leading" id="kaS-IB-LV9"/>
<constraint firstAttribute="bottom" secondItem="q1P-kd-995" secondAttribute="bottom" id="ngA-1s-M67"/>
<constraint firstItem="Qwv-ci-fVA" firstAttribute="trailing" secondItem="q1P-kd-995" secondAttribute="trailing" id="w93-aW-WKu"/>
</constraints>
</view>
<connections>
<outlet property="editContainerView" destination="rTT-RO-I3T" id="V33-lc-vP3"/>
<outlet property="editView" destination="q1P-kd-995" id="Dhz-gl-7Ut"/>
<outlet property="editViewHeight" destination="Saf-eA-IEJ" id="oOC-Nd-tqI"/>
<outlet property="gutterView" destination="3Js-0o-jqZ" id="BLh-Gh-ebk"/>
<outlet property="gutterViewWidth" destination="Smj-nI-f5r" id="kjP-so-JhO"/>
<outlet property="editViewHeight" destination="dVF-N3-gwQ" id="izi-zu-iyl"/>
<outlet property="scrollView" destination="Qwv-ci-fVA" id="4cE-10-pSP"/>
<segue destination="9Ak-VM-8lJ" kind="modal" identifier="UserInputSegue" id="GqW-PV-ZCy"/>
</connections>
Expand Down

0 comments on commit dc4840e

Please sign in to comment.