@@ -9,12 +9,17 @@ import AppKit
99import SwiftUI
1010import LanguageServerProtocol
1111
12+ /// Represents an item that can be displayed in the ItemBox
1213public protocol ItemBoxEntry {
1314 var view : NSView { get }
1415}
1516
17+ private let WINDOW_PADDING : CGFloat = 5
18+
1619public final class ItemBoxWindowController : NSWindowController {
1720
21+ // MARK: - Properties
22+
1823 /// Default size of the window when opened
1924 public static let DEFAULT_SIZE = NSSize ( width: 300 , height: 212 )
2025
@@ -23,54 +28,27 @@ public final class ItemBoxWindowController: NSWindowController {
2328 didSet { updateItems ( ) }
2429 }
2530
26- private let tableView = NSTableView ( )
27- private let scrollView = NSScrollView ( )
28- /// An event monitor for keyboard events
29- private var localEventMonitor : Any ?
30-
3131 /// Whether the ItemBox window is visbile
3232 public var isVisible : Bool {
3333 window? . isVisible ?? false
3434 }
3535
36- public init ( ) {
37- let window = NSWindow (
38- contentRect: NSRect ( origin: CGPoint . zero, size: ItemBoxWindowController . DEFAULT_SIZE) ,
39- styleMask: [ . resizable, . fullSizeContentView, . nonactivatingPanel] ,
40- backing: . buffered,
41- defer: false
42- )
36+ // MARK: - Private Properties
4337
44- // Style window
45- window. titleVisibility = . hidden
46- window. titlebarAppearsTransparent = true
47- window. isExcludedFromWindowsMenu = true
48- window. isReleasedWhenClosed = false
49- window. level = . popUpMenu
50- window. hasShadow = true
51- window. isOpaque = false
52- window. tabbingMode = . disallowed
53- window. hidesOnDeactivate = true
54- window. backgroundColor = . clear
55- window. minSize = ItemBoxWindowController . DEFAULT_SIZE
38+ private let tableView = NSTableView ( )
39+ private let scrollView = NSScrollView ( )
5640
57- // Style the content with custom borders and colors
58- window. contentView? . wantsLayer = true
59- window. contentView? . layer? . backgroundColor = CGColor (
60- srgbRed: 31.0 / 255.0 , green: 31.0 / 255.0 , blue: 36.0 / 255.0 , alpha: 1.0
61- )
62- window. contentView? . layer? . cornerRadius = 8.5
63- window. contentView? . layer? . borderWidth = 1
64- window. contentView? . layer? . borderColor = NSColor . gray. withAlphaComponent ( 0.45 ) . cgColor
65- let innerShadow = NSShadow ( )
66- innerShadow. shadowColor = NSColor . black. withAlphaComponent ( 0.1 )
67- innerShadow. shadowOffset = NSSize ( width: 0 , height: - 1 )
68- innerShadow. shadowBlurRadius = 2
69- window. contentView? . shadow = innerShadow
41+ /// An event monitor for keyboard events
42+ private var localEventMonitor : Any ?
7043
44+ // MARK: - Initialization
45+
46+ public init ( ) {
47+ let window = Self . makeWindow ( )
7148 super. init ( window: window)
7249
73- setupTableView ( )
50+ configureTableView ( tableView)
51+ configureScrollView ( scrollView)
7452 }
7553
7654 required init ? ( coder: NSCoder ) {
@@ -85,18 +63,18 @@ public final class ItemBoxWindowController: NSWindowController {
8563
8664 /// Opens the window as a child of another window
8765 public func showWindow( attachedTo parentWindow: NSWindow ) {
88- guard let window = self . window else { return }
66+ guard let window = window else { return }
67+
8968 parentWindow. addChildWindow ( window, ordered: . above)
9069 window. orderFront ( nil )
9170
9271 // Close on window switch
9372 NotificationCenter . default. addObserver (
94- forName: NSWindow . didResignKeyNotification,
95- object: parentWindow,
96- queue: . current
97- ) { [ weak self] _ in
98- self ? . close ( )
99- }
73+ self ,
74+ selector: #selector( parentWindowDidResignKey) ,
75+ name: NSWindow . didResignKeyNotification,
76+ object: parentWindow
77+ )
10078
10179 self . show ( )
10280 }
@@ -108,24 +86,76 @@ public final class ItemBoxWindowController: NSWindowController {
10886 super. close ( )
10987 }
11088
111- private func setupTableView( ) {
89+ // MARK: - Private Methods
90+
91+ private static func makeWindow( ) -> NSWindow {
92+ let window = NSWindow (
93+ contentRect: NSRect ( origin: . zero, size: ItemBoxWindowController . DEFAULT_SIZE) ,
94+ styleMask: [ . resizable, . fullSizeContentView, . nonactivatingPanel] ,
95+ backing: . buffered,
96+ defer: false
97+ )
98+
99+ configureWindow ( window)
100+ configureWindowContent ( window)
101+ return window
102+ }
103+
104+ private static func configureWindow( _ window: NSWindow ) {
105+ window. titleVisibility = . hidden
106+ window. titlebarAppearsTransparent = true
107+ window. isExcludedFromWindowsMenu = true
108+ window. isReleasedWhenClosed = false
109+ window. level = . popUpMenu
110+ window. hasShadow = true
111+ window. isOpaque = false
112+ window. tabbingMode = . disallowed
113+ window. hidesOnDeactivate = true
114+ window. backgroundColor = . clear
115+ window. minSize = ItemBoxWindowController . DEFAULT_SIZE
116+ }
117+
118+ private static func configureWindowContent( _ window: NSWindow ) {
119+ guard let contentView = window. contentView else { return }
120+
121+ contentView. wantsLayer = true
122+ contentView. layer? . backgroundColor = CGColor (
123+ srgbRed: 31.0 / 255.0 ,
124+ green: 31.0 / 255.0 ,
125+ blue: 36.0 / 255.0 ,
126+ alpha: 1.0
127+ )
128+ contentView. layer? . cornerRadius = 8.5
129+ contentView. layer? . borderWidth = 1
130+ contentView. layer? . borderColor = NSColor . gray. withAlphaComponent ( 0.45 ) . cgColor
131+
132+ let innerShadow = NSShadow ( )
133+ innerShadow. shadowColor = NSColor . black. withAlphaComponent ( 0.1 )
134+ innerShadow. shadowOffset = NSSize ( width: 0 , height: - 1 )
135+ innerShadow. shadowBlurRadius = 2
136+ contentView. shadow = innerShadow
137+ }
138+
139+ private func configureTableView( _ tableView: NSTableView ) {
112140 tableView. delegate = self
113141 tableView. dataSource = self
114142 tableView. headerView = nil
115143 tableView. backgroundColor = . clear
116- tableView. intercellSpacing = NSSize . zero
144+ tableView. intercellSpacing = . zero
117145 tableView. allowsEmptySelection = false
118146 tableView. selectionHighlightStyle = . regular
119- tableView. headerView = nil
120147 tableView. style = . plain
121148 tableView. usesAutomaticRowHeights = false
122149 tableView. rowSizeStyle = . custom
123150 tableView. rowHeight = 21
124151 tableView. gridStyleMask = [ ]
152+
125153 let column = NSTableColumn ( identifier: NSUserInterfaceItemIdentifier ( " ItemsCell " ) )
126154 column. width = ItemBoxWindowController . DEFAULT_SIZE. width
127155 tableView. addTableColumn ( column)
156+ }
128157
158+ private func configureScrollView( _ scrollView: NSScrollView ) {
129159 scrollView. documentView = tableView
130160 scrollView. hasVerticalScroller = true
131161 scrollView. verticalScroller = NoSlotScroller ( )
@@ -135,17 +165,23 @@ public final class ItemBoxWindowController: NSWindowController {
135165 scrollView. automaticallyAdjustsContentInsets = false
136166 scrollView. translatesAutoresizingMaskIntoConstraints = false
137167 scrollView. verticalScrollElasticity = . allowed
138- scrollView. contentInsets = NSEdgeInsets ( top: 5 , left: 0 , bottom: 5 , right: 0 )
139- window? . contentView? . addSubview ( scrollView)
168+ scrollView. contentInsets = NSEdgeInsets ( top: WINDOW_PADDING, left: 0 , bottom: WINDOW_PADDING, right: 0 )
169+
170+ guard let contentView = window? . contentView else { return }
171+ contentView. addSubview ( scrollView)
140172
141173 NSLayoutConstraint . activate ( [
142- scrollView. topAnchor. constraint ( equalTo: window! . contentView! . topAnchor) ,
143- scrollView. leadingAnchor. constraint ( equalTo: window! . contentView! . leadingAnchor) ,
144- scrollView. trailingAnchor. constraint ( equalTo: window! . contentView! . trailingAnchor) ,
145- scrollView. bottomAnchor. constraint ( equalTo: window! . contentView! . bottomAnchor)
174+ scrollView. topAnchor. constraint ( equalTo: contentView. topAnchor) ,
175+ scrollView. leadingAnchor. constraint ( equalTo: contentView. leadingAnchor) ,
176+ scrollView. trailingAnchor. constraint ( equalTo: contentView. trailingAnchor) ,
177+ scrollView. bottomAnchor. constraint ( equalTo: contentView. bottomAnchor)
146178 ] )
147179 }
148180
181+ @objc private func parentWindowDidResignKey( ) {
182+ close ( )
183+ }
184+
149185 private func updateItems( ) {
150186 tableView. reloadData ( )
151187 }
@@ -159,26 +195,26 @@ public final class ItemBoxWindowController: NSWindowController {
159195 switch event. type {
160196 case . keyDown:
161197 switch event. keyCode {
162- case 53 : // Escape key
198+ case 53 : // Escape
163199 self . close ( )
164- case 125 , 126 : // Down Arrow and Up Arrow
200+ return nil
201+ case 125 , 126 : // Down/Up Arrow
165202 self . tableView. keyDown ( with: event)
166203 return nil
167- case 36 , 48 : // Return and Tab key
204+ case 36 , 48 : // Return/ Tab
168205 return nil
169206 default :
170- break
207+ return event
171208 }
172209 case . leftMouseDown, . rightMouseDown:
173210 // If we click outside the window, close the window
174211 if !NSMouseInRect( NSEvent . mouseLocation, self . window!. frame, false ) {
175212 self . close ( )
176213 }
214+ return event
177215 default :
178- break
216+ return event
179217 }
180-
181- return event
182218 }
183219 }
184220
@@ -194,13 +230,11 @@ public final class ItemBoxWindowController: NSWindowController {
194230 }
195231}
196232
197- extension ItemBoxWindowController : NSTableViewDataSource {
233+ extension ItemBoxWindowController : NSTableViewDataSource , NSTableViewDelegate {
198234 public func numberOfRows( in tableView: NSTableView ) -> Int {
199235 return items. count
200236 }
201- }
202237
203- extension ItemBoxWindowController : NSTableViewDelegate {
204238 public func tableView( _ tableView: NSTableView , viewFor tableColumn: NSTableColumn ? , row: Int ) -> NSView ? {
205239 items [ row] . view
206240 }
@@ -224,27 +258,25 @@ private class ItemBoxRowView: NSTableRowView {
224258 guard let context = NSGraphicsContext . current? . cgContext else { return }
225259
226260 context. saveGState ( )
261+ defer { context. restoreGState ( ) }
227262
228263 // Create a rect that's inset from the edges and has proper padding
229264 // TODO: We create a new selectionRect instead of using dirtyRect
230265 // because there is a visual bug when holding down the arrow keys
231266 // to select the first or last item that draws a clipped rectangular
232267 // selection highlight shape instead of the whole rectangle. Replace
233268 // this when it gets fixed.
234- let padding : CGFloat = 5
235269 let selectionRect = NSRect (
236- x: padding ,
270+ x: WINDOW_PADDING ,
237271 y: 0 ,
238- width: bounds. width - ( padding * 2 ) ,
272+ width: bounds. width - ( WINDOW_PADDING * 2 ) ,
239273 height: bounds. height
240274 )
241-
242275 let cornerRadius : CGFloat = 5
243276 let path = NSBezierPath ( roundedRect: selectionRect, xRadius: cornerRadius, yRadius: cornerRadius)
244277 let selectionColor = NSColor . gray. withAlphaComponent ( 0.19 )
245278
246279 context. setFillColor ( selectionColor. cgColor)
247280 path. fill ( )
248- context. restoreGState ( )
249281 }
250282}
0 commit comments