From a4e7400a6323bd516dfd9882ec18ecedea81fe27 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Tue, 2 Sep 2025 14:41:52 -0500 Subject: [PATCH 01/18] Squashed commit of the following: 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 --- CodeEdit.xcodeproj/project.pbxproj | 68 ++++-- .../xcshareddata/swiftpm/Package.resolved | 8 +- .../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 | 8 +- .../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 | 52 +++++ .../OpenWithCodeEdit.entitlements | 4 - 30 files changed, 752 insertions(+), 281 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 create mode 100644 CodeEdit/Utils/Extensions/View/View+if.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index ff63c4974c..7b6eb5c3fe 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -18,11 +18,11 @@ 58F2EB03292FB2B0004A9BDE /* Documentation.docc in Sources */ = {isa = PBXBuildFile; fileRef = 58F2EACE292FB2B0004A9BDE /* Documentation.docc */; }; 58F2EB1E292FB954004A9BDE /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 58F2EB1D292FB954004A9BDE /* Sparkle */; }; 5E4485612DF600D9008BBE69 /* AboutWindow in Frameworks */ = {isa = PBXBuildFile; productRef = 5E4485602DF600D9008BBE69 /* AboutWindow */; }; - 5EACE6222DF4BF08005E08B8 /* WelcomeWindow in Frameworks */ = {isa = PBXBuildFile; productRef = 5EACE6212DF4BF08005E08B8 /* WelcomeWindow */; }; 6C0617D62BDB4432008C9C42 /* LogStream in Frameworks */ = {isa = PBXBuildFile; productRef = 6C0617D52BDB4432008C9C42 /* LogStream */; }; 6C0824A12C5C0C9700A0751E /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = 6C0824A02C5C0C9700A0751E /* SwiftTerm */; }; 6C147C4529A329350089B630 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 6C147C4429A329350089B630 /* OrderedCollections */; }; 6C315FC82E05E33D0011BFC5 /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6C315FC72E05E33D0011BFC5 /* CodeEditSourceEditor */; }; + 6C33D9FB2E5F9184007782E7 /* WelcomeWindow in Frameworks */ = {isa = PBXBuildFile; productRef = 6C33D9FA2E5F9184007782E7 /* WelcomeWindow */; }; 6C66C31329D05CDC00DE9ED2 /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 6C66C31229D05CDC00DE9ED2 /* GRDB */; }; 6C6BD6F429CD142C00235D17 /* CollectionConcurrencyKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C6BD6F329CD142C00235D17 /* CollectionConcurrencyKit */; }; 6C6BD6F829CD14D100235D17 /* CodeEditKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C6BD6F729CD14D100235D17 /* CodeEditKit */; }; @@ -32,6 +32,7 @@ 6C81916B29B41DD300B75C92 /* DequeModule in Frameworks */ = {isa = PBXBuildFile; productRef = 6C81916A29B41DD300B75C92 /* DequeModule */; }; 6C85BB402C2105ED00EB5DEF /* CodeEditKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C85BB3F2C2105ED00EB5DEF /* CodeEditKit */; }; 6C85BB442C210EFD00EB5DEF /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = 6C85BB432C210EFD00EB5DEF /* SwiftUIIntrospect */; }; + 6C883FD42E620E2B005BCFE8 /* WelcomeWindow in Frameworks */ = {isa = PBXBuildFile; productRef = 6C883FD32E620E2B005BCFE8 /* WelcomeWindow */; }; 6C9DB9E42D55656300ACD86E /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6C9DB9E32D55656300ACD86E /* CodeEditSourceEditor */; }; 6CAAF68A29BC9C2300A1F48A /* (null) in Sources */ = {isa = PBXBuildFile; }; 6CAAF69229BCC71C00A1F48A /* (null) in Sources */ = {isa = PBXBuildFile; }; @@ -171,6 +172,7 @@ buildActionMask = 2147483647; files = ( 302AD7FF2D8054D500231E16 /* ZIPFoundation in Frameworks */, + 6C883FD42E620E2B005BCFE8 /* WelcomeWindow in Frameworks */, 6C85BB402C2105ED00EB5DEF /* CodeEditKit in Frameworks */, 6C66C31329D05CDC00DE9ED2 /* GRDB in Frameworks */, 58F2EB1E292FB954004A9BDE /* Sparkle in Frameworks */, @@ -187,12 +189,12 @@ 30CB64912C16CA8100CC8A9E /* LanguageServerProtocol in Frameworks */, 5E4485612DF600D9008BBE69 /* AboutWindow in Frameworks */, 6C6BD6F429CD142C00235D17 /* CollectionConcurrencyKit in Frameworks */, + 6C33D9FB2E5F9184007782E7 /* WelcomeWindow in Frameworks */, 6C85BB442C210EFD00EB5DEF /* SwiftUIIntrospect in Frameworks */, 6CB446402B6DFF3A00539ED0 /* CodeEditSourceEditor in Frameworks */, 6C73A6D32D4F1E550012D95C /* CodeEditSourceEditor in Frameworks */, 2816F594280CF50500DD548B /* CodeEditSymbols in Frameworks */, 30CB64942C16CA9100CC8A9E /* LanguageClient in Frameworks */, - 5EACE6222DF4BF08005E08B8 /* WelcomeWindow in Frameworks */, 6C6BD6F829CD14D100235D17 /* CodeEditKit in Frameworks */, 6C0824A12C5C0C9700A0751E /* SwiftTerm in Frameworks */, 6C81916B29B41DD300B75C92 /* DequeModule in Frameworks */, @@ -331,12 +333,13 @@ 6CC00A8A2CBEF150004E8134 /* CodeEditSourceEditor */, 30818CB42D4E563900967860 /* ZIPFoundation */, 6C73A6D22D4F1E550012D95C /* CodeEditSourceEditor */, - 5EACE6212DF4BF08005E08B8 /* WelcomeWindow */, 5E4485602DF600D9008BBE69 /* AboutWindow */, 6C315FC72E05E33D0011BFC5 /* CodeEditSourceEditor */, 6C76D6D32E15B91E00EF52C3 /* CodeEditSourceEditor */, 6CCF6DD22E26D48F00B94F75 /* SwiftTerm */, 6CCF73CF2E26DE3200B94F75 /* SwiftTerm */, + 6C33D9FA2E5F9184007782E7 /* WelcomeWindow */, + 6C883FD32E620E2B005BCFE8 /* WelcomeWindow */, ); productName = CodeEdit; productReference = B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */; @@ -397,7 +400,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1330; - LastUpgradeCheck = 1640; + LastUpgradeCheck = 2600; TargetAttributes = { 2BE487EB28245162003F3F64 = { CreatedOnToolsVersion = 13.3.1; @@ -439,10 +442,10 @@ 303E88462C276FD600EEA8D9 /* XCRemoteSwiftPackageReference "LanguageServerProtocol" */, 6CB94D012CA1205100E8651C /* XCRemoteSwiftPackageReference "swift-async-algorithms" */, 30ED7B722DD299E600ACC922 /* XCRemoteSwiftPackageReference "ZIPFoundation" */, - 5EACE6202DF4BF08005E08B8 /* XCRemoteSwiftPackageReference "WelcomeWindow" */, 5E44855F2DF600D9008BBE69 /* XCRemoteSwiftPackageReference "AboutWindow" */, 6C76D6D22E15B91E00EF52C3 /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */, 6CCF73CE2E26DE3200B94F75 /* XCRemoteSwiftPackageReference "SwiftTerm" */, + 6C883FD22E620E2A005BCFE8 /* XCRemoteSwiftPackageReference "WelcomeWindow" */, ); preferredProjectObjectVersion = 55; productRefGroup = B658FB2D27DA9E0F00EA4DBD /* Products */; @@ -650,6 +653,7 @@ OTHER_SWIFT_FLAGS = "-D ALPHA"; RUN_DOCUMENTATION_COMPILER = YES; SDKROOT = macosx; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SYSTEM_FRAMEWORK_SEARCH_PATHS = ""; @@ -673,6 +677,7 @@ DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"CodeEdit/Preview Content\""; DEVELOPMENT_TEAM = ""; + ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = NO; @@ -690,6 +695,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; REGISTER_APP_GROUPS = YES; + RUNTIME_EXCEPTION_ALLOW_JIT = YES; + RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = YES; RUN_DOCUMENTATION_COMPILER = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; @@ -766,6 +773,7 @@ CURRENT_PROJECT_VERSION = 47; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; + ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = OpenWithCodeEdit/Info.plist; @@ -847,6 +855,7 @@ OTHER_SWIFT_FLAGS = "-D BETA"; RUN_DOCUMENTATION_COMPILER = YES; SDKROOT = macosx; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SYSTEM_FRAMEWORK_SEARCH_PATHS = ""; @@ -870,6 +879,7 @@ DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"CodeEdit/Preview Content\""; DEVELOPMENT_TEAM = ""; + ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = NO; @@ -887,6 +897,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; REGISTER_APP_GROUPS = YES; + RUNTIME_EXCEPTION_ALLOW_JIT = YES; + RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = YES; RUN_DOCUMENTATION_COMPILER = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; @@ -963,6 +975,7 @@ CURRENT_PROJECT_VERSION = 47; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; + ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = OpenWithCodeEdit/Info.plist; @@ -998,6 +1011,7 @@ CURRENT_PROJECT_VERSION = 47; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; + ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = OpenWithCodeEdit/Info.plist; @@ -1033,6 +1047,7 @@ CURRENT_PROJECT_VERSION = 47; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; + ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = OpenWithCodeEdit/Info.plist; @@ -1115,6 +1130,7 @@ OTHER_SWIFT_FLAGS = "-D ALPHA"; RUN_DOCUMENTATION_COMPILER = YES; SDKROOT = macosx; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SYSTEM_FRAMEWORK_SEARCH_PATHS = ""; @@ -1139,6 +1155,7 @@ DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"CodeEdit/Preview Content\""; DEVELOPMENT_TEAM = ""; + ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = NO; @@ -1156,6 +1173,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; REGISTER_APP_GROUPS = YES; + RUNTIME_EXCEPTION_ALLOW_JIT = YES; + RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = YES; RUN_DOCUMENTATION_COMPILER = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; @@ -1232,6 +1251,7 @@ CURRENT_PROJECT_VERSION = 47; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; + ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = OpenWithCodeEdit/Info.plist; @@ -1319,6 +1339,7 @@ ONLY_ACTIVE_ARCH = YES; RUN_DOCUMENTATION_COMPILER = YES; SDKROOT = macosx; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SYSTEM_FRAMEWORK_SEARCH_PATHS = ""; @@ -1384,6 +1405,7 @@ MTL_FAST_MATH = YES; RUN_DOCUMENTATION_COMPILER = YES; SDKROOT = macosx; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SYSTEM_FRAMEWORK_SEARCH_PATHS = ""; @@ -1407,6 +1429,7 @@ DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"CodeEdit/Preview Content\""; DEVELOPMENT_TEAM = ""; + ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = NO; @@ -1424,6 +1447,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; REGISTER_APP_GROUPS = YES; + RUNTIME_EXCEPTION_ALLOW_JIT = YES; + RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = YES; RUN_DOCUMENTATION_COMPILER = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; @@ -1448,6 +1473,7 @@ DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"CodeEdit/Preview Content\""; DEVELOPMENT_TEAM = ""; + ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = NO; @@ -1465,6 +1491,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; REGISTER_APP_GROUPS = YES; + RUNTIME_EXCEPTION_ALLOW_JIT = YES; + RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = YES; RUN_DOCUMENTATION_COMPILER = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; @@ -1738,14 +1766,6 @@ minimumVersion = 1.0.0; }; }; - 5EACE6202DF4BF08005E08B8 /* XCRemoteSwiftPackageReference "WelcomeWindow" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/CodeEditApp/WelcomeWindow"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.0.0; - }; - }; 6C0617D42BDB4432008C9C42 /* XCRemoteSwiftPackageReference "LogStream" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Wouter01/LogStream"; @@ -1802,6 +1822,14 @@ minimumVersion = 1.2.0; }; }; + 6C883FD22E620E2A005BCFE8 /* XCRemoteSwiftPackageReference "WelcomeWindow" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/CodeEditApp/WelcomeWindow"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; 6C9DB9E22D55656300ACD86E /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/CodeEditApp/CodeEditSourceEditor"; @@ -1864,11 +1892,6 @@ package = 5E44855F2DF600D9008BBE69 /* XCRemoteSwiftPackageReference "AboutWindow" */; productName = AboutWindow; }; - 5EACE6212DF4BF08005E08B8 /* WelcomeWindow */ = { - isa = XCSwiftPackageProductDependency; - package = 5EACE6202DF4BF08005E08B8 /* XCRemoteSwiftPackageReference "WelcomeWindow" */; - productName = WelcomeWindow; - }; 6C0617D52BDB4432008C9C42 /* LogStream */ = { isa = XCSwiftPackageProductDependency; package = 6C0617D42BDB4432008C9C42 /* XCRemoteSwiftPackageReference "LogStream" */; @@ -1887,6 +1910,10 @@ isa = XCSwiftPackageProductDependency; productName = CodeEditSourceEditor; }; + 6C33D9FA2E5F9184007782E7 /* WelcomeWindow */ = { + isa = XCSwiftPackageProductDependency; + productName = WelcomeWindow; + }; 6C66C31229D05CDC00DE9ED2 /* GRDB */ = { isa = XCSwiftPackageProductDependency; package = 6C66C31129D05CC800DE9ED2 /* XCRemoteSwiftPackageReference "GRDB.swift" */; @@ -1930,6 +1957,11 @@ package = 6C85BB422C210EFD00EB5DEF /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; productName = SwiftUIIntrospect; }; + 6C883FD32E620E2B005BCFE8 /* WelcomeWindow */ = { + isa = XCSwiftPackageProductDependency; + package = 6C883FD22E620E2A005BCFE8 /* XCRemoteSwiftPackageReference "WelcomeWindow" */; + productName = WelcomeWindow; + }; 6C9DB9E32D55656300ACD86E /* CodeEditSourceEditor */ = { isa = XCSwiftPackageProductDependency; package = 6C9DB9E22D55656300ACD86E /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */; diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9532347a14..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" } }, { @@ -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.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 544aed54e5..4795d7119a 100644 --- a/CodeEdit/Features/Editor/Views/EditorAreaView.swift +++ b/CodeEdit/Features/Editor/Views/EditorAreaView.swift @@ -129,7 +129,13 @@ struct EditorAreaView: View { } } .environment(\.isActiveEditor, editor == editorManager.activeEditor) - .background(EffectView(.headerView)) + .if(.tahoe) { + // 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)) + } } } .focused($focus, equals: editor) 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 new file mode 100644 index 0000000000..1275187510 --- /dev/null +++ b/CodeEdit/Utils/Extensions/View/View+if.swift @@ -0,0 +1,52 @@ +// +// View+if.swift +// CodeEdit +// +// Created by Khan Winter on 8/28/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 + } + } + } 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 f97b0899aa455af53b235b13d819c3f5ed13ae25 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Tue, 2 Sep 2025 14:58:17 -0500 Subject: [PATCH 02/18] Fix Navigator Width Bug --- .../CodeEditSplitViewController.swift | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift index 39735c8de1..0de4017e7c 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift @@ -19,6 +19,8 @@ final class CodeEditSplitViewController: NSSplitViewController { private weak var windowRef: NSWindow? private unowned var hapticPerformer: NSHapticFeedbackPerformer + private weak var navigatorItem: NSSplitViewItem? + // MARK: - Initialization init( @@ -66,6 +68,7 @@ final class CodeEditSplitViewController: NSSplitViewController { .environmentObject(editorManager) }) + self.navigatorItem = navigator addSplitViewItem(navigator) let workspaceView = SettingsInjector { @@ -121,6 +124,8 @@ final class CodeEditSplitViewController: NSSplitViewController { guard let workspace else { return } + workspace.notificationPanel.updateToolbarItem() + let navigatorWidth = workspace.getFromWorkspaceState(.splitViewWidth) as? CGFloat splitView.setPosition(navigatorWidth ?? Self.minSidebarWidth, ofDividerAt: 0) @@ -136,7 +141,6 @@ final class CodeEditSplitViewController: NSSplitViewController { ) as? Bool ?? true } - workspace.notificationPanel.updateToolbarItem() } // MARK: - NSSplitViewDelegate @@ -200,8 +204,14 @@ final class CodeEditSplitViewController: NSSplitViewController { } if resizedDivider == 0 { - let panel = splitView.subviews[0] - let width = panel.frame.size.width + let width: CGFloat + if #available(macOS 26, *) { + let panel = splitViewItems[0] + width = panel.viewController.view.frame.size.width + } else { + let panel = splitView.subviews[0] + width = panel.frame.size.width + } if width > 0 { workspace?.addToWorkspaceState(key: .splitViewWidth, value: width) } From d26af965e97d49e80dfc2d48d0ec47703958ba2e Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Tue, 2 Sep 2025 15:54:33 -0500 Subject: [PATCH 03/18] Create New Pane Filter Style --- .../CodeEditUI/Views/PaneTextField.swift | 1 - .../FindNavigatorToolbarBottom.swift | 47 +++--- .../FilterDropDownIconButton.swift | 33 ++++ .../ProjectNavigatorToolbarBottom.swift | 117 ++++++--------- .../SourceControlNavigatorToolbarBottom.swift | 52 +++---- .../Views/NavigatorFilterView.swift | 141 ++++++++++++++++++ 6 files changed, 258 insertions(+), 133 deletions(-) create mode 100644 CodeEdit/Features/NavigatorArea/ProjectNavigator/FilterDropDownIconButton.swift create mode 100644 CodeEdit/Features/NavigatorArea/Views/NavigatorFilterView.swift diff --git a/CodeEdit/Features/CodeEditUI/Views/PaneTextField.swift b/CodeEdit/Features/CodeEditUI/Views/PaneTextField.swift index 3997a9dd55..78174452ce 100644 --- a/CodeEdit/Features/CodeEditUI/Views/PaneTextField.swift +++ b/CodeEdit/Features/CodeEditUI/Views/PaneTextField.swift @@ -122,7 +122,6 @@ struct PaneTextField: View .disabled(true) .edgesIgnoringSafeArea(.all) ) - .onTapGesture { isFocused = true } diff --git a/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorToolbarBottom.swift b/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorToolbarBottom.swift index 073296f719..7bf248dffa 100644 --- a/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorToolbarBottom.swift +++ b/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorToolbarBottom.swift @@ -11,33 +11,24 @@ struct FindNavigatorToolbarBottom: View { @State private var text = "" var body: some View { - HStack(spacing: 2) { - PaneTextField( - "Filter", - text: $text, - leadingAccessories: { - Image( - systemName: text.isEmpty - ? "line.3.horizontal.decrease.circle" - : "line.3.horizontal.decrease.circle.fill" - ) - .foregroundStyle( - text.isEmpty - ? Color(nsColor: .secondaryLabelColor) - : Color(nsColor: .controlAccentColor) - ) - .padding(.leading, 4) - .help("Show results with matching text") - }, - clearable: true - ) - } - .frame(height: 28, alignment: .center) - .frame(maxWidth: .infinity) - .padding(.horizontal, 5) - .overlay(alignment: .top) { - Divider() - .opacity(0) - } + NavigatorFilterView( + text: $text, + menu: { EmptyView() }, + leadingAccessories: { + Image( + systemName: text.isEmpty + ? "line.3.horizontal.decrease.circle" + : "line.3.horizontal.decrease.circle.fill" + ) + .foregroundStyle( + text.isEmpty + ? Color(nsColor: .secondaryLabelColor) + : Color(nsColor: .controlAccentColor) + ) + .padding(.leading, 4) + .help("Show results with matching text") + }, + trailingAccessories: { EmptyView() } + ) } } diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/FilterDropDownIconButton.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/FilterDropDownIconButton.swift new file mode 100644 index 0000000000..ab2b8a0bfd --- /dev/null +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/FilterDropDownIconButton.swift @@ -0,0 +1,33 @@ +// +// FilterDropDownIconButton.swift +// CodeEdit +// +// Created by Khan Winter on 9/2/25. +// + +import SwiftUI + +struct FilterDropDownIconButton: View { + @Environment(\.controlActiveState) + private var activeState + + var menu: () -> MenuView + + var isOn: Bool? + + var body: some View { + Menu { menu() } label: {} + .background { + if isOn == true { + Image(ImageResource.line3HorizontalDecreaseChevronFilled) + .foregroundStyle(.tint) + } else { + Image(ImageResource.line3HorizontalDecreaseChevron) + } + } + .menuStyle(.borderlessButton) + .menuIndicator(.hidden) + .frame(width: 26, height: 13) + .clipShape(.rect(cornerRadius: 6.5)) + } +} diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorToolbarBottom.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorToolbarBottom.swift index bb1e44fb8e..6d465d1360 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorToolbarBottom.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorToolbarBottom.swift @@ -20,56 +20,50 @@ struct ProjectNavigatorToolbarBottom: View { @State var recentsFilter: Bool = false var body: some View { - HStack(spacing: 5) { - addNewFileButton - PaneTextField( - "Filter", - text: $workspace.navigatorFilter, - leadingAccessories: { - FilterDropDownIconButton(menu: { - ForEach([(true, "Folders on top"), (false, "Alphabetically")], id: \.0) { value, title in - Toggle(title, isOn: Binding(get: { - workspace.sortFoldersOnTop == value - }, set: { _ in - // Avoid calling the handleFilterChange method - if workspace.sortFoldersOnTop != value { - workspace.sortFoldersOnTop = value - } - })) - } - }, isOn: !workspace.navigatorFilter.isEmpty) - .padding(.leading, 4) - .foregroundStyle( - workspace.navigatorFilter.isEmpty - ? Color(nsColor: .secondaryLabelColor) - : Color(nsColor: .controlAccentColor) - ) - .help("Show files with matching name") - }, - trailingAccessories: { - HStack(spacing: 0) { - Toggle(isOn: $recentsFilter) { - Image(systemName: "clock") - } - .help("Show only recent files") - Toggle(isOn: $workspace.sourceControlFilter) { - Image(systemName: "plusminus.circle") - } - .help("Show only files with source-control status") + NavigatorFilterView( + text: $workspace.navigatorFilter, + hasValue: { !workspace.navigatorFilter.isEmpty || recentsFilter || workspace.sourceControlFilter }, + menu: { addNewFileButton }, + leadingAccessories: { leadingAccessories }, + trailingAccessories: { trailingAccessories } + ) + } + + @ViewBuilder private var leadingAccessories: some View { + FilterDropDownIconButton(menu: { + ForEach([(true, "Folders on top"), (false, "Alphabetically")], id: \.0) { value, title in + Toggle(title, isOn: Binding(get: { + workspace.sortFoldersOnTop == value + }, set: { _ in + // Avoid calling the handleFilterChange method + if workspace.sortFoldersOnTop != value { + workspace.sortFoldersOnTop = value } - .toggleStyle(.icon(font: .system(size: 14), size: CGSize(width: 18, height: 20))) - .padding(.trailing, 2.5) - }, - clearable: true, - hasValue: !workspace.navigatorFilter.isEmpty || recentsFilter || workspace.sourceControlFilter - ) - } - .padding(.horizontal, 5) - .frame(height: 28, alignment: .center) - .frame(maxWidth: .infinity) - .overlay(alignment: .top) { - Divider() + })) + } + }, isOn: !workspace.navigatorFilter.isEmpty) + .padding(.leading, 4) + .foregroundStyle( + workspace.navigatorFilter.isEmpty + ? Color(nsColor: .secondaryLabelColor) + : Color(nsColor: .controlAccentColor) + ) + .help("Show files with matching name") + } + + @ViewBuilder private var trailingAccessories: some View { + HStack(spacing: 0) { + Toggle(isOn: $recentsFilter) { + Image(systemName: "clock") + } + .help("Show only recent files") + Toggle(isOn: $workspace.sourceControlFilter) { + Image(systemName: "plusminus.circle") + } + .help("Show only files with source-control status") } + .toggleStyle(.icon(font: .system(size: 14), size: CGSize(width: 18, height: 20))) + .padding(.trailing, 2.5) } /// Retrieves the active tab URL from the underlying editor instance, if theres no @@ -96,7 +90,7 @@ struct ProjectNavigatorToolbarBottom: View { return workspace.workspaceFileManager.unsafelyUnwrapped.folderUrl } - private var addNewFileButton: some View { + @ViewBuilder private var addNewFileButton: some View { Menu { Button("Add File") { let filePathURL = activeTabURL() @@ -159,28 +153,3 @@ struct ProjectNavigatorToolbarBottom: View { .opacity(activeState == .inactive ? 0.45 : 1) } } - -struct FilterDropDownIconButton: View { - @Environment(\.controlActiveState) - private var activeState - - var menu: () -> MenuView - - var isOn: Bool? - - var body: some View { - Menu { menu() } label: {} - .background { - if isOn == true { - Image(ImageResource.line3HorizontalDecreaseChevronFilled) - .foregroundStyle(.tint) - } else { - Image(ImageResource.line3HorizontalDecreaseChevron) - } - } - .menuStyle(.borderlessButton) - .menuIndicator(.hidden) - .frame(width: 26, height: 13) - .clipShape(.rect(cornerRadius: 6.5)) - } -} diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorToolbarBottom.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorToolbarBottom.swift index c2c4d60dd9..e476145451 100644 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorToolbarBottom.swift +++ b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorToolbarBottom.swift @@ -14,38 +14,30 @@ struct SourceControlNavigatorToolbarBottom: View { @State private var text = "" var body: some View { - HStack(spacing: 5) { - sourceControlMenu - PaneTextField( - "Filter", - text: $text, - leadingAccessories: { - Image( - systemName: text.isEmpty - ? "line.3.horizontal.decrease.circle" - : "line.3.horizontal.decrease.circle.fill" - ) - .foregroundStyle( - text.isEmpty - ? Color(nsColor: .secondaryLabelColor) - : Color(nsColor: .controlAccentColor) - ) - .padding(.leading, 4) - .help("Filter Changes Navigator") - }, - clearable: true - ) - } - .frame(height: 28, alignment: .center) - .frame(maxWidth: .infinity) - .padding(.horizontal, 5) - .overlay(alignment: .top) { - Divider() - .opacity(0) - } + NavigatorFilterView( + text: $text, + menu: { sourceControlMenu }, + leadingAccessories: { leadingAccessories }, + trailingAccessories: { EmptyView() } + ) + } + + @ViewBuilder private var leadingAccessories: some View { + Image( + systemName: text.isEmpty + ? "line.3.horizontal.decrease.circle" + : "line.3.horizontal.decrease.circle.fill" + ) + .foregroundStyle( + text.isEmpty + ? Color(nsColor: .secondaryLabelColor) + : Color(nsColor: .controlAccentColor) + ) + .padding(.leading, 4) + .help("Filter Changes Navigator") } - private var sourceControlMenu: some View { + @ViewBuilder private var sourceControlMenu: some View { Menu { Button("Discard All Changes...") { if sourceControlManager.changedFiles.isEmpty { diff --git a/CodeEdit/Features/NavigatorArea/Views/NavigatorFilterView.swift b/CodeEdit/Features/NavigatorArea/Views/NavigatorFilterView.swift new file mode 100644 index 0000000000..05d36fd8c8 --- /dev/null +++ b/CodeEdit/Features/NavigatorArea/Views/NavigatorFilterView.swift @@ -0,0 +1,141 @@ +// +// NavigatorFilterView.swift +// CodeEdit +// +// Created by Khan Winter on 9/2/25. +// + +import SwiftUI + +struct NavigatorFilterView< + MenuContents: View, + LeadingAccessories: View, + TrailingAccessories: View +>: View { + @Environment(\.colorScheme) + private var colorScheme + + @Environment(\.controlActiveState) + private var controlActive + + @FocusState private var isFocused: Bool + + @Binding var text: String + let hasValue: Bool + let menu: MenuContents + let leadingAccessories: LeadingAccessories + let trailingAccessories: TrailingAccessories + + init( + text: Binding, + hasValue: (() -> Bool)? = nil, + @ViewBuilder menu: () -> MenuContents, + @ViewBuilder leadingAccessories: () -> LeadingAccessories, + @ViewBuilder trailingAccessories: () -> TrailingAccessories + ) { + self._text = text + self.hasValue = hasValue?() ?? false + self.menu = menu() + self.leadingAccessories = leadingAccessories() + self.trailingAccessories = trailingAccessories() + } + + var body: some View { + VStack(spacing: 0) { + Divider() + HStack(spacing: 5) { + menu + if #available(macOS 26, *) { + textField + } else { + PaneTextField( + "Filter", + text: $text, + leadingAccessories: { leadingAccessories }, + trailingAccessories: { trailingAccessories }, + clearable: true, + hasValue: hasValue + ) + } + } + .frame(maxWidth: .infinity) + .padding(8) + } + } + + @available(macOS 26, *) + @ViewBuilder var textField: some View { + HStack(alignment: .center, spacing: 0) { + leadingAccessories + TextField("Filter", text: $text, axis: .vertical) + .textFieldStyle(.plain) + .focused($isFocused) + .controlSize(.small) + .padding(.horizontal, 8) + .foregroundStyle(.primary) + .font(.system(size: 13)) + Button { + self.text = "" + } label: { + Image(systemName: "xmark.circle.fill") + } + .buttonStyle(buttonStyle) + .opacity(text.isEmpty ? 0 : 1) + .disabled(text.isEmpty) + trailingAccessories + } + .padding(.horizontal, 3) + .fixedSize(horizontal: false, vertical: true) + .buttonStyle(buttonStyle) + .toggleStyle(toggleStyle) + .frame(minHeight: 28) + .background( + selectionBackground(isFocused) + .clipShape(Capsule()) + .edgesIgnoringSafeArea(.all) + ) + .overlay( + Capsule() + .stroke(isFocused || !text.isEmpty || hasValue ? .tertiary : .quaternary, lineWidth: 1.25) + .clipShape(Capsule()) + .disabled(true) + .edgesIgnoringSafeArea(.all) + ) + .onTapGesture { + isFocused = true + } + } + + @available(macOS 26, *) + @ViewBuilder + public func selectionBackground( + _ isFocused: Bool = false + ) -> some View { + if self.controlActive != .inactive || !text.isEmpty || hasValue { + if isFocused || !text.isEmpty || hasValue { + Color(.textBackgroundColor) + } else { + if colorScheme == .light { + Color.black.opacity(0.06) + } else { + Color.white.opacity(0.24) + } + } + } else { + if colorScheme == .light { + Color.clear + } else { + Color.white.opacity(0.14) + } + } + } + + @available(macOS 26, *) + private var buttonStyle: some ButtonStyle { + .icon(font: .system(size: 16, weight: .semibold), size: CGSize(width: 20, height: 20)) + } + @available(macOS 26, *) + private var toggleStyle: some ToggleStyle { + .icon(font: .system(size: 16, weight: .semibold), size: CGSize(width: 20, height: 20)) + } +} From fc4ab713d16e77b437a84ed2261002baec576960 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Tue, 2 Sep 2025 15:55:24 -0500 Subject: [PATCH 04/18] Use Glass in Navigator Background --- .../CodeEditUI/Views/GlassEffectView.swift | 22 +++++++++++++++++++ .../Views/NavigatorAreaView.swift | 3 +++ 2 files changed, 25 insertions(+) create mode 100644 CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift diff --git a/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift b/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift new file mode 100644 index 0000000000..c841017ed3 --- /dev/null +++ b/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift @@ -0,0 +1,22 @@ +// +// GlassEffectView.swift +// CodeEdit +// +// Created by Khan Winter on 9/2/25. +// + + +struct GlassEffectView: NSViewRepresentable { + func makeNSView(context: Context) -> NSView { + if #available(macOS 26, *) { + let view = NSGlassEffectView() + view.cornerRadius = 0 + view.tintColor = .clear + return view + } else { + return NSView() + } + } + + func updateNSView(_ nsView: NSView, context: Context) { } +} diff --git a/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift b/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift index a3fdabf65c..43cf335277 100644 --- a/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift +++ b/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift @@ -43,5 +43,8 @@ struct NavigatorAreaView: View { .environmentObject(workspace) .accessibilityElement(children: .contain) .accessibilityLabel("navigator") + .if(.tahoe) { + $0.background(GlassEffectView().ignoresSafeArea(.all)) + } } } From fd9ed44efc2230c24bd4316036f349539f7778d4 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Tue, 2 Sep 2025 15:59:03 -0500 Subject: [PATCH 05/18] Revert That --- CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift | 2 ++ CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift b/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift index c841017ed3..023979dcb6 100644 --- a/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift +++ b/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift @@ -5,6 +5,8 @@ // Created by Khan Winter on 9/2/25. // +import SwiftUI +import AppKit struct GlassEffectView: NSViewRepresentable { func makeNSView(context: Context) -> NSView { diff --git a/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift b/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift index 43cf335277..a3fdabf65c 100644 --- a/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift +++ b/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift @@ -43,8 +43,5 @@ struct NavigatorAreaView: View { .environmentObject(workspace) .accessibilityElement(children: .contain) .accessibilityLabel("navigator") - .if(.tahoe) { - $0.background(GlassEffectView().ignoresSafeArea(.all)) - } } } From a822c40a154ac33083b0c71956ea2fbdb82bc263 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:48:43 -0500 Subject: [PATCH 06/18] New Panel Tab Style --- .../Styles/CapsuleButtonStyle.swift | 121 ++++++++++++++++++ .../CodeEditUI/Styles/IconButtonStyle.swift | 65 +++++----- .../CodeEditUI/Views/GlassEffectView.swift | 14 +- .../WorkspacePanelTabBar+IconButton.swift | 77 +++++++++++ .../WorkspacePanelTabBar.swift | 117 ++++++++++------- .../WorkspacePanel/WorkspacePanelView.swift | 95 ++++++++++++++ .../CodeEditUI/Views/WorkspacePanelView.swift | 64 --------- .../Views/InspectorAreaView.swift | 3 +- .../UtilityArea/Views/UtilityAreaView.swift | 3 +- 9 files changed, 411 insertions(+), 148 deletions(-) create mode 100644 CodeEdit/Features/CodeEditUI/Styles/CapsuleButtonStyle.swift create mode 100644 CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar+IconButton.swift rename CodeEdit/Features/CodeEditUI/Views/{ => WorkspacePanel}/WorkspacePanelTabBar.swift (75%) create mode 100644 CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift delete mode 100644 CodeEdit/Features/CodeEditUI/Views/WorkspacePanelView.swift diff --git a/CodeEdit/Features/CodeEditUI/Styles/CapsuleButtonStyle.swift b/CodeEdit/Features/CodeEditUI/Styles/CapsuleButtonStyle.swift new file mode 100644 index 0000000000..50ae176a73 --- /dev/null +++ b/CodeEdit/Features/CodeEditUI/Styles/CapsuleButtonStyle.swift @@ -0,0 +1,121 @@ +// +// CapsuleButtonStyle.swift +// CodeEdit +// +// Created by Khan Winter on 9/3/25. +// + +import SwiftUI + +struct CapsuleButtonStyle: ButtonStyle { + var isActive: Bool? + var font: Font? + var width: CGFloat? + var height: CGFloat? + + init(isActive: Bool? = nil, font: Font? = nil, size: CGFloat? = nil) { + self.isActive = isActive + self.font = font + self.width = size + self.height = size + } + + init(isActive: Bool? = nil, font: Font? = nil, width: CGFloat?, height: CGFloat?) { + self.isActive = isActive + self.font = font + self.width = width + self.height = height + } + + func makeBody(configuration: ButtonStyle.Configuration) -> some View { + CapsuleButton( + configuration: configuration, + isActive: isActive, + font: font, + width: width, + height: height + ) + } + + struct CapsuleButton: View { + @Environment(\.controlActiveState) + private var controlActiveState + @Environment(\.isEnabled) + private var isEnabled: Bool + @Environment(\.colorScheme) + private var colorScheme + + let configuration: ButtonStyle.Configuration + var isActive: Bool + var font: Font + var width: CGFloat? + var height: CGFloat? + + init( + configuration: ButtonStyle.Configuration, + isActive: Bool?, + font: Font?, + width: CGFloat?, + height: CGFloat? + ) { + self.configuration = configuration + self.isActive = isActive ?? false + self.font = font ?? Font.system(size: 14.5, weight: .regular, design: .default) + self.width = width + self.height = height + } + + var body: some View { + configuration.label + .font(font) + .foregroundStyle( + isActive + ? Color(.white) + : Color(.labelColor) + ) + .frame(width: width, height: height, alignment: .center) + .contentShape(Capsule()) + .brightness( + configuration.isPressed + ? colorScheme == .dark + ? 0.5 + : isActive ? -0.25 : -0.75 + : 0 + ) + .opacity(controlActiveState == .inactive ? 0.5 : 1) + .background(Capsule().fill(isActive ? Color.accentColor : .clear)) + } + } +} + +extension ButtonStyle where Self == CapsuleButtonStyle { + static func capsuleIcon( + isActive: Bool? = false, + font: Font? = Font.system(size: 14.5, weight: .regular, design: .default), + size: CGFloat? = 24 + ) -> CapsuleButtonStyle { + return CapsuleButtonStyle(isActive: isActive, font: font, size: size) + } + static func capsuleIcon( + isActive: Bool? = false, + font: Font? = Font.system(size: 14.5, weight: .regular, design: .default), + size: CGSize? = CGSize(width: 24, height: 24) + ) -> CapsuleButtonStyle { + return CapsuleButtonStyle(isActive: isActive, font: font, width: size?.width, height: size?.height) + } + static func capsuleIcon( + isActive: Bool? = false, + font: Font? = Font.system(size: 14.5, weight: .regular, design: .default), + width: CGFloat? = nil, + height: CGFloat? = nil + ) -> CapsuleButtonStyle { + return CapsuleButtonStyle(isActive: isActive, font: font, width: width, height: height) + } + static func capsuleIcon( + isActive: Bool? = false, + font: Font? = Font.system(size: 14.5, weight: .regular, design: .default) + ) -> CapsuleButtonStyle { + return CapsuleButtonStyle(isActive: isActive, font: font) + } + static var capsuleIcon: CapsuleButtonStyle { .init() } +} diff --git a/CodeEdit/Features/CodeEditUI/Styles/IconButtonStyle.swift b/CodeEdit/Features/CodeEditUI/Styles/IconButtonStyle.swift index dfc6ff7852..13628a28f8 100644 --- a/CodeEdit/Features/CodeEditUI/Styles/IconButtonStyle.swift +++ b/CodeEdit/Features/CodeEditUI/Styles/IconButtonStyle.swift @@ -10,24 +10,21 @@ import SwiftUI struct IconButtonStyle: ButtonStyle { var isActive: Bool? var font: Font? - var size: CGSize? + var width: CGFloat? + var height: CGFloat? init(isActive: Bool? = nil, font: Font? = nil, size: CGFloat? = nil) { self.isActive = isActive self.font = font - self.size = size == nil ? nil : CGSize(width: size ?? 0, height: size ?? 0) + self.width = size + self.height = size } - init(isActive: Bool? = nil, font: Font? = nil, size: CGSize? = nil) { + init(isActive: Bool? = nil, font: Font? = nil, width: CGFloat?, height: CGFloat?) { self.isActive = isActive self.font = font - self.size = size - } - - init(isActive: Bool? = nil, font: Font? = nil) { - self.isActive = isActive - self.font = font - self.size = nil + self.width = width + self.height = height } func makeBody(configuration: ButtonStyle.Configuration) -> some View { @@ -35,15 +32,12 @@ struct IconButtonStyle: ButtonStyle { configuration: configuration, isActive: isActive, font: font, - size: size + width: width, + height: height ) } struct IconButton: View { - let configuration: ButtonStyle.Configuration - var isActive: Bool - var font: Font - var size: CGSize? @Environment(\.controlActiveState) private var controlActiveState @Environment(\.isEnabled) @@ -51,25 +45,24 @@ struct IconButtonStyle: ButtonStyle { @Environment(\.colorScheme) private var colorScheme - init(configuration: ButtonStyle.Configuration, isActive: Bool?, font: Font?, size: CGFloat?) { - self.configuration = configuration - self.isActive = isActive ?? false - self.font = font ?? Font.system(size: 14.5, weight: .regular, design: .default) - self.size = size == nil ? nil : CGSize(width: size ?? 0, height: size ?? 0) - } - - init(configuration: ButtonStyle.Configuration, isActive: Bool?, font: Font?, size: CGSize?) { - self.configuration = configuration - self.isActive = isActive ?? false - self.font = font ?? Font.system(size: 14.5, weight: .regular, design: .default) - self.size = size ?? nil - } + let configuration: ButtonStyle.Configuration + var isActive: Bool + var font: Font + var width: CGFloat? + var height: CGFloat? - init(configuration: ButtonStyle.Configuration, isActive: Bool?, font: Font?) { + init( + configuration: ButtonStyle.Configuration, + isActive: Bool?, + font: Font?, + width: CGFloat?, + height: CGFloat? + ) { self.configuration = configuration self.isActive = isActive ?? false self.font = font ?? Font.system(size: 14.5, weight: .regular, design: .default) - self.size = nil + self.width = width + self.height = height } var body: some View { @@ -80,7 +73,7 @@ struct IconButtonStyle: ButtonStyle { ? Color(.controlAccentColor) : Color(.secondaryLabelColor) ) - .frame(width: size?.width, height: size?.height, alignment: .center) + .frame(width: width, height: height, alignment: .center) .contentShape(Rectangle()) .brightness( configuration.isPressed @@ -108,7 +101,15 @@ extension ButtonStyle where Self == IconButtonStyle { font: Font? = Font.system(size: 14.5, weight: .regular, design: .default), size: CGSize? = CGSize(width: 24, height: 24) ) -> IconButtonStyle { - return IconButtonStyle(isActive: isActive, font: font, size: size) + return IconButtonStyle(isActive: isActive, font: font, width: size?.width, height: size?.height) + } + static func icon( + isActive: Bool? = false, + font: Font? = Font.system(size: 14.5, weight: .regular, design: .default), + width: CGFloat? = nil, + height: CGFloat? = nil + ) -> IconButtonStyle { + return IconButtonStyle(isActive: isActive, font: font, width: width, height: height) } static func icon( isActive: Bool? = false, diff --git a/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift b/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift index 023979dcb6..87faa93ce8 100644 --- a/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift +++ b/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift @@ -9,16 +9,26 @@ import SwiftUI import AppKit struct GlassEffectView: NSViewRepresentable { + var tintColor: NSColor? + + init(tintColor: NSColor? = nil) { + self.tintColor = tintColor + } + func makeNSView(context: Context) -> NSView { if #available(macOS 26, *) { let view = NSGlassEffectView() view.cornerRadius = 0 - view.tintColor = .clear + view.tintColor = tintColor return view } else { return NSView() } } - func updateNSView(_ nsView: NSView, context: Context) { } + func updateNSView(_ nsView: NSView, context: Context) { + if #available(macOS 26, *), let view = nsView as? NSGlassEffectView { + view.tintColor = tintColor + } + } } diff --git a/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar+IconButton.swift b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar+IconButton.swift new file mode 100644 index 0000000000..9125d86754 --- /dev/null +++ b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar+IconButton.swift @@ -0,0 +1,77 @@ +// +// IconButton.swift +// CodeEdit +// +// Created by Khan Winter on 9/3/25. +// + +import SwiftUI + +extension WorkspacePanelTabBar { + struct IconButton: View { + let tab: Tab + let scale: Image.Scale = .medium + let size: CGSize + + var position: SettingsData.SidebarTabBarPosition + + @Binding var selection: Tab? + + var body: some View { + Button { + selection = tab + } label: { + getSafeImage(named: tab.systemImage, accessibilityDescription: tab.title) + .font(.system(size: 13)) + .symbolVariant(tab == selection ? .fill : .none) + .help(tab.title) + .frame(maxWidth: .infinity) + } + .if(.tahoe) { + $0.buttonStyle(capsuleButtonStyle) + } else: { + $0.buttonStyle(buttonStyle) + } + .focusable(false) + .accessibilityIdentifier("WorkspacePanelTab-\(tab.title)") + .accessibilityLabel(tab.title) + } + + private func getSafeImage(named: String, accessibilityDescription: String?) -> Image { + // We still use the NSImage init to check if a symbol with the name exists. + if NSImage(systemSymbolName: named, accessibilityDescription: nil) != nil { + return Image(systemName: named) + } else { + return Image(symbol: named) + } + } + + private var capsuleButtonStyle: CapsuleButtonStyle { + if #available(macOS 26, *) { + if position == .side { + .capsuleIcon( + isActive: tab == selection, + size: CGSize(width: 26, height: 40) + ) + } else { + .capsuleIcon( + isActive: tab == selection, + height: 28 + ) + } + } else { + fatalError("Used on non tahoe platform") + } + } + + private var buttonStyle: IconButtonStyle { + .icon( + isActive: tab == selection, + size: CGSize( + width: position == .side ? 24 : 42, + height: position == .side ? 40 : size.height + ) + ) + } + } +} diff --git a/CodeEdit/Features/CodeEditUI/Views/WorkspacePanelTabBar.swift b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar.swift similarity index 75% rename from CodeEdit/Features/CodeEditUI/Views/WorkspacePanelTabBar.swift rename to CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar.swift index d3ef997a01..922e073c30 100644 --- a/CodeEdit/Features/CodeEditUI/Views/WorkspacePanelTabBar.swift +++ b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar.swift @@ -46,26 +46,35 @@ struct WorkspacePanelTabBar: View { } } - var topBody: some View { + @ViewBuilder var topBody: some View { GeometryReader { proxy in iconsView(size: proxy.size) .frame(maxWidth: .infinity, maxHeight: .infinity) .animation(.default, value: items) } .clipped() - .frame(maxWidth: .infinity, idealHeight: 27) + .if(.tahoe) { + $0.frame(maxWidth: .infinity, idealHeight: 28).padding(.horizontal, 8) + } else: { + $0.frame(maxWidth: .infinity, idealHeight: 27) + } .fixedSize(horizontal: false, vertical: true) } - var sideBody: some View { + @ViewBuilder var sideBody: some View { GeometryReader { proxy in iconsView(size: proxy.size) - .padding(.vertical, 5) - .frame(maxWidth: .infinity, maxHeight: .infinity) + .if(!.tahoe) { + $0.padding(.vertical, 5).frame(maxWidth: .infinity, maxHeight: .infinity) + } .animation(.default, value: items) } .clipped() - .frame(idealWidth: 40, maxHeight: .infinity) + .if(.tahoe) { + $0.frame(idealWidth: 26, maxHeight: .infinity) + } else: { + $0.frame(idealWidth: 40, maxHeight: .infinity) + } .fixedSize(horizontal: true, vertical: false) } @@ -74,47 +83,68 @@ struct WorkspacePanelTabBar: View { let layout = position == .top ? AnyLayout(HStackLayout(spacing: 0)) : AnyLayout(VStackLayout(spacing: 0)) + layout { - ForEach(items) { tab in - makeIcon(tab: tab, size: size) - .offset( - x: (position == .top) ? (tabOffsets[tab] ?? 0) : 0, - y: (position == .side) ? (tabOffsets[tab] ?? 0) : 0 - ) - .background(makeTabItemGeometryReader(tab: tab)) - .simultaneousGesture(makeAreaTabDragGesture(tab: tab)) + if #available(macOS 26, *) { + ForEach(Array(items.enumerated()), id: \.element) { (idx, tab) in + tabViewTahoe(tab, next: items[safe: idx + 1], size: size) + } + } else { + ForEach(items) { tab in + tabView(tab, size: size) + } } - if position == .side { + + if position == .side, #unavailable(macOS 26) { Spacer() } } + .if(.tahoe) { + if #available(macOS 14.0, *) { + $0.background(GlassEffectView(tintColor: .secondarySystemFill)).clipShape(Capsule()) + } + } } - private func makeIcon( - tab: Tab, - scale: Image.Scale = .medium, - size: CGSize - ) -> some View { - Button { - selection = tab - } label: { - getSafeImage(named: tab.systemImage, accessibilityDescription: tab.title) - .font(.system(size: 12.5)) - .symbolVariant(tab == selection ? .fill : .none) - .help(tab.title) - } - .buttonStyle( - .icon( - isActive: tab == selection, - size: CGSize( - width: position == .side ? 40 : 24, - height: position == .side ? 28 : size.height - ) + @ViewBuilder + private func tabView(_ tab: Tab, size: CGSize) -> some View { + IconButton(tab: tab, size: size, position: position, selection: $selection) + .offset( + x: (position == .top) ? (tabOffsets[tab] ?? 0) : 0, + y: (position == .side) ? (tabOffsets[tab] ?? 0) : 0 ) - ) - .focusable(false) - .accessibilityIdentifier("WorkspacePanelTab-\(tab.title)") - .accessibilityLabel(tab.title) + .background(makeTabItemGeometryReader(tab: tab)) + .simultaneousGesture(makeAreaTabDragGesture(tab: tab)) + } + + @available(macOS 26, *) + @ViewBuilder + private func tabViewTahoe(_ tab: Tab, next: Tab?, size: CGSize) -> some View { + let layout = position == .top + ? AnyLayout(HStackLayout(spacing: 0)) + : AnyLayout(VStackLayout(spacing: 0)) + let paddingDirection: Edge.Set = position == .top + ? .vertical + : .horizontal + let paddingAmount: CGFloat = position == .top + ? 5 + : 2 + + IconButton(tab: tab, size: size, position: position, selection: $selection) + .offset( + x: (position == .top) ? (tabOffsets[tab] ?? 0) : 0, + y: (position == .side) ? (tabOffsets[tab] ?? 0) : 0 + ) + .background(makeTabItemGeometryReader(tab: tab)) + .simultaneousGesture(makeAreaTabDragGesture(tab: tab)) + .overlay { // overlay to avoid layout adjustment when appearing/disappearing + layout { + Spacer() + if tab != items.last && selection != tab && next != selection { + Divider().padding(paddingDirection, paddingAmount) + } + } + } } private func makeAreaTabDragGesture(tab: Tab) -> some Gesture { @@ -289,13 +319,4 @@ struct WorkspacePanelTabBar: View { } } } - - private func getSafeImage(named: String, accessibilityDescription: String?) -> Image { - // We still use the NSImage init to check if a symbol with the name exists. - if NSImage(systemSymbolName: named, accessibilityDescription: nil) != nil { - return Image(systemName: named) - } else { - return Image(symbol: named) - } - } } diff --git a/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift new file mode 100644 index 0000000000..a8510b84cb --- /dev/null +++ b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift @@ -0,0 +1,95 @@ +// +// WorkspacePanelView.swift +// CodeEdit +// +// Created by Austin Condiff on 1/4/25. +// + +import SwiftUI + +struct WorkspacePanelView: View { + @ObservedObject var viewModel: ViewModel + @Binding var selectedTab: Tab? + @Binding var tabItems: [Tab] + + @Environment(\.colorScheme) + private var colorScheme + + var sidebarPosition: SettingsData.SidebarTabBarPosition + var darkDivider: Bool + let padSideItemVertically: Bool + let sideOnTrailing: Bool + + init( + viewModel: ViewModel, + selectedTab: Binding, + tabItems: Binding<[Tab]>, + sidebarPosition: SettingsData.SidebarTabBarPosition, + darkDivider: Bool = false, + padSideItemVertically: Bool = false, + sideOnTrailing: Bool = false + ) { + self.viewModel = viewModel + self._selectedTab = selectedTab + self._tabItems = tabItems + self.sidebarPosition = sidebarPosition + self.darkDivider = darkDivider + self.padSideItemVertically = padSideItemVertically + if #available(macOS 26, *) { + self.sideOnTrailing = sideOnTrailing + } else { + self.sideOnTrailing = false + } + } + + var body: some View { + VStack(spacing: 0) { + if let selection = selectedTab { + selection + } else { + CEContentUnavailableView("No Selection") + } + } + .safeAreaInset(edge: .leading, spacing: 0) { + if sidebarPosition == .side && !sideOnTrailing { + sideTabBar + } + } + .safeAreaInset(edge: .trailing, spacing: 0) { + if sidebarPosition == .side && sideOnTrailing { + sideTabBar + } + } + .safeAreaInset(edge: .top, spacing: 0) { + if sidebarPosition == .top { + VStack(spacing: 0) { + if #unavailable(macOS 26) { + Divider() + } + + WorkspacePanelTabBar(items: $tabItems, selection: $selectedTab, position: sidebarPosition) + + if #unavailable(macOS 26) { + Divider() + } + } + } else if !darkDivider, #unavailable(macOS 26) { + Divider() + } + } + } + + @ViewBuilder private var sideTabBar: some View { + HStack(spacing: 0) { + WorkspacePanelTabBar(items: $tabItems, selection: $selectedTab, position: sidebarPosition) + .if(.tahoe) { + $0.padding(.vertical, padSideItemVertically ? 8 : 0) + .padding(sideOnTrailing ? .trailing : .leading, 8) + } + if #unavailable(macOS 26) { + Divider() + .overlay(Color(nsColor: darkDivider && colorScheme == .dark ? .black : .clear)) + } + } + } +} diff --git a/CodeEdit/Features/CodeEditUI/Views/WorkspacePanelView.swift b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanelView.swift deleted file mode 100644 index 4637d2785e..0000000000 --- a/CodeEdit/Features/CodeEditUI/Views/WorkspacePanelView.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// WorkspacePanelView.swift -// CodeEdit -// -// Created by Austin Condiff on 1/4/25. -// - -import SwiftUI - -struct WorkspacePanelView: View { - @ObservedObject var viewModel: ViewModel - @Binding var selectedTab: Tab? - @Binding var tabItems: [Tab] - - @Environment(\.colorScheme) - private var colorScheme - - var sidebarPosition: SettingsData.SidebarTabBarPosition - var darkDivider: Bool - - init( - viewModel: ViewModel, - selectedTab: Binding, - tabItems: Binding<[Tab]>, - sidebarPosition: SettingsData.SidebarTabBarPosition, - darkDivider: Bool = false - ) { - self.viewModel = viewModel - self._selectedTab = selectedTab - self._tabItems = tabItems - self.sidebarPosition = sidebarPosition - self.darkDivider = darkDivider - } - - var body: some View { - VStack(spacing: 0) { - if let selection = selectedTab { - selection - } else { - CEContentUnavailableView("No Selection") - } - } - .safeAreaInset(edge: .leading, spacing: 0) { - if sidebarPosition == .side { - HStack(spacing: 0) { - WorkspacePanelTabBar(items: $tabItems, selection: $selectedTab, position: sidebarPosition) - Divider() - .overlay(Color(nsColor: darkDivider && colorScheme == .dark ? .black : .clear)) - } - } - } - .safeAreaInset(edge: .top, spacing: 0) { - if sidebarPosition == .top { - VStack(spacing: 0) { - Divider() - WorkspacePanelTabBar(items: $tabItems, selection: $selectedTab, position: sidebarPosition) - Divider() - } - } else if !darkDivider { - Divider() - } - } - } -} diff --git a/CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift b/CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift index ecfe7df841..c7c08d2e7b 100644 --- a/CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift +++ b/CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift @@ -49,7 +49,8 @@ struct InspectorAreaView: View { viewModel: viewModel, selectedTab: $viewModel.selectedTab, tabItems: $viewModel.tabItems, - sidebarPosition: sidebarPosition + sidebarPosition: sidebarPosition, + sideOnTrailing: true ) .formStyle(.grouped) .accessibilityElement(children: .contain) diff --git a/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift b/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift index 6d35448169..af54f99ef6 100644 --- a/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift +++ b/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift @@ -16,7 +16,8 @@ struct UtilityAreaView: View { selectedTab: $utilityAreaViewModel.selectedTab, tabItems: $utilityAreaViewModel.tabItems, sidebarPosition: .side, - darkDivider: true + darkDivider: true, + padSideItemVertically: true ) .accessibilityElement(children: .contain) .accessibilityLabel("Utility Area") From c9501cac84df22575b654e14fc706357937a31d8 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Wed, 3 Sep 2025 17:09:13 -0500 Subject: [PATCH 07/18] Finish Utility Area --- .../StatusBar/Views/StatusBarView.swift | 19 ++++- .../UtilityArea/Views/PaneToolbar.swift | 84 ++++++++++++++----- .../Views/UtilityAreaTabView.swift | 12 ++- CodeEdit/WorkspaceView.swift | 6 +- 4 files changed, 91 insertions(+), 30 deletions(-) diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift index cb73012d8a..ba736bebb7 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift @@ -22,7 +22,21 @@ struct StatusBarView: View { @Environment(\.controlActiveState) private var controlActive - static let height = 28.0 + static var height: CGFloat { + if #available(macOS 26, *) { + 37.0 + } else { + 29.0 + } + } + + private var trailingPadding: CGFloat { + if #available(macOS 26, *) { + 8 + } else { + 0 + } + } @Environment(\.colorScheme) private var colorScheme @@ -43,8 +57,9 @@ struct StatusBarView: View { StatusBarToggleUtilityAreaButton() } .padding(.horizontal, 10) + .padding(.trailing, trailingPadding) .cursor(.resizeUpDown) - .frame(height: Self.height) + .frame(height: Self.height - 1.0) .background(.bar) .padding(.top, 1) .overlay(alignment: .top) { diff --git a/CodeEdit/Features/UtilityArea/Views/PaneToolbar.swift b/CodeEdit/Features/UtilityArea/Views/PaneToolbar.swift index 658cc46fca..a6c67238dc 100644 --- a/CodeEdit/Features/UtilityArea/Views/PaneToolbar.swift +++ b/CodeEdit/Features/UtilityArea/Views/PaneToolbar.swift @@ -13,14 +13,33 @@ struct PaneToolbar: View { @Environment(\.paneArea) var paneArea: PaneArea? + private var height: CGFloat? { + if #available(macOS 26, *) { + 36.0 + } else { + nil + } + } + + private var maxHeight: CGFloat? { + if #available(macOS 26, *) { + nil + } else { + 27.0 + } + } + + private var padding: CGSize { + if #available(macOS 26, *) { + CGSize(width: 5.0, height: 0) + } else { + CGSize(width: 5.0, height: 8.0) + } + } + var body: some View { - HStack(spacing: 5) { - if model.hasLeadingSidebar - && ( - ((paneArea == .main || paneArea == .mainLeading) - && model.leadingSidebarIsCollapsed) - || paneArea == .leading - ) { + HStack(alignment: .center, spacing: 5) { + if shouldShowLeadingSection() { PaneToolbarSection { Spacer() .frame(width: 24) @@ -28,24 +47,45 @@ struct PaneToolbar: View { .opacity(0) } content - if model.hasTrailingSidebar - && ( - ((paneArea == .main || paneArea == .mainTrailing) - && model.trailingSidebarIsCollapsed) - || paneArea == .trailing - ) || !model.hasTrailingSidebar { - if model.hasTrailingSidebar { - PaneToolbarSection { - Spacer() - .frame(width: 24) - } - .opacity(0) + if shouldShowTrailingSection() { + PaneToolbarSection { + Spacer() + .frame(width: 24) } + .opacity(0) + } + if #available(macOS 26, *), isTrailingItem() { + Spacer().frame(width: 5) } } .buttonStyle(.icon(size: 24)) - .padding(.horizontal, 5) - .padding(.vertical, 8) - .frame(maxHeight: 27) + .padding(.horizontal, padding.width) + .padding(.vertical, padding.height) + .frame(maxHeight: maxHeight) + .frame(height: height) + } + + private func shouldShowLeadingSection() -> Bool { + model.hasLeadingSidebar + && ( + ((paneArea == .main || paneArea == .mainLeading) && model.leadingSidebarIsCollapsed) + || paneArea == .leading + ) + } + + private func shouldShowTrailingSection() -> Bool { + model.hasTrailingSidebar + && ( + ((paneArea == .main || paneArea == .mainTrailing) && model.trailingSidebarIsCollapsed) + || paneArea == .trailing + ) + } + + private func isTrailingItem() -> Bool { + paneArea == .trailing + || ( + (paneArea == .main || paneArea == .mainTrailing) + && (model.trailingSidebarIsCollapsed || !model.hasTrailingSidebar) + ) } } diff --git a/CodeEdit/Features/UtilityArea/Views/UtilityAreaTabView.swift b/CodeEdit/Features/UtilityArea/Views/UtilityAreaTabView.swift index bb36bcc5ab..c3d4a0654a 100644 --- a/CodeEdit/Features/UtilityArea/Views/UtilityAreaTabView.swift +++ b/CodeEdit/Features/UtilityArea/Views/UtilityAreaTabView.swift @@ -118,14 +118,22 @@ struct UtilityAreaTabView Date: Thu, 4 Sep 2025 10:03:59 -0500 Subject: [PATCH 08/18] Finish Navigator Filter Bar & Tab Bar Work --- .../WorkspacePanelTabBar+IconButton.swift | 10 ++++- .../WorkspacePanel/WorkspacePanelTabBar.swift | 20 ++++++---- .../WorkspacePanel/WorkspacePanelView.swift | 40 ++++++++++++++++++- .../FindNavigator/FindNavigatorView.swift | 3 -- .../NavigatorArea/Models/NavigatorTab.swift | 16 ++++++++ .../ProjectNavigatorView.swift | 3 -- .../Views/SourceControlNavigatorView.swift | 6 +-- .../Views/NavigatorAreaView.swift | 5 ++- 8 files changed, 80 insertions(+), 23 deletions(-) diff --git a/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar+IconButton.swift b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar+IconButton.swift index 9125d86754..1ad6b07ccc 100644 --- a/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar+IconButton.swift +++ b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar+IconButton.swift @@ -17,13 +17,21 @@ extension WorkspacePanelTabBar { @Binding var selection: Tab? + var symbolVariant: SymbolVariants { + if #unavailable(macOS 26), selection == tab { + .fill + } else { + .none + } + } + var body: some View { Button { selection = tab } label: { getSafeImage(named: tab.systemImage, accessibilityDescription: tab.title) .font(.system(size: 13)) - .symbolVariant(tab == selection ? .fill : .none) + .symbolVariant(symbolVariant) .help(tab.title) .frame(maxWidth: .infinity) } diff --git a/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar.swift b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar.swift index 922e073c30..3b64e377e6 100644 --- a/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar.swift +++ b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar.swift @@ -146,8 +146,12 @@ struct WorkspacePanelTabBar: View { } } } +} + +// MARK: - Drag Gesture - private func makeAreaTabDragGesture(tab: Tab) -> some Gesture { +private extension WorkspacePanelTabBar { + func makeAreaTabDragGesture(tab: Tab) -> some Gesture { DragGesture(minimumDistance: 2, coordinateSpace: .global) .onChanged({ value in if draggingTab != tab { @@ -203,7 +207,7 @@ struct WorkspacePanelTabBar: View { }) } - private func initializeDragGesture(value: DragGesture.Value, for tab: Tab) { + func initializeDragGesture(value: DragGesture.Value, for tab: Tab) { draggingTab = tab let initialLocation = position == .top ? value.startLocation.x : value.startLocation.y draggingStartLocation = initialLocation @@ -216,7 +220,7 @@ struct WorkspacePanelTabBar: View { } // swiftlint:disable:next function_parameter_count - private func swapTab( + func swapTab( tab: Tab, currentIndex: Int, currentLocation: CGFloat, @@ -267,7 +271,7 @@ struct WorkspacePanelTabBar: View { } } - private func isWithinPrevTopBounds( + func isWithinPrevTopBounds( _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat ) -> Bool { return curLocation < max( @@ -276,7 +280,7 @@ struct WorkspacePanelTabBar: View { ) } - private func isWithinNextTopBounds( + func isWithinNextTopBounds( _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat, _ curWidth: CGFloat ) -> Bool { return curLocation > min( @@ -285,7 +289,7 @@ struct WorkspacePanelTabBar: View { ) } - private func isWithinPrevBottomBounds( + func isWithinPrevBottomBounds( _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat ) -> Bool { return curLocation < max( @@ -294,7 +298,7 @@ struct WorkspacePanelTabBar: View { ) } - private func isWithinNextBottomBounds( + func isWithinNextBottomBounds( _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat, _ curWidth: CGFloat ) -> Bool { return curLocation > min( @@ -303,7 +307,7 @@ struct WorkspacePanelTabBar: View { ) } - private func makeTabItemGeometryReader(tab: Tab) -> some View { + func makeTabItemGeometryReader(tab: Tab) -> some View { GeometryReader { geometry in Rectangle() .foregroundColor(.clear) diff --git a/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift index a8510b84cb..cd45680ad7 100644 --- a/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift +++ b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift @@ -7,7 +7,7 @@ import SwiftUI -struct WorkspacePanelView: View { +struct WorkspacePanelView: View { @ObservedObject var viewModel: ViewModel @Binding var selectedTab: Tab? @Binding var tabItems: [Tab] @@ -19,6 +19,7 @@ struct WorkspacePanelView: var darkDivider: Bool let padSideItemVertically: Bool let sideOnTrailing: Bool + let bottomAccessory: BottomAccessory init( viewModel: ViewModel, @@ -27,7 +28,8 @@ struct WorkspacePanelView: sidebarPosition: SettingsData.SidebarTabBarPosition, darkDivider: Bool = false, padSideItemVertically: Bool = false, - sideOnTrailing: Bool = false + sideOnTrailing: Bool = false, + @ViewBuilder bottomAccessory: () -> BottomAccessory ) { self.viewModel = viewModel self._selectedTab = selectedTab @@ -40,12 +42,41 @@ struct WorkspacePanelView: } else { self.sideOnTrailing = false } + self.bottomAccessory = bottomAccessory() + } + + init( + viewModel: ViewModel, + selectedTab: Binding, + tabItems: Binding<[Tab]>, + sidebarPosition: SettingsData.SidebarTabBarPosition, + darkDivider: Bool = false, + padSideItemVertically: Bool = false, + sideOnTrailing: Bool = false, + ) where BottomAccessory == EmptyView { + self.viewModel = viewModel + self._selectedTab = selectedTab + self._tabItems = tabItems + self.sidebarPosition = sidebarPosition + self.darkDivider = darkDivider + self.padSideItemVertically = padSideItemVertically + if #available(macOS 26, *) { + self.sideOnTrailing = sideOnTrailing + } else { + self.sideOnTrailing = false + } + self.bottomAccessory = EmptyView() } var body: some View { VStack(spacing: 0) { if let selection = selectedTab { selection + .safeAreaInset(edge: .bottom, spacing: 0) { + if #unavailable(macOS 26) { + bottomAccessory + } + } } else { CEContentUnavailableView("No Selection") } @@ -77,6 +108,11 @@ struct WorkspacePanelView: Divider() } } + .safeAreaInset(edge: .bottom, spacing: 0) { + if #available(macOS 26, *) { + bottomAccessory + } + } } @ViewBuilder private var sideTabBar: some View { diff --git a/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorView.swift b/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorView.swift index 5e8fb370be..58e211412d 100644 --- a/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorView.swift +++ b/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorView.swift @@ -86,9 +86,6 @@ struct FindNavigatorView: View { ) } } - .safeAreaInset(edge: .bottom, spacing: 0) { - FindNavigatorToolbarBottom() - } .onReceive(state.$searchResult, perform: { value in self.foundFilesCount = value.count }) diff --git a/CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift b/CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift index f8d240e798..cd0d342462 100644 --- a/CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift +++ b/CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift @@ -60,4 +60,20 @@ enum NavigatorTab: WorkspacePanelTab { ExtensionSceneView(with: endpoint, sceneID: data.sceneID) } } + + @ViewBuilder func bottomView(workspace: WorkspaceDocument) -> some View { + switch self { + case .project: + ProjectNavigatorToolbarBottom() + case .sourceControl: + if let sourceControlManager = workspace.sourceControlManager { + SourceControlNavigatorToolbarBottom() + .environmentObject(sourceControlManager) + } + case .search: + FindNavigatorToolbarBottom() + case .uiExtension: + EmptyView() + } + } } diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorView.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorView.swift index 23536c06e0..9d03312d76 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorView.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorView.swift @@ -17,8 +17,5 @@ import SwiftUI struct ProjectNavigatorView: View { var body: some View { ProjectNavigatorOutlineView() - .safeAreaInset(edge: .bottom, spacing: 0) { - ProjectNavigatorToolbarBottom() - } } } diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorView.swift index 31363fc913..749bf2bc7b 100644 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorView.swift +++ b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorView.swift @@ -14,7 +14,7 @@ struct SourceControlNavigatorView: View { var fetchRefreshServerStatus var body: some View { - if let sourceControlManager = workspace.workspaceFileManager?.sourceControlManager { + if let sourceControlManager = workspace.sourceControlManager { VStack(spacing: 0) { SourceControlNavigatorTabs() .environmentObject(sourceControlManager) @@ -31,10 +31,6 @@ struct SourceControlNavigatorView: View { } } } - .safeAreaInset(edge: .bottom, spacing: 0) { - SourceControlNavigatorToolbarBottom() - .environmentObject(sourceControlManager) - } } } } diff --git a/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift b/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift index a3fdabf65c..47fa52ac99 100644 --- a/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift +++ b/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift @@ -38,7 +38,10 @@ struct NavigatorAreaView: View { viewModel: viewModel, selectedTab: $viewModel.selectedTab, tabItems: $viewModel.tabItems, - sidebarPosition: sidebarPosition + sidebarPosition: sidebarPosition, + bottomAccessory: { + viewModel.selectedTab?.bottomView(workspace: workspace) + } ) .environmentObject(workspace) .accessibilityElement(children: .contain) From eaa3de7253c355322c92b67776ab5efbb28d7853 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu, 4 Sep 2025 10:16:31 -0500 Subject: [PATCH 09/18] 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 --- .../CodeEditSplitViewController.swift | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift index 0de4017e7c..39735c8de1 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift @@ -19,8 +19,6 @@ final class CodeEditSplitViewController: NSSplitViewController { private weak var windowRef: NSWindow? private unowned var hapticPerformer: NSHapticFeedbackPerformer - private weak var navigatorItem: NSSplitViewItem? - // MARK: - Initialization init( @@ -68,7 +66,6 @@ final class CodeEditSplitViewController: NSSplitViewController { .environmentObject(editorManager) }) - self.navigatorItem = navigator addSplitViewItem(navigator) let workspaceView = SettingsInjector { @@ -124,8 +121,6 @@ final class CodeEditSplitViewController: NSSplitViewController { guard let workspace else { return } - workspace.notificationPanel.updateToolbarItem() - let navigatorWidth = workspace.getFromWorkspaceState(.splitViewWidth) as? CGFloat splitView.setPosition(navigatorWidth ?? Self.minSidebarWidth, ofDividerAt: 0) @@ -141,6 +136,7 @@ final class CodeEditSplitViewController: NSSplitViewController { ) as? Bool ?? true } + workspace.notificationPanel.updateToolbarItem() } // MARK: - NSSplitViewDelegate @@ -204,14 +200,8 @@ final class CodeEditSplitViewController: NSSplitViewController { } if resizedDivider == 0 { - let width: CGFloat - if #available(macOS 26, *) { - let panel = splitViewItems[0] - width = panel.viewController.view.frame.size.width - } else { - let panel = splitView.subviews[0] - width = panel.frame.size.width - } + let panel = splitView.subviews[0] + let width = panel.frame.size.width if width > 0 { workspace?.addToWorkspaceState(key: .splitViewWidth, value: width) } From 78984c103b0dbf13d29a3aab4dcc1d0832b6c5a2 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu, 4 Sep 2025 14:41:35 -0500 Subject: [PATCH 10/18] Re-fix navigator width bug --- .../CodeEditSplitViewController.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift index 39735c8de1..f8f5afc28c 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift @@ -19,6 +19,8 @@ final class CodeEditSplitViewController: NSSplitViewController { private weak var windowRef: NSWindow? private unowned var hapticPerformer: NSHapticFeedbackPerformer + private weak var navigatorItem: NSSplitViewItem? + // MARK: - Initialization init( @@ -66,6 +68,7 @@ final class CodeEditSplitViewController: NSSplitViewController { .environmentObject(editorManager) }) + self.navigatorItem = navigator addSplitViewItem(navigator) let workspaceView = SettingsInjector { @@ -120,6 +123,7 @@ final class CodeEditSplitViewController: NSSplitViewController { super.viewWillAppear() guard let workspace else { return } + workspace.notificationPanel.updateToolbarItem() let navigatorWidth = workspace.getFromWorkspaceState(.splitViewWidth) as? CGFloat splitView.setPosition(navigatorWidth ?? Self.minSidebarWidth, ofDividerAt: 0) @@ -135,8 +139,6 @@ final class CodeEditSplitViewController: NSSplitViewController { .inspectorCollapsed ) as? Bool ?? true } - - workspace.notificationPanel.updateToolbarItem() } // MARK: - NSSplitViewDelegate @@ -200,8 +202,14 @@ final class CodeEditSplitViewController: NSSplitViewController { } if resizedDivider == 0 { - let panel = splitView.subviews[0] - let width = panel.frame.size.width + let width: CGFloat + if #available(macOS 26, *) { + let panel = splitViewItems[0] + width = panel.viewController.view.frame.size.width + } else { + let panel = splitView.subviews[0] + width = panel.frame.size.width + } if width > 0 { workspace?.addToWorkspaceState(key: .splitViewWidth, value: width) } From 2dbcdd5c5396c73ea6ab9521088ce05e571f196b Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu, 4 Sep 2025 15:10:44 -0500 Subject: [PATCH 11/18] Fix Project Navigator Tint Bug --- ...igatorViewController+NSOutlineViewDelegate.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDelegate.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDelegate.swift index 9256c3e3e1..0d6c71630d 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDelegate.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDelegate.swift @@ -181,4 +181,17 @@ extension ProjectNavigatorViewController: NSOutlineViewDelegate { } return "" } + + func outlineView(_ outlineView: NSOutlineView, tintConfigurationForItem item: Any) -> NSTintConfiguration? { + NSTintConfiguration(fixedColor: NSColor(name: nil, dynamicProvider: { appearance in + switch appearance.name { + case .darkAqua, .vibrantDark: + .white + case .aqua, .vibrantLight: + .black + default: + .black + } + })) + } } From 326319bc27c7dbfacce060475c634e9815c7fa93 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu, 4 Sep 2025 15:11:14 -0500 Subject: [PATCH 12/18] fix:lint --- CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift b/CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift index cd0d342462..9f0c90f54f 100644 --- a/CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift +++ b/CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift @@ -61,7 +61,8 @@ enum NavigatorTab: WorkspacePanelTab { } } - @ViewBuilder func bottomView(workspace: WorkspaceDocument) -> some View { + @ViewBuilder + func bottomView(workspace: WorkspaceDocument) -> some View { switch self { case .project: ProjectNavigatorToolbarBottom() From bf74654ef2adab468cb3a7371d63154ec17eaf43 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu, 4 Sep 2025 15:15:35 -0500 Subject: [PATCH 13/18] Add Padding to Side Tabs --- .../Views/WorkspacePanel/WorkspacePanelView.swift | 10 ++++++++-- .../NavigatorArea/Views/NavigatorAreaView.swift | 7 +++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift index cd45680ad7..2855dfd01e 100644 --- a/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift +++ b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift @@ -19,6 +19,7 @@ struct WorkspacePanelView (Edge.Set, CGFloat) let bottomAccessory: BottomAccessory init( @@ -29,6 +30,7 @@ struct WorkspacePanelView (Edge.Set, CGFloat) = { ([], 0) }, @ViewBuilder bottomAccessory: () -> BottomAccessory ) { self.viewModel = viewModel @@ -42,6 +44,7 @@ struct WorkspacePanelView (Edge.Set, CGFloat) = { ([], 0) }, sideOnTrailing: Bool = false, ) where BottomAccessory == EmptyView { self.viewModel = viewModel @@ -65,6 +69,7 @@ struct WorkspacePanelView Date: Thu, 4 Sep 2025 15:16:47 -0500 Subject: [PATCH 14/18] Conditionally Compile NSGlassEffectView --- CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift b/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift index 87faa93ce8..ff0784847d 100644 --- a/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift +++ b/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift @@ -16,6 +16,7 @@ struct GlassEffectView: NSViewRepresentable { } func makeNSView(context: Context) -> NSView { +#if compiler(>=6.2) if #available(macOS 26, *) { let view = NSGlassEffectView() view.cornerRadius = 0 @@ -24,11 +25,16 @@ struct GlassEffectView: NSViewRepresentable { } else { return NSView() } +#else + return NSView() +#endif } func updateNSView(_ nsView: NSView, context: Context) { +#if compiler(>=6.2) if #available(macOS 26, *), let view = nsView as? NSGlassEffectView { view.tintColor = tintColor } +#endif } } From 04cb71bde2d0ce664882e982e82546e5e97a9237 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu, 4 Sep 2025 15:26:03 -0500 Subject: [PATCH 15/18] Found The "Real Cause" --- ...igatorViewController+NSOutlineViewDelegate.swift | 13 ------------- .../ProjectNavigatorViewController.swift | 1 + .../NavigatorArea/Views/NavigatorAreaView.swift | 1 + 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDelegate.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDelegate.swift index 0d6c71630d..9256c3e3e1 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDelegate.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDelegate.swift @@ -181,17 +181,4 @@ extension ProjectNavigatorViewController: NSOutlineViewDelegate { } return "" } - - func outlineView(_ outlineView: NSOutlineView, tintConfigurationForItem item: Any) -> NSTintConfiguration? { - NSTintConfiguration(fixedColor: NSColor(name: nil, dynamicProvider: { appearance in - switch appearance.name { - case .darkAqua, .vibrantDark: - .white - case .aqua, .vibrantLight: - .black - default: - .black - } - })) - } } diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift index f681705351..a29fd310ae 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift @@ -77,6 +77,7 @@ final class ProjectNavigatorViewController: NSViewController { self.view = scrollView self.outlineView = ProjectNavigatorNSOutlineView() + self.outlineView.style = .inset self.outlineView.dataSource = self self.outlineView.delegate = self self.outlineView.autosaveExpandedItems = true diff --git a/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift b/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift index 2537f02d93..12e15850cf 100644 --- a/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift +++ b/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift @@ -50,6 +50,7 @@ struct NavigatorAreaView: View { viewModel.selectedTab?.bottomView(workspace: workspace) } ) + .listStyle(.inset) .environmentObject(workspace) .accessibilityElement(children: .contain) .accessibilityLabel("navigator") From 142b71896382f01acd8229d1144a3fa7c9b369b6 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu, 4 Sep 2025 15:56:25 -0500 Subject: [PATCH 16/18] Adjust Clipping and overflow --- .../WorkspacePanel/WorkspacePanelView.swift | 3 ++ .../ProjectNavigatorViewController.swift | 54 +++++++++++-------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift index 2855dfd01e..2123551083 100644 --- a/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift +++ b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift @@ -114,6 +114,9 @@ struct WorkspacePanelView Date: Mon, 8 Sep 2025 13:29:27 -0500 Subject: [PATCH 17/18] Adjust Nav Filter View Color & Border Width --- .../Views/NavigatorFilterView.swift | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/CodeEdit/Features/NavigatorArea/Views/NavigatorFilterView.swift b/CodeEdit/Features/NavigatorArea/Views/NavigatorFilterView.swift index 05d36fd8c8..4fb6721429 100644 --- a/CodeEdit/Features/NavigatorArea/Views/NavigatorFilterView.swift +++ b/CodeEdit/Features/NavigatorArea/Views/NavigatorFilterView.swift @@ -26,6 +26,20 @@ struct NavigatorFilterView< let leadingAccessories: LeadingAccessories let trailingAccessories: TrailingAccessories + /// Indicates that the filter view should have more emphasis, when it's focused or has a value. + private var shouldEmphasize: Bool { + isFocused || !text.isEmpty || hasValue + } + + /// The border width to use, changes based on macOS version. + private var strokeWidth: CGFloat { + if #available(macOS 26, *) { + 1.0 + } else { + 1.25 + } + } + init( text: Binding, hasValue: (() -> Bool)? = nil, @@ -96,7 +110,7 @@ struct NavigatorFilterView< ) .overlay( Capsule() - .stroke(isFocused || !text.isEmpty || hasValue ? .tertiary : .quaternary, lineWidth: 1.25) + .stroke(shouldEmphasize ? .tertiary : .quaternary, lineWidth: strokeWidth) .clipShape(Capsule()) .disabled(true) .edgesIgnoringSafeArea(.all) @@ -112,13 +126,15 @@ struct NavigatorFilterView< _ isFocused: Bool = false ) -> some View { if self.controlActive != .inactive || !text.isEmpty || hasValue { - if isFocused || !text.isEmpty || hasValue { + if shouldEmphasize { Color(.textBackgroundColor) } else { if colorScheme == .light { Color.black.opacity(0.06) - } else { + } else if #unavailable(macOS 26) { Color.white.opacity(0.24) + } else { + Color.white.opacity(0.09) } } } else { From b6acef6cced1832b5738c7cd29688fe2d7d9845c Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:42:09 -0500 Subject: [PATCH 18/18] (temp) Move Utility Area Tabs to StatusBar --- .../StatusBar/Views/StatusBarView.swift | 14 +++++++++++ .../UtilityArea/Views/UtilityAreaView.swift | 23 ++++++++++--------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift index ba736bebb7..82154bbe8f 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift @@ -22,6 +22,8 @@ struct StatusBarView: View { @Environment(\.controlActiveState) private var controlActive + @EnvironmentObject private var utilityAreaViewModel: UtilityAreaViewModel + static var height: CGFloat { if #available(macOS 26, *) { 37.0 @@ -48,6 +50,18 @@ struct StatusBarView: View { /// The actual status bar var body: some View { HStack(alignment: .center, spacing: 10) { + ForEach(utilityAreaViewModel.tabItems) { tab in + Button { + utilityAreaViewModel.selectedTab = tab + } label: { + Image(systemName: tab.systemImage) + .foregroundStyle(Color( + utilityAreaViewModel.selectedTab == tab ? .controlAccentColor : .secondaryLabelColor + )) + } + .buttonStyle(.icon) + .help(tab.title) + } // StatusBarBreakpointButton() // StatusBarDivider() Spacer() diff --git a/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift b/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift index af54f99ef6..92c4188c41 100644 --- a/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift +++ b/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift @@ -11,16 +11,17 @@ struct UtilityAreaView: View { @EnvironmentObject private var utilityAreaViewModel: UtilityAreaViewModel var body: some View { - WorkspacePanelView( - viewModel: utilityAreaViewModel, - selectedTab: $utilityAreaViewModel.selectedTab, - tabItems: $utilityAreaViewModel.tabItems, - sidebarPosition: .side, - darkDivider: true, - padSideItemVertically: true - ) - .accessibilityElement(children: .contain) - .accessibilityLabel("Utility Area") - .accessibilityIdentifier("UtilityArea") +// WorkspacePanelView( +// viewModel: utilityAreaViewModel, +// selectedTab: $utilityAreaViewModel.selectedTab, +// tabItems: $utilityAreaViewModel.tabItems, +// sidebarPosition: .side, +// darkDivider: true, +// padSideItemVertically: true +// ) + utilityAreaViewModel.selectedTab + .accessibilityElement(children: .contain) + .accessibilityLabel("Utility Area") + .accessibilityIdentifier("UtilityArea") } }