ui: integrate captions and unify playback chrome backgrounds

This commit is contained in:
2026-10-12 23:13:05 +02:00
parent 0509eee28e
commit 7d24ce609e
2 changed files with 67 additions and 41 deletions
+63 -39
View File
@@ -1455,11 +1455,7 @@ private struct PlayerContent: View {
.padding(.vertical, 4)
.background(PlayerGlass(cornerRadius: 8).clipShape(RoundedRectangle(cornerRadius: 8)).allowsHitTesting(false))
Spacer(minLength: 12)
iconButton(model.isOnTop ? "pin.fill" : "pin",
model.isOnTop ? "Stop Keeping Window on Top" : "Keep Window on Top", action: model.toggleOnTop)
.imageScale(.small)
.foregroundStyle(Color(model.isOnTop ? SunsetPalette.accent : SunsetPalette.secondaryText))
.disabled(model.isFullScreen || model.compactWindow.isTransitioning)
titleControls
}
.padding(.leading, model.isFullScreen ? 16 : 86)
.padding(.trailing, 12)
@@ -1471,6 +1467,42 @@ private struct PlayerContent: View {
.accessibilityHidden(!model.controlsVisible)
}
private var titleControls: some View {
HStack(spacing: 4) {
iconButton(model.isOnTop ? "pin.fill" : "pin",
model.isOnTop ? "Stop Keeping Window on Top" : "Keep Window on Top", action: model.toggleOnTop)
.imageScale(.small)
.foregroundStyle(Color(model.isOnTop ? SunsetPalette.accent : SunsetPalette.secondaryText))
.disabled(model.isFullScreen || model.compactWindow.isTransitioning)
actionsMenu
}
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(PlayerGlass(cornerRadius: 8).clipShape(RoundedRectangle(cornerRadius: 8)).allowsHitTesting(false))
}
private var captionsMenu: some View {
Menu {
Button("Off") { model.selectSubtitle(nil) }
ForEach(model.subtitleTracks.indices, id: \.self) { index in
Button {
model.selectSubtitle(index)
} label: {
Label(model.subtitleTracks[index].label + " (" + model.subtitleTracks[index].language + ")",
systemImage: model.selectedSubtitle == index ? "checkmark" : "text.bubble")
}
}
} label: {
Image(systemName: "captions.bubble").font(.system(size: 17)).frame(width: 24, height: 24)
}
.menuStyle(.borderlessButton)
.menuIndicator(.hidden)
.fixedSize()
.disabled(model.subtitleTracks.isEmpty)
.accessibilityLabel("Subtitles")
.help("Subtitles")
}
private var actionsMenu: some View {
Menu {
PlayerActionButton(model: model, action: .openFile, title: "Open File…")
@@ -1507,7 +1539,7 @@ private struct PlayerContent: View {
.disabled(!model.canTogglePlayback)
iconButton("pip.exit", "Exit Compact Mode", action: model.toggleCompact)
.disabled(!model.canToggleCompact)
actionsMenu
captionsMenu
}
if showsBufferReadout { bufferReadout }
}
@@ -1557,7 +1589,7 @@ private struct PlayerContent: View {
HStack(spacing: 12) {
iconButton("pip.enter", "Enter Compact Mode", action: model.toggleCompact)
.disabled(!model.canToggleCompact)
actionsMenu
captionsMenu
}
.frame(width: 106, alignment: .trailing)
}
@@ -1638,33 +1670,20 @@ private struct PlayerContent: View {
.foregroundStyle(.white)
.multilineTextAlignment(.center)
.padding(8)
.background(.black.opacity(0.75), in: RoundedRectangle(cornerRadius: 4))
.background(PlayerGlass().clipShape(RoundedRectangle(cornerRadius: 12)).allowsHitTesting(false))
.padding(.horizontal, 24)
.padding(.bottom, model.controlsVisible ? 110 : 24)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
.allowsHitTesting(false)
}
if !model.subtitleTracks.isEmpty && (model.controlsVisible || model.subtitleError != nil) {
VStack(alignment: .trailing) {
Menu {
Button("Off") { model.selectSubtitle(nil) }
ForEach(model.subtitleTracks.indices, id: \.self) { index in
Button {
model.selectSubtitle(index)
} label: {
Label(model.subtitleTracks[index].label + " (" + model.subtitleTracks[index].language + ")",
systemImage: model.selectedSubtitle == index ? "checkmark" : "text.bubble")
}
}
} label: {
Image(systemName: "captions.bubble").accessibilityLabel("Subtitles")
}
if let error = model.subtitleError {
Text(error).font(.caption).foregroundStyle(.yellow)
}
}
.padding(.top, 44).padding(.trailing, 16)
.frame(maxWidth: .infinity, alignment: .trailing)
if let error = model.subtitleError {
Text(error)
.font(.caption)
.foregroundStyle(.yellow)
.padding(8)
.background(PlayerGlass().clipShape(RoundedRectangle(cornerRadius: 12)).allowsHitTesting(false))
.padding(.top, 44).padding(.trailing, 16)
.frame(maxWidth: .infinity, alignment: .trailing)
}
if let error = model.errorText {
VStack(spacing: 12) {
@@ -1789,22 +1808,27 @@ private struct PlayerContent: View {
let progress = model.isCompact || model.compactWindow.isTransitioning ? model.compactProgress : nil
ZStack(alignment: .top) {
ViewThatFits(in: .horizontal) {
compactTitleLabel(progress: progress, constrained: false)
.fixedSize(horizontal: true, vertical: false)
compactTitleLabel(progress: progress, constrained: true)
}
HStack(spacing: 8) {
ViewThatFits(in: .horizontal) {
compactTitleLabel(progress: progress, constrained: false)
.fixedSize(horizontal: true, vertical: false)
compactTitleLabel(progress: progress, constrained: true)
}
.font(.system(size: 11, weight: .medium))
.accessibilityElement(children: .combine)
.help((model.fileName ?? "terebi") + (progress.map { " · \($0)" } ?? ""))
.padding(8)
.background(PlayerGlass(cornerRadius: 10).clipShape(RoundedRectangle(cornerRadius: 10)).allowsHitTesting(false))
.frame(maxWidth: .infinity, alignment: .leading)
.padding(8)
.foregroundStyle(Color(SunsetPalette.text))
.opacity(model.controlsVisible ? 1 : 0)
.allowsHitTesting(false)
.accessibilityHidden(!model.controlsVisible)
Spacer(minLength: 0)
titleControls
}
.padding(8)
.foregroundStyle(Color(SunsetPalette.text))
.onHover(perform: model.setTitleHovered)
.opacity(model.controlsVisible ? 1 : 0)
.allowsHitTesting(model.controlsVisible)
.accessibilityHidden(!model.controlsVisible)
compactControls
.position(x: geometry.size.width / 2, y: geometry.size.height - compactControlHeight / 2 - 8)
}