diff --git a/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift b/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift new file mode 100644 index 0000000000..8532ef7fa7 --- /dev/null +++ b/CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift @@ -0,0 +1,37 @@ +// +// GlassEffectView.swift +// CodeEdit +// +// Created by Khan Winter on 9/2/25. +// + +import SwiftUI +import AppKit + +struct GlassEffectView: NSViewRepresentable { + var tintColor: NSColor? + + init(tintColor: NSColor? = nil) { + self.tintColor = tintColor + } + + func makeNSView(context: Context) -> NSView { +#if compiler(>=6.2) + if #available(macOS 26, *) { + let view = NSGlassEffectView() + view.cornerRadius = 0 + view.tintColor = tintColor + return view + } +#endif + return NSView() + } + + func updateNSView(_ nsView: NSView, context: Context) { +#if compiler(>=6.2) + if #available(macOS 26, *), let view = nsView as? NSGlassEffectView { + view.tintColor = tintColor + } +#endif + } +} diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift index 5330a85dc8..91c9ccd514 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift @@ -29,8 +29,11 @@ struct EditorTabBackground: View { ZStack { if isActive { // Content background (visible if active) - EffectView(.contentBackground) - .opacity(isActive ? 1 : 0) + if #available(macOS 26, *) { + GlassEffectView() + } else { + EffectView(.contentBackground) + } // Accent color (visible if active) Color(.controlAccentColor) diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift index 98c10258cd..132664b0f0 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift @@ -42,7 +42,11 @@ struct EditorTabCloseButton: View { .frame(width: buttonSize, height: buttonSize) .background(backgroundColor) .foregroundColor(isPressingClose ? .primary : .secondary) - .clipShape(RoundedRectangle(cornerRadius: 2)) + .if(.tahoe) { + $0.clipShape(Circle()) + } else: { + $0.clipShape(RoundedRectangle(cornerRadius: 2)) + } .contentShape(Rectangle()) .gesture( DragGesture(minimumDistance: 0) diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift index 073ceb757e..006716749e 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift @@ -122,10 +122,11 @@ struct EditorTabView: View { @ViewBuilder var content: some View { HStack(spacing: 0.0) { - EditorTabDivider() - .opacity( - (isActive || inHoldingState) ? 0.0 : 1.0 - ) + + if #unavailable(macOS 26) { + EditorTabDivider() + .opacity((isActive || inHoldingState) ? 0.0 : 1.0) + } // Tab content (icon and text). HStack(alignment: .center, spacing: 3) { Image(nsImage: tabFile.nsIcon) @@ -165,14 +166,19 @@ struct EditorTabView: View { } .frame(maxWidth: .infinity, alignment: .leading) } + .if(.tahoe) { + $0.padding(.horizontal, 1.5) + } .opacity( // Inactive states for tab bar item content. activeState != .inactive ? 1.0 : isActive ? 0.6 : 0.4 ) - EditorTabDivider() - .opacity((isActive || inHoldingState) ? 0.0 : 1.0) + if #unavailable(macOS 26) { + EditorTabDivider() + .opacity((isActive || inHoldingState) ? 0.0 : 1.0) + } } .foregroundColor( isActive && isActiveEditor @@ -220,6 +226,11 @@ struct EditorTabView: View { EditorTabBackground(isActive: isActive, isPressing: isPressing, isDragging: isDragging) .animation(.easeInOut(duration: 0.08), value: isHovering) } + .if(.tahoe) { + if #available(macOS 26, *) { + $0.clipShape(Capsule()).clipped().containerShape(Capsule()) + } + } // TODO: Enable the following code snippet when dragging-out behavior should be allowed. // Since we didn't handle the drop-outside event, dragging-out is disabled for now. // .onDrag({ diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift index 1bcc3639c9..2b2d5acbc2 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift @@ -260,6 +260,12 @@ struct EditorTabs: View { ) { ForEach(Array(openedTabs.enumerated()), id: \.element) { index, id in if let item = editor.tabs.first(where: { $0.file.id == id }) { + if index != 0 + && editor.selectedTab?.file.id != id + && editor.selectedTab?.file.id != openedTabs[index - 1] { + EditorTabDivider() + } + EditorTabView( file: item.file, index: index, @@ -293,6 +299,12 @@ struct EditorTabs: View { tabWidth: $tabWidth ) ) + + if index < openedTabs.count - 1 + && editor.selectedTab?.file.id != id + && editor.selectedTab?.file.id != openedTabs[index + 1] { + EditorTabDivider() + } } } } @@ -357,6 +369,19 @@ struct EditorTabs: View { ) .opacity((scrollTrailingOffset ?? 0) <= 0 ? 0 : 1) } + .if(.tahoe) { + if #available(macOS 26.0, *) { + // Unfortunate triple if here due to needing to compile on + // earlier Xcodes. +#if compiler(>=6.2) + $0.background(GlassEffectView(tintColor: .tertiarySystemFill)) + .clipShape(Capsule()) + .clipped() +#else + $0 +#endif + } + } } } diff --git a/CodeEdit/Features/Editor/Views/EditorAreaView.swift b/CodeEdit/Features/Editor/Views/EditorAreaView.swift index 4795d7119a..00732c3058 100644 --- a/CodeEdit/Features/Editor/Views/EditorAreaView.swift +++ b/CodeEdit/Features/Editor/Views/EditorAreaView.swift @@ -30,6 +30,9 @@ struct EditorAreaView: View { @Environment(\.window.value) private var window: NSWindow? + @Environment(\.isEditorLayoutAtEdge) + private var isAtEdge + init(editor: Editor, focus: FocusState.Binding) { self.editor = editor self._focus = focus @@ -101,6 +104,10 @@ struct EditorAreaView: View { } VStack(spacing: 0) { + if isAtEdge != .top, #available(macOS 26, *) { + Spacer().frame(height: 4) + } + if topSafeArea > 0 { Rectangle() .fill(.clear) @@ -111,7 +118,9 @@ struct EditorAreaView: View { EditorTabBarView(hasTopInsets: topSafeArea > 0, codeFile: fileBinding) .id("TabBarView" + editor.id.uuidString) .environmentObject(editor) - Divider() + if #unavailable(macOS 26) { + Divider() + } } if showEditorJumpBar { EditorJumpBarView( @@ -125,6 +134,12 @@ struct EditorAreaView: View { } .environmentObject(editor) .padding(.top, shouldShowTabBar ? -1 : 0) + if #unavailable(macOS 26) { + Divider() + } + } + // On Tahoe we only show one divider + if #available(macOS 26, *), shouldShowTabBar || showEditorJumpBar { Divider() } } @@ -132,6 +147,34 @@ struct EditorAreaView: View { .if(.tahoe) { // FB20047271: Glass toolbar effect ignores floating scroll view views. // https://openradar.appspot.com/radar?id=EhAKBVJhZGFyEICAgKbGmesJ + + // FB20191516: Can't disable backgrounded liquid glass tint + // https://openradar.appspot.com/radar?id=EhAKBVJhZGFyEICAgLqTk-4J + // Tracking Issue: #2191 + // Add this to the top: + // ``` + // @AppSettings(\.theme.useThemeBackground) + // var useThemeBackground + // + // private var backgroundColor: NSColor { + // let fallback = NSColor.textBackgroundColor + // return if useThemeBackground { + // ThemeModel.shared.selectedTheme?.editor.background.nsColor ?? fallback + // } else { + // fallback + // } + // } + // ``` + // And use this: + // ``` + // $0.background( + // Rectangle().fill(.clear) + // .glassEffect(.regular.tint(Color(backgroundColor)) + // .ignoresSafeArea(.all) + // ) + // ``` + // When we can figure out how to disable the 'not focused' glass effect. + $0.background(EffectView(.headerView).ignoresSafeArea(.all)) } else: { $0.background(EffectView(.headerView)) diff --git a/CodeEdit/Features/Editor/Views/EditorLayoutView.swift b/CodeEdit/Features/Editor/Views/EditorLayoutView.swift index a1637db30b..aa7fa3252c 100644 --- a/CodeEdit/Features/Editor/Views/EditorLayoutView.swift +++ b/CodeEdit/Features/Editor/Views/EditorLayoutView.swift @@ -15,7 +15,7 @@ struct EditorLayoutView: View { @Environment(\.window.value) private var window - @Environment(\.isAtEdge) + @Environment(\.isEditorLayoutAtEdge) private var isAtEdge var toolbarHeight: CGFloat { @@ -63,7 +63,7 @@ struct EditorLayoutView: View { var splitView: some View { ForEach(Array(data.editorLayouts.enumerated()), id: \.offset) { index, item in EditorLayoutView(layout: item, focus: $focus) - .transformEnvironment(\.isAtEdge) { belowToolbar in + .transformEnvironment(\.isEditorLayoutAtEdge) { belowToolbar in calcIsAtEdge(current: &belowToolbar, index: index) } .environment(\.splitEditor) { [weak data] edge, newEditor in @@ -87,12 +87,12 @@ struct EditorLayoutView: View { } } -private struct BelowToolbarEnvironmentKey: EnvironmentKey { +struct BelowToolbarEnvironmentKey: EnvironmentKey { static var defaultValue: VerticalEdge.Set = .all } extension EnvironmentValues { - fileprivate var isAtEdge: BelowToolbarEnvironmentKey.Value { + var isEditorLayoutAtEdge: BelowToolbarEnvironmentKey.Value { get { self[BelowToolbarEnvironmentKey.self] } set { self[BelowToolbarEnvironmentKey.self] = newValue } } diff --git a/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift b/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift index 12aa37f506..d0094d3aad 100644 --- a/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift +++ b/CodeEdit/Features/SplitView/Views/SplitViewControllerView.swift @@ -96,6 +96,16 @@ final class SplitViewController: NSSplitViewController { override var dividerThickness: CGFloat { customDividerStyle.customThickness ?? super.dividerThickness } + + override func drawDivider(in rect: NSRect) { + let safeRect = NSRect( + x: rect.origin.x, + y: max(rect.origin.y, safeAreaRect.origin.y), + width: isVertical ? dividerThickness : rect.width, + height: isVertical ? safeAreaRect.height : dividerThickness + ) + super.drawDivider(in: safeRect) + } } var items: [SplitViewItem] = []