Skip to content

Commit a0e6e47

Browse files
Updates
1 parent 63776a1 commit a0e6e47

File tree

1 file changed

+70
-111
lines changed

1 file changed

+70
-111
lines changed

Sources/CodeEditTextView/TextView/TextView+ItemBox.swift

Lines changed: 70 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,30 @@
77

88
import AppKit
99
import SwiftUI
10+
import LanguageServerProtocol
1011

1112
public protocol ItemBoxEntry {
1213
var view: NSView { get }
1314
}
1415

1516
public 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

215203
extension 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

Comments
 (0)