Skip to content

Commit ef2f8f0

Browse files
committed
Make Scroller on Top, Utilize Content Insets
1 parent 6cf5e5f commit ef2f8f0

File tree

9 files changed

+71
-52
lines changed

9 files changed

+71
-52
lines changed

Example/CodeEditSourceEditorExample/CodeEditSourceEditorExample/Views/ContentView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ struct ContentView: View {
4646
useThemeBackground: true,
4747
highlightProviders: [treeSitterClient],
4848
contentInsets: NSEdgeInsets(top: proxy.safeAreaInsets.top, left: 0, bottom: 28.0, right: 0),
49+
additionalTextInsets: NSEdgeInsets(top: 1, left: 0, bottom: proxy.size.height * 0.3, right: 0),
4950
useSystemCursor: useSystemCursor
5051
)
5152
.overlay(alignment: .bottom) {

Sources/CodeEditSourceEditor/Controller/TextViewController+FindPanelTarget.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,11 @@ import CodeEditTextView
1010

1111
extension TextViewController: FindPanelTarget {
1212
func findPanelWillShow(panelHeight: CGFloat) {
13-
scrollView.contentInsets.top += panelHeight
14-
gutterView.frame.origin.y = -scrollView.contentInsets.top
13+
updateContentInsets()
1514
}
1615

1716
func findPanelWillHide(panelHeight: CGFloat) {
18-
scrollView.contentInsets.top -= panelHeight
19-
gutterView.frame.origin.y = -scrollView.contentInsets.top
17+
updateContentInsets()
2018
}
2119

2220
var emphasisManager: EmphasisManager? {

Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ extension TextViewController {
3838
findViewController.view.viewDidMoveToSuperview()
3939
self.findViewController = findViewController
4040

41-
findViewController.topPadding = contentInsets?.top
42-
4341
if let _undoManager {
4442
textView.setUndoManager(_undoManager)
4543
}
@@ -63,6 +61,7 @@ extension TextViewController {
6361
NSEvent.removeMonitor(localEventMonitor)
6462
}
6563
setUpKeyBindings(eventMonitor: &self.localEvenMonitor)
64+
updateContentInsets()
6665
}
6766

6867
func setUpConstraints() {
@@ -85,8 +84,8 @@ extension TextViewController {
8584
findViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
8685
findViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
8786

88-
minimapView.topAnchor.constraint(equalTo: scrollView.contentView.safeAreaLayoutGuide.topAnchor),
89-
minimapView.bottomAnchor.constraint(equalTo: scrollView.contentView.safeAreaLayoutGuide.bottomAnchor),
87+
minimapView.topAnchor.constraint(equalTo: scrollView.contentView.topAnchor),
88+
minimapView.bottomAnchor.constraint(equalTo: scrollView.contentView.bottomAnchor),
9089
minimapXConstraint,
9190
maxWidthConstraint,
9291
relativeWidthConstraint,

Sources/CodeEditSourceEditor/Controller/TextViewController+StyleViews.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ extension TextViewController {
4646

4747
/// Style the gutter view.
4848
package func styleGutterView() {
49-
// Note: If changing this value, also change in ``findPanelWillShow/Hide()``
50-
gutterView.frame.origin.y = -scrollView.contentInsets.top
5149
gutterView.selectedLineColor = useThemeBackground ? theme.lineHighlight : systemAppearance == .darkAqua
5250
? NSColor.quaternaryLabelColor
5351
: NSColor.selectedTextBackgroundColor.withSystemEffect(.disabled)
@@ -66,19 +64,18 @@ extension TextViewController {
6664
scrollView.contentView.postsFrameChangedNotifications = true
6765
scrollView.hasVerticalScroller = true
6866
scrollView.hasHorizontalScroller = !wrapLines
69-
7067
scrollView.scrollerStyle = .overlay
71-
72-
updateContentInsets()
7368
}
7469

7570
package func updateContentInsets() {
7671
scrollView.contentView.postsBoundsChangedNotifications = true
7772
if let contentInsets {
7873
scrollView.automaticallyAdjustsContentInsets = false
7974
scrollView.contentInsets = contentInsets
75+
76+
minimapView.scrollView.automaticallyAdjustsContentInsets = false
8077
minimapView.scrollView.contentInsets.top = contentInsets.top
81-
minimapView.scrollView.contentInsets.top = contentInsets.bottom
78+
minimapView.scrollView.contentInsets.bottom = contentInsets.bottom
8279
} else {
8380
scrollView.automaticallyAdjustsContentInsets = true
8481
minimapView.scrollView.automaticallyAdjustsContentInsets = true
@@ -95,5 +92,9 @@ extension TextViewController {
9592

9693
scrollView.reflectScrolledClipView(scrollView.contentView)
9794
minimapView.scrollView.reflectScrolledClipView(minimapView.scrollView.contentView)
95+
96+
findViewController?.topPadding = contentInsets?.top
97+
98+
gutterView.frame.origin.y = -scrollView.contentInsets.top
9899
}
99100
}

Sources/CodeEditSourceEditor/Controller/TextViewController.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ public class TextViewController: NSViewController {
128128
public var contentInsets: NSEdgeInsets? {
129129
didSet {
130130
updateContentInsets()
131-
findViewController?.topPadding = contentInsets?.top
132131
}
133132
}
134133

Sources/CodeEditSourceEditor/Minimap/MinimapLineFragmentView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ final class MinimapLineFragmentView: LineFragmentView {
119119
height: 2.0
120120
)
121121
context.setFillColor(run.color.cgColor)
122-
context.fill(rect.pixelAligned)
122+
context.fill(rect)
123123
}
124124

125125
context.restoreGState()

Sources/CodeEditSourceEditor/Minimap/MinimapView+DocumentVisibleView.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ extension MinimapView {
3838
}
3939

4040
private func setScrollViewPosition(scrollPercentage: CGFloat) {
41-
let totalHeight = contentView.frame.height + scrollView.contentInsets.top
42-
let topInset = scrollView.contentInsets.top
41+
let topInsets = scrollView.contentInsets.top
42+
let totalHeight = contentView.frame.height + topInsets
4343
scrollView.contentView.scroll(
4444
to: NSPoint(
4545
x: scrollView.contentView.frame.origin.x,
4646
y: (
47-
scrollPercentage * (totalHeight - (scrollView.documentVisibleRect.height - topInset))
48-
) - topInset
47+
scrollPercentage * (totalHeight - (scrollView.documentVisibleRect.height - topInsets))
48+
) - topInsets
4949
)
5050
)
5151
scrollView.reflectScrolledClipView(scrollView.contentView)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// MinimapView+DragVisibleView.swift
3+
// CodeEditSourceEditor
4+
//
5+
// Created by Khan Winter on 4/16/25.
6+
//
7+
8+
import AppKit
9+
10+
extension MinimapView {
11+
/// Responds to a drag gesture on the document visible view. Dragging the view scrolls the editor a relative amount.
12+
@objc func documentVisibleViewDragged(_ sender: NSPanGestureRecognizer) {
13+
guard let editorScrollView = textView?.enclosingScrollView else {
14+
return
15+
}
16+
17+
// Convert the drag distance in the minimap to the drag distance in the editor.
18+
let translation = sender.translation(in: documentVisibleView)
19+
let ratio = if minimapHeight > containerHeight {
20+
containerHeight / (textView?.frame.height ?? 0.0)
21+
} else {
22+
editorToMinimapHeightRatio
23+
}
24+
let editorTranslation = translation.y / ratio
25+
sender.setTranslation(.zero, in: documentVisibleView)
26+
27+
// Clamp the scroll amount to the content, so we don't scroll crazy far past the end of the document.
28+
var newScrollViewY = editorScrollView.contentView.bounds.origin.y - editorTranslation
29+
// Minimum Y value is the top of the scroll view
30+
newScrollViewY = max(-editorScrollView.contentInsets.top, newScrollViewY)
31+
newScrollViewY = min( // Max y value needs to take into account the editor overscroll
32+
editorScrollView.documentMaxOriginY
33+
- editorScrollView.contentInsets.top
34+
+ editorScrollView.contentInsets.bottom,
35+
newScrollViewY
36+
)
37+
38+
editorScrollView.contentView.scroll(
39+
to: NSPoint(
40+
x: editorScrollView.contentView.bounds.origin.x,
41+
y: newScrollViewY
42+
)
43+
)
44+
editorScrollView.reflectScrolledClipView(editorScrollView.contentView)
45+
}
46+
}

Sources/CodeEditSourceEditor/Minimap/MinimapView.swift

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class MinimapView: FlippedNSView {
1717
let contentView: FlippedNSView
1818
/// The box displaying the visible region on the minimap.
1919
let documentVisibleView: NSView
20-
20+
/// A small gray line on the left of the minimap distinguishing it from the editor.
2121
let separatorView: NSView
2222

2323
var documentVisibleViewDragGesture: NSPanGestureRecognizer?
@@ -52,6 +52,8 @@ class MinimapView: FlippedNSView {
5252
- (textView?.enclosingScrollView?.contentInsets.vertical ?? 0.0)
5353
}
5454

55+
// MARK: - Init
56+
5557
init(textView: TextView, theme: EditorTheme) {
5658
self.textView = textView
5759
self.theme = theme
@@ -111,6 +113,8 @@ class MinimapView: FlippedNSView {
111113
setUpListeners()
112114
}
113115

116+
// MARK: - Constraints
117+
114118
private func setUpConstraints() {
115119
NSLayoutConstraint.activate([
116120
// Constrain to all sides
@@ -135,6 +139,8 @@ class MinimapView: FlippedNSView {
135139
])
136140
}
137141

142+
// MARK: - Scroll listeners
143+
138144
private func setUpListeners() {
139145
guard let editorScrollView = textView?.enclosingScrollView else { return }
140146
// Need to listen to:
@@ -184,35 +190,4 @@ class MinimapView: FlippedNSView {
184190
return super.hitTest(point)
185191
}
186192
}
187-
188-
/// Responds to a drag gesture on the document visible view. Dragging the view scrolls the editor a relative amount.
189-
@objc func documentVisibleViewDragged(_ sender: NSPanGestureRecognizer) {
190-
guard let editorScrollView = textView?.enclosingScrollView else {
191-
return
192-
}
193-
194-
let translation = sender.translation(in: documentVisibleView)
195-
let ratio = if minimapHeight > containerHeight {
196-
containerHeight / (textView?.frame.height ?? 0.0)
197-
} else {
198-
editorToMinimapHeightRatio
199-
}
200-
let editorTranslation = translation.y / ratio
201-
sender.setTranslation(.zero, in: documentVisibleView)
202-
203-
var newScrollViewY = editorScrollView.contentView.bounds.origin.y - editorTranslation
204-
newScrollViewY = max(-editorScrollView.contentInsets.top, newScrollViewY)
205-
newScrollViewY = min(
206-
editorScrollView.documentMaxOriginY - editorScrollView.contentInsets.top,
207-
newScrollViewY
208-
)
209-
210-
editorScrollView.contentView.scroll(
211-
to: NSPoint(
212-
x: editorScrollView.contentView.bounds.origin.x,
213-
y: newScrollViewY
214-
)
215-
)
216-
editorScrollView.reflectScrolledClipView(editorScrollView.contentView)
217-
}
218193
}

0 commit comments

Comments
 (0)