Skip to content

Commit 87a0698

Browse files
authored
Adopt Liquid Glass for the Pesty bar (#14)
* Fix Liquid Glass bar integration * Build with the macOS 26 SDK
1 parent bbce746 commit 87a0698

5 files changed

Lines changed: 58 additions & 8 deletions

File tree

.github/workflows/ci.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ jobs:
1313
steps:
1414
- uses: actions/checkout@v4
1515

16+
- name: Select Xcode 26.3
17+
uses: maxim-lobanov/setup-xcode@v1
18+
with:
19+
xcode-version: '26.3'
20+
1621
- name: Show toolchain
1722
run: swift --version
1823

.github/workflows/release.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ jobs:
2424
steps:
2525
- uses: actions/checkout@v4
2626

27+
- name: Select Xcode 26.3
28+
uses: maxim-lobanov/setup-xcode@v1
29+
with:
30+
xcode-version: '26.3'
31+
2732
- name: Derive version
2833
id: ver
2934
run: echo "version=${{ inputs.version }}" >> "$GITHUB_OUTPUT"

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Thanks for your interest. Pesty is a small, native macOS app with no third-party
55
## Prerequisites
66

77
- macOS 14 (Sonoma) or later
8-
- Xcode 16+ / Swift 6 toolchain
8+
- Xcode 26.3+ / Swift 6 toolchain
99

1010
## Build and run
1111

Sources/Pesty/UI/BarView.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ struct BarView: View {
66

77
var body: some View {
88
ZStack {
9-
VisualEffectView(material: .hudWindow)
10-
Theme.panelTint
9+
panelBackground
1110
}
1211
.overlay(alignment: .top) {
1312
VStack(spacing: 0) {
@@ -19,6 +18,16 @@ struct BarView: View {
1918
.ignoresSafeArea()
2019
}
2120

21+
@ViewBuilder
22+
private var panelBackground: some View {
23+
if #available(macOS 26.0, *) {
24+
Color.clear
25+
} else {
26+
VisualEffectView(material: .hudWindow)
27+
Theme.panelTint
28+
}
29+
}
30+
2231
private var topBar: some View {
2332
HStack(spacing: 14) {
2433
// The two builds sync through different systems: the App Store build uses

Sources/Pesty/UI/BarWindowController.swift

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ final class BarWindowController: NSWindowController, NSWindowDelegate {
4646
private static let showDuration: TimeInterval = 0.22
4747
private static let hideDuration: TimeInterval = 0.16
4848

49+
private static var contentBottomExtension: CGFloat {
50+
if #available(macOS 26.0, *) { return Theme.cornerRadius }
51+
return 0
52+
}
53+
4954
init() {
5055
let panel = BarPanel(
5156
contentRect: NSRect(x: 0, y: 0, width: 800, height: 360),
@@ -63,7 +68,29 @@ final class BarWindowController: NSWindowController, NSWindowDelegate {
6368
// Without this a stray close() would deallocate the panel and leave `window`
6469
// nil, which is another way to never show the bar again.
6570
panel.isReleasedWhenClosed = false
66-
panel.contentView = NSHostingView(rootView: BarView())
71+
let content = NSHostingView(rootView: BarView())
72+
if #available(macOS 26.0, *) {
73+
let glassContent = NSView()
74+
content.translatesAutoresizingMaskIntoConstraints = false
75+
glassContent.addSubview(content)
76+
NSLayoutConstraint.activate([
77+
content.leadingAnchor.constraint(equalTo: glassContent.leadingAnchor),
78+
content.trailingAnchor.constraint(equalTo: glassContent.trailingAnchor),
79+
content.topAnchor.constraint(equalTo: glassContent.topAnchor),
80+
content.bottomAnchor.constraint(equalTo: glassContent.bottomAnchor,
81+
constant: -Theme.cornerRadius),
82+
])
83+
84+
let glass = NSGlassEffectView()
85+
glass.contentView = glassContent
86+
glass.cornerRadius = Theme.cornerRadius
87+
glass.tintColor = NSColor.black.withAlphaComponent(0.12)
88+
glass.style = .regular
89+
panel.contentView = glass
90+
panel.hasShadow = false
91+
} else {
92+
panel.contentView = content
93+
}
6794
super.init(window: panel)
6895
panel.delegate = self
6996
}
@@ -128,16 +155,19 @@ final class BarWindowController: NSWindowController, NSWindowDelegate {
128155
// flew across the bezel. A view clipped to the window can never escape it.
129156
panel.setFrame(onScreen, display: false)
130157
guard let content = panel.contentView else { return }
158+
let bottomExtension = Self.contentBottomExtension
159+
let contentHeight = height + bottomExtension
131160
content.autoresizingMask = []
132-
content.frame = NSRect(x: 0, y: -height, width: onScreen.width, height: height)
161+
content.frame = NSRect(x: 0, y: -contentHeight,
162+
width: onScreen.width, height: contentHeight)
133163

134164
let token = beginTransition()
135165
phase = .showing(token)
136166

137167
NSApp.activate(ignoringOtherApps: true)
138168
panel.makeKeyAndOrderFront(nil)
139169

140-
let finish = { [weak self] in
170+
let finish: @MainActor @Sendable () -> Void = { [weak self] in
141171
guard let self else { return }
142172
self.settle(token) {
143173
self.phase = .shown
@@ -152,7 +182,8 @@ final class BarWindowController: NSWindowController, NSWindowDelegate {
152182
NSAnimationContext.runAnimationGroup({ ctx in
153183
ctx.duration = Self.showDuration
154184
ctx.timingFunction = CAMediaTimingFunction(name: .easeOut)
155-
content.animator().frame = NSRect(x: 0, y: 0, width: onScreen.width, height: height)
185+
content.animator().frame = NSRect(x: 0, y: -bottomExtension,
186+
width: onScreen.width, height: contentHeight)
156187
}, completionHandler: { DispatchQueue.main.async(execute: finish) })
157188
DispatchQueue.main.asyncAfter(deadline: .now() + Self.showDuration + 0.05, execute: finish)
158189
}
@@ -166,7 +197,7 @@ final class BarWindowController: NSWindowController, NSWindowDelegate {
166197
let down = NSRect(x: 0, y: -content.frame.height,
167198
width: content.frame.width, height: content.frame.height)
168199

169-
let finish = { [weak self] in
200+
let finish: @MainActor @Sendable () -> Void = { [weak self] in
170201
guard let self else { return }
171202
self.settle(token) {
172203
panel.orderOut(nil)

0 commit comments

Comments
 (0)