From 92e44be1c7ca826aa0d2bb59de6fb5eeafaa9766 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Fri, 5 Sep 2025 10:18:04 -0500 Subject: [PATCH 1/7] Begin Work --- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../CodeEditUI/Views/GlassEffectView.swift | 37 +++++++++++++ .../Editor/TabBar/Tabs/Views/EditorTabs.swift | 36 +++++++------ .../Editor/Views/EditorAreaView.swift | 19 ++++++- CodeEdit/Utils/Extensions/View/View+if.swift | 52 +++++++++++++++++++ 5 files changed, 129 insertions(+), 19 deletions(-) create mode 100644 CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift create mode 100644 CodeEdit/Utils/Extensions/View/View+if.swift diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9532347a14..a468a71f65 100644 --- a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -294,8 +294,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/CodeEditApp/WelcomeWindow", "state" : { - "revision" : "5168cf1ce9579b35ad00706fafef441418d8011f", - "version" : "1.0.0" + "revision" : "cbd5c0d6f432449e2a8618e2b24e4691acbfcc98", + "version" : "1.1.0" } }, { diff --git a/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift b/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift new file mode 100644 index 0000000000..8532ef7fa7 --- /dev/null +++ b/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift @@ -0,0 +1,37 @@ +// +// GlassEffectView.swift +// CodeEdit +// +// Created by Khan Winter on 9/2/25. +// + +import SwiftUI +import AppKit + +struct GlassEffectView: NSViewRepresentable { + var tintColor: NSColor? + + init(tintColor: NSColor? = nil) { + self.tintColor = tintColor + } + + func makeNSView(context: Context) -> NSView { +#if compiler(>=6.2) + if #available(macOS 26, *) { + let view = NSGlassEffectView() + view.cornerRadius = 0 + view.tintColor = tintColor + return view + } +#endif + return NSView() + } + + func updateNSView(_ nsView: NSView, context: Context) { +#if compiler(>=6.2) + if #available(macOS 26, *), let view = nsView as? NSGlassEffectView { + view.tintColor = tintColor + } +#endif + } +} diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift index 1bcc3639c9..547b2d048a 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift @@ -341,21 +341,27 @@ struct EditorTabs: View { // To fill up the parent space of tab bar. .frame(maxWidth: .infinity) } - .overlay(alignment: .leading) { - EditorTabsOverflowShadow( - width: colorScheme == .dark ? 5 : 7, - startPoint: .leading, - endPoint: .trailing - ) - .opacity(scrollOffset >= 0 ? 0 : 1) - } - .overlay(alignment: .trailing) { - EditorTabsOverflowShadow( - width: colorScheme == .dark ? 5 : 7, - startPoint: .trailing, - endPoint: .leading - ) - .opacity((scrollTrailingOffset ?? 0) <= 0 ? 0 : 1) + .if(.tahoe) { + if #available(macOS 26.0, *) { + $0.background(GlassEffectView(tintColor: .secondarySystemFill)).clipShape(Capsule()) + } + } else: { + $0.overlay(alignment: .leading) { + EditorTabsOverflowShadow( + width: colorScheme == .dark ? 5 : 7, + startPoint: .leading, + endPoint: .trailing + ) + .opacity(scrollOffset >= 0 ? 0 : 1) + } + .overlay(alignment: .trailing) { + EditorTabsOverflowShadow( + width: colorScheme == .dark ? 5 : 7, + startPoint: .trailing, + endPoint: .leading + ) + .opacity((scrollTrailingOffset ?? 0) <= 0 ? 0 : 1) + } } } } diff --git a/CodeEdit/Features/Editor/Views/EditorAreaView.swift b/CodeEdit/Features/Editor/Views/EditorAreaView.swift index 544aed54e5..a5260a1d9b 100644 --- a/CodeEdit/Features/Editor/Views/EditorAreaView.swift +++ b/CodeEdit/Features/Editor/Views/EditorAreaView.swift @@ -19,6 +19,18 @@ struct EditorAreaView: View { @AppSettings(\.general.dimEditorsWithoutFocus) var dimEditorsWithoutFocus + @AppSettings(\.theme.useThemeBackground) + var useThemeBackground + + private var backgroundColor: NSColor { + let fallback = NSColor.textBackgroundColor + return if useThemeBackground { + ThemeModel.shared.selectedTheme?.editor.background.nsColor ?? fallback + } else { + fallback + } + } + @ObservedObject var editor: Editor @FocusState.Binding var focus: Editor? @@ -111,7 +123,9 @@ struct EditorAreaView: View { EditorTabBarView(hasTopInsets: topSafeArea > 0, codeFile: fileBinding) .id("TabBarView" + editor.id.uuidString) .environmentObject(editor) - Divider() + if #unavailable(macOS 26) { + Divider() + } } if showEditorJumpBar { EditorJumpBarView( @@ -129,7 +143,8 @@ struct EditorAreaView: View { } } .environment(\.isActiveEditor, editor == editorManager.activeEditor) - .background(EffectView(.headerView)) +// .background(EffectView(.headerView)) + .background(GlassEffectView()) } } .focused($focus, equals: editor) diff --git a/CodeEdit/Utils/Extensions/View/View+if.swift b/CodeEdit/Utils/Extensions/View/View+if.swift new file mode 100644 index 0000000000..57defc484e --- /dev/null +++ b/CodeEdit/Utils/Extensions/View/View+if.swift @@ -0,0 +1,52 @@ +// +// View+if.swift +// CodeEdit +// +// Created by Khan Winter on 9/5/25. +// + +import SwiftUI + +extension View { + /// Applies the given transform if the given condition evaluates to `true`. + /// - Parameters: + /// - condition: The condition to evaluate. + /// - transform: The transform to apply to the source `View`. + /// - Returns: Either the original `View` or the modified `View` if the condition is `true`. + @ViewBuilder + func `if`(_ condition: Bool, @ViewBuilder transform: (Self) -> Content) -> some View { + if condition { + transform(self) + } else { + self + } + } + + /// Applies the given transform if the given condition evaluates to `true`. + /// - Parameters: + /// - condition: The condition to evaluate. + /// - transform: The transform to apply to the source `View`. + /// - Returns: Either the original `View` or the modified `View` if the condition is `true`. + @ViewBuilder + func `if`( + _ condition: Bool, + @ViewBuilder transform: (Self) -> Content, + @ViewBuilder else elseTransform: (Self) -> ElseContent + ) -> some View { + if condition { + transform(self) + } else { + elseTransform(self) + } + } +} + +extension Bool { + static var tahoe: Bool { + if #available(macOS 26, *) { + return true + } else { + return false + } + } +} From 5e9c40b3df2a12bbb8dddc48771eb401d5fd0d47 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:08:21 -0500 Subject: [PATCH 2/7] Experiment with area view --- .../Features/Editor/Views/EditorAreaView.swift | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/CodeEdit/Features/Editor/Views/EditorAreaView.swift b/CodeEdit/Features/Editor/Views/EditorAreaView.swift index a5260a1d9b..b9e6380ad4 100644 --- a/CodeEdit/Features/Editor/Views/EditorAreaView.swift +++ b/CodeEdit/Features/Editor/Views/EditorAreaView.swift @@ -18,7 +18,7 @@ struct EditorAreaView: View { @AppSettings(\.general.dimEditorsWithoutFocus) var dimEditorsWithoutFocus - + @AppSettings(\.theme.useThemeBackground) var useThemeBackground @@ -111,7 +111,7 @@ struct EditorAreaView: View { } set: { newFile in codeFile = { [weak newFile] in newFile } } - + VStack(spacing: 0) { if topSafeArea > 0 { Rectangle() @@ -143,8 +143,14 @@ struct EditorAreaView: View { } } .environment(\.isActiveEditor, editor == editorManager.activeEditor) -// .background(EffectView(.headerView)) - .background(GlassEffectView()) +// .background(EffectView(.headerView, blendingMode: .withinWindow).ignoresSafeArea()) + .if(.tahoe) { + if #available(macOS 26, *) { + $0.background( + EffectView(.headerView, blendingMode: .withinWindow, emphasized: true).ignoresSafeArea() + ) + } + } } } .focused($focus, equals: editor) From 4533a94611428a5a526ff7661e998cc00f0f2a1c Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:08:46 -0500 Subject: [PATCH 3/7] Squashed commit of the following: commit 6218d89ea8af4b007ddb1e2c62d3cc82b64f14e1 Author: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu Sep 4 10:09:23 2025 -0500 Revert Project File Changes commit 46f4e364ca869bfbf178875ad10fdceff08479a5 Author: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Tue Sep 2 14:41:03 2025 -0500 Toolbar Opacity commit 79b49b39bc3a956bcadf0e4747f990b2049667db Author: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Fri Aug 29 16:45:12 2025 -0500 Correct Arrow Edge in Scheme Popover commit c2e7e7034098f12ff8e47d39252b7c32ce276270 Author: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Fri Aug 29 16:37:39 2025 -0500 fix:lint commit 940857772ce3b655fd026254d6f6a68e8cba5e8f Author: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Fri Aug 29 16:36:26 2025 -0500 Remove Unified Style (and fix the rabbit hole of bugs that caused) commit 22b5b93d6775a482c16686814f9b9c2de1d00d2e Author: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Fri Aug 29 12:38:53 2025 -0500 Adjust Task Notification View commit c7c30af901c9327f3263f71baa0ce7ec5be0c9fa Author: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Fri Aug 29 11:58:05 2025 -0500 Update Popover Style commit bc152a080d07e79603e9914fae05b47f6f2569aa Author: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Fri Aug 29 11:37:46 2025 -0500 Use new WelcomeWindow Release commit 45aa64c8b2db8061711a09b401246136d881508e Author: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Fri Aug 29 11:32:43 2025 -0500 Finish Activity Viewer Pills commit 7e50ff943a2c777f05909095ad540c1e82cf0dce Author: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu Aug 28 16:22:32 2025 -0500 Adjust Branch Picker and Scheme Dropdown commit f39c4976529fb563f7fb7837a08f5e5f96148aeb Author: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu Aug 28 16:22:17 2025 -0500 Fix Warning commit a4c879f4a48b77929324be5cfa6b8ae3778ee8a0 Author: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu Aug 28 16:22:11 2025 -0500 Add Start/Stop Task Toolbar Group commit 9d74f8af509f179c03ca2f27d7c4397e0e558b7e Author: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu Aug 28 16:21:33 2025 -0500 Fix Concurrency Issues commit 1d590a9657bde6ccd650ce241c94200d3ba076ec Author: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu Aug 28 16:20:58 2025 -0500 Update Project, Use local WelcomeWindow for now --- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../xcshareddata/xcschemes/CodeEdit.xcscheme | 2 +- .../xcschemes/OpenWithCodeEdit.xcscheme | 2 +- CodeEdit/CodeEditApp.swift | 4 +- .../ActivityViewer/ActivityViewer.swift | 51 +++-- .../Notifications/TaskNotificationView.swift | 94 ++++---- .../Tasks/DropdownMenuItemStyleModifier.swift | 16 ++ .../Tasks/OptionMenuItemView.swift | 6 +- .../Tasks/SchemeDropDownView.swift | 130 +++++++---- .../Tasks/TaskDropDownView.swift | 96 +++++--- .../Tasks/TasksPopoverMenuItem.swift | 5 +- .../Tasks/WorkspaceMenuItemView.swift | 4 +- .../Views/InstantPopoverModifier.swift | 4 +- .../CodeEditUI/Views/PopoverContainer.swift | 31 +++ .../Views/ToolbarBranchPicker.swift | 3 + .../CodeEditDocumentController.swift | 18 +- .../CodeEditSplitViewController.swift | 6 +- .../CodeEditWindowController+Toolbar.swift | 213 ++++++++++++------ .../CodeEditWindowControllerExtensions.swift | 2 + .../WorkspaceDocument/WorkspaceDocument.swift | 3 +- .../Editor/Views/EditorAreaView.swift | 29 +-- .../NotificationPanelViewModel.swift | 68 +++++- .../Views/NotificationToolbarItem.swift | 4 +- .../Models/NSFont+WithWeight.swift | 2 +- .../ToolbarItems/StartTaskToolbarItem.swift | 44 ++++ .../ToolbarItems/StopTaskToolbarItem.swift | 81 +++++++ .../Tasks/Views/StartTaskToolbarButton.swift | 4 +- CodeEdit/Utils/Extensions/View/View+if.swift | 18 +- .../OpenWithCodeEdit.entitlements | 4 - 29 files changed, 657 insertions(+), 291 deletions(-) create mode 100644 CodeEdit/Features/CodeEditUI/Views/PopoverContainer.swift create mode 100644 CodeEdit/Features/Tasks/ToolbarItems/StartTaskToolbarItem.swift create mode 100644 CodeEdit/Features/Tasks/ToolbarItems/StopTaskToolbarItem.swift diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a468a71f65..835319d36b 100644 --- a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -285,8 +285,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/tree-sitter/tree-sitter", "state" : { - "revision" : "bf655c0beaf4943573543fa77c58e8006ff34971", - "version" : "0.25.6" + "revision" : "f2f197b6b27ce75c280c20f131d4f71e906b86f7", + "version" : "0.25.8" } }, { diff --git a/CodeEdit.xcodeproj/xcshareddata/xcschemes/CodeEdit.xcscheme b/CodeEdit.xcodeproj/xcshareddata/xcschemes/CodeEdit.xcscheme index d05b0034c1..2c80a13978 100644 --- a/CodeEdit.xcodeproj/xcshareddata/xcschemes/CodeEdit.xcscheme +++ b/CodeEdit.xcodeproj/xcshareddata/xcschemes/CodeEdit.xcscheme @@ -1,6 +1,6 @@ 1 { - Text("\(taskNotificationHandler.notifications.count)") - .font(.caption) - .padding(5) - .background( - Circle() - .foregroundStyle(.gray) - .opacity(0.2) - ) - .padding(-5) - } + HStack { + if let notification { + HStack { + Text(notification.title) + .font(.subheadline) + .transition( + .asymmetric(insertion: .move(edge: .top), removal: .move(edge: .bottom)) + .combined(with: .opacity) + ) + .id("NotificationTitle" + notification.title) } + .transition(.opacity.combined(with: .move(edge: .trailing))) + + loaderView(notification: notification) + .transition(.opacity) + .id("Loader") + } else { + Text("") + .id("Loader") } - .transition(.opacity.combined(with: .move(edge: .trailing))) - .opacity(activeState == .inactive ? 0.4 : 1.0) - .padding(3) - .padding(-3) - .padding(.trailing, 3) - .popover(isPresented: $isPresented, arrowEdge: .bottom) { - TaskNotificationsDetailView(taskNotificationHandler: taskNotificationHandler) - } - .onTapGesture { - self.isPresented.toggle() - } + } + .opacity(activeState == .inactive ? 0.4 : 1.0) + .padding(3) + .padding(-3) + .popover(isPresented: $isPresented, arrowEdge: .bottom) { + TaskNotificationsDetailView(taskNotificationHandler: taskNotificationHandler) + } + .onTapGesture { + self.isPresented.toggle() } } .animation(.easeInOut, value: notification) @@ -69,6 +56,33 @@ struct TaskNotificationView: View { } } + @ViewBuilder + private func loaderView(notification: TaskNotificationModel) -> some View { + if notification.isLoading { + CECircularProgressView( + progress: notification.percentage, + currentTaskCount: taskNotificationHandler.notifications.count + ) + .if(.tahoe) { + $0.padding(.leading, 1) + } else: { + $0.padding(.horizontal, -1) + } + .frame(height: 16) + } else { + if taskNotificationHandler.notifications.count > 1 { + Text("\(taskNotificationHandler.notifications.count)") + .font(.caption) + .padding(5) + .background( + Circle() + .foregroundStyle(.gray) + .opacity(0.2) + ) + .padding(-5) + } + } + } } #Preview { diff --git a/CodeEdit/Features/ActivityViewer/Tasks/DropdownMenuItemStyleModifier.swift b/CodeEdit/Features/ActivityViewer/Tasks/DropdownMenuItemStyleModifier.swift index 859a7ade29..89226ae038 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/DropdownMenuItemStyleModifier.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/DropdownMenuItemStyleModifier.swift @@ -7,17 +7,33 @@ import SwiftUI +extension View { + @ViewBuilder + func dropdownItemStyle() -> some View { + self.modifier(DropdownMenuItemStyleModifier()) + } +} + struct DropdownMenuItemStyleModifier: ViewModifier { @State private var isHovering = false func body(content: Content) -> some View { content + .padding(.vertical, 4) + .padding(.horizontal, 8) .background( isHovering ? AnyView(EffectView(.selection, blendingMode: .withinWindow, emphasized: true)) : AnyView(Color.clear) ) .foregroundColor(isHovering ? Color(NSColor.white) : .primary) + .if(.tahoe) { + if #available(macOS 26, *) { + $0.clipShape(ContainerRelativeShape()) + } + } else: { + $0.clipShape(RoundedRectangle(cornerRadius: 5)) + } .onHover(perform: { hovering in self.isHovering = hovering }) diff --git a/CodeEdit/Features/ActivityViewer/Tasks/OptionMenuItemView.swift b/CodeEdit/Features/ActivityViewer/Tasks/OptionMenuItemView.swift index 49a78560ef..eaa487bfc5 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/OptionMenuItemView.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/OptionMenuItemView.swift @@ -16,10 +16,8 @@ struct OptionMenuItemView: View { Text(label) Spacer() } - .padding(.vertical, 4) - .padding(.horizontal, 28) - .modifier(DropdownMenuItemStyleModifier()) - .clipShape(RoundedRectangle(cornerRadius: 5)) + .padding(.horizontal, 20) + .dropdownItemStyle() .onTapGesture { action() } diff --git a/CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift b/CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift index c25a985d44..5067871f69 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift @@ -32,36 +32,17 @@ struct SchemeDropDownView: View { } var body: some View { - HStack(spacing: 6) { - Image(systemName: "folder.badge.gearshape") - .imageScale(.medium) - Text(workspaceDisplayName) - .frame(minWidth: 0) - } - .opacity(activeState == .inactive ? 0.4 : 1.0) - .font(.subheadline) - .padding(.trailing, 11.5) - .padding(.horizontal, 2.5) - .padding(.vertical, 2.5) - .background { - Color(nsColor: colorScheme == .dark ? .white : .black) - .opacity(isHoveringScheme || isSchemePopOverPresented ? 0.05 : 0) - .clipShape(RoundedRectangle(cornerSize: CGSize(width: 4, height: 4))) - HStack { - Spacer() - if isHoveringScheme || isSchemePopOverPresented { - chevronDown - .padding(.trailing, 2) - } else { - chevron - .padding(.trailing, 4) - } + Group { + if #available(macOS 26, *) { + tahoe + } else { + seqouia } } .onHover(perform: { hovering in self.isHoveringScheme = hovering }) - .instantPopover(isPresented: $isSchemePopOverPresented, arrowEdge: .bottom) { + .instantPopover(isPresented: $isSchemePopOverPresented, arrowEdge: .top) { popoverContent } .onTapGesture { @@ -78,7 +59,65 @@ struct SchemeDropDownView: View { } } - private var chevron: some View { + @available(macOS 26, *) + @ViewBuilder private var tahoe: some View { + HStack(spacing: 4) { + label + chevron + .offset(x: 2) + .opacity(isHoveringScheme || isSchemePopOverPresented ? 0.0 : 1.0) + } + .background { + if isHoveringScheme || isSchemePopOverPresented { + HStack { + Spacer() + chevronDown + } + } + } + .padding(6) + .padding(.leading, 2) // apparently this is cummulative? + .background { + Color(nsColor: colorScheme == .dark ? .white : .black) + .opacity(isHoveringScheme || isSchemePopOverPresented ? 0.05 : 0) + .clipShape(Capsule()) + } + } + + @ViewBuilder private var seqouia: some View { + label + .padding(.trailing, 11.5) + .padding(.horizontal, 2.5) + .padding(.vertical, 2.5) + .background { + Color(nsColor: colorScheme == .dark ? .white : .black) + .opacity(isHoveringScheme || isSchemePopOverPresented ? 0.05 : 0) + .clipShape(RoundedRectangle(cornerSize: CGSize(width: 4, height: 4))) + HStack { + Spacer() + if isHoveringScheme || isSchemePopOverPresented { + chevronDown + .padding(.trailing, 2) + } else { + chevron + .padding(.trailing, 4) + } + } + } + } + + @ViewBuilder private var label: some View { + HStack(spacing: 6) { + Image(systemName: "folder.badge.gearshape") + .imageScale(.medium) + Text(workspaceDisplayName) + .frame(minWidth: 0) + } + .opacity(activeState == .inactive ? 0.4 : 1.0) + .font(.subheadline) + } + + @ViewBuilder private var chevron: some View { Image(systemName: "chevron.compact.right") .font(.system(size: 9, weight: .medium, design: .default)) .foregroundStyle(.secondary) @@ -86,7 +125,7 @@ struct SchemeDropDownView: View { .imageScale(.large) } - private var chevronDown: some View { + @ViewBuilder private var chevronDown: some View { VStack(spacing: 1) { Image(systemName: "chevron.down") } @@ -95,29 +134,24 @@ struct SchemeDropDownView: View { } @ViewBuilder var popoverContent: some View { - VStack(alignment: .leading, spacing: 0) { - WorkspaceMenuItemView( - workspaceFileManager: workspaceFileManager, - item: workspaceFileManager?.workspaceItem - ) - Divider() - .padding(.vertical, 5) - Group { - OptionMenuItemView(label: "Add Folder...") { - // TODO: Implment Add Folder - print("NOT IMPLEMENTED") - } - .disabled(true) - OptionMenuItemView(label: "Workspace Settings...") { - NSApp.sendAction( - #selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil - ) - } + WorkspaceMenuItemView( + workspaceFileManager: workspaceFileManager, + item: workspaceFileManager?.workspaceItem + ) + Divider() + .padding(.vertical, 5) + Group { + OptionMenuItemView(label: "Add Folder...") { + // TODO: Implment Add Folder + print("NOT IMPLEMENTED") + } + .disabled(true) + OptionMenuItemView(label: "Workspace Settings...") { + NSApp.sendAction( + #selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil + ) } } - .font(.subheadline) - .padding(5) - .frame(minWidth: 215) } } diff --git a/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift b/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift index dc897d2687..6ce8699311 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift @@ -21,25 +21,12 @@ struct TaskDropDownView: View { var body: some View { Group { - if let selectedTask = taskManager.selectedTask { - if let selectedActiveTask = taskManager.activeTasks[selectedTask.id] { - ActiveTaskView(activeTask: selectedActiveTask) - .fixedSize() - } else { - TaskView(task: selectedTask, status: CETaskStatus.notRunning) - .fixedSize() - } + if #available(macOS 26, *) { + tahoe } else { - Text("Create Tasks") - .frame(minWidth: 0) + seqouia } } - .opacity(activeState == .inactive ? 0.4 : 1.0) - .font(.subheadline) - .padding(.trailing, 11.5) - .padding(.horizontal, 2.5) - .padding(.vertical, 2.5) - .background(backgroundColor) .onHover { hovering in self.isHoveringTasks = hovering } @@ -60,7 +47,49 @@ struct TaskDropDownView: View { } } - private var backgroundColor: some View { + @available(macOS 26, *) + @ViewBuilder private var tahoe: some View { + HStack(spacing: 4) { + label + chevronIcon + .opacity(isHoveringTasks || isTaskPopOverPresented ? 1.0 : 0.0) + } + .padding(6) + .background { + Color(nsColor: colorScheme == .dark ? .white : .black) + .opacity(isHoveringTasks || isTaskPopOverPresented ? 0.05 : 0) + .clipShape(Capsule()) + } + } + + @ViewBuilder private var seqouia: some View { + label + .opacity(activeState == .inactive ? 0.4 : 1.0) + .padding(.trailing, 11.5) + .padding(.horizontal, 2.5) + .padding(.vertical, 2.5) + .background(backgroundColor) + } + + @ViewBuilder private var label: some View { + Group { + if let selectedTask = taskManager.selectedTask { + if let selectedActiveTask = taskManager.activeTasks[selectedTask.id] { + ActiveTaskView(activeTask: selectedActiveTask) + .fixedSize() + } else { + TaskView(task: selectedTask, status: CETaskStatus.notRunning) + .fixedSize() + } + } else { + Text("Create Tasks") + .frame(minWidth: 0) + } + } + .font(.subheadline) + } + + @ViewBuilder private var backgroundColor: some View { Color(nsColor: colorScheme == .dark ? .white : .black) .opacity(isHoveringTasks || isTaskPopOverPresented ? 0.05 : 0) .clipShape(RoundedRectangle(cornerSize: CGSize(width: 4, height: 4))) @@ -74,33 +103,28 @@ struct TaskDropDownView: View { ) } - private var chevronIcon: some View { + @ViewBuilder private var chevronIcon: some View { Image(systemName: "chevron.down") .font(.system(size: 8, weight: .bold, design: .default)) .padding(.top, 0.5) .padding(.trailing, 2) } - private var taskPopoverContent: some View { - VStack(alignment: .leading, spacing: 0) { - if !taskManager.availableTasks.isEmpty { - ForEach(taskManager.availableTasks, id: \.id) { task in - TasksPopoverMenuItem(taskManager: taskManager, task: task) { - isTaskPopOverPresented = false - } + @ViewBuilder private var taskPopoverContent: some View { + if !taskManager.availableTasks.isEmpty { + ForEach(taskManager.availableTasks, id: \.id) { task in + TasksPopoverMenuItem(taskManager: taskManager, task: task) { + isTaskPopOverPresented = false } - Divider() - .padding(.vertical, 5) - } - OptionMenuItemView(label: "Add Task...") { - NSApp.sendAction(#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil) - } - OptionMenuItemView(label: "Manage Tasks...") { - NSApp.sendAction(#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil) } + Divider() + .padding(.vertical, 5) + } + OptionMenuItemView(label: "Add Task...") { + NSApp.sendAction(#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil) + } + OptionMenuItemView(label: "Manage Tasks...") { + NSApp.sendAction(#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil) } - .font(.subheadline) - .padding(5) - .frame(minWidth: 215) } } diff --git a/CodeEdit/Features/ActivityViewer/Tasks/TasksPopoverMenuItem.swift b/CodeEdit/Features/ActivityViewer/Tasks/TasksPopoverMenuItem.swift index 2205660b3c..528e0c96b5 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/TasksPopoverMenuItem.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/TasksPopoverMenuItem.swift @@ -20,11 +20,8 @@ struct TasksPopoverMenuItem: View { selectionIndicator popoverContent } - .padding(.vertical, 4) - .padding(.horizontal, 8) - .modifier(DropdownMenuItemStyleModifier()) + .dropdownItemStyle() .onTapGesture(perform: selectAction) - .clipShape(RoundedRectangle(cornerRadius: 5)) .accessibilityElement() .accessibilityLabel(task.name) .accessibilityAction(.default, selectAction) diff --git a/CodeEdit/Features/ActivityViewer/Tasks/WorkspaceMenuItemView.swift b/CodeEdit/Features/ActivityViewer/Tasks/WorkspaceMenuItemView.swift index 6eaa8f262e..9c12b49342 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/WorkspaceMenuItemView.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/WorkspaceMenuItemView.swift @@ -27,9 +27,7 @@ struct WorkspaceMenuItemView: View { Text(item?.name ?? "") Spacer() } - .padding(.vertical, 4) - .padding(.horizontal, 8) - .modifier(DropdownMenuItemStyleModifier()) + .dropdownItemStyle() .onTapGesture { } // add accessibility action when this is filled in .clipShape(RoundedRectangle(cornerRadius: 5)) .accessibilityElement() diff --git a/CodeEdit/Features/CodeEditUI/Views/InstantPopoverModifier.swift b/CodeEdit/Features/CodeEditUI/Views/InstantPopoverModifier.swift index c1978b1396..037f7a701d 100644 --- a/CodeEdit/Features/CodeEditUI/Views/InstantPopoverModifier.swift +++ b/CodeEdit/Features/CodeEditUI/Views/InstantPopoverModifier.swift @@ -126,13 +126,13 @@ extension View { func instantPopover( isPresented: Binding, arrowEdge: Edge = .bottom, - @ViewBuilder content: () -> Content + @ViewBuilder content: @escaping () -> Content ) -> some View { self.modifier( InstantPopoverModifier( isPresented: isPresented, arrowEdge: arrowEdge, - popoverContent: content() + popoverContent: PopoverContainer(content: content) ) ) } diff --git a/CodeEdit/Features/CodeEditUI/Views/PopoverContainer.swift b/CodeEdit/Features/CodeEditUI/Views/PopoverContainer.swift new file mode 100644 index 0000000000..7d62b94f56 --- /dev/null +++ b/CodeEdit/Features/CodeEditUI/Views/PopoverContainer.swift @@ -0,0 +1,31 @@ +// +// PopoverContainer.swift +// CodeEdit +// +// Created by Khan Winter on 8/29/25. +// + +import SwiftUI + +/// Container for SwiftUI views presented in a popover. +/// On tahoe and above, adds the correct container shape. +struct PopoverContainer: View { + let content: () -> ContentView + + init(@ViewBuilder content: @escaping () -> ContentView) { + self.content = content + } + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + content() + } + .font(.subheadline) + .if(.tahoe) { + $0.padding(13).containerShape(RoundedRectangle(cornerRadius: 20, style: .continuous)) + } else: { + $0.padding(5) + } + .frame(minWidth: 215) + } +} diff --git a/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift b/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift index b199e7b204..7993da1802 100644 --- a/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift +++ b/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift @@ -89,6 +89,9 @@ struct ToolbarBranchPicker: View { await self.sourceControlManager?.refreshBranches() } } + .if(.tahoe) { + $0.padding(.leading, 10).frame(minWidth: 140) + } } private var inactiveColor: Color { diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditDocumentController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditDocumentController.swift index 757aad3fdf..e666f7668f 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditDocumentController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditDocumentController.swift @@ -85,15 +85,17 @@ final class CodeEditDocumentController: NSDocumentController { } super.openDocument(withContentsOf: url, display: displayDocument) { document, documentWasAlreadyOpen, error in - if let document { - self.addDocument(document) - } else { - let errorMessage = error?.localizedDescription ?? "unknown error" - print("Unable to open document '\(url)': \(errorMessage)") - } + MainActor.assumeIsolated { + if let document { + self.addDocument(document) + } else { + let errorMessage = error?.localizedDescription ?? "unknown error" + print("Unable to open document '\(url)': \(errorMessage)") + } - RecentsStore.documentOpened(at: url) - completionHandler(document, documentWasAlreadyOpen, error) + RecentsStore.documentOpened(at: url) + completionHandler(document, documentWasAlreadyOpen, error) + } } } diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift index a15ac9311e..39735c8de1 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift @@ -97,7 +97,9 @@ final class CodeEditSplitViewController: NSSplitViewController { private func makeNavigator(view: some View) -> NSSplitViewItem { let navigator = NSSplitViewItem(sidebarWithViewController: NSHostingController(rootView: view)) - navigator.titlebarSeparatorStyle = .none + if #unavailable(macOS 26) { + navigator.titlebarSeparatorStyle = .none + } navigator.isSpringLoaded = true navigator.minimumThickness = Self.minSidebarWidth navigator.collapseBehavior = .useConstraints @@ -133,6 +135,8 @@ final class CodeEditSplitViewController: NSSplitViewController { .inspectorCollapsed ) as? Bool ?? true } + + workspace.notificationPanel.updateToolbarItem() } // MARK: - NSSplitViewDelegate diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift index 4b434fbd44..e9730abc55 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift @@ -13,34 +13,67 @@ extension CodeEditWindowController { internal func setupToolbar() { let toolbar = NSToolbar(identifier: UUID().uuidString) toolbar.delegate = self - toolbar.displayMode = .labelOnly toolbar.showsBaselineSeparator = false self.window?.titleVisibility = toolbarCollapsed ? .visible : .hidden - self.window?.toolbarStyle = .unifiedCompact + if #available(macOS 26, *) { + self.window?.toolbarStyle = .automatic + toolbar.centeredItemIdentifiers = [.activityViewer, .notificationItem] + toolbar.displayMode = .iconOnly + self.window?.titlebarAppearsTransparent = true + } else { + self.window?.toolbarStyle = .unifiedCompact + toolbar.displayMode = .labelOnly + } self.window?.titlebarSeparatorStyle = .automatic self.window?.toolbar = toolbar } func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { - [ + var items: [NSToolbarItem.Identifier] = [ .toggleFirstSidebarItem, .flexibleSpace, - .stopTaskSidebarItem, - .startTaskSidebarItem, + ] + + if #available(macOS 26, *) { + items += [.taskSidebarItem] + } else { + items += [ + .stopTaskSidebarItem, + .startTaskSidebarItem, + ] + } + + items += [ .sidebarTrackingSeparator, .branchPicker, .flexibleSpace, - .activityViewer, - .notificationItem, + ] + + if #available(macOS 26, *) { + items += [ + .activityViewer, + .space, + .notificationItem, + ] + } else { + items += [ + .activityViewer, + .notificationItem, + ] + } + + items += [ .flexibleSpace, .itemListTrackingSeparator, .flexibleSpace, .toggleLastSidebarItem ] + + return items } func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { - [ + var items: [NSToolbarItem.Identifier] = [ .toggleFirstSidebarItem, .sidebarTrackingSeparator, .flexibleSpace, @@ -49,9 +82,20 @@ extension CodeEditWindowController { .branchPicker, .activityViewer, .notificationItem, - .startTaskSidebarItem, - .stopTaskSidebarItem ] + + if #available(macOS 26, *) { + items += [ + .taskSidebarItem + ] + } else { + items += [ + .startTaskSidebarItem, + .stopTaskSidebarItem + ] + } + + return items } func toggleToolbar() { @@ -88,7 +132,6 @@ extension CodeEditWindowController { ) case .toggleFirstSidebarItem: let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.toggleFirstSidebarItem) - toolbarItem.label = "Navigator Sidebar" toolbarItem.paletteLabel = " Navigator Sidebar" toolbarItem.toolTip = "Hide or show the Navigator" toolbarItem.isBordered = true @@ -102,7 +145,6 @@ extension CodeEditWindowController { return toolbarItem case .toggleLastSidebarItem: let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.toggleLastSidebarItem) - toolbarItem.label = "Inspector Sidebar" toolbarItem.paletteLabel = "Inspector Sidebar" toolbarItem.toolTip = "Hide or show the Inspectors" toolbarItem.isBordered = true @@ -115,30 +157,9 @@ extension CodeEditWindowController { return toolbarItem case .stopTaskSidebarItem: - let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.stopTaskSidebarItem) - - guard let taskManager = workspace?.taskManager - else { return nil } - - let view = NSHostingView( - rootView: StopTaskToolbarButton(taskManager: taskManager) - ) - toolbarItem.view = view - - return toolbarItem + return stopTaskSidebarItem() case .startTaskSidebarItem: - let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.startTaskSidebarItem) - - guard let taskManager = workspace?.taskManager else { return nil } - guard let workspace = workspace else { return nil } - - let view = NSHostingView( - rootView: StartTaskToolbarButton(taskManager: taskManager) - .environmentObject(workspace) - ) - toolbarItem.view = view - - return toolbarItem + return startTaskSidebarItem() case .branchPicker: let toolbarItem = NSToolbarItem(itemIdentifier: .branchPicker) let view = NSHostingView( @@ -147,48 +168,98 @@ extension CodeEditWindowController { ) ) toolbarItem.view = view - + toolbarItem.isBordered = false return toolbarItem case .activityViewer: - let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.activityViewer) - toolbarItem.visibilityPriority = .user - guard let workspaceSettingsManager = workspace?.workspaceSettingsManager, - let taskNotificationHandler = workspace?.taskNotificationHandler, - let taskManager = workspace?.taskManager - else { return nil } - - let view = NSHostingView( - rootView: ActivityViewer( - workspaceFileManager: workspace?.workspaceFileManager, - workspaceSettingsManager: workspaceSettingsManager, - taskNotificationHandler: taskNotificationHandler, - taskManager: taskManager - ) - ) - - let weakWidth = view.widthAnchor.constraint(equalToConstant: 650) - weakWidth.priority = .defaultLow - let strongWidth = view.widthAnchor.constraint(greaterThanOrEqualToConstant: 200) - strongWidth.priority = .defaultHigh + return activityViewerItem() + case .notificationItem: + return notificationItem() + case .taskSidebarItem: + guard #available(macOS 26, *) else { + fatalError("Unified task sidebar item used on pre-tahoe platform.") + } + guard let workspace, + let stop = StopTaskToolbarItem(workspace: workspace) else { + return nil + } + let start = StartTaskToolbarItem(workspace: workspace) - NSLayoutConstraint.activate([ - weakWidth, - strongWidth - ]) + let group = NSToolbarItemGroup(itemIdentifier: .taskSidebarItem) + group.isBordered = true + group.controlRepresentation = .expanded + group.selectionMode = .momentary + group.subitems = [stop, start] - toolbarItem.view = view - return toolbarItem - case .notificationItem: - let toolbarItem = NSToolbarItem(itemIdentifier: .notificationItem) - guard let workspace = workspace else { return nil } - let view = NSHostingView( - rootView: NotificationToolbarItem() - .environmentObject(workspace) - ) - toolbarItem.view = view - return toolbarItem + return group default: return NSToolbarItem(itemIdentifier: itemIdentifier) } } + + private func stopTaskSidebarItem() -> NSToolbarItem? { + let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.stopTaskSidebarItem) + + guard let taskManager = workspace?.taskManager else { return nil } + + let view = NSHostingView( + rootView: StopTaskToolbarButton(taskManager: taskManager) + ) + toolbarItem.view = view + + return toolbarItem + } + + private func startTaskSidebarItem() -> NSToolbarItem? { + let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.startTaskSidebarItem) + + guard let taskManager = workspace?.taskManager else { return nil } + guard let workspace = workspace else { return nil } + + let view = NSHostingView( + rootView: StartTaskToolbarButton(taskManager: taskManager) + .environmentObject(workspace) + ) + toolbarItem.view = view + + return toolbarItem + } + + private func notificationItem() -> NSToolbarItem? { + let toolbarItem = NSToolbarItem(itemIdentifier: .notificationItem) + guard let workspace = workspace else { return nil } + let view = NSHostingView(rootView: NotificationToolbarItem().environmentObject(workspace)) + toolbarItem.view = view + return toolbarItem + } + + private func activityViewerItem() -> NSToolbarItem? { + let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.activityViewer) + toolbarItem.visibilityPriority = .user + guard let workspaceSettingsManager = workspace?.workspaceSettingsManager, + let taskNotificationHandler = workspace?.taskNotificationHandler, + let taskManager = workspace?.taskManager + else { return nil } + + let view = NSHostingView( + rootView: ActivityViewer( + workspaceFileManager: workspace?.workspaceFileManager, + workspaceSettingsManager: workspaceSettingsManager, + taskNotificationHandler: taskNotificationHandler, + taskManager: taskManager + ) + ) + + let weakWidth = view.widthAnchor.constraint(equalToConstant: 650) + weakWidth.priority = .defaultLow + let strongWidth = view.widthAnchor.constraint(greaterThanOrEqualToConstant: 200) + strongWidth.priority = .defaultHigh + + NSLayoutConstraint.activate([ + weakWidth, + strongWidth + ]) + + toolbarItem.view = view + return toolbarItem + } } diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift index baade6dfdf..d8cb37450c 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift @@ -121,4 +121,6 @@ extension NSToolbarItem.Identifier { static let branchPicker: NSToolbarItem.Identifier = NSToolbarItem.Identifier("BranchPicker") static let activityViewer: NSToolbarItem.Identifier = NSToolbarItem.Identifier("ActivityViewer") static let notificationItem = NSToolbarItem.Identifier("notificationItem") + + static let taskSidebarItem: NSToolbarItem.Identifier = NSToolbarItem.Identifier("TaskSidebarItem") } diff --git a/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument.swift index 9d20cb57d2..4671b57f4f 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument.swift @@ -47,11 +47,12 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { var undoRegistration: UndoManagerRegistration = UndoManagerRegistration() - @Published var notificationPanel = NotificationPanelViewModel() + var notificationPanel = NotificationPanelViewModel() private var cancellables = Set() override init() { super.init() + notificationPanel.workspace = self // Observe changes to notification panel notificationPanel.objectWillChange diff --git a/CodeEdit/Features/Editor/Views/EditorAreaView.swift b/CodeEdit/Features/Editor/Views/EditorAreaView.swift index b9e6380ad4..4795d7119a 100644 --- a/CodeEdit/Features/Editor/Views/EditorAreaView.swift +++ b/CodeEdit/Features/Editor/Views/EditorAreaView.swift @@ -18,18 +18,6 @@ struct EditorAreaView: View { @AppSettings(\.general.dimEditorsWithoutFocus) var dimEditorsWithoutFocus - - @AppSettings(\.theme.useThemeBackground) - var useThemeBackground - - private var backgroundColor: NSColor { - let fallback = NSColor.textBackgroundColor - return if useThemeBackground { - ThemeModel.shared.selectedTheme?.editor.background.nsColor ?? fallback - } else { - fallback - } - } @ObservedObject var editor: Editor @@ -111,7 +99,7 @@ struct EditorAreaView: View { } set: { newFile in codeFile = { [weak newFile] in newFile } } - + VStack(spacing: 0) { if topSafeArea > 0 { Rectangle() @@ -123,9 +111,7 @@ struct EditorAreaView: View { EditorTabBarView(hasTopInsets: topSafeArea > 0, codeFile: fileBinding) .id("TabBarView" + editor.id.uuidString) .environmentObject(editor) - if #unavailable(macOS 26) { - Divider() - } + Divider() } if showEditorJumpBar { EditorJumpBarView( @@ -143,13 +129,12 @@ struct EditorAreaView: View { } } .environment(\.isActiveEditor, editor == editorManager.activeEditor) -// .background(EffectView(.headerView, blendingMode: .withinWindow).ignoresSafeArea()) .if(.tahoe) { - if #available(macOS 26, *) { - $0.background( - EffectView(.headerView, blendingMode: .withinWindow, emphasized: true).ignoresSafeArea() - ) - } + // FB20047271: Glass toolbar effect ignores floating scroll view views. + // https://openradar.appspot.com/radar?id=EhAKBVJhZGFyEICAgKbGmesJ + $0.background(EffectView(.headerView).ignoresSafeArea(.all)) + } else: { + $0.background(EffectView(.headerView)) } } } diff --git a/CodeEdit/Features/Notifications/ViewModels/NotificationPanelViewModel.swift b/CodeEdit/Features/Notifications/ViewModels/NotificationPanelViewModel.swift index 9343856b92..6fdcc89143 100644 --- a/CodeEdit/Features/Notifications/ViewModels/NotificationPanelViewModel.swift +++ b/CodeEdit/Features/Notifications/ViewModels/NotificationPanelViewModel.swift @@ -30,6 +30,13 @@ final class NotificationPanelViewModel: ObservableObject { @Published var scrolledToTop: Bool = true + /// A filtered list of active notifications. + var visibleNotifications: [CENotification] { + activeNotifications.filter { !hiddenNotificationIds.contains($0.id) } + } + + weak var workspace: WorkspaceDocument? + /// Whether a notification should be visible in the panel func isNotificationVisible(_ notification: CENotification) -> Bool { if notification.isBeingDismissed { @@ -171,12 +178,20 @@ final class NotificationPanelViewModel: ObservableObject { /// Handles a new notification being added func handleNewNotification(_ notification: CENotification) { - withAnimation(.easeInOut(duration: 0.3)) { - insertNotification(notification) - hiddenNotificationIds.remove(notification.id) - if !isPresented && !notification.isSticky { - startHideTimer(for: notification) + let operation = { + self.insertNotification(notification) + self.hiddenNotificationIds.remove(notification.id) + if !self.isPresented && !notification.isSticky { + self.startHideTimer(for: notification) + } + } + + if #available(macOS 26, *) { + withAnimation(.easeInOut(duration: 0.3), operation) { + self.updateToolbarItem() } + } else { + withAnimation(.easeInOut(duration: 0.3), operation) } } @@ -215,6 +230,31 @@ final class NotificationPanelViewModel: ObservableObject { } } + func updateToolbarItem() { + if #available(macOS 15.0, *) { + self.workspace?.windowControllers.forEach { controller in + guard let toolbar = controller.window?.toolbar else { + return + } + let shouldShow = !self.visibleNotifications.isEmpty || NotificationManager.shared.unreadCount > 0 + if shouldShow && toolbar.items.filter({ $0.itemIdentifier == .notificationItem }).first == nil { + guard let activityItemIdx = toolbar.items + .firstIndex(where: { $0.itemIdentifier == .activityViewer }) else { + return + } + toolbar.insertItem(withItemIdentifier: .space, at: activityItemIdx + 1) + toolbar.insertItem(withItemIdentifier: .notificationItem, at: activityItemIdx + 2) + } + + if !shouldShow, let index = toolbar.items + .firstIndex(where: { $0.itemIdentifier == .notificationItem }) { + toolbar.removeItem(at: index) + toolbar.removeItem(at: index) + } + } + } + } + init() { // Observe new notifications NotificationCenter.default.addObserver( @@ -252,14 +292,22 @@ final class NotificationPanelViewModel: ObservableObject { private func handleNotificationRemoved(_ notification: Notification) { guard let ceNotification = notification.object as? CENotification else { return } - // Just remove from active notifications without triggering global state changes - withAnimation(.easeOut(duration: 0.2)) { - activeNotifications.removeAll(where: { $0.id == ceNotification.id }) + let operation: () -> Void = { + self.activeNotifications.removeAll(where: { $0.id == ceNotification.id }) // If this was the last notification and they were manually shown, hide the panel - if activeNotifications.isEmpty && isPresented { - isPresented = false + if self.activeNotifications.isEmpty && self.isPresented { + self.isPresented = false } } + + // Just remove from active notifications without triggering global state changes + if #available(macOS 26, *) { + withAnimation(.easeOut(duration: 0.2), operation) { + self.updateToolbarItem() + } + } else { + withAnimation(.easeOut(duration: 0.2), operation) + } } } diff --git a/CodeEdit/Features/Notifications/Views/NotificationToolbarItem.swift b/CodeEdit/Features/Notifications/Views/NotificationToolbarItem.swift index 9729330ddd..ecf8ea94a5 100644 --- a/CodeEdit/Features/Notifications/Views/NotificationToolbarItem.swift +++ b/CodeEdit/Features/Notifications/Views/NotificationToolbarItem.swift @@ -14,9 +14,7 @@ struct NotificationToolbarItem: View { private var controlActiveState var body: some View { - let visibleNotifications = workspace.notificationPanel.activeNotifications.filter { - !workspace.notificationPanel.hiddenNotificationIds.contains($0.id) - } + let visibleNotifications = workspace.notificationPanel.visibleNotifications if notificationManager.unreadCount > 0 || !visibleNotifications.isEmpty { Button { diff --git a/CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/NSFont+WithWeight.swift b/CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/NSFont+WithWeight.swift index 561ea4f712..e3b7871f1b 100644 --- a/CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/NSFont+WithWeight.swift +++ b/CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/NSFont+WithWeight.swift @@ -44,7 +44,7 @@ extension NSFont { } } -extension NSFont.Weight: Codable { +extension NSFont.Weight: @retroactive Codable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(self.rawValue) diff --git a/CodeEdit/Features/Tasks/ToolbarItems/StartTaskToolbarItem.swift b/CodeEdit/Features/Tasks/ToolbarItems/StartTaskToolbarItem.swift new file mode 100644 index 0000000000..2d2b5d6e1b --- /dev/null +++ b/CodeEdit/Features/Tasks/ToolbarItems/StartTaskToolbarItem.swift @@ -0,0 +1,44 @@ +// +// StartTaskToolbarItem.swift +// CodeEdit +// +// Created by Khan Winter on 8/28/25. +// + +import AppKit + +@available(macOS 26, *) +final class StartTaskToolbarItem: NSToolbarItem { + private weak var workspace: WorkspaceDocument? + + private var utilityAreaCollapsed: Bool { + workspace?.utilityAreaModel?.isCollapsed ?? true + } + + init(workspace: WorkspaceDocument) { + self.workspace = workspace + super.init(itemIdentifier: NSToolbarItem.Identifier("StartTaskToolbarItem")) + + image = NSImage(systemSymbolName: "play.fill", accessibilityDescription: nil) + let config = NSImage.SymbolConfiguration(pointSize: 14, weight: .regular) + image = image?.withSymbolConfiguration(config) ?? image + + paletteLabel = "Start Task" + toolTip = "Run the selected task" + target = self + action = #selector(startTask) + isBordered = true + } + + @objc + func startTask() { + guard let taskManager = workspace?.taskManager else { return } + + taskManager.executeActiveTask() + if utilityAreaCollapsed { + CommandManager.shared.executeCommand("open.drawer") + } + workspace?.utilityAreaModel?.selectedTab = .debugConsole + taskManager.taskShowingOutput = taskManager.selectedTaskID + } +} diff --git a/CodeEdit/Features/Tasks/ToolbarItems/StopTaskToolbarItem.swift b/CodeEdit/Features/Tasks/ToolbarItems/StopTaskToolbarItem.swift new file mode 100644 index 0000000000..eaa5148439 --- /dev/null +++ b/CodeEdit/Features/Tasks/ToolbarItems/StopTaskToolbarItem.swift @@ -0,0 +1,81 @@ +// +// StopTaskToolbarItem.swift +// CodeEdit +// +// Created by Khan Winter on 8/28/25. +// + +import AppKit +import Combine + +@available(macOS 26, *) +final class StopTaskToolbarItem: NSToolbarItem { + private weak var workspace: WorkspaceDocument? + + private var taskManager: TaskManager? { + workspace?.taskManager + } + + /// The listener that listens to the active task's status publisher. Is updated frequently as the active task + /// changes. + private var statusListener: AnyCancellable? + private var otherListeners: Set = [] + + init?(workspace: WorkspaceDocument) { + guard let taskManager = workspace.taskManager else { return nil } + + self.workspace = workspace + super.init(itemIdentifier: NSToolbarItem.Identifier("StopTaskToolbarItem")) + + image = NSImage(systemSymbolName: "stop.fill", accessibilityDescription: nil) + let config = NSImage.SymbolConfiguration(pointSize: 14, weight: .regular) + image = image?.withSymbolConfiguration(config) ?? image + + paletteLabel = "Stop Task" + toolTip = "Stop the selected task" + target = self + isEnabled = false + isBordered = true + + taskManager.$selectedTaskID.sink { [weak self] selectedId in + self?.updateStatusListener(activeTasks: taskManager.activeTasks, selectedId: selectedId) + } + .store(in: &otherListeners) + + taskManager.$activeTasks.sink { [weak self] activeTasks in + self?.updateStatusListener(activeTasks: activeTasks, selectedId: taskManager.selectedTaskID) + } + .store(in: &otherListeners) + + updateStatusListener(activeTasks: taskManager.activeTasks, selectedId: taskManager.selectedTaskID) + } + + /// Update the ``statusListener`` to listen to a potentially new active task. + private func updateStatusListener(activeTasks: [UUID: CEActiveTask], selectedId: UUID?) { + statusListener?.cancel() + + if let status = activeTasks[selectedId ?? UUID()]?.status { + updateForNewStatus(status) + } + + guard let id = selectedId else { return } + statusListener = activeTasks[id]?.$status.sink { [weak self] status in + self?.updateForNewStatus(status) + } + } + + private func updateForNewStatus(_ status: CETaskStatus) { + isEnabled = status == .running + action = isEnabled ? #selector(stopTask) : nil + } + + @objc + func stopTask() { + taskManager?.terminateActiveTask() + } + + deinit { + statusListener?.cancel() + otherListeners.removeAll() + } +} diff --git a/CodeEdit/Features/Tasks/Views/StartTaskToolbarButton.swift b/CodeEdit/Features/Tasks/Views/StartTaskToolbarButton.swift index 4ab7174746..01bbb97498 100644 --- a/CodeEdit/Features/Tasks/Views/StartTaskToolbarButton.swift +++ b/CodeEdit/Features/Tasks/Views/StartTaskToolbarButton.swift @@ -11,13 +11,11 @@ struct StartTaskToolbarButton: View { @Environment(\.controlActiveState) private var activeState - @UpdatingWindowController var windowController: CodeEditWindowController? - @ObservedObject var taskManager: TaskManager @EnvironmentObject var workspace: WorkspaceDocument var utilityAreaCollapsed: Bool { - windowController?.workspace?.utilityAreaModel?.isCollapsed ?? true + workspace.utilityAreaModel?.isCollapsed ?? true } var body: some View { diff --git a/CodeEdit/Utils/Extensions/View/View+if.swift b/CodeEdit/Utils/Extensions/View/View+if.swift index 57defc484e..1275187510 100644 --- a/CodeEdit/Utils/Extensions/View/View+if.swift +++ b/CodeEdit/Utils/Extensions/View/View+if.swift @@ -2,7 +2,7 @@ // View+if.swift // CodeEdit // -// Created by Khan Winter on 9/5/25. +// Created by Khan Winter on 8/28/25. // import SwiftUI @@ -42,11 +42,11 @@ extension View { } extension Bool { - static var tahoe: Bool { - if #available(macOS 26, *) { - return true - } else { - return false - } - } -} + static var tahoe: Bool { + if #available(macOS 26, *) { + return true + } else { + return false + } + } + } diff --git a/OpenWithCodeEdit/OpenWithCodeEdit.entitlements b/OpenWithCodeEdit/OpenWithCodeEdit.entitlements index d04b0ef679..c0c4f5e905 100644 --- a/OpenWithCodeEdit/OpenWithCodeEdit.entitlements +++ b/OpenWithCodeEdit/OpenWithCodeEdit.entitlements @@ -2,14 +2,10 @@ - com.apple.security.app-sandbox - com.apple.security.application-groups app.codeedit.CodeEdit.shared $(TeamIdentifierPrefix) - com.apple.security.files.user-selected.read-only - From ce3dd04351b753d601b6ec8276e30574ead8b26a Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:24:33 -0500 Subject: [PATCH 4/7] Finish Up Toolbar Look --- .../TabBar/Tabs/Tab/EditorTabBackground.swift | 7 ++- .../Tabs/Tab/EditorTabCloseButton.swift | 6 +- .../TabBar/Tabs/Tab/EditorTabView.swift | 23 ++++++-- .../Editor/TabBar/Tabs/Views/EditorTabs.swift | 55 +++++++++++++------ .../Editor/Views/EditorAreaView.swift | 10 +++- 5 files changed, 73 insertions(+), 28 deletions(-) diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift index 5330a85dc8..91c9ccd514 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift @@ -29,8 +29,11 @@ struct EditorTabBackground: View { ZStack { if isActive { // Content background (visible if active) - EffectView(.contentBackground) - .opacity(isActive ? 1 : 0) + if #available(macOS 26, *) { + GlassEffectView() + } else { + EffectView(.contentBackground) + } // Accent color (visible if active) Color(.controlAccentColor) diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift index 98c10258cd..132664b0f0 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift @@ -42,7 +42,11 @@ struct EditorTabCloseButton: View { .frame(width: buttonSize, height: buttonSize) .background(backgroundColor) .foregroundColor(isPressingClose ? .primary : .secondary) - .clipShape(RoundedRectangle(cornerRadius: 2)) + .if(.tahoe) { + $0.clipShape(Circle()) + } else: { + $0.clipShape(RoundedRectangle(cornerRadius: 2)) + } .contentShape(Rectangle()) .gesture( DragGesture(minimumDistance: 0) diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift index 5ba88387be..68cb97b2d0 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift @@ -120,10 +120,11 @@ struct EditorTabView: View { @ViewBuilder var content: some View { HStack(spacing: 0.0) { - EditorTabDivider() - .opacity( - (isActive || inHoldingState) ? 0.0 : 1.0 - ) + + if #unavailable(macOS 26) { + EditorTabDivider() + .opacity((isActive || inHoldingState) ? 0.0 : 1.0) + } // Tab content (icon and text). HStack(alignment: .center, spacing: 3) { Image(nsImage: tabFile.nsIcon) @@ -163,14 +164,19 @@ struct EditorTabView: View { } .frame(maxWidth: .infinity, alignment: .leading) } + .if(.tahoe) { + $0.padding(.horizontal, 1.5) + } .opacity( // Inactive states for tab bar item content. activeState != .inactive ? 1.0 : isActive ? 0.6 : 0.4 ) - EditorTabDivider() - .opacity((isActive || inHoldingState) ? 0.0 : 1.0) + if #unavailable(macOS 26) { + EditorTabDivider() + .opacity((isActive || inHoldingState) ? 0.0 : 1.0) + } } .foregroundColor( isActive && isActiveEditor @@ -202,6 +208,11 @@ struct EditorTabView: View { EditorTabBackground(isActive: isActive, isPressing: isPressing, isDragging: isDragging) .animation(.easeInOut(duration: 0.08), value: isHovering) } + .if(.tahoe) { + if #available(macOS 26, *) { + $0.clipShape(Capsule()).clipped().containerShape(Capsule()) + } + } // TODO: Enable the following code snippet when dragging-out behavior should be allowed. // Since we didn't handle the drop-outside event, dragging-out is disabled for now. // .onDrag({ diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift index 547b2d048a..2b2d5acbc2 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift @@ -260,6 +260,12 @@ struct EditorTabs: View { ) { ForEach(Array(openedTabs.enumerated()), id: \.element) { index, id in if let item = editor.tabs.first(where: { $0.file.id == id }) { + if index != 0 + && editor.selectedTab?.file.id != id + && editor.selectedTab?.file.id != openedTabs[index - 1] { + EditorTabDivider() + } + EditorTabView( file: item.file, index: index, @@ -293,6 +299,12 @@ struct EditorTabs: View { tabWidth: $tabWidth ) ) + + if index < openedTabs.count - 1 + && editor.selectedTab?.file.id != id + && editor.selectedTab?.file.id != openedTabs[index + 1] { + EditorTabDivider() + } } } } @@ -341,26 +353,33 @@ struct EditorTabs: View { // To fill up the parent space of tab bar. .frame(maxWidth: .infinity) } + .overlay(alignment: .leading) { + EditorTabsOverflowShadow( + width: colorScheme == .dark ? 5 : 7, + startPoint: .leading, + endPoint: .trailing + ) + .opacity(scrollOffset >= 0 ? 0 : 1) + } + .overlay(alignment: .trailing) { + EditorTabsOverflowShadow( + width: colorScheme == .dark ? 5 : 7, + startPoint: .trailing, + endPoint: .leading + ) + .opacity((scrollTrailingOffset ?? 0) <= 0 ? 0 : 1) + } .if(.tahoe) { if #available(macOS 26.0, *) { - $0.background(GlassEffectView(tintColor: .secondarySystemFill)).clipShape(Capsule()) - } - } else: { - $0.overlay(alignment: .leading) { - EditorTabsOverflowShadow( - width: colorScheme == .dark ? 5 : 7, - startPoint: .leading, - endPoint: .trailing - ) - .opacity(scrollOffset >= 0 ? 0 : 1) - } - .overlay(alignment: .trailing) { - EditorTabsOverflowShadow( - width: colorScheme == .dark ? 5 : 7, - startPoint: .trailing, - endPoint: .leading - ) - .opacity((scrollTrailingOffset ?? 0) <= 0 ? 0 : 1) + // Unfortunate triple if here due to needing to compile on + // earlier Xcodes. +#if compiler(>=6.2) + $0.background(GlassEffectView(tintColor: .tertiarySystemFill)) + .clipShape(Capsule()) + .clipped() +#else + $0 +#endif } } } diff --git a/CodeEdit/Features/Editor/Views/EditorAreaView.swift b/CodeEdit/Features/Editor/Views/EditorAreaView.swift index 4795d7119a..eccadf8eb3 100644 --- a/CodeEdit/Features/Editor/Views/EditorAreaView.swift +++ b/CodeEdit/Features/Editor/Views/EditorAreaView.swift @@ -111,7 +111,9 @@ struct EditorAreaView: View { EditorTabBarView(hasTopInsets: topSafeArea > 0, codeFile: fileBinding) .id("TabBarView" + editor.id.uuidString) .environmentObject(editor) - Divider() + if #unavailable(macOS 26) { + Divider() + } } if showEditorJumpBar { EditorJumpBarView( @@ -125,6 +127,12 @@ struct EditorAreaView: View { } .environmentObject(editor) .padding(.top, shouldShowTabBar ? -1 : 0) + if #unavailable(macOS 26) { + Divider() + } + } + // On Tahoe we only show one divider + if #available(macOS 26, *), shouldShowTabBar || showEditorJumpBar { Divider() } } From 23fd2e64e05b703be63e0b9fcad6d32cab0dab1b Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Wed, 10 Sep 2025 09:16:39 -0500 Subject: [PATCH 5/7] Update EditorAreaView.swift --- CodeEdit/Features/Editor/Views/EditorAreaView.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CodeEdit/Features/Editor/Views/EditorAreaView.swift b/CodeEdit/Features/Editor/Views/EditorAreaView.swift index eccadf8eb3..30c1816ed5 100644 --- a/CodeEdit/Features/Editor/Views/EditorAreaView.swift +++ b/CodeEdit/Features/Editor/Views/EditorAreaView.swift @@ -18,6 +18,18 @@ struct EditorAreaView: View { @AppSettings(\.general.dimEditorsWithoutFocus) var dimEditorsWithoutFocus + + @AppSettings(\.theme.useThemeBackground) + var useThemeBackground + + private var backgroundColor: NSColor { + let fallback = NSColor.textBackgroundColor + return if useThemeBackground { + ThemeModel.shared.selectedTheme?.editor.background.nsColor ?? fallback + } else { + fallback + } + } @ObservedObject var editor: Editor From f86dd7e9bbb96a330a4fcc869359eae5717c1db1 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Wed, 10 Sep 2025 09:53:12 -0500 Subject: [PATCH 6/7] Note Issue With Glass Here --- .../Editor/Views/EditorAreaView.swift | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/CodeEdit/Features/Editor/Views/EditorAreaView.swift b/CodeEdit/Features/Editor/Views/EditorAreaView.swift index 30c1816ed5..59e9a15d2a 100644 --- a/CodeEdit/Features/Editor/Views/EditorAreaView.swift +++ b/CodeEdit/Features/Editor/Views/EditorAreaView.swift @@ -18,18 +18,6 @@ struct EditorAreaView: View { @AppSettings(\.general.dimEditorsWithoutFocus) var dimEditorsWithoutFocus - - @AppSettings(\.theme.useThemeBackground) - var useThemeBackground - - private var backgroundColor: NSColor { - let fallback = NSColor.textBackgroundColor - return if useThemeBackground { - ThemeModel.shared.selectedTheme?.editor.background.nsColor ?? fallback - } else { - fallback - } - } @ObservedObject var editor: Editor @@ -152,6 +140,34 @@ struct EditorAreaView: View { .if(.tahoe) { // FB20047271: Glass toolbar effect ignores floating scroll view views. // https://openradar.appspot.com/radar?id=EhAKBVJhZGFyEICAgKbGmesJ + + // FB20191516: Can't disable backgrounded liquid glass tint + // https://openradar.appspot.com/radar?id=EhAKBVJhZGFyEICAgLqTk-4J + // Tracking Issue: #2191 + // Add this to the top: + // ``` + // @AppSettings(\.theme.useThemeBackground) + // var useThemeBackground + // + // private var backgroundColor: NSColor { + // let fallback = NSColor.textBackgroundColor + // return if useThemeBackground { + // ThemeModel.shared.selectedTheme?.editor.background.nsColor ?? fallback + // } else { + // fallback + // } + // } + // ``` + // And use this: + // ``` + // $0.background( + // Rectangle().fill(.clear) + // .glassEffect(.regular.tint(Color(backgroundColor)) + // .ignoresSafeArea(.all) + // ) + // ``` + // When we can figure out how to disable the 'not focused' glass effect. + $0.background(EffectView(.headerView).ignoresSafeArea(.all)) } else: { $0.background(EffectView(.headerView)) From 06d308291a6b26ec65104a0ce78cc45548f769e1 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Wed, 10 Sep 2025 11:15:18 -0500 Subject: [PATCH 7/7] Adjust Padding, Draw Split Edges Only in safe Area --- CodeEdit/Features/Editor/Views/EditorAreaView.swift | 8 +++++++- CodeEdit/Features/Editor/Views/EditorLayoutView.swift | 8 ++++---- .../SplitView/Views/SplitViewControllerView.swift | 10 ++++++++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CodeEdit/Features/Editor/Views/EditorAreaView.swift b/CodeEdit/Features/Editor/Views/EditorAreaView.swift index 59e9a15d2a..9496fbe837 100644 --- a/CodeEdit/Features/Editor/Views/EditorAreaView.swift +++ b/CodeEdit/Features/Editor/Views/EditorAreaView.swift @@ -30,6 +30,9 @@ struct EditorAreaView: View { @Environment(\.window.value) private var window: NSWindow? + @Environment(\.isEditorLayoutAtEdge) + private var isAtEdge + init(editor: Editor, focus: FocusState.Binding) { self.editor = editor self._focus = focus @@ -101,6 +104,10 @@ struct EditorAreaView: View { } VStack(spacing: 0) { + if isAtEdge != .top, #available(macOS 26, *) { + Spacer().frame(height: 4) + } + if topSafeArea > 0 { Rectangle() .fill(.clear) @@ -167,7 +174,6 @@ struct EditorAreaView: View { // ) // ``` // When we can figure out how to disable the 'not focused' glass effect. - $0.background(EffectView(.headerView).ignoresSafeArea(.all)) } else: { $0.background(EffectView(.headerView)) diff --git a/CodeEdit/Features/Editor/Views/EditorLayoutView.swift b/CodeEdit/Features/Editor/Views/EditorLayoutView.swift index a1637db30b..aa7fa3252c 100644 --- a/CodeEdit/Features/Editor/Views/EditorLayoutView.swift +++ b/CodeEdit/Features/Editor/Views/EditorLayoutView.swift @@ -15,7 +15,7 @@ struct EditorLayoutView: View { @Environment(\.window.value) private var window - @Environment(\.isAtEdge) + @Environment(\.isEditorLayoutAtEdge) private var isAtEdge var toolbarHeight: CGFloat { @@ -63,7 +63,7 @@ struct EditorLayoutView: View { var splitView: some View { ForEach(Array(data.editorLayouts.enumerated()), id: \.offset) { index, item in EditorLayoutView(layout: item, focus: $focus) - .transformEnvironment(\.isAtEdge) { belowToolbar in + .transformEnvironment(\.isEditorLayoutAtEdge) { belowToolbar in calcIsAtEdge(current: &belowToolbar, index: index) } .environment(\.splitEditor) { [weak data] edge, newEditor in @@ -87,12 +87,12 @@ struct EditorLayoutView: View { } } -private struct BelowToolbarEnvironmentKey: EnvironmentKey { +struct BelowToolbarEnvironmentKey: EnvironmentKey { static var defaultValue: VerticalEdge.Set = .all } extension EnvironmentValues { - fileprivate var isAtEdge: BelowToolbarEnvironmentKey.Value { + var isEditorLayoutAtEdge: BelowToolbarEnvironmentKey.Value { get { self[BelowToolbarEnvironmentKey.self] } set { self[BelowToolbarEnvironmentKey.self] = newValue } } diff --git a/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift b/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift index 12aa37f506..d0094d3aad 100644 --- a/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift +++ b/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift @@ -96,6 +96,16 @@ final class SplitViewController: NSSplitViewController { 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] = []