From 96fef3663fcd992a004a036e124b62c3690a29a5 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Tue, 2 Jun 2026 19:38:46 +0200 Subject: [PATCH] Add glass background theme setting for macOS control bar Rename the macOS "Control Bar Buttons" settings entry to "Control Bar" and add an Auto/Light/Dark glass background picker on that page. The choice is stored per-preset as GlobalLayoutSettings.controlBarTheme and forces the bar's glass color scheme via glassBackground(colorScheme:). --- Yattee/Localizable.xcstrings | 26 +++++++++++- .../PlayerControls/GlobalLayoutSettings.swift | 10 ++++- .../Views/Player/macOS/MacOSControlBar.swift | 2 +- .../PlayerControlsSettingsView.swift | 2 +- .../PlayerControls/SectionEditorView.swift | 40 +++++++++++++++++++ 5 files changed, 75 insertions(+), 5 deletions(-) diff --git a/Yattee/Localizable.xcstrings b/Yattee/Localizable.xcstrings index 6033cdb1..36802df3 100644 --- a/Yattee/Localizable.xcstrings +++ b/Yattee/Localizable.xcstrings @@ -1859,6 +1859,17 @@ } } }, + "controls.theme.auto" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Auto" + } + } + } + }, "controls.theme.dark" : { "localizations" : { "en" : { @@ -13315,13 +13326,13 @@ } } }, - "settings.playerControls.controlBarButtons" : { + "settings.playerControls.controlBar" : { "extractionState" : "extracted_with_value", "localizations" : { "en" : { "stringUnit" : { "state" : "new", - "value" : "Control Bar Buttons" + "value" : "Control Bar" } } } @@ -13396,6 +13407,17 @@ } } }, + "settings.playerControls.glassBackground" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Glass Background" + } + } + } + }, "settings.playerControls.import.error.emptyFile" : { "localizations" : { "en" : { diff --git a/Yattee/Models/PlayerControls/GlobalLayoutSettings.swift b/Yattee/Models/PlayerControls/GlobalLayoutSettings.swift index 5f6fa612..bdb2017d 100644 --- a/Yattee/Models/PlayerControls/GlobalLayoutSettings.swift +++ b/Yattee/Models/PlayerControls/GlobalLayoutSettings.swift @@ -411,6 +411,9 @@ struct GlobalLayoutSettings: Codable, Hashable, Sendable { /// How volume is controlled during playback. var volumeMode: VolumeMode + /// Forced appearance for the macOS control bar's glass background. + var controlBarTheme: ControlsTheme + /// Theme derived from style (for backwards compatibility). var theme: ControlsTheme { style.theme } @@ -433,6 +436,7 @@ struct GlobalLayoutSettings: Codable, Hashable, Sendable { case systemControlsMode case systemControlsSeekDuration case volumeMode + case controlBarTheme } init(from decoder: Decoder) throws { @@ -445,6 +449,7 @@ struct GlobalLayoutSettings: Codable, Hashable, Sendable { systemControlsMode = try container.decodeIfPresent(SystemControlsMode.self, forKey: .systemControlsMode) ?? .seek systemControlsSeekDuration = try container.decodeIfPresent(SystemControlsSeekDuration.self, forKey: .systemControlsSeekDuration) ?? .tenSeconds volumeMode = try container.decodeIfPresent(VolumeMode.self, forKey: .volumeMode) ?? .mpv + controlBarTheme = try container.decodeIfPresent(ControlsTheme.self, forKey: .controlBarTheme) ?? .system } func encode(to encoder: Encoder) throws { @@ -457,6 +462,7 @@ struct GlobalLayoutSettings: Codable, Hashable, Sendable { try container.encode(systemControlsMode, forKey: .systemControlsMode) try container.encode(systemControlsSeekDuration, forKey: .systemControlsSeekDuration) try container.encode(volumeMode, forKey: .volumeMode) + try container.encode(controlBarTheme, forKey: .controlBarTheme) } // MARK: - Initialization @@ -469,7 +475,8 @@ struct GlobalLayoutSettings: Codable, Hashable, Sendable { controlsFadeOpacity: Double = 0.5, systemControlsMode: SystemControlsMode = .seek, systemControlsSeekDuration: SystemControlsSeekDuration = .tenSeconds, - volumeMode: VolumeMode = .mpv + volumeMode: VolumeMode = .mpv, + controlBarTheme: ControlsTheme = .system ) { self.style = style self.buttonSize = buttonSize @@ -478,6 +485,7 @@ struct GlobalLayoutSettings: Codable, Hashable, Sendable { self.systemControlsMode = systemControlsMode self.systemControlsSeekDuration = systemControlsSeekDuration self.volumeMode = volumeMode + self.controlBarTheme = controlBarTheme } // MARK: - Defaults diff --git a/Yattee/Views/Player/macOS/MacOSControlBar.swift b/Yattee/Views/Player/macOS/MacOSControlBar.swift index 90a44780..9dc664dd 100644 --- a/Yattee/Views/Player/macOS/MacOSControlBar.swift +++ b/Yattee/Views/Player/macOS/MacOSControlBar.swift @@ -80,7 +80,7 @@ struct MacOSControlBar: View { .padding(.horizontal, 16) .padding(.vertical, 10) .frame(maxWidth: 500) - .glassBackground(.regular, in: .rect(cornerRadius: 16)) + .glassBackground(.regular, in: .rect(cornerRadius: 16), colorScheme: globalSettings.controlBarTheme.colorScheme) .shadow(color: .black.opacity(0.3), radius: 8, y: 4) // Seek preview overlay - positioned above the control bar .overlay(alignment: .bottom) { diff --git a/Yattee/Views/Settings/PlayerControls/PlayerControlsSettingsView.swift b/Yattee/Views/Settings/PlayerControls/PlayerControlsSettingsView.swift index 579516ab..971f3a59 100644 --- a/Yattee/Views/Settings/PlayerControls/PlayerControlsSettingsView.swift +++ b/Yattee/Views/Settings/PlayerControls/PlayerControlsSettingsView.swift @@ -354,7 +354,7 @@ private struct LayoutSectionsSection: View { HStack { #if os(macOS) Label( - String(localized: "settings.playerControls.controlBarButtons", defaultValue: "Control Bar Buttons"), + String(localized: "settings.playerControls.controlBar", defaultValue: "Control Bar"), systemImage: "rectangle.bottomthird.inset.filled" ) #else diff --git a/Yattee/Views/Settings/PlayerControls/SectionEditorView.swift b/Yattee/Views/Settings/PlayerControls/SectionEditorView.swift index 5a21e24a..f9590c93 100644 --- a/Yattee/Views/Settings/PlayerControls/SectionEditorView.swift +++ b/Yattee/Views/Settings/PlayerControls/SectionEditorView.swift @@ -15,6 +15,7 @@ struct SectionEditorView: View { // Local state for immediate UI updates @State private var buttons: [ControlButtonConfiguration] = [] @State private var availableTypes: [ControlButtonType] = [] + @State private var controlBarTheme: ControlsTheme = .system var body: some View { List { @@ -45,6 +46,29 @@ struct SectionEditorView: View { .listRowBackground(Color.clear) #endif + // The macOS bar draws a glass capsule whose appearance can be forced. + #if os(macOS) + if sectionType == .bottom { + Section { + Picker( + String(localized: "settings.playerControls.glassBackground", defaultValue: "Glass Background"), + selection: $controlBarTheme + ) { + ForEach(ControlsTheme.allCases, id: \.self) { theme in + Text(glassBackgroundLabel(for: theme)).tag(theme) + } + } + .disabled(!viewModel.canEditActivePreset) + .onChange(of: controlBarTheme) { _, newTheme in + guard newTheme != viewModel.currentLayout.globalSettings.controlBarTheme else { return } + viewModel.updateGlobalSettingsSync { $0.controlBarTheme = newTheme } + } + } header: { + Text(String(localized: "settings.playerControls.appearance")) + } + } + #endif + // Added buttons Section { ForEach(buttons) { button in @@ -139,6 +163,7 @@ struct SectionEditorView: View { case .bottom: buttons = preset.layout.bottomSection.buttons } + controlBarTheme = preset.layout.globalSettings.controlBarTheme syncAvailableTypes() } @@ -160,12 +185,27 @@ struct SectionEditorView: View { viewModel.addButtonSync(buttonType, to: sectionType) } + #if os(macOS) + private func glassBackgroundLabel(for theme: ControlsTheme) -> String { + switch theme { + case .system: + return String(localized: "controls.theme.auto", defaultValue: "Auto") + case .light, .dark: + return theme.displayName + } + } + #endif + private var sectionTitle: String { switch sectionType { case .top: return String(localized: "settings.playerControls.topButtons") case .bottom: + #if os(macOS) + return String(localized: "settings.playerControls.controlBar", defaultValue: "Control Bar") + #else return String(localized: "settings.playerControls.bottomButtons") + #endif } } }