-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathSplitViewControllerView.swift
More file actions
161 lines (136 loc) Β· 5.29 KB
/
Copy pathSplitViewControllerView.swift
File metadata and controls
161 lines (136 loc) Β· 5.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//
// SplitViewControllerView.swift
// CodeEdit
//
// Created by Wouter Hennen on 20/02/2023.
//
import SwiftUI
struct SplitViewControllerView: NSViewControllerRepresentable {
var axis: Axis
var dividerStyle: CodeEditDividerStyle
var children: _VariadicView.Children
@Binding var viewController: () -> SplitViewController?
func makeNSViewController(context: Context) -> SplitViewController {
let controller = SplitViewController(axis: axis, parentView: self) { controller in
updateItems(controller: controller)
}
return controller
}
func updateNSViewController(_ controller: SplitViewController, context: Context) {
updateItems(controller: controller)
controller.setDividerStyle(dividerStyle)
}
private func updateItems(controller: SplitViewController) {
var hasChanged = false
// Reorder viewcontrollers if needed and add new ones.
controller.items = children.map { child in
let item: SplitViewItem
if let foundItem = controller.items.first(where: { $0.id == child.id }) {
item = foundItem
item.update(child: child)
} else {
hasChanged = true
item = SplitViewItem(child: child)
}
return item
}
controller.splitViewItems = controller.items.map(\.item)
if hasChanged && controller.splitViewItems.count > 1 {
let splitView = controller.splitView
let numerator = splitView.isVertical ? splitView.frame.width : splitView.frame.height
for idx in 0..<controller.items.count-1 {
// If the next view is collapsed, don't reposition the divider.
guard !controller.items[idx+1].item.isCollapsed else { continue }
// This method needs to be run twice to ensure the split works correctly if split vertical.
// I've absolutely no idea why but it works.
splitView.setPosition(
CGFloat(idx + 1) * numerator/CGFloat(controller.items.count),
ofDividerAt: idx
)
splitView.setPosition(
CGFloat(idx + 1) * numerator/CGFloat(controller.items.count),
ofDividerAt: idx
)
}
}
}
}
final class SplitViewController: NSSplitViewController {
final class CustomSplitView: NSSplitView {
@Invalidating(.display)
var customDividerStyle: CodeEditDividerStyle = .system(.thin) {
didSet {
switch customDividerStyle {
case .system(let dividerStyle):
self.dividerStyle = dividerStyle
case .editorDivider:
return
}
}
}
init() {
super.init(frame: .zero)
dividerStyle = .thin
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var dividerColor: NSColor {
customDividerStyle.customColor ?? super.dividerColor
}
override var dividerThickness: CGFloat {
customDividerStyle.customThickness ?? super.dividerThickness
}
override func drawDivider(in rect: NSRect) {
let safeRect = NSRect(
x: rect.origin.x,
y: max(rect.origin.y, safeAreaRect.origin.y),
width: isVertical ? dividerThickness : rect.width,
height: isVertical ? safeAreaRect.height : dividerThickness
)
super.drawDivider(in: safeRect)
}
}
var items: [SplitViewItem] = []
var axis: Axis
var parentView: SplitViewControllerView?
var setUpItems: ((SplitViewController) -> Void)?
init(axis: Axis, parentView: SplitViewControllerView?, setUpItems: ((SplitViewController) -> Void)?) {
self.axis = axis
self.parentView = parentView
self.setUpItems = setUpItems
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
splitView = CustomSplitView()
super.loadView()
}
override func viewDidLoad() {
super.viewDidLoad()
splitView.isVertical = axis != .vertical
setUpItems?(self)
DispatchQueue.main.async { [weak self] in
self?.parentView?.viewController = { [weak self] in
self
}
}
}
override func splitView(_ splitView: NSSplitView, shouldHideDividerAt dividerIndex: Int) -> Bool {
// For some reason, AppKit _really_ wants to hide dividers when there's only one item (and no dividers)
// so we do this check for them.
guard items.count > 1 else { return false }
return super.splitView(splitView, shouldHideDividerAt: dividerIndex)
}
func setDividerStyle(_ dividerStyle: CodeEditDividerStyle) {
guard let splitView = splitView as? CustomSplitView else {
return
}
splitView.customDividerStyle = dividerStyle
}
func collapse(for id: AnyHashable, enabled: Bool) {
items.first { $0.id == id }?.item.animator().isCollapsed = enabled
}
}