mirror of
https://github.com/yattee/yattee.git
synced 2026-07-20 14:22:02 +00:00
Split macOS player mode into window toggle + floating control
Replace the three-way macOS Player Mode picker (Separate Window / Floating Window / Inline) with a single 'Play in a separate window' toggle in Playback settings, and move the always-on-top choice to a pin button in the player's top bar (window mode only). Floating was really a transient window property (NSWindow.level), not a peer of the window/inline structural choice — so it belongs on a live control, not in Settings. The pin state persists across sessions. - Replace MacPlayerMode enum with macPlayerSeparateWindow / macPlayerFloating bool settings - Branch runtime presentation and window level off the two bools - Add pin.fill/pin toggle to MacOSPlayerControlsView top bar - Update localization; drop obsolete playerMode strings and enum tests
This commit is contained in:
@@ -61,7 +61,7 @@ struct ContentView: View {
|
||||
}
|
||||
#if os(macOS)
|
||||
.onChange(of: appEnvironment.navigationCoordinator.playerExpandTrigger) { _, _ in
|
||||
if appEnvironment.settingsManager.macPlayerMode.usesWindow {
|
||||
if appEnvironment.settingsManager.macPlayerSeparateWindow {
|
||||
presentExpandedPlayerWindow(appEnvironment: appEnvironment)
|
||||
}
|
||||
}
|
||||
@@ -70,26 +70,33 @@ struct ContentView: View {
|
||||
ExpandedPlayerWindowManager.shared.hide()
|
||||
}
|
||||
}
|
||||
.onChange(of: appEnvironment.settingsManager.macPlayerMode) { oldMode, newMode in
|
||||
.onChange(of: appEnvironment.settingsManager.macPlayerSeparateWindow) { _, separateWindow in
|
||||
guard appEnvironment.navigationCoordinator.isPlayerExpanded else { return }
|
||||
|
||||
if oldMode.usesWindow && newMode.usesWindow {
|
||||
ExpandedPlayerWindowManager.shared.updateWindowLevel(floating: newMode.isFloating)
|
||||
} else if oldMode.usesWindow && !newMode.usesWindow {
|
||||
ExpandedPlayerWindowManager.shared.hide(animated: false)
|
||||
} else if !oldMode.usesWindow && newMode.usesWindow {
|
||||
if separateWindow {
|
||||
// Inline sheet → separate window: let the sheet dismiss first, then
|
||||
// present the window (mirrors the previous inline→window transition).
|
||||
Task { @MainActor in
|
||||
try? await Task.sleep(for: .milliseconds(300))
|
||||
if appEnvironment.navigationCoordinator.isPlayerExpanded {
|
||||
presentExpandedPlayerWindow(appEnvironment: appEnvironment)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Separate window → inline sheet: hide the window so the sheet binding
|
||||
// (isPlayerExpanded && !separateWindow) takes over.
|
||||
ExpandedPlayerWindowManager.shared.hide(animated: false)
|
||||
}
|
||||
}
|
||||
.onChange(of: appEnvironment.settingsManager.macPlayerFloating) { _, floating in
|
||||
guard appEnvironment.navigationCoordinator.isPlayerExpanded,
|
||||
appEnvironment.settingsManager.macPlayerSeparateWindow else { return }
|
||||
ExpandedPlayerWindowManager.shared.updateWindowLevel(floating: floating)
|
||||
}
|
||||
.sheet(isPresented: Binding(
|
||||
get: {
|
||||
appEnvironment.navigationCoordinator.isPlayerExpanded &&
|
||||
!appEnvironment.settingsManager.macPlayerMode.usesWindow
|
||||
!appEnvironment.settingsManager.macPlayerSeparateWindow
|
||||
},
|
||||
set: { appEnvironment.navigationCoordinator.isPlayerExpanded = $0 }
|
||||
)) {
|
||||
@@ -164,7 +171,7 @@ struct ContentView: View {
|
||||
let isExpanded = appEnvironment.navigationCoordinator.isPlayerExpanded
|
||||
// The expanded player is a separate window in window mode, so keep the capsule
|
||||
// visible alongside it. In sheet mode the sheet covers the window, so hide it.
|
||||
let usesWindow = appEnvironment.settingsManager.macPlayerMode.usesWindow
|
||||
let usesWindow = appEnvironment.settingsManager.macPlayerSeparateWindow
|
||||
|
||||
if hasActiveVideo && (!isExpanded || usesWindow) {
|
||||
VStack(spacing: 0) {
|
||||
|
||||
@@ -45,7 +45,8 @@ enum SettingsKey: String, CaseIterable {
|
||||
case resolveShortLinksEnabled
|
||||
|
||||
// Platform-specific
|
||||
case macPlayerMode
|
||||
case macPlayerSeparateWindow
|
||||
case macPlayerFloating
|
||||
case playerSheetAutoResize
|
||||
case listStyle
|
||||
|
||||
@@ -136,7 +137,8 @@ enum SettingsKey: String, CaseIterable {
|
||||
/// in both UserDefaults and iCloud, so each platform family syncs independently.
|
||||
var isPlatformSpecific: Bool {
|
||||
switch self {
|
||||
case .preferredQuality, .cellularQuality, .allowSoftwareDecodedFormats, .macPlayerMode, .listStyle,
|
||||
case .preferredQuality, .cellularQuality, .allowSoftwareDecodedFormats,
|
||||
.macPlayerSeparateWindow, .macPlayerFloating, .listStyle,
|
||||
// Home layout — different UI paradigms per platform
|
||||
.homeShortcutOrder, .homeShortcutVisibility, .homeShortcutLayout, .homeShortcutCardStyle,
|
||||
.homeShortcutColorfulPalette, .homeShortcutCustomPaletteColors,
|
||||
|
||||
@@ -68,14 +68,30 @@ extension SettingsManager {
|
||||
#endif
|
||||
|
||||
#if os(macOS)
|
||||
var macPlayerMode: MacPlayerMode {
|
||||
/// Whether the expanded player opens in a separate window (vs. an inline sheet).
|
||||
/// Default is `true`.
|
||||
var macPlayerSeparateWindow: Bool {
|
||||
get {
|
||||
if let cached = _macPlayerMode { return cached }
|
||||
return MacPlayerMode(rawValue: string(for: .macPlayerMode) ?? "") ?? .window
|
||||
if let cached = _macPlayerSeparateWindow { return cached }
|
||||
return bool(for: .macPlayerSeparateWindow, default: true)
|
||||
}
|
||||
set {
|
||||
_macPlayerMode = newValue
|
||||
set(newValue.rawValue, for: .macPlayerMode)
|
||||
_macPlayerSeparateWindow = newValue
|
||||
set(newValue, for: .macPlayerSeparateWindow)
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the separate player window floats above other windows (always on top).
|
||||
/// Toggled live from the player's top-bar pin button and remembered across sessions.
|
||||
/// Default is `false`.
|
||||
var macPlayerFloating: Bool {
|
||||
get {
|
||||
if let cached = _macPlayerFloating { return cached }
|
||||
return bool(for: .macPlayerFloating, default: false)
|
||||
}
|
||||
set {
|
||||
_macPlayerFloating = newValue
|
||||
set(newValue, for: .macPlayerFloating)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -201,37 +201,6 @@ enum DownloadQuality: String, CaseIterable, Codable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - macOS Player Mode
|
||||
|
||||
#if os(macOS)
|
||||
enum MacPlayerMode: String, CaseIterable, Codable {
|
||||
case window
|
||||
case floatingWindow
|
||||
case inline
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .window: return String(localized: "settings.playback.macOS.playerMode.window")
|
||||
case .floatingWindow: return String(localized: "settings.playback.macOS.playerMode.floatingWindow")
|
||||
case .inline: return String(localized: "settings.playback.macOS.playerMode.inline")
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this mode uses a separate window (vs sheet/inline)
|
||||
var usesWindow: Bool {
|
||||
switch self {
|
||||
case .window, .floatingWindow: return true
|
||||
case .inline: return false
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the window should float above other windows
|
||||
var isFloating: Bool {
|
||||
self == .floatingWindow
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// MARK: - Haptic Feedback
|
||||
|
||||
/// Intensity levels for haptic feedback.
|
||||
|
||||
@@ -72,7 +72,8 @@ final class SettingsManager {
|
||||
var _preferPortraitBrowsing: Bool?
|
||||
#endif
|
||||
#if os(macOS)
|
||||
var _macPlayerMode: MacPlayerMode?
|
||||
var _macPlayerSeparateWindow: Bool?
|
||||
var _macPlayerFloating: Bool?
|
||||
var _playerSheetAutoResize: Bool?
|
||||
#endif
|
||||
|
||||
@@ -456,7 +457,8 @@ final class SettingsManager {
|
||||
_syncSearchHistory = nil
|
||||
_searchHistoryLimit = nil
|
||||
#if os(macOS)
|
||||
_macPlayerMode = nil
|
||||
_macPlayerSeparateWindow = nil
|
||||
_macPlayerFloating = nil
|
||||
_playerSheetAutoResize = nil
|
||||
#endif
|
||||
// miniPlayerShowVideo and miniPlayerVideoTapAction moved to preset
|
||||
|
||||
@@ -7234,6 +7234,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"player.controls.keepOnTop" : {
|
||||
"comment" : "Toggles whether the player window floats above other windows",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Keep on Top"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"player.controls.pause" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
@@ -12442,46 +12453,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.playback.macOS.playerMode" : {
|
||||
"comment" : "macOS player mode setting",
|
||||
"settings.playback.macOS.separateWindow" : {
|
||||
"comment" : "Toggle: play the expanded video in a separate window vs. an inline sheet",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Player Mode"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.playback.macOS.playerMode.floatingWindow" : {
|
||||
"comment" : "Floating window player mode - stays on top of other windows",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Floating Window"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.playback.macOS.playerMode.inline" : {
|
||||
"comment" : "Inline (sheet) player mode",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Inline (Sheet)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.playback.macOS.playerMode.window" : {
|
||||
"comment" : "Separate window player mode",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Separate Window"
|
||||
"value" : "Play in a separate window"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,8 @@ final class ExpandedPlayerWindowManager: NSObject {
|
||||
// Mark expanding state for mini player coordination
|
||||
appEnvironment.navigationCoordinator.isPlayerExpanding = true
|
||||
|
||||
// Get the current player mode for window configuration
|
||||
let mode = appEnvironment.settingsManager.macPlayerMode
|
||||
// Whether the window should float above other windows (always on top).
|
||||
let floating = appEnvironment.settingsManager.macPlayerFloating
|
||||
|
||||
// Host a lightweight two-phase root instead of ExpandedPlayerSheet directly.
|
||||
// AppKit won't composite the window to screen until the hosted SwiftUI view
|
||||
@@ -133,8 +133,8 @@ final class ExpandedPlayerWindowManager: NSObject {
|
||||
// Make ExpandedPlayerWindowManager itself the delegate to avoid lifecycle issues
|
||||
window.delegate = self
|
||||
|
||||
// Configure window level based on mode
|
||||
configureWindowLevel(window, floating: mode.isFloating)
|
||||
// Configure window level based on the floating preference
|
||||
configureWindowLevel(window, floating: floating)
|
||||
|
||||
// Center window on screen
|
||||
window.center()
|
||||
|
||||
@@ -113,6 +113,24 @@ struct MacOSPlayerControlsView: View {
|
||||
|
||||
Spacer(minLength: 12)
|
||||
|
||||
// Floating (always-on-top) toggle — only meaningful for the separate
|
||||
// window presentation, so hidden in the inline sheet and in fullscreen.
|
||||
if let settings = appEnvironment?.settingsManager,
|
||||
settings.macPlayerSeparateWindow, !isFullscreen {
|
||||
Button {
|
||||
settings.macPlayerFloating.toggle()
|
||||
} label: {
|
||||
Image(systemName: settings.macPlayerFloating ? "pin.fill" : "pin")
|
||||
.font(.system(size: 15, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 30, height: 30)
|
||||
.background(.ultraThinMaterial, in: Circle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.help(Text("player.controls.keepOnTop"))
|
||||
.accessibilityLabel(Text("player.controls.keepOnTop"))
|
||||
}
|
||||
|
||||
if onClose != nil {
|
||||
Button {
|
||||
onClose?()
|
||||
|
||||
@@ -250,14 +250,10 @@ private struct BehaviorSection: View {
|
||||
|
||||
SettingsFormSection("settings.playback.behavior.header", footer: footer) {
|
||||
#if os(macOS)
|
||||
PlatformMenuPicker(
|
||||
String(localized: "settings.playback.macOS.playerMode"),
|
||||
selection: $settings.macPlayerMode
|
||||
) {
|
||||
ForEach(MacPlayerMode.allCases, id: \.self) { mode in
|
||||
Text(mode.displayName).tag(mode)
|
||||
}
|
||||
}
|
||||
Toggle(
|
||||
String(localized: "settings.playback.macOS.separateWindow"),
|
||||
isOn: $settings.macPlayerSeparateWindow
|
||||
)
|
||||
|
||||
Toggle(
|
||||
String(localized: "settings.playback.macOS.autoResizePlayer"),
|
||||
|
||||
Reference in New Issue
Block a user