Skip to content

Commit a822c40

Browse files
committed
New Panel Tab Style
1 parent fd9ed44 commit a822c40

9 files changed

Lines changed: 411 additions & 148 deletions

File tree

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//
2+
// CapsuleButtonStyle.swift
3+
// CodeEdit
4+
//
5+
// Created by Khan Winter on 9/3/25.
6+
//
7+
8+
import SwiftUI
9+
10+
struct CapsuleButtonStyle: ButtonStyle {
11+
var isActive: Bool?
12+
var font: Font?
13+
var width: CGFloat?
14+
var height: CGFloat?
15+
16+
init(isActive: Bool? = nil, font: Font? = nil, size: CGFloat? = nil) {
17+
self.isActive = isActive
18+
self.font = font
19+
self.width = size
20+
self.height = size
21+
}
22+
23+
init(isActive: Bool? = nil, font: Font? = nil, width: CGFloat?, height: CGFloat?) {
24+
self.isActive = isActive
25+
self.font = font
26+
self.width = width
27+
self.height = height
28+
}
29+
30+
func makeBody(configuration: ButtonStyle.Configuration) -> some View {
31+
CapsuleButton(
32+
configuration: configuration,
33+
isActive: isActive,
34+
font: font,
35+
width: width,
36+
height: height
37+
)
38+
}
39+
40+
struct CapsuleButton: View {
41+
@Environment(\.controlActiveState)
42+
private var controlActiveState
43+
@Environment(\.isEnabled)
44+
private var isEnabled: Bool
45+
@Environment(\.colorScheme)
46+
private var colorScheme
47+
48+
let configuration: ButtonStyle.Configuration
49+
var isActive: Bool
50+
var font: Font
51+
var width: CGFloat?
52+
var height: CGFloat?
53+
54+
init(
55+
configuration: ButtonStyle.Configuration,
56+
isActive: Bool?,
57+
font: Font?,
58+
width: CGFloat?,
59+
height: CGFloat?
60+
) {
61+
self.configuration = configuration
62+
self.isActive = isActive ?? false
63+
self.font = font ?? Font.system(size: 14.5, weight: .regular, design: .default)
64+
self.width = width
65+
self.height = height
66+
}
67+
68+
var body: some View {
69+
configuration.label
70+
.font(font)
71+
.foregroundStyle(
72+
isActive
73+
? Color(.white)
74+
: Color(.labelColor)
75+
)
76+
.frame(width: width, height: height, alignment: .center)
77+
.contentShape(Capsule())
78+
.brightness(
79+
configuration.isPressed
80+
? colorScheme == .dark
81+
? 0.5
82+
: isActive ? -0.25 : -0.75
83+
: 0
84+
)
85+
.opacity(controlActiveState == .inactive ? 0.5 : 1)
86+
.background(Capsule().fill(isActive ? Color.accentColor : .clear))
87+
}
88+
}
89+
}
90+
91+
extension ButtonStyle where Self == CapsuleButtonStyle {
92+
static func capsuleIcon(
93+
isActive: Bool? = false,
94+
font: Font? = Font.system(size: 14.5, weight: .regular, design: .default),
95+
size: CGFloat? = 24
96+
) -> CapsuleButtonStyle {
97+
return CapsuleButtonStyle(isActive: isActive, font: font, size: size)
98+
}
99+
static func capsuleIcon(
100+
isActive: Bool? = false,
101+
font: Font? = Font.system(size: 14.5, weight: .regular, design: .default),
102+
size: CGSize? = CGSize(width: 24, height: 24)
103+
) -> CapsuleButtonStyle {
104+
return CapsuleButtonStyle(isActive: isActive, font: font, width: size?.width, height: size?.height)
105+
}
106+
static func capsuleIcon(
107+
isActive: Bool? = false,
108+
font: Font? = Font.system(size: 14.5, weight: .regular, design: .default),
109+
width: CGFloat? = nil,
110+
height: CGFloat? = nil
111+
) -> CapsuleButtonStyle {
112+
return CapsuleButtonStyle(isActive: isActive, font: font, width: width, height: height)
113+
}
114+
static func capsuleIcon(
115+
isActive: Bool? = false,
116+
font: Font? = Font.system(size: 14.5, weight: .regular, design: .default)
117+
) -> CapsuleButtonStyle {
118+
return CapsuleButtonStyle(isActive: isActive, font: font)
119+
}
120+
static var capsuleIcon: CapsuleButtonStyle { .init() }
121+
}

CodeEdit/Features/CodeEditUI/Styles/IconButtonStyle.swift

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,66 +10,59 @@ import SwiftUI
1010
struct IconButtonStyle: ButtonStyle {
1111
var isActive: Bool?
1212
var font: Font?
13-
var size: CGSize?
13+
var width: CGFloat?
14+
var height: CGFloat?
1415

1516
init(isActive: Bool? = nil, font: Font? = nil, size: CGFloat? = nil) {
1617
self.isActive = isActive
1718
self.font = font
18-
self.size = size == nil ? nil : CGSize(width: size ?? 0, height: size ?? 0)
19+
self.width = size
20+
self.height = size
1921
}
2022

21-
init(isActive: Bool? = nil, font: Font? = nil, size: CGSize? = nil) {
23+
init(isActive: Bool? = nil, font: Font? = nil, width: CGFloat?, height: CGFloat?) {
2224
self.isActive = isActive
2325
self.font = font
24-
self.size = size
25-
}
26-
27-
init(isActive: Bool? = nil, font: Font? = nil) {
28-
self.isActive = isActive
29-
self.font = font
30-
self.size = nil
26+
self.width = width
27+
self.height = height
3128
}
3229

3330
func makeBody(configuration: ButtonStyle.Configuration) -> some View {
3431
IconButton(
3532
configuration: configuration,
3633
isActive: isActive,
3734
font: font,
38-
size: size
35+
width: width,
36+
height: height
3937
)
4038
}
4139

4240
struct IconButton: View {
43-
let configuration: ButtonStyle.Configuration
44-
var isActive: Bool
45-
var font: Font
46-
var size: CGSize?
4741
@Environment(\.controlActiveState)
4842
private var controlActiveState
4943
@Environment(\.isEnabled)
5044
private var isEnabled: Bool
5145
@Environment(\.colorScheme)
5246
private var colorScheme
5347

54-
init(configuration: ButtonStyle.Configuration, isActive: Bool?, font: Font?, size: CGFloat?) {
55-
self.configuration = configuration
56-
self.isActive = isActive ?? false
57-
self.font = font ?? Font.system(size: 14.5, weight: .regular, design: .default)
58-
self.size = size == nil ? nil : CGSize(width: size ?? 0, height: size ?? 0)
59-
}
60-
61-
init(configuration: ButtonStyle.Configuration, isActive: Bool?, font: Font?, size: CGSize?) {
62-
self.configuration = configuration
63-
self.isActive = isActive ?? false
64-
self.font = font ?? Font.system(size: 14.5, weight: .regular, design: .default)
65-
self.size = size ?? nil
66-
}
48+
let configuration: ButtonStyle.Configuration
49+
var isActive: Bool
50+
var font: Font
51+
var width: CGFloat?
52+
var height: CGFloat?
6753

68-
init(configuration: ButtonStyle.Configuration, isActive: Bool?, font: Font?) {
54+
init(
55+
configuration: ButtonStyle.Configuration,
56+
isActive: Bool?,
57+
font: Font?,
58+
width: CGFloat?,
59+
height: CGFloat?
60+
) {
6961
self.configuration = configuration
7062
self.isActive = isActive ?? false
7163
self.font = font ?? Font.system(size: 14.5, weight: .regular, design: .default)
72-
self.size = nil
64+
self.width = width
65+
self.height = height
7366
}
7467

7568
var body: some View {
@@ -80,7 +73,7 @@ struct IconButtonStyle: ButtonStyle {
8073
? Color(.controlAccentColor)
8174
: Color(.secondaryLabelColor)
8275
)
83-
.frame(width: size?.width, height: size?.height, alignment: .center)
76+
.frame(width: width, height: height, alignment: .center)
8477
.contentShape(Rectangle())
8578
.brightness(
8679
configuration.isPressed
@@ -108,7 +101,15 @@ extension ButtonStyle where Self == IconButtonStyle {
108101
font: Font? = Font.system(size: 14.5, weight: .regular, design: .default),
109102
size: CGSize? = CGSize(width: 24, height: 24)
110103
) -> IconButtonStyle {
111-
return IconButtonStyle(isActive: isActive, font: font, size: size)
104+
return IconButtonStyle(isActive: isActive, font: font, width: size?.width, height: size?.height)
105+
}
106+
static func icon(
107+
isActive: Bool? = false,
108+
font: Font? = Font.system(size: 14.5, weight: .regular, design: .default),
109+
width: CGFloat? = nil,
110+
height: CGFloat? = nil
111+
) -> IconButtonStyle {
112+
return IconButtonStyle(isActive: isActive, font: font, width: width, height: height)
112113
}
113114
static func icon(
114115
isActive: Bool? = false,

CodeEdit/Features/CodeEditUI/Views/GlassEffectView.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,26 @@ import SwiftUI
99
import AppKit
1010

1111
struct GlassEffectView: NSViewRepresentable {
12+
var tintColor: NSColor?
13+
14+
init(tintColor: NSColor? = nil) {
15+
self.tintColor = tintColor
16+
}
17+
1218
func makeNSView(context: Context) -> NSView {
1319
if #available(macOS 26, *) {
1420
let view = NSGlassEffectView()
1521
view.cornerRadius = 0
16-
view.tintColor = .clear
22+
view.tintColor = tintColor
1723
return view
1824
} else {
1925
return NSView()
2026
}
2127
}
2228

23-
func updateNSView(_ nsView: NSView, context: Context) { }
29+
func updateNSView(_ nsView: NSView, context: Context) {
30+
if #available(macOS 26, *), let view = nsView as? NSGlassEffectView {
31+
view.tintColor = tintColor
32+
}
33+
}
2434
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//
2+
// IconButton.swift
3+
// CodeEdit
4+
//
5+
// Created by Khan Winter on 9/3/25.
6+
//
7+
8+
import SwiftUI
9+
10+
extension WorkspacePanelTabBar {
11+
struct IconButton: View {
12+
let tab: Tab
13+
let scale: Image.Scale = .medium
14+
let size: CGSize
15+
16+
var position: SettingsData.SidebarTabBarPosition
17+
18+
@Binding var selection: Tab?
19+
20+
var body: some View {
21+
Button {
22+
selection = tab
23+
} label: {
24+
getSafeImage(named: tab.systemImage, accessibilityDescription: tab.title)
25+
.font(.system(size: 13))
26+
.symbolVariant(tab == selection ? .fill : .none)
27+
.help(tab.title)
28+
.frame(maxWidth: .infinity)
29+
}
30+
.if(.tahoe) {
31+
$0.buttonStyle(capsuleButtonStyle)
32+
} else: {
33+
$0.buttonStyle(buttonStyle)
34+
}
35+
.focusable(false)
36+
.accessibilityIdentifier("WorkspacePanelTab-\(tab.title)")
37+
.accessibilityLabel(tab.title)
38+
}
39+
40+
private func getSafeImage(named: String, accessibilityDescription: String?) -> Image {
41+
// We still use the NSImage init to check if a symbol with the name exists.
42+
if NSImage(systemSymbolName: named, accessibilityDescription: nil) != nil {
43+
return Image(systemName: named)
44+
} else {
45+
return Image(symbol: named)
46+
}
47+
}
48+
49+
private var capsuleButtonStyle: CapsuleButtonStyle {
50+
if #available(macOS 26, *) {
51+
if position == .side {
52+
.capsuleIcon(
53+
isActive: tab == selection,
54+
size: CGSize(width: 26, height: 40)
55+
)
56+
} else {
57+
.capsuleIcon(
58+
isActive: tab == selection,
59+
height: 28
60+
)
61+
}
62+
} else {
63+
fatalError("Used on non tahoe platform")
64+
}
65+
}
66+
67+
private var buttonStyle: IconButtonStyle {
68+
.icon(
69+
isActive: tab == selection,
70+
size: CGSize(
71+
width: position == .side ? 24 : 42,
72+
height: position == .side ? 40 : size.height
73+
)
74+
)
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)