ui: match empty player styling to playback controls

This commit is contained in:
2026-10-12 12:27:16 +02:00
parent 1e75f554f0
commit 39efa96986
2 changed files with 46 additions and 9 deletions
+2
View File
@@ -186,6 +186,8 @@ Terebi reports facts, not watched status. A consumer such as Tubular decides com
The layout follows [IINA's video-first window](https://iina.io/img/screenshot.png) and the supplied screenshots: a 32-point frosted titlebar with native traffic lights and file icon, plus a dark frosted 440 × 68-point floating controller. AppKit materials blur the video beneath the chrome; muted icons, an orange volume track, a narrow seek thumb, and zero-padded time labels follow the reference. Shared vertical padding and an optical offset for symbol/slider insets balance the controller's visible top and bottom margins in both play and pause states. The controller starts centered with a four-percent bottom inset and keeps its relative position when the window changes size. The layout follows [IINA's video-first window](https://iina.io/img/screenshot.png) and the supplied screenshots: a 32-point frosted titlebar with native traffic lights and file icon, plus a dark frosted 440 × 68-point floating controller. AppKit materials blur the video beneath the chrome; muted icons, an orange volume track, a narrow seek thumb, and zero-padded time labels follow the reference. Shared vertical padding and an optical offset for symbol/slider insets balance the controller's visible top and bottom margins in both play and pause states. The controller starts centered with a four-percent bottom inset and keeps its relative position when the window changes size.
The empty player uses the same HUD-glass material, 12-point rounded corners, and muted icon/text palette in a centered welcome card. File and YouTube opening actions share a consistently sized row; the card fits the 400 × 240-point minimum window, and the full window still accepts media-file and YouTube-link drops.
Opening video fits the window to its display aspect ratio, including non-square pixels, and constrains subsequent window resizing. Fullscreen retains the normal letterboxed renderer; audio-only playback and close release the aspect constraint. Extreme ratios that cannot fit both the screen and usable controls keep a freely resizable letterboxed window. Opening video fits the window to its display aspect ratio, including non-square pixels, and constrains subsequent window resizing. Fullscreen retains the normal letterboxed renderer; audio-only playback and close release the aspect constraint. Extreme ratios that cannot fit both the screen and usable controls keep a freely resizable letterboxed window.
**Compact mode** uses the same window, Metal view, and playback session—not a second window or macOS system PiP. Use the PiP-shaped button in the normal playback controls, **⌃⌘P**, or **Playback Actions → Enter Compact Mode**. The compact-mode button replaces the fullscreen button; fullscreen remains available through its menu, keyboard shortcuts, and double-clicking normal-mode video. Compact mode starts at the current screen's top-right corner with a 16-point inset and a preferred width of 320 points, preserving display aspect where usable screen bounds and the 160 × 90-point minimum allow; extreme ratios retain letterboxing. Compact mode floats above ordinary windows, joins all Spaces, and is configured to join eligible other-app fullscreen spaces and Stage Manager sets. Small hover controls provide play/pause, restore, and Playback Actions; keyboard playback controls remain available. **Compact mode** uses the same window, Metal view, and playback session—not a second window or macOS system PiP. Use the PiP-shaped button in the normal playback controls, **⌃⌘P**, or **Playback Actions → Enter Compact Mode**. The compact-mode button replaces the fullscreen button; fullscreen remains available through its menu, keyboard shortcuts, and double-clicking normal-mode video. Compact mode starts at the current screen's top-right corner with a 16-point inset and a preferred width of 320 points, preserving display aspect where usable screen bounds and the 160 × 90-point minimum allow; extreme ratios retain letterboxing. Compact mode floats above ordinary windows, joins all Spaces, and is configured to join eligible other-app fullscreen spaces and Stage Manager sets. Small hover controls provide play/pause, restore, and Playback Actions; keyboard playback controls remain available.
+44 -9
View File
@@ -1420,17 +1420,52 @@ private struct PlayerContent: View {
} }
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
} else if model.fileName == nil { } else if model.fileName == nil {
VStack(spacing: 14) { VStack(spacing: 18) {
Image(systemName: "play.rectangle").font(.system(size: 56, weight: .ultraLight)) HStack(spacing: 12) {
.foregroundStyle(.white.opacity(0.65)) Image(systemName: "play.rectangle.fill")
Text("terebi").font(.system(size: 22, weight: .medium)) .font(.system(size: 28))
Text("Drop a media file or YouTube link here").font(.callout).foregroundStyle(.secondary) .frame(width: 40, height: 40)
Button("Open File…", action: model.openPanel) .foregroundStyle(.white.opacity(0.6))
.buttonStyle(.bordered).controlSize(.large).disabled(!model.canOpen) .accessibilityHidden(true)
Button("Open YouTube URL…", action: model.openYouTubePanel) VStack(alignment: .leading, spacing: 4) {
.buttonStyle(.bordered).disabled(!model.canOpen) Text("terebi").font(.system(size: 20, weight: .semibold))
.foregroundStyle(.white.opacity(0.85))
Text("Open media to get started")
.font(.callout)
.foregroundStyle(.white.opacity(0.6))
} }
Spacer(minLength: 0)
}
HStack(spacing: 0) {
Button(action: model.openPanel) {
Label("Open File…", systemImage: "doc")
.frame(maxWidth: .infinity, minHeight: 40)
.contentShape(Rectangle())
}
Divider().frame(height: 20).accessibilityHidden(true)
Button(action: model.openYouTubePanel) {
Label("YouTube URL…", systemImage: "link")
.frame(maxWidth: .infinity, minHeight: 40)
.contentShape(Rectangle())
}
.accessibilityLabel("Open YouTube URL…")
}
.font(.system(size: 13, weight: .medium))
.foregroundStyle(.white.opacity(0.85))
.imageScale(.large)
.buttonStyle(.plain)
.disabled(!model.canOpen)
.background(.white.opacity(0.06), in: RoundedRectangle(cornerRadius: 8))
Text("Drop a media file or YouTube link here")
.font(.system(size: 12))
.foregroundStyle(.white.opacity(0.6))
.multilineTextAlignment(.center)
}
.padding(20)
.frame(width: min(440, geometry.size.width - 48))
.background(PlayerGlass().clipShape(RoundedRectangle(cornerRadius: 12)).allowsHitTesting(false))
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(.top, 32)
} else if !model.state.has_video && model.state.has_audio { } else if !model.state.has_video && model.state.has_audio {
VStack(spacing: 16) { VStack(spacing: 16) {
Image(systemName: "waveform").font(.system(size: 64, weight: .ultraLight)) Image(systemName: "waveform").font(.system(size: 64, weight: .ultraLight))