From c286866e5d1f7d7b0843391578022a3f9f6e9edc Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Thu, 29 May 2025 15:50:26 -0500 Subject: [PATCH 1/4] Made editor separators thicker by conditionally adding separators to editors depending on its position in the split layout. --- .../Editor/Views/EditorLayoutView.swift | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/CodeEdit/Features/Editor/Views/EditorLayoutView.swift b/CodeEdit/Features/Editor/Views/EditorLayoutView.swift index 2744a65d3..7fbd8f05b 100644 --- a/CodeEdit/Features/Editor/Views/EditorLayoutView.swift +++ b/CodeEdit/Features/Editor/Views/EditorLayoutView.swift @@ -48,7 +48,6 @@ struct EditorLayoutView: View { struct SubEditorLayoutView: View { @ObservedObject var data: SplitViewData - @FocusState.Binding var focus: Editor? var body: some View { @@ -58,8 +57,10 @@ struct EditorLayoutView: View { .edgesIgnoringSafeArea([.top, .bottom]) } - var splitView: some View { - ForEach(Array(data.editorLayouts.enumerated()), id: \.offset) { index, item in + func stackContentsView(index: Int, item: EditorLayout) -> some View { + Group { + // Add a divider before the editor if it's not the first + if index != 0 { PanelDivider() } EditorLayoutView(layout: item, focus: $focus) .transformEnvironment(\.isAtEdge) { belowToolbar in calcIsAtEdge(current: &belowToolbar, index: index) @@ -67,6 +68,22 @@ struct EditorLayoutView: View { .environment(\.splitEditor) { [weak data] edge, newEditor in data?.split(edge, at: index, new: newEditor) } + // Add a divider after the editor if it's not the last + if index != data.editorLayouts.count - 1 { PanelDivider() } + } + } + + var splitView: some View { + ForEach(Array(data.editorLayouts.enumerated()), id: \.offset) { index, item in + if data.axis == .horizontal { + HStack(spacing: 0) { + stackContentsView(index: index, item: item) + } + } else { + VStack(spacing: 0) { + stackContentsView(index: index, item: item) + } + } } } From 59d39161f19422d9b88894246de18c771b058bb1 Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Wed, 4 Jun 2025 09:19:51 -0500 Subject: [PATCH 2/4] WIP: Chaging approach to how we are modifying thickness and color for dividers --- .../Editor/Views/EditorLayoutView.swift | 56 +++++++------- .../Model/CodeEditDividerStyle.swift | 22 ++++++ .../Features/SplitView/Views/SplitView.swift | 13 +++- .../Views/SplitViewControllerView.swift | 75 +++++++++++++++++-- 4 files changed, 130 insertions(+), 36 deletions(-) create mode 100644 CodeEdit/Features/SplitView/Model/CodeEditDividerStyle.swift diff --git a/CodeEdit/Features/Editor/Views/EditorLayoutView.swift b/CodeEdit/Features/Editor/Views/EditorLayoutView.swift index 7fbd8f05b..1251b81dd 100644 --- a/CodeEdit/Features/Editor/Views/EditorLayoutView.swift +++ b/CodeEdit/Features/Editor/Views/EditorLayoutView.swift @@ -47,43 +47,49 @@ struct EditorLayoutView: View { } struct SubEditorLayoutView: View { + @Environment(\.colorScheme) + private var colorScheme + @ObservedObject var data: SplitViewData @FocusState.Binding var focus: Editor? + @State private var dividerColor: NSColor = .blue + var body: some View { - SplitView(axis: data.axis) { + SplitView( + axis: data.axis, + dividerStyle: .thick(color: dividerColor) + ) { splitView } .edgesIgnoringSafeArea([.top, .bottom]) - } - - func stackContentsView(index: Int, item: EditorLayout) -> some View { - Group { - // Add a divider before the editor if it's not the first - if index != 0 { PanelDivider() } - EditorLayoutView(layout: item, focus: $focus) - .transformEnvironment(\.isAtEdge) { belowToolbar in - calcIsAtEdge(current: &belowToolbar, index: index) - } - .environment(\.splitEditor) { [weak data] edge, newEditor in - data?.split(edge, at: index, new: newEditor) - } - // Add a divider after the editor if it's not the last - if index != data.editorLayouts.count - 1 { PanelDivider() } + .onChange(of: colorScheme) { newValue in + print("ColorScheme changed to: \(newValue == .dark ? "dark" : "light")") + if newValue == .dark { + dividerColor = .red + } else { + dividerColor = .blue + } + } + .onAppear { + print("SubEditorLayoutView appeared with colorScheme: \(colorScheme == .dark ? "dark" : "light")") + if colorScheme == .dark { + dividerColor = .red + } else { + dividerColor = .blue + } } } var splitView: some View { ForEach(Array(data.editorLayouts.enumerated()), id: \.offset) { index, item in - if data.axis == .horizontal { - HStack(spacing: 0) { - stackContentsView(index: index, item: item) - } - } else { - VStack(spacing: 0) { - stackContentsView(index: index, item: item) - } - } + EditorLayoutView(layout: item, focus: $focus) + .transformEnvironment(\.isAtEdge) { belowToolbar in + calcIsAtEdge(current: &belowToolbar, index: index) + } + .environment(\.splitEditor) { [weak data] edge, newEditor in + data?.split(edge, at: index, new: newEditor) + } } } diff --git a/CodeEdit/Features/SplitView/Model/CodeEditDividerStyle.swift b/CodeEdit/Features/SplitView/Model/CodeEditDividerStyle.swift new file mode 100644 index 000000000..f816ba0b6 --- /dev/null +++ b/CodeEdit/Features/SplitView/Model/CodeEditDividerStyle.swift @@ -0,0 +1,22 @@ +// +// CodeEditDividerStyle.swift +// CodeEdit +// +// Created by Khan Winter on 5/30/25. +// + +import AppKit + +enum CodeEditDividerStyle: Equatable { + case system(NSSplitView.DividerStyle, color: NSColor? = nil) + case thick(color: NSColor? = nil) + + var color: NSColor? { + switch self { + case .system(_, let color): + return color + case .thick(let color): + return color + } + } +} diff --git a/CodeEdit/Features/SplitView/Views/SplitView.swift b/CodeEdit/Features/SplitView/Views/SplitView.swift index 12b752f4f..9fe9463c9 100644 --- a/CodeEdit/Features/SplitView/Views/SplitView.swift +++ b/CodeEdit/Features/SplitView/Views/SplitView.swift @@ -9,19 +9,26 @@ import SwiftUI struct SplitView: View { var axis: Axis + var dividerStyle: CodeEditDividerStyle var content: Content - init(axis: Axis, @ViewBuilder content: () -> Content) { + init(axis: Axis, dividerStyle: CodeEditDividerStyle = .system(.thin), @ViewBuilder content: () -> Content) { self.axis = axis + self.dividerStyle = dividerStyle self.content = content() } - @State var viewController: () -> SplitViewController? = { nil } + @State private var viewController: () -> SplitViewController? = { nil } var body: some View { VStack { content.variadic { children in - SplitViewControllerView(axis: axis, children: children, viewController: $viewController) + SplitViewControllerView( + axis: axis, + dividerStyle: dividerStyle, + children: children, + viewController: $viewController + ) } } ._trait(SplitViewControllerLayoutValueKey.self, viewController) diff --git a/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift b/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift index 66b8b157d..d414816b7 100644 --- a/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift +++ b/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift @@ -10,15 +10,19 @@ import SwiftUI struct SplitViewControllerView: NSViewControllerRepresentable { var axis: Axis + var dividerStyle: CodeEditDividerStyle var children: _VariadicView.Children @Binding var viewController: () -> SplitViewController? func makeNSViewController(context: Context) -> SplitViewController { - context.coordinator + let controller = SplitViewController(axis: axis) + updateItems(controller: controller) + return controller } func updateNSViewController(_ controller: SplitViewController, context: Context) { updateItems(controller: controller) + controller.setDividerStyle(dividerStyle) } private func updateItems(controller: SplitViewController) { @@ -61,19 +65,62 @@ struct SplitViewControllerView: NSViewControllerRepresentable { } func makeCoordinator() -> SplitViewController { - SplitViewController(parent: self, axis: axis) + SplitViewController(axis: axis) } } 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 .thick: + return + } + } + } + + init() { + super.init(frame: .zero) + dividerStyle = .thin + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var dividerColor: NSColor { + if let customColor = customDividerStyle.color { + return customColor + } + + switch customDividerStyle { + case .system: + return super.dividerColor + case .thick: + return NSColor.separatorColor + } + } + + override var dividerThickness: CGFloat { + switch customDividerStyle { + case .system: + return super.dividerThickness + case .thick: + return 3.0 + } + } + } var items: [SplitViewItem] = [] var axis: Axis - var parentView: SplitViewControllerView + var parentView: SplitViewControllerView? - init(parent: SplitViewControllerView, axis: Axis = .horizontal) { + init(axis: Axis) { self.axis = axis - self.parentView = parent super.init(nibName: nil, bundle: nil) } @@ -81,20 +128,32 @@ final class SplitViewController: NSSplitViewController { fatalError("init(coder:) has not been implemented") } + override func loadView() { + splitView = CustomSplitView() + super.loadView() + } + override func viewDidLoad() { + super.viewDidLoad() splitView.isVertical = axis != .vertical - splitView.dividerStyle = .thin DispatchQueue.main.async { [weak self] in - self?.parentView.viewController = { [weak self] in + self?.parentView?.viewController = { [weak self] in self } } } - override func splitView(_ splitView: NSSplitView, shouldHideDividerAt dividerIndex: Int) -> Bool { + override func splitView(_ splitView: NSSplitView, canCollapseSubview subview: NSView) -> Bool { false } + 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 } From 78a328c8f08fc74e59947104b277813919dfa386 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Wed, 4 Jun 2025 10:41:46 -0500 Subject: [PATCH 3/4] Finalize Custom Divider Style API --- .../Editor/Views/EditorLayoutView.swift | 23 +---------- .../Model/CodeEditDividerStyle.swift | 34 ++++++++++++---- .../Views/SplitViewControllerView.swift | 40 +++++++++---------- 3 files changed, 46 insertions(+), 51 deletions(-) diff --git a/CodeEdit/Features/Editor/Views/EditorLayoutView.swift b/CodeEdit/Features/Editor/Views/EditorLayoutView.swift index 1251b81dd..a1637db30 100644 --- a/CodeEdit/Features/Editor/Views/EditorLayoutView.swift +++ b/CodeEdit/Features/Editor/Views/EditorLayoutView.swift @@ -53,32 +53,11 @@ struct EditorLayoutView: View { @ObservedObject var data: SplitViewData @FocusState.Binding var focus: Editor? - @State private var dividerColor: NSColor = .blue - var body: some View { - SplitView( - axis: data.axis, - dividerStyle: .thick(color: dividerColor) - ) { + SplitView(axis: data.axis, dividerStyle: .editorDivider) { splitView } .edgesIgnoringSafeArea([.top, .bottom]) - .onChange(of: colorScheme) { newValue in - print("ColorScheme changed to: \(newValue == .dark ? "dark" : "light")") - if newValue == .dark { - dividerColor = .red - } else { - dividerColor = .blue - } - } - .onAppear { - print("SubEditorLayoutView appeared with colorScheme: \(colorScheme == .dark ? "dark" : "light")") - if colorScheme == .dark { - dividerColor = .red - } else { - dividerColor = .blue - } - } } var splitView: some View { diff --git a/CodeEdit/Features/SplitView/Model/CodeEditDividerStyle.swift b/CodeEdit/Features/SplitView/Model/CodeEditDividerStyle.swift index f816ba0b6..1f41340b6 100644 --- a/CodeEdit/Features/SplitView/Model/CodeEditDividerStyle.swift +++ b/CodeEdit/Features/SplitView/Model/CodeEditDividerStyle.swift @@ -7,16 +7,36 @@ import AppKit +/// The style of divider used by ``SplitView``. +/// +/// To add a new style, add another case to this enum and fill in the ``customColor`` and ``customThickness`` +/// variables. When passed to ``SplitView``, the custom styles will be used instead of the default styles. Leave +/// values as `nil` to use default styles. enum CodeEditDividerStyle: Equatable { - case system(NSSplitView.DividerStyle, color: NSColor? = nil) - case thick(color: NSColor? = nil) + case system(NSSplitView.DividerStyle) + case editorDivider - var color: NSColor? { + var customColor: NSColor? { switch self { - case .system(_, let color): - return color - case .thick(let color): - return color + case .system: + return nil + case .editorDivider: + return NSColor(name: nil) { appearance in + if appearance.name == .darkAqua { + NSColor.black + } else { + NSColor(white: 203.0 / 255.0, alpha: 1.0) + } + } + } + } + + var customThickness: CGFloat? { + switch self { + case .system: + return nil + case .editorDivider: + return 3.0 } } } diff --git a/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift b/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift index d414816b7..05e729b08 100644 --- a/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift +++ b/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift @@ -15,8 +15,9 @@ struct SplitViewControllerView: NSViewControllerRepresentable { @Binding var viewController: () -> SplitViewController? func makeNSViewController(context: Context) -> SplitViewController { - let controller = SplitViewController(axis: axis) - updateItems(controller: controller) + let controller = SplitViewController(axis: axis) { controller in + updateItems(controller: controller) + } return controller } @@ -65,7 +66,7 @@ struct SplitViewControllerView: NSViewControllerRepresentable { } func makeCoordinator() -> SplitViewController { - SplitViewController(axis: axis) + SplitViewController(axis: axis, setUpItems: nil) } } @@ -75,9 +76,9 @@ final class SplitViewController: NSSplitViewController { var customDividerStyle: CodeEditDividerStyle = .system(.thin) { didSet { switch customDividerStyle { - case .system(let dividerStyle, _): + case .system(let dividerStyle): self.dividerStyle = dividerStyle - case .thick: + case .editorDivider: return } } @@ -93,25 +94,11 @@ final class SplitViewController: NSSplitViewController { } override var dividerColor: NSColor { - if let customColor = customDividerStyle.color { - return customColor - } - - switch customDividerStyle { - case .system: - return super.dividerColor - case .thick: - return NSColor.separatorColor - } + customDividerStyle.customColor ?? super.dividerColor } override var dividerThickness: CGFloat { - switch customDividerStyle { - case .system: - return super.dividerThickness - case .thick: - return 3.0 - } + customDividerStyle.customThickness ?? super.dividerThickness } } @@ -119,8 +106,11 @@ final class SplitViewController: NSSplitViewController { var axis: Axis var parentView: SplitViewControllerView? - init(axis: Axis) { + var setUpItems: ((SplitViewController) -> Void)? + + init(axis: Axis, setUpItems: ((SplitViewController) -> Void)?) { self.axis = axis + self.setUpItems = setUpItems super.init(nibName: nil, bundle: nil) } @@ -136,6 +126,7 @@ final class SplitViewController: NSSplitViewController { override func viewDidLoad() { super.viewDidLoad() splitView.isVertical = axis != .vertical + setUpItems?(self) DispatchQueue.main.async { [weak self] in self?.parentView?.viewController = { [weak self] in self @@ -147,6 +138,11 @@ final class SplitViewController: NSSplitViewController { false } + override func splitView(_ splitView: NSSplitView, shouldHideDividerAt dividerIndex: Int) -> Bool { + 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 From e08e482b9387f7e3e7c1f796c14b1afdd686df29 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Wed, 4 Jun 2025 10:43:21 -0500 Subject: [PATCH 4/4] Docs --- CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift b/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift index 05e729b08..57a47cd7d 100644 --- a/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift +++ b/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift @@ -139,6 +139,8 @@ final class SplitViewController: NSSplitViewController { } 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) }