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/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/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/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar+IconButton.swift b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar+IconButton.swift new file mode 100644 index 0000000000..1ad6b07ccc --- /dev/null +++ b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar+IconButton.swift @@ -0,0 +1,85 @@ +// +// 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 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(symbolVariant) + .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 72% rename from CodeEdit/Features/CodeEditUI/Views/WorkspacePanelTabBar.swift rename to CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelTabBar.swift index d3ef997a01..3b64e377e6 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,50 +83,75 @@ 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)) } - private func makeAreaTabDragGesture(tab: Tab) -> some Gesture { + @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) + } + } + } + } +} + +// MARK: - Drag Gesture + +private extension WorkspacePanelTabBar { + func makeAreaTabDragGesture(tab: Tab) -> some Gesture { DragGesture(minimumDistance: 2, coordinateSpace: .global) .onChanged({ value in if draggingTab != tab { @@ -173,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 @@ -186,7 +220,7 @@ struct WorkspacePanelTabBar: View { } // swiftlint:disable:next function_parameter_count - private func swapTab( + func swapTab( tab: Tab, currentIndex: Int, currentLocation: CGFloat, @@ -237,7 +271,7 @@ struct WorkspacePanelTabBar: View { } } - private func isWithinPrevTopBounds( + func isWithinPrevTopBounds( _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat ) -> Bool { return curLocation < max( @@ -246,7 +280,7 @@ struct WorkspacePanelTabBar: View { ) } - private func isWithinNextTopBounds( + func isWithinNextTopBounds( _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat, _ curWidth: CGFloat ) -> Bool { return curLocation > min( @@ -255,7 +289,7 @@ struct WorkspacePanelTabBar: View { ) } - private func isWithinPrevBottomBounds( + func isWithinPrevBottomBounds( _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat ) -> Bool { return curLocation < max( @@ -264,7 +298,7 @@ struct WorkspacePanelTabBar: View { ) } - private func isWithinNextBottomBounds( + func isWithinNextBottomBounds( _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat, _ curWidth: CGFloat ) -> Bool { return curLocation > min( @@ -273,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) @@ -289,13 +323,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..2123551083 --- /dev/null +++ b/CodeEdit/Features/CodeEditUI/Views/WorkspacePanel/WorkspacePanelView.swift @@ -0,0 +1,140 @@ +// +// 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 + let sidebarPadding: () -> (Edge.Set, CGFloat) + let bottomAccessory: BottomAccessory + + init( + viewModel: ViewModel, + selectedTab: Binding, + tabItems: Binding<[Tab]>, + sidebarPosition: SettingsData.SidebarTabBarPosition, + darkDivider: Bool = false, + padSideItemVertically: Bool = false, + sideOnTrailing: Bool = false, + sidebarPadding: @escaping () -> (Edge.Set, CGFloat) = { ([], 0) }, + @ViewBuilder bottomAccessory: () -> BottomAccessory + ) { + 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.sidebarPadding = sidebarPadding + self.bottomAccessory = bottomAccessory() + } + + init( + viewModel: ViewModel, + selectedTab: Binding, + tabItems: Binding<[Tab]>, + sidebarPosition: SettingsData.SidebarTabBarPosition, + darkDivider: Bool = false, + padSideItemVertically: Bool = false, + sidebarPadding: @escaping () -> (Edge.Set, CGFloat) = { ([], 0) }, + 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.sidebarPadding = sidebarPadding + 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") + } + } + .safeAreaInset(edge: .leading, spacing: 0) { + if sidebarPosition == .side && !sideOnTrailing { + sideTabBar.padding(sidebarPadding().0, sidebarPadding().1) + } + } + .safeAreaInset(edge: .trailing, spacing: 0) { + if sidebarPosition == .side && sideOnTrailing { + sideTabBar.padding(sidebarPadding().0, sidebarPadding().1) + } + } + .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() + } + } + .padding(sidebarPadding().0, sidebarPadding().1) + } else if !darkDivider, #unavailable(macOS 26) { + Divider() + } + } + .if(.tahoe) { + $0.clipped() + } + .safeAreaInset(edge: .bottom, spacing: 0) { + if #available(macOS 26, *) { + bottomAccessory + } + } + } + + @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/Documents/Controllers/CodeEditSplitViewController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift index 39735c8de1..82b17afdee 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) @@ -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) } 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/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/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..9f0c90f54f 100644 --- a/CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift +++ b/CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift @@ -60,4 +60,21 @@ 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/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/OutlineView/ProjectNavigatorViewController.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift index f681705351..1cdaabe686 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift @@ -77,29 +77,16 @@ final class ProjectNavigatorViewController: NSViewController { self.view = scrollView self.outlineView = ProjectNavigatorNSOutlineView() - self.outlineView.dataSource = self - self.outlineView.delegate = self - self.outlineView.autosaveExpandedItems = true - self.outlineView.autosaveName = workspace?.workspaceFileManager?.folderUrl.path ?? "" - self.outlineView.headerView = nil - self.outlineView.menu = ProjectNavigatorMenu(self) - self.outlineView.menu?.delegate = self - self.outlineView.doubleAction = #selector(onItemDoubleClicked) - self.outlineView.allowsMultipleSelection = true - - self.outlineView.setAccessibilityIdentifier("ProjectNavigator") - self.outlineView.setAccessibilityLabel("Project Navigator") - - let column = NSTableColumn(identifier: .init(rawValue: "Cell")) - column.title = "Cell" - outlineView.addTableColumn(column) - - outlineView.setDraggingSourceOperationMask(.move, forLocal: false) - outlineView.registerForDraggedTypes([.fileURL]) + configureOutlineView() scrollView.documentView = outlineView scrollView.contentView.automaticallyAdjustsContentInsets = false - scrollView.contentView.contentInsets = .init(top: 10, left: 0, bottom: 0, right: 0) + if #available(macOS 26, *) { + scrollView.clipsToBounds = false + scrollView.contentView.clipsToBounds = false + } else { + scrollView.contentView.contentInsets = .init(top: 10, left: 0, bottom: 0, right: 0) + } scrollView.scrollerStyle = .overlay scrollView.hasVerticalScroller = true scrollView.hasHorizontalScroller = false @@ -129,6 +116,32 @@ final class ProjectNavigatorViewController: NSViewController { ]) } + private func configureOutlineView() { + if #available(macOS 26, *) { + self.outlineView.style = .inset + self.outlineView.clipsToBounds = false + } + self.outlineView.dataSource = self + self.outlineView.delegate = self + self.outlineView.autosaveExpandedItems = true + self.outlineView.autosaveName = workspace?.workspaceFileManager?.folderUrl.path ?? "" + self.outlineView.headerView = nil + self.outlineView.menu = ProjectNavigatorMenu(self) + self.outlineView.menu?.delegate = self + self.outlineView.doubleAction = #selector(onItemDoubleClicked) + self.outlineView.allowsMultipleSelection = true + + self.outlineView.setAccessibilityIdentifier("ProjectNavigator") + self.outlineView.setAccessibilityLabel("Project Navigator") + + let column = NSTableColumn(identifier: .init(rawValue: "Cell")) + column.title = "Cell" + outlineView.addTableColumn(column) + + outlineView.setDraggingSourceOperationMask(.move, forLocal: false) + outlineView.registerForDraggedTypes([.fileURL]) + } + init() { super.init(nibName: nil, bundle: nil) } 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/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/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/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..12e15850cf 100644 --- a/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift +++ b/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift @@ -38,8 +38,19 @@ struct NavigatorAreaView: View { viewModel: viewModel, selectedTab: $viewModel.selectedTab, tabItems: $viewModel.tabItems, - sidebarPosition: sidebarPosition + sidebarPosition: sidebarPosition, + sidebarPadding: { + if sidebarPosition == .side { + return (.trailing, 8) + } + + return ([], 0) + }, + bottomAccessory: { + viewModel.selectedTab?.bottomView(workspace: workspace) + } ) + .listStyle(.inset) .environmentObject(workspace) .accessibilityElement(children: .contain) .accessibilityLabel("navigator") diff --git a/CodeEdit/Features/NavigatorArea/Views/NavigatorFilterView.swift b/CodeEdit/Features/NavigatorArea/Views/NavigatorFilterView.swift new file mode 100644 index 0000000000..4fb6721429 --- /dev/null +++ b/CodeEdit/Features/NavigatorArea/Views/NavigatorFilterView.swift @@ -0,0 +1,157 @@ +// +// 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 + + /// 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, + @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(shouldEmphasize ? .tertiary : .quaternary, lineWidth: strokeWidth) + .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 shouldEmphasize { + Color(.textBackgroundColor) + } else { + if colorScheme == .light { + Color.black.opacity(0.06) + } else if #unavailable(macOS 26) { + Color.white.opacity(0.24) + } else { + Color.white.opacity(0.09) + } + } + } 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)) + } +} diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift index cb73012d8a..82154bbe8f 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift @@ -22,7 +22,23 @@ struct StatusBarView: View { @Environment(\.controlActiveState) private var controlActive - static let height = 28.0 + @EnvironmentObject private var utilityAreaViewModel: UtilityAreaViewModel + + 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 @@ -34,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() @@ -43,8 +71,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