From e553ccf66741c3fae101ef07fa48897de1d3635c Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Sat, 21 Feb 2026 09:49:33 +0530 Subject: [PATCH] Add liquid glass to osd --- DynamicIsland/Localizable.xcstrings | 6 + .../components/OSD/CustomOSDView.swift | 24 +++- .../components/OSD/VerticalHUDView.swift | 44 +++++- .../components/Settings/SettingsView.swift | 130 +++++++++++++++++- DynamicIsland/models/Constants.swift | 17 +++ 5 files changed, 208 insertions(+), 13 deletions(-) diff --git a/DynamicIsland/Localizable.xcstrings b/DynamicIsland/Localizable.xcstrings index c1acf0f..6966391 100644 --- a/DynamicIsland/Localizable.xcstrings +++ b/DynamicIsland/Localizable.xcstrings @@ -7617,6 +7617,9 @@ } } } + }, + "Custom Liquid is available on macOS 26 or later." : { + }, "Custom Liquid settings require the Liquid Glass material." : { "localizations" : { @@ -7627,6 +7630,9 @@ } } } + }, + "Custom liquid variant" : { + }, "Custom music live activity animation" : { "localizations" : { diff --git a/DynamicIsland/components/OSD/CustomOSDView.swift b/DynamicIsland/components/OSD/CustomOSDView.swift index 67d19ac..645ae69 100644 --- a/DynamicIsland/components/OSD/CustomOSDView.swift +++ b/DynamicIsland/components/OSD/CustomOSDView.swift @@ -28,6 +28,8 @@ struct CustomOSDView: View { @Binding var icon: String @Default(.osdMaterial) var osdMaterial + @Default(.osdLiquidGlassCustomizationMode) var osdLiquidGlassCustomizationMode + @Default(.osdLiquidGlassVariant) var osdLiquidGlassVariant @Default(.osdIconColorStyle) var osdIconColorStyle @Environment(\.colorScheme) var colorScheme @@ -160,11 +162,23 @@ struct CustomOSDView: View { .fill(.ultraThinMaterial) case .liquid: if #available(macOS 26.0, *) { - RoundedRectangle(cornerRadius: 18, style: .continuous) - .glassEffect( - .clear.interactive(), - in: .rect(cornerRadius: 18) - ) + if osdLiquidGlassCustomizationMode == .customLiquid { + TimelineView(.periodic(from: .now, by: 1.0 / 30.0)) { context in + LiquidGlassBackground( + variant: osdLiquidGlassVariant, + cornerRadius: 18, + trigger: context.date.timeIntervalSinceReferenceDate + ) { + Color.white.opacity(0.04) + } + } + } else { + RoundedRectangle(cornerRadius: 18, style: .continuous) + .glassEffect( + .clear.interactive(), + in: .rect(cornerRadius: 18) + ) + } } else { RoundedRectangle(cornerRadius: 18, style: .continuous) .fill(.ultraThinMaterial) diff --git a/DynamicIsland/components/OSD/VerticalHUDView.swift b/DynamicIsland/components/OSD/VerticalHUDView.swift index f9eacff..eaa2e3b 100644 --- a/DynamicIsland/components/OSD/VerticalHUDView.swift +++ b/DynamicIsland/components/OSD/VerticalHUDView.swift @@ -46,6 +46,9 @@ struct VerticalHUDView: View { @Default(.verticalHUDHeight) var hudHeight @Default(.verticalHUDWidth) var hudWidth @Default(.verticalHUDUseAccentColor) var useAccentColor + @Default(.verticalHUDMaterial) var verticalHUDMaterial + @Default(.verticalHUDLiquidGlassCustomizationMode) var verticalHUDLiquidGlassCustomizationMode + @Default(.verticalHUDLiquidGlassVariant) var verticalHUDLiquidGlassVariant @Default(.verticalHUDInteractive) var isInteractive @Environment(\.colorScheme) var colorScheme @@ -68,8 +71,8 @@ struct VerticalHUDView: View { // The Actual HUD Pill ZStack { // Background - Capsule() - .fill(.ultraThinMaterial) + verticalBackground + .clipShape(Capsule()) .overlay { Capsule() .stroke(.white.opacity(0.1), lineWidth: 1) @@ -253,5 +256,42 @@ struct VerticalHUDView: View { } return .secondary } + + @ViewBuilder + private var verticalBackground: some View { + switch verticalHUDMaterial { + case .frosted: + Capsule().fill(.ultraThinMaterial) + case .liquid: + if #available(macOS 26.0, *) { + if verticalHUDLiquidGlassCustomizationMode == .customLiquid { + TimelineView(.periodic(from: .now, by: 1.0 / 30.0)) { context in + LiquidGlassBackground( + variant: verticalHUDLiquidGlassVariant, + cornerRadius: max(currentWidth, hudWidth), + trigger: context.date.timeIntervalSinceReferenceDate + ) { + Color.white.opacity(0.04) + } + } + } else { + Capsule() + .fill(.clear) + .glassEffect( + .clear.interactive(), + in: .capsule + ) + } + } else { + Capsule().fill(.ultraThinMaterial) + } + case .solidDark: + Capsule().fill(Color.black.opacity(0.85)) + case .solidLight: + Capsule().fill(Color.white.opacity(0.85)) + case .solidAuto: + Capsule().fill((colorScheme == .dark ? Color.black : Color.white).opacity(0.85)) + } + } } #endif diff --git a/DynamicIsland/components/Settings/SettingsView.swift b/DynamicIsland/components/Settings/SettingsView.swift index d14e0b0..0b3336e 100644 --- a/DynamicIsland/components/Settings/SettingsView.swift +++ b/DynamicIsland/components/Settings/SettingsView.swift @@ -1369,6 +1369,9 @@ private struct HUDAndOSDSettingsView: View { @Default(.verticalHUDWidth) var verticalHUDWidth @Default(.verticalHUDPadding) var verticalHUDPadding @Default(.verticalHUDUseAccentColor) var verticalHUDUseAccentColor + @Default(.verticalHUDMaterial) var verticalHUDMaterial + @Default(.verticalHUDLiquidGlassCustomizationMode) var verticalHUDLiquidGlassCustomizationMode + @Default(.verticalHUDLiquidGlassVariant) var verticalHUDLiquidGlassVariant // Circular HUD Props @Default(.circularHUDShowValue) var circularHUDShowValue @@ -1391,6 +1394,27 @@ private struct HUDAndOSDSettingsView: View { Color(nsColor: .controlBackgroundColor) } + private var liquidVariantRange: ClosedRange { + Double(LiquidGlassVariant.supportedRange.lowerBound)...Double(LiquidGlassVariant.supportedRange.upperBound) + } + + private var availableVerticalMaterials: [OSDMaterial] { + if #available(macOS 26.0, *) { + return OSDMaterial.allCases + } + return OSDMaterial.allCases.filter { $0 != .liquid } + } + + private var verticalLiquidVariantBinding: Binding { + Binding( + get: { Double(verticalHUDLiquidGlassVariant.rawValue) }, + set: { newValue in + let raw = Int(newValue.rounded()) + verticalHUDLiquidGlassVariant = LiquidGlassVariant.clamped(raw) + } + ) + } + var body: some View { VStack(spacing: 20) { HStack(spacing: 16) { @@ -1608,6 +1632,39 @@ private struct HUDAndOSDSettingsView: View { Toggle("Show Percentage", isOn: $verticalHUDShowValue) Toggle("Use Accent Color", isOn: $verticalHUDUseAccentColor) Toggle("Interactive (Drag to Change)", isOn: $verticalHUDInteractive) + Picker("Material", selection: $verticalHUDMaterial) { + ForEach(availableVerticalMaterials, id: \.self) { material in + Text(material.rawValue).tag(material) + } + } + + if verticalHUDMaterial == .liquid { + if #available(macOS 26.0, *) { + Picker("Glass mode", selection: $verticalHUDLiquidGlassCustomizationMode) { + ForEach(LockScreenGlassCustomizationMode.allCases) { mode in + Text(mode.rawValue).tag(mode) + } + } + .pickerStyle(.segmented) + + if verticalHUDLiquidGlassCustomizationMode == .customLiquid { + VStack(alignment: .leading, spacing: 6) { + HStack { + Text("Custom liquid variant") + Spacer() + Text("v\(verticalHUDLiquidGlassVariant.rawValue)") + .font(.caption) + .foregroundStyle(.secondary) + } + Slider(value: verticalLiquidVariantBinding, in: liquidVariantRange, step: 1) + } + } + } else { + Text("Custom Liquid is available on macOS 26 or later.") + .font(.caption) + .foregroundStyle(.secondary) + } + } Defaults.Toggle("Color-coded Volume", key: .useColorCodedVolumeDisplay) if Defaults[.useColorCodedVolumeDisplay] { Defaults.Toggle("Smooth color transitions", key: .useSmoothColorGradient) @@ -1719,6 +1776,12 @@ private struct HUDAndOSDSettingsView: View { .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .background(paneBackgroundColor) .navigationTitle("Controls") + .onAppear { + if #unavailable(macOS 26.0), verticalHUDMaterial == .liquid { + verticalHUDMaterial = .frosted + verticalHUDLiquidGlassCustomizationMode = .standard + } + } } } @@ -5608,34 +5671,34 @@ struct TimerSettings: View { let nextIndex = timerPresets.count + 1 let defaultColor = Defaults[.accentColor] let newPreset = TimerPreset(name: "Preset \(nextIndex)", duration: 5 * 60, color: defaultColor) - withAnimation(.smooth) { + _ = withAnimation(.smooth) { timerPresets.append(newPreset) } } private func movePresetUp(_ index: Int) { guard index > timerPresets.startIndex else { return } - withAnimation(.smooth) { + _ = withAnimation(.smooth) { timerPresets.swapAt(index, index - 1) } } private func movePresetDown(_ index: Int) { guard index < timerPresets.index(before: timerPresets.endIndex) else { return } - withAnimation(.smooth) { + _ = withAnimation(.smooth) { timerPresets.swapAt(index, index + 1) } } private func removePreset(_ index: Int) { guard timerPresets.indices.contains(index) else { return } - withAnimation(.smooth) { + _ = withAnimation(.smooth) { timerPresets.remove(at: index) } } private func resetPresets() { - withAnimation(.smooth) { + _ = withAnimation(.smooth) { timerPresets = TimerPreset.defaultPresets } } @@ -6550,6 +6613,8 @@ struct CustomOSDSettings: View { @Default(.enableOSDBrightness) var enableOSDBrightness @Default(.enableOSDKeyboardBacklight) var enableOSDKeyboardBacklight @Default(.osdMaterial) var osdMaterial + @Default(.osdLiquidGlassCustomizationMode) var osdLiquidGlassCustomizationMode + @Default(.osdLiquidGlassVariant) var osdLiquidGlassVariant @Default(.osdIconColorStyle) var osdIconColorStyle @Default(.enableSystemHUD) var enableSystemHUD @ObservedObject private var accessibilityPermission = AccessibilityPermissionStore.shared @@ -6565,6 +6630,27 @@ struct CustomOSDSettings: View { private var hasAccessibilityPermission: Bool { accessibilityPermission.isAuthorized } + + private var availableOSDMaterials: [OSDMaterial] { + if #available(macOS 26.0, *) { + return OSDMaterial.allCases + } + return OSDMaterial.allCases.filter { $0 != .liquid } + } + + private var liquidVariantRange: ClosedRange { + Double(LiquidGlassVariant.supportedRange.lowerBound)...Double(LiquidGlassVariant.supportedRange.upperBound) + } + + private var osdLiquidVariantBinding: Binding { + Binding( + get: { Double(osdLiquidGlassVariant.rawValue) }, + set: { newValue in + let raw = Int(newValue.rounded()) + osdLiquidGlassVariant = LiquidGlassVariant.clamped(raw) + } + ) + } var body: some View { Form { @@ -6598,7 +6684,7 @@ struct CustomOSDSettings: View { Section { Picker("Material", selection: $osdMaterial) { - ForEach(OSDMaterial.allCases, id: \.self) { material in + ForEach(availableOSDMaterials, id: \.self) { material in Text(material.rawValue).tag(material) } } @@ -6606,6 +6692,34 @@ struct CustomOSDSettings: View { .onChange(of: osdMaterial) { _, _ in previewValue = previewValue == 0.65 ? 0.651 : 0.65 } + + if osdMaterial == .liquid { + if #available(macOS 26.0, *) { + Picker("Glass mode", selection: $osdLiquidGlassCustomizationMode) { + ForEach(LockScreenGlassCustomizationMode.allCases) { mode in + Text(mode.rawValue).tag(mode) + } + } + .pickerStyle(.segmented) + + if osdLiquidGlassCustomizationMode == .customLiquid { + VStack(alignment: .leading, spacing: 6) { + HStack { + Text("Custom liquid variant") + Spacer() + Text("v\(osdLiquidGlassVariant.rawValue)") + .font(.caption) + .foregroundStyle(.secondary) + } + Slider(value: osdLiquidVariantBinding, in: liquidVariantRange, step: 1) + } + } + } else { + Text("Custom Liquid is available on macOS 26 or later.") + .font(.caption) + .foregroundStyle(.secondary) + } + } Picker("Icon & Progress Color", selection: $osdIconColorStyle) { ForEach(OSDIconColorStyle.allCases, id: \.self) { style in @@ -6682,6 +6796,10 @@ struct CustomOSDSettings: View { .navigationTitle("Custom OSD") .onAppear { accessibilityPermission.refreshStatus() + if #unavailable(macOS 26.0), osdMaterial == .liquid { + osdMaterial = .frosted + osdLiquidGlassCustomizationMode = .standard + } } .onChange(of: accessibilityPermission.isAuthorized) { _, granted in if !granted { diff --git a/DynamicIsland/models/Constants.swift b/DynamicIsland/models/Constants.swift index c77f251..2ed3592 100644 --- a/DynamicIsland/models/Constants.swift +++ b/DynamicIsland/models/Constants.swift @@ -973,6 +973,15 @@ extension Defaults.Keys { static let verticalHUDWidth = Key("verticalHUDWidth", default: 36) static let verticalHUDPadding = Key("verticalHUDPadding", default: 24) static let verticalHUDUseAccentColor = Key("verticalHUDUseAccentColor", default: false) + static let verticalHUDMaterial = Key("verticalHUDMaterial", default: .frosted) + static let verticalHUDLiquidGlassCustomizationMode = Key( + "verticalHUDLiquidGlassCustomizationMode", + default: .standard + ) + static let verticalHUDLiquidGlassVariant = Key( + "verticalHUDLiquidGlassVariant", + default: .defaultVariant + ) // Circular HUD Customization static let circularHUDShowValue = Key("circularHUDShowValue", default: true) @@ -985,6 +994,14 @@ extension Defaults.Keys { static let enableOSDBrightness = Key("enableOSDBrightness", default: true) static let enableOSDKeyboardBacklight = Key("enableOSDKeyboardBacklight", default: true) static let osdMaterial = Key("osdMaterial", default: .frosted) + static let osdLiquidGlassCustomizationMode = Key( + "osdLiquidGlassCustomizationMode", + default: .standard + ) + static let osdLiquidGlassVariant = Key( + "osdLiquidGlassVariant", + default: .defaultVariant + ) static let osdIconColorStyle = Key("osdIconColorStyle", default: .white) // MARK: Screen Recording Detection Feature