Skip to content

Commit 95e0b91

Browse files
committed
Scroll Syncing Done!
1 parent a08aee6 commit 95e0b91

File tree

5 files changed

+97
-47
lines changed

5 files changed

+97
-47
lines changed

Sources/CodeEditSourceEditor/Controller/TextViewController+StyleViews.swift

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ extension TextViewController {
6767
scrollView.hasVerticalScroller = true
6868
scrollView.hasHorizontalScroller = !wrapLines
6969

70+
updateContentInsets()
71+
}
72+
73+
package func updateContentInsets() {
7074
scrollView.contentView.postsBoundsChangedNotifications = true
7175
if let contentInsets {
7276
scrollView.automaticallyAdjustsContentInsets = false
@@ -80,10 +84,14 @@ extension TextViewController {
8084

8185
scrollView.contentInsets.top += additionalTextInsets?.top ?? 0
8286
scrollView.contentInsets.bottom += additionalTextInsets?.bottom ?? 0
87+
minimapView.scrollView.contentInsets.top += additionalTextInsets?.top ?? 0
88+
minimapView.scrollView.contentInsets.bottom += additionalTextInsets?.bottom ?? 0
89+
90+
let findInset = (findViewController?.isShowingFindPanel ?? false) ? FindPanel.height : 0
91+
scrollView.contentInsets.top += findInset
92+
minimapView.scrollView.contentInsets.top += findInset
8393

84-
scrollView.contentInsets.top += (findViewController?.isShowingFindPanel ?? false) ? FindPanel.height : 0
85-
minimapView.scrollView.contentInsets.top += (
86-
findViewController?.isShowingFindPanel ?? false
87-
) ? FindPanel.height : 0
94+
scrollView.reflectScrolledClipView(scrollView.contentView)
95+
minimapView.scrollView.reflectScrolledClipView(minimapView.scrollView.contentView)
8896
}
8997
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// NSEdgeInsets+Helpers.swift
3+
// CodeEditSourceEditor
4+
//
5+
// Created by Khan Winter on 4/15/25.
6+
//
7+
8+
import Foundation
9+
10+
extension NSEdgeInsets {
11+
var vertical: CGFloat {
12+
top + bottom
13+
}
14+
15+
var horizontal: CGFloat {
16+
left + right
17+
}
18+
}

Sources/CodeEditSourceEditor/Minimap/MinimapContentView.swift

Lines changed: 0 additions & 22 deletions
This file was deleted.

Sources/CodeEditSourceEditor/Minimap/MinimapView+DocumentVisibleView.swift

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,59 @@
77

88
import AppKit
99

10+
extension NSScrollView {
11+
var percentScrolled: CGFloat {
12+
get {
13+
let currentYPos = documentVisibleRect.origin.y + contentInsets.top
14+
let totalHeight = (documentView?.frame.height ?? 0.0) + contentInsets.top
15+
let goalYPos = totalHeight - (documentVisibleRect.height - contentInsets.top)
16+
17+
return currentYPos / goalYPos
18+
}
19+
set {
20+
let totalHeight = (documentView?.frame.height ?? 0.0) + contentInsets.top
21+
contentView.scroll(
22+
to: NSPoint(
23+
x: contentView.frame.origin.x,
24+
y: (newValue * (totalHeight - (documentVisibleRect.height - contentInsets.top))) - contentInsets.top
25+
)
26+
)
27+
reflectScrolledClipView(contentView)
28+
}
29+
}
30+
}
31+
1032
extension MinimapView {
1133
func updateDocumentVisibleViewPosition() {
12-
guard let textView = textView, let editorScrollView = textView.enclosingScrollView else { return }
13-
layoutManager?.layoutLines(in: scrollView.documentVisibleRect)
14-
let editorHeight = textView.frame.height
34+
guard let textView = textView, let editorScrollView = textView.enclosingScrollView, let layoutManager else {
35+
return
36+
}
37+
1538
let minimapHeight = contentView.frame.height
39+
let editorHeight = textView.frame.height
40+
let editorToMinimapHeightRatio = minimapHeight / editorHeight
41+
42+
let containerHeight = editorScrollView.visibleRect.height - editorScrollView.contentInsets.vertical
43+
let availableHeight = min(minimapHeight, containerHeight)
44+
let scrollPercentage = editorScrollView.percentScrolled
45+
46+
// Update Visible Pane, should scroll down slowly as the user scrolls the document, following the scroller.
47+
// Visible pane's height = scrollview visible height * (minimap line height / editor line height)
48+
// Visible pane's position = (container height - visible pane height) * scrollPercentage
49+
let visibleRectHeight = containerHeight * editorToMinimapHeightRatio
50+
guard visibleRectHeight < 1e100 else { return }
51+
52+
let availableContainerHeight = (availableHeight - visibleRectHeight)
53+
let visibleRectYPos = availableContainerHeight * scrollPercentage
1654

17-
let containerHeight = scrollView.documentVisibleRect.height
18-
let scrollPercentage = (
19-
editorScrollView.documentVisibleRect.origin.y + editorScrollView.contentInsets.top
20-
) / textView.frame.height
21-
// let scrollOffset = editorScrollView.documentVisibleRect.origin.y
55+
documentVisibleView.frame.origin.y = scrollView.contentInsets.top + visibleRectYPos
56+
documentVisibleView.frame.size.height = visibleRectHeight
2257

23-
// let scrollMultiplier: CGFloat = if minimapHeight < containerHeight {
24-
// 1.0
25-
// } else {
26-
// 1.0 - (minimapHeight - containerHeight) / (editorHeight - containerHeight)
27-
// }
58+
// Minimap scroll offset slowly scrolls down with the visible pane.
59+
if minimapHeight > containerHeight {
60+
scrollView.percentScrolled = scrollPercentage
61+
}
2862

29-
let newMinimapOrigin = minimapHeight * scrollPercentage
30-
scrollView.contentView.bounds.origin.y = newMinimapOrigin - editorScrollView.contentInsets.top
63+
layoutManager.layoutLines()
3164
}
3265
}

Sources/CodeEditSourceEditor/Minimap/MinimapView.swift

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
import AppKit
99
import CodeEditTextView
1010

11-
class MinimapView: NSView {
11+
class MinimapView: FlippedNSView {
1212
weak var textView: TextView?
1313

1414
/// The container scrollview for the minimap contents.
1515
let scrollView: NSScrollView
1616
/// The view text lines are rendered into.
17-
let contentView: MinimapContentView
17+
let contentView: FlippedNSView
1818
/// The box displaying the visible region on the minimap.
1919
let documentVisibleView: NSView
2020

@@ -26,7 +26,7 @@ class MinimapView: NSView {
2626

2727
var theme: EditorTheme {
2828
didSet {
29-
documentVisibleView.layer?.backgroundColor = theme.text.color.withAlphaComponent(0.1).cgColor
29+
documentVisibleView.layer?.backgroundColor = theme.text.color.withAlphaComponent(0.05).cgColor
3030
layer?.backgroundColor = theme.background.cgColor
3131
}
3232
}
@@ -57,13 +57,13 @@ class MinimapView: NSView {
5757
scrollView.drawsBackground = false
5858
scrollView.verticalScrollElasticity = .none
5959

60-
self.contentView = MinimapContentView(frame: .zero)
60+
self.contentView = FlippedNSView(frame: .zero)
6161
contentView.translatesAutoresizingMaskIntoConstraints = false
6262

6363
self.documentVisibleView = NSView()
6464
documentVisibleView.translatesAutoresizingMaskIntoConstraints = false
6565
documentVisibleView.wantsLayer = true
66-
documentVisibleView.layer?.backgroundColor = theme.text.color.withAlphaComponent(0.1).cgColor
66+
documentVisibleView.layer?.backgroundColor = theme.text.color.withAlphaComponent(0.05).cgColor
6767

6868
super.init(frame: .zero)
6969

@@ -97,7 +97,8 @@ class MinimapView: NSView {
9797
scrollView.leadingAnchor.constraint(equalTo: leadingAnchor),
9898
scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
9999

100-
contentView.widthAnchor.constraint(equalTo: widthAnchor),
100+
contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
101+
contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
101102

102103
documentVisibleView.leadingAnchor.constraint(equalTo: leadingAnchor),
103104
documentVisibleView.trailingAnchor.constraint(equalTo: trailingAnchor)
@@ -153,4 +154,16 @@ class MinimapView: NSView {
153154
return super.hitTest(point)
154155
}
155156
}
157+
158+
override func draw(_ dirtyRect: NSRect) {
159+
guard let context = NSGraphicsContext.current?.cgContext else { return }
160+
context.saveGState()
161+
162+
context.setFillColor(NSColor.separatorColor.cgColor)
163+
context.fill([
164+
CGRect(x: 0, y: 0, width: 1, height: frame.height)
165+
])
166+
167+
context.restoreGState()
168+
}
156169
}

0 commit comments

Comments
 (0)