Skip to content

Commit 838d939

Browse files
Refactors
1 parent a0e6e47 commit 838d939

File tree

1 file changed

+101
-69
lines changed

1 file changed

+101
-69
lines changed

Sources/CodeEditTextView/TextView/TextView+ItemBox.swift

Lines changed: 101 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,17 @@ import AppKit
99
import SwiftUI
1010
import LanguageServerProtocol
1111

12+
/// Represents an item that can be displayed in the ItemBox
1213
public protocol ItemBoxEntry {
1314
var view: NSView { get }
1415
}
1516

17+
private let WINDOW_PADDING: CGFloat = 5
18+
1619
public 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

Comments
 (0)