88import AppKit
99import CodeEditTextView
1010
11- class MinimapView : FlippedNSView {
11+ /// The minimap view displays a copy of editor contents as a series of small bubbles in place of text.
12+ ///
13+ /// This view consists of the following subviews in order
14+ /// ```
15+ /// MinimapView
16+ /// |-> separatorView: A small, grey, leading, separator that distinguishes the minimap from other content.
17+ /// |-> documentVisibleView: Displays a rectangle that represents the portion of the minimap visible in the editor's
18+ /// | visible rect. This is draggable and responds to the editor's height.
19+ /// |-> scrollView: Container for the summary bubbles
20+ /// | |-> contentView: Target view for the summary bubble content
21+ /// ```
22+ ///
23+ /// To keep contents in sync with the text view, this view requires that its ``scrollView`` have the same vertical
24+ /// content insets as the editor's content insets.
25+ ///
26+ /// The minimap can be styled using an ``EditorTheme``. See ``setTheme(_:)`` for use and colors used by this view.
27+ public class MinimapView : FlippedNSView {
1228 weak var textView : TextView ?
1329
1430 /// The container scrollview for the minimap contents.
15- let scrollView : ForwardingScrollView
31+ public let scrollView : ForwardingScrollView
1632 /// The view text lines are rendered into.
17- let contentView : FlippedNSView
33+ public let contentView : FlippedNSView
1834 /// The box displaying the visible region on the minimap.
19- let documentVisibleView : NSView
35+ public let documentVisibleView : NSView
2036 /// A small gray line on the left of the minimap distinguishing it from the editor.
21- let separatorView : NSView
37+ public let separatorView : NSView
2238
39+ /// Responder for a drag gesture on the ``documentVisibleView``.
2340 var documentVisibleViewDragGesture : NSPanGestureRecognizer ?
2441
2542 /// The layout manager that uses the ``lineRenderer`` to render and layout lines.
@@ -28,35 +45,33 @@ class MinimapView: FlippedNSView {
2845 /// using ``MinimapLineFragmentView``
2946 let lineRenderer : MinimapLineRenderer
3047
31- var theme : EditorTheme {
32- didSet {
33- documentVisibleView. layer? . backgroundColor = theme. text. color. withAlphaComponent ( 0.05 ) . cgColor
34- layer? . backgroundColor = theme. background. cgColor
35- }
36- }
48+ // MARK: - Calculated Variables
3749
3850 var minimapHeight : CGFloat {
3951 contentView. frame. height
4052 }
4153
4254 var editorHeight : CGFloat {
43- textView? . frame . height ?? 0 .0
55+ textView? . layoutManager . estimatedHeight ( ) ?? 1 .0
4456 }
4557
4658 var editorToMinimapHeightRatio : CGFloat {
4759 minimapHeight / editorHeight
4860 }
4961
62+ /// The height of the available container, less the scroll insets to reflect the visible height.
5063 var containerHeight : CGFloat {
51- ( textView? . enclosingScrollView? . visibleRect. height ?? 0.0 )
52- - ( textView? . enclosingScrollView? . contentInsets. vertical ?? 0.0 )
64+ scrollView. visibleRect. height - scrollView. contentInsets. vertical
5365 }
5466
5567 // MARK: - Init
5668
57- init ( textView: TextView , theme: EditorTheme ) {
69+ /// Creates a minimap view with the text view to track, and an initial theme.
70+ /// - Parameters:
71+ /// - textView: The text view to match contents with.
72+ /// - theme: The theme for the minimap to use.
73+ public init ( textView: TextView , theme: EditorTheme ) {
5874 self . textView = textView
59- self . theme = theme
6075 self . lineRenderer = MinimapLineRenderer ( textView: textView)
6176
6277 self . scrollView = ForwardingScrollView ( )
@@ -67,7 +82,7 @@ class MinimapView: FlippedNSView {
6782 scrollView. verticalScrollElasticity = . none
6883 scrollView. receiver = textView. enclosingScrollView
6984
70- self . contentView = FlippedNSView ( frame : . zero )
85+ self . contentView = FlippedNSView ( )
7186 contentView. translatesAutoresizingMaskIntoConstraints = false
7287
7388 self . documentVisibleView = NSView ( )
@@ -162,6 +177,7 @@ class MinimapView: FlippedNSView {
162177 queue: . main
163178 ) { [ weak self] _ in
164179 // Frame changed
180+ self ? . updateContentViewHeight ( )
165181 self ? . updateDocumentVisibleViewPosition ( )
166182 }
167183 }
@@ -176,18 +192,59 @@ class MinimapView: FlippedNSView {
176192 return rect. pixelAligned
177193 }
178194
179- override func layout( ) {
195+ override public func resetCursorRects( ) {
196+ // Don't use an iBeam
197+ addCursorRect ( bounds, cursor: . arrow)
198+ }
199+
200+ override public func layout( ) {
180201 layoutManager? . layoutLines ( )
181202 super. layout ( )
182203 }
183204
184- override func hitTest( _ point: NSPoint ) -> NSView ? {
205+ override public func hitTest( _ point: NSPoint ) -> NSView ? {
206+ guard let point = superview? . convert ( point, to: self ) else { return nil }
207+ // For performance, don't hitTest the layout fragment views, but make sure the `documentVisibleView` is
208+ // hittable.
185209 if documentVisibleView. frame. contains ( point) {
186210 return documentVisibleView
187211 } else if visibleRect. contains ( point) {
188- return textView
212+ return self
189213 } else {
190214 return super. hitTest ( point)
191215 }
192216 }
217+
218+ // Eat mouse events so we don't pass them on to the text view. Leads to some odd behavior.
219+
220+ override public func mouseDown( with event: NSEvent ) { }
221+ override public func mouseDragged( with event: NSEvent ) { }
222+
223+ /// Sets the content view height, matching the text view's overscroll setting as well as the layout manager's
224+ /// cached height.
225+ func updateContentViewHeight( ) {
226+ guard let estimatedContentHeight = layoutManager? . estimatedHeight ( ) ,
227+ let overscrollAmount = textView? . overscrollAmount else {
228+ return
229+ }
230+ let overscroll = containerHeight * overscrollAmount * editorToMinimapHeightRatio
231+ let height = estimatedContentHeight + overscroll
232+
233+ // Only update a frame if needed
234+ if contentView. frame. height != height {
235+ contentView. frame. size. height = height
236+ }
237+ }
238+
239+ /// Updates the minimap to reflect a new theme.
240+ ///
241+ /// Colors used:
242+ /// - ``documentVisibleView``'s background color = `theme.text` with `0.05` alpha.
243+ /// - The minimap's background color = `theme.background`.
244+ ///
245+ /// - Parameter theme: The selected theme.
246+ public func setTheme( _ theme: EditorTheme ) {
247+ documentVisibleView. layer? . backgroundColor = theme. text. color. withAlphaComponent ( 0.05 ) . cgColor
248+ layer? . backgroundColor = theme. background. cgColor
249+ }
193250}
0 commit comments