77
88import AppKit
99import SwiftUI
10+ import LanguageServerProtocol
1011
1112public protocol ItemBoxEntry {
1213 var view : NSView { get }
1314}
1415
1516public final class ItemBoxWindowController : NSWindowController {
1617
18+ /// Default size of the window when opened
1719 public static let DEFAULT_SIZE = NSSize ( width: 300 , height: 212 )
1820
21+ /// The items to be displayed in the window
1922 public var items : [ any ItemBoxEntry ] = [ ] {
20- didSet {
21- updateItems ( )
22- }
23+ didSet { updateItems ( ) }
2324 }
2425
2526 private let tableView = NSTableView ( )
2627 private let scrollView = NSScrollView ( )
28+ /// An event monitor for keyboard events
2729 private var localEventMonitor : Any ?
2830
31+ /// Whether the ItemBox window is visbile
2932 public var isVisible : Bool {
30- return window? . isVisible ?? false
33+ window? . isVisible ?? false
3134 }
3235
3336 public init ( ) {
@@ -56,9 +59,9 @@ public final class ItemBoxWindowController: NSWindowController {
5659 window. contentView? . layer? . backgroundColor = CGColor (
5760 srgbRed: 31.0 / 255.0 , green: 31.0 / 255.0 , blue: 36.0 / 255.0 , alpha: 1.0
5861 )
59- window. contentView? . layer? . cornerRadius = 8
62+ window. contentView? . layer? . cornerRadius = 8.5
6063 window. contentView? . layer? . borderWidth = 1
61- window. contentView? . layer? . borderColor = NSColor . gray. withAlphaComponent ( 0.4 ) . cgColor
64+ window. contentView? . layer? . borderColor = NSColor . gray. withAlphaComponent ( 0.45 ) . cgColor
6265 let innerShadow = NSShadow ( )
6366 innerShadow. shadowColor = NSColor . black. withAlphaComponent ( 0.1 )
6467 innerShadow. shadowOffset = NSSize ( width: 0 , height: - 1 )
@@ -80,6 +83,7 @@ public final class ItemBoxWindowController: NSWindowController {
8083 setupEventMonitor ( )
8184 }
8285
86+ /// Opens the window as a child of another window
8387 public func showWindow( attachedTo parentWindow: NSWindow ) {
8488 guard let window = self . window else { return }
8589 parentWindow. addChildWindow ( window, ordered: . above)
@@ -97,6 +101,7 @@ public final class ItemBoxWindowController: NSWindowController {
97101 self . show ( )
98102 }
99103
104+ /// Close the window
100105 public override func close( ) {
101106 guard isVisible else { return }
102107 removeEventMonitor ( )
@@ -108,45 +113,43 @@ public final class ItemBoxWindowController: NSWindowController {
108113 tableView. dataSource = self
109114 tableView. headerView = nil
110115 tableView. backgroundColor = . clear
111- tableView. intercellSpacing = . zero
112- tableView. selectionHighlightStyle = . none
113- tableView. backgroundColor = . clear
114- tableView. enclosingScrollView? . drawsBackground = false
115- tableView. rowHeight = 24
116-
116+ tableView. intercellSpacing = NSSize . zero
117+ tableView. allowsEmptySelection = false
118+ tableView. selectionHighlightStyle = . regular
119+ tableView. headerView = nil
120+ tableView. style = . plain
121+ tableView. usesAutomaticRowHeights = false
122+ tableView. rowSizeStyle = . custom
123+ tableView. rowHeight = 21
124+ tableView. gridStyleMask = [ ]
117125 let column = NSTableColumn ( identifier: NSUserInterfaceItemIdentifier ( " ItemsCell " ) )
118126 column. width = ItemBoxWindowController . DEFAULT_SIZE. width
119127 tableView. addTableColumn ( column)
120128
121129 scrollView. documentView = tableView
122130 scrollView. hasVerticalScroller = true
123- scrollView. verticalScroller? . controlSize = . large
131+ scrollView. verticalScroller = NoSlotScroller ( )
132+ scrollView. scrollerStyle = . overlay
124133 scrollView. autohidesScrollers = true
134+ scrollView. drawsBackground = false
125135 scrollView. automaticallyAdjustsContentInsets = false
126- scrollView. contentInsets = NSEdgeInsetsZero
136+ scrollView. translatesAutoresizingMaskIntoConstraints = false
137+ scrollView. verticalScrollElasticity = . allowed
138+ scrollView. contentInsets = NSEdgeInsets ( top: 5 , left: 0 , bottom: 5 , right: 0 )
127139 window? . contentView? . addSubview ( scrollView)
128140
129- scrollView. translatesAutoresizingMaskIntoConstraints = false
130- NSLayoutConstraint . activate ( [
131- scrollView. topAnchor. constraint ( equalTo: window!. contentView!. topAnchor) ,
132- scrollView. leadingAnchor. constraint ( equalTo: window!. contentView!. leadingAnchor) ,
133- scrollView. trailingAnchor. constraint ( equalTo: window!. contentView!. trailingAnchor) ,
134- scrollView. bottomAnchor. constraint ( equalTo: window!. contentView!. bottomAnchor)
135- ] )
141+ 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)
146+ ] )
136147 }
137148
138149 private func updateItems( ) {
139150 tableView. reloadData ( )
140151 }
141152
142- public func tableViewSelectionDidChange( _ notification: Notification ) {
143- tableView. enumerateAvailableRowViews { ( rowView, row) in
144- if let cellView = rowView. view ( atColumn: 0 ) as? CustomTableCellView {
145- cellView. backgroundStyle = tableView. selectedRow == row ? . emphasized : . normal
146- }
147- }
148- }
149-
150153 private func setupEventMonitor( ) {
151154 localEventMonitor = NSEvent . addLocalMonitorForEvents (
152155 matching: [ . keyDown, . leftMouseDown, . rightMouseDown]
@@ -158,13 +161,10 @@ public final class ItemBoxWindowController: NSWindowController {
158161 switch event. keyCode {
159162 case 53 : // Escape key
160163 self . close ( )
161- case 125 : // Down arrow
162- self . selectNextItemInTable ( )
163- return nil
164- case 126 : // Up arrow
165- self . selectPreviousItemInTable ( )
164+ case 125 , 126 : // Down Arrow and Up Arrow
165+ self . tableView. keyDown ( with: event)
166166 return nil
167- case 36 : // Return key
167+ case 36 , 48 : // Return and Tab key
168168 return nil
169169 default :
170170 break
@@ -189,18 +189,6 @@ public final class ItemBoxWindowController: NSWindowController {
189189 }
190190 }
191191
192- private func selectNextItemInTable( ) {
193- let nextIndex = min ( tableView. selectedRow + 1 , items. count - 1 )
194- tableView. selectRowIndexes ( IndexSet ( integer: nextIndex) , byExtendingSelection: false )
195- tableView. scrollRowToVisible ( nextIndex)
196- }
197-
198- private func selectPreviousItemInTable( ) {
199- let previousIndex = max ( tableView. selectedRow - 1 , 0 )
200- tableView. selectRowIndexes ( IndexSet ( integer: previousIndex) , byExtendingSelection: false )
201- tableView. scrollRowToVisible ( previousIndex)
202- }
203-
204192 deinit {
205193 removeEventMonitor ( )
206194 }
@@ -213,79 +201,50 @@ extension ItemBoxWindowController: NSTableViewDataSource {
213201}
214202
215203extension ItemBoxWindowController : NSTableViewDelegate {
216- // public func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
217- // items[row].view
218- // }
219-
220204 public func tableView( _ tableView: NSTableView , viewFor tableColumn: NSTableColumn ? , row: Int ) -> NSView ? {
221- let cellIdentifier = NSUserInterfaceItemIdentifier ( " CustomCell " )
222- var cell = tableView. makeView ( withIdentifier: cellIdentifier, owner: nil ) as? CustomTableCellView
223-
224- if cell == nil {
225- cell = CustomTableCellView ( frame: . zero)
226- cell? . identifier = cellIdentifier
227- }
228-
229- // Remove any existing subviews
230- cell? . subviews. forEach { $0. removeFromSuperview ( ) }
231-
232- let itemView = items [ row] . view
233- cell? . addSubview ( itemView)
234- itemView. translatesAutoresizingMaskIntoConstraints = false
235- NSLayoutConstraint . activate ( [
236- itemView. topAnchor. constraint ( equalTo: cell!. topAnchor) ,
237- itemView. leadingAnchor. constraint ( equalTo: cell!. leadingAnchor, constant: 4 ) ,
238- itemView. trailingAnchor. constraint ( equalTo: cell!. trailingAnchor, constant: - 4 ) ,
239- itemView. bottomAnchor. constraint ( equalTo: cell!. bottomAnchor)
240- ] )
205+ items [ row] . view
206+ }
241207
242- return cell
208+ public func tableView( _ tableView: NSTableView , rowViewForRow row: Int ) -> NSTableRowView ? {
209+ ItemBoxRowView ( )
243210 }
244211}
245212
246- private class CustomTableCellView : NSTableCellView {
247- private let backgroundView = NSView ( )
213+ private class NoSlotScroller : NSScroller {
214+ override class var isCompatibleWithOverlayScrollers : Bool { true }
248215
249- override init ( frame frameRect: NSRect ) {
250- super. init ( frame: frameRect)
251- setup ( )
216+ override func drawKnobSlot( in slotRect: NSRect , highlight flag: Bool ) {
217+ // Don't draw the knob slot (the scrollbar background)
252218 }
219+ }
253220
254- required init ? ( coder : NSCoder ) {
255- fatalError ( " init(coder:) has not been implemented " )
256- }
257-
258- private func setup ( ) {
259- wantsLayer = true
260- layerContentsRedrawPolicy = . onSetNeedsDisplay
261-
262- backgroundView . wantsLayer = true
263- backgroundView . layer ? . cornerRadius = 4
264- addSubview ( backgroundView , positioned : . below , relativeTo : nil )
265-
266- backgroundView . translatesAutoresizingMaskIntoConstraints = false
267- NSLayoutConstraint . activate ( [
268- backgroundView . topAnchor . constraint ( equalTo : topAnchor ) ,
269- backgroundView . leadingAnchor . constraint ( equalTo : leadingAnchor ) ,
270- backgroundView . trailingAnchor . constraint ( equalTo : trailingAnchor ) ,
271- backgroundView . bottomAnchor . constraint ( equalTo : bottomAnchor )
272- ] )
273- }
221+ private class ItemBoxRowView : NSTableRowView {
222+ override func drawSelection ( in dirtyRect : NSRect ) {
223+ guard isSelected else { return }
224+ guard let context = NSGraphicsContext . current ? . cgContext else { return }
225+
226+ context . saveGState ( )
227+
228+ // Create a rect that's inset from the edges and has proper padding
229+ // TODO: We create a new selectionRect instead of using dirtyRect
230+ // because there is a visual bug when holding down the arrow keys
231+ // to select the first or last item that draws a clipped rectangular
232+ // selection highlight shape instead of the whole rectangle. Replace
233+ // this when it gets fixed.
234+ let padding : CGFloat = 5
235+ let selectionRect = NSRect (
236+ x : padding ,
237+ y : 0 ,
238+ width : bounds . width - ( padding * 2 ) ,
239+ height : bounds . height
240+ )
274241
275- override var backgroundStyle : NSView . BackgroundStyle {
276- didSet {
277- updateBackgroundColor ( )
278- }
279- }
242+ let cornerRadius : CGFloat = 5
243+ let path = NSBezierPath ( roundedRect: selectionRect, xRadius: cornerRadius, yRadius: cornerRadius)
244+ let selectionColor = NSColor . gray. withAlphaComponent ( 0.19 )
280245
281- private func updateBackgroundColor( ) {
282- switch backgroundStyle {
283- case . normal:
284- backgroundView. layer? . backgroundColor = NSColor . clear. cgColor
285- case . emphasized:
286- backgroundView. layer? . backgroundColor = NSColor . systemBlue. withAlphaComponent ( 0.5 ) . cgColor
287- @unknown default :
288- backgroundView. layer? . backgroundColor = NSColor . clear. cgColor
289- }
246+ context. setFillColor ( selectionColor. cgColor)
247+ path. fill ( )
248+ context. restoreGState ( )
290249 }
291250}
0 commit comments