From 1aa5454194f9e53440f92e2dfd13ed52bb62bd44 Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Mon, 12 Oct 2026 23:58:24 +0200 Subject: [PATCH] ui: refine subtitle layout and preserve hidden controls on seeks --- Checks/system-playback.swift | 15 +++++++++++++++ README.md | 4 +++- Sources/App/TerebiApp.swift | 17 +++++++++++------ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Checks/system-playback.swift b/Checks/system-playback.swift index b7ec38f..2af1568 100644 --- a/Checks/system-playback.swift +++ b/Checks/system-playback.swift @@ -183,6 +183,21 @@ struct SystemPlaybackChecks { require(model.setPlayback(playing: true), "repeated play accepted") until("repeated play keeps playing") { model.state.status == PLAYER_PLAYING && model.state.position > 0.2 } + if !NSWorkspace.shared.isVoiceOverEnabled { + require(model.perform(.enterCompact), "compact playback is available for hidden-keyboard seeking") + until("compact entry completes before hiding controls") { !model.compactWindow.isTransitioning } + model.hideControls(hideCursor: false) + require(!model.controlsVisible, "playing chrome can be hidden before a keyboard seek") + require(model.handleKey(try! KeyChord("left")), "keyboard seek is accepted while chrome is hidden") + require(!model.controlsVisible, "keyboard seek must not flash playback chrome") + until("hidden keyboard seek acknowledges the start boundary") { + require(!model.controlsVisible, "seek acknowledgement must preserve hidden chrome") + return !model.state.seek_pending && model.state.position < 0.15 + } + require(model.perform(.exitCompact), "keyboard seek check restores normal mode") + until("compact exit completes") { !model.compactWindow.isTransitioning } + } + // Closing must hide the last polled retained range before the next state poll. var retainedSnapshot = model.state retainedSnapshot.buffered_start = retainedSnapshot.position diff --git a/README.md b/README.md index 542876c..b24c9d8 100644 --- a/README.md +++ b/README.md @@ -251,7 +251,9 @@ The inline URL field, including its placeholder, uses the same 12-point system f With no media open, the full window uses native behind-window HUD glass with a 35%-opaque brown tint. Static, cached amber/rust radial gradients add ambient color only to the empty screen; they disappear during loading or errors, and are never drawn over video or playback chrome. Glass title/control/error surfaces use the same brown tint with half-point sand outlines. Decorative drawing does not intercept input. The idle Metal surface stays attached but hidden; opening media restores a solid black background and letterboxing without tinting the video, and closing it restores the ambient frosted background. macOS controls the material’s appearance, including Reduce Transparency. -In normal mode, the captions selector sits immediately right of the compact-mode button and is disabled when no external subtitle tracks are available. Its native symbol uses a 17-point configuration within a 24-point click target. The native pull-down menu includes a visible Off option and prefers opening above the button; AppKit can reposition it to fit the screen. In compact mode, subtitle selection and Off are available through the settings button's Subtitles submenu. Subtitle text uses the floating controller's rounded HUD-glass background with 12-point internal padding. Subtitle-load errors remain visible independently of auto-hidden controls. +In normal mode, the captions selector sits immediately right of the compact-mode button and is disabled when no external subtitle tracks are available. Its native symbol uses a 16-point configuration within a 24-point click target. The native pull-down menu includes a visible Off option and prefers opening above the button; AppKit can reposition it to fit the screen. In compact mode, subtitle selection and Off are available through the settings button's Subtitles submenu; the compact cog uses a 21-point symbol to balance the adjacent icons. Subtitle lines can use the player width with 8-point outer side margins and 12-point internal padding. Their HUD-glass background uses 60% opacity without dimming the text. Visible compact controls raise subtitles by the actual compact control height plus spacing, rather than the normal controller's clearance. Subtitle-load errors remain visible independently of auto-hidden controls. + +Accepted relative seeks, including keyboard shortcuts, preserve control visibility instead of revealing hidden chrome. Seek failures still reveal the error; dragging the timeline continues to show the controls. Normal and compact titlebars have no full-width glass strip or separator: content extends beneath AppKit’s transparent titlebar using `fullSizeContentView`, `titlebarAppearsTransparent`, and `titlebarSeparatorStyle = .none`. Titles retain their rounded HUD-glass backdrop. In normal mode, the pin and settings buttons share a separate container matching the title's 24-point height, horizontal padding, vertical inset, tint, outline and corner radius. Settings sits immediately right of the pin, and both retain 24-point click targets. That button container is not shown in compact mode; only the compact title and adjacent progress occupy the top row with 8-point outer insets. Native traffic lights and window dragging are preserved. diff --git a/Sources/App/TerebiApp.swift b/Sources/App/TerebiApp.swift index f7277ba..5296abf 100644 --- a/Sources/App/TerebiApp.swift +++ b/Sources/App/TerebiApp.swift @@ -528,7 +528,7 @@ final class PlayerModel: ObservableObject { let accepted = transition.seek(target, handle: handle) if accepted { seekPosition = target } commandError = accepted ? nil : "Could not queue the seek. Please try again." - revealControls() + if !accepted { revealControls() } return accepted } @@ -1328,7 +1328,7 @@ private struct CaptionSelector: NSViewRepresentable { imageScaling = .scaleProportionallyUpOrDown (cell as? NSPopUpButtonCell)?.arrowPosition = .noArrow let symbol = NSImage(systemSymbolName: "captions.bubble", accessibilityDescription: "Subtitles")? - .withSymbolConfiguration(.init(pointSize: 17, weight: .regular)) + .withSymbolConfiguration(.init(pointSize: 16, weight: .regular)) captionItem.image = symbol contentTintColor = SunsetPalette.text toolTip = "Subtitles" @@ -1591,7 +1591,7 @@ private struct PlayerContent: View { PlayerActionButton(model: model, action: model.onTopAction, title: model.isOnTop ? "Stop Keeping Window on Top" : "Keep Window on Top") } label: { - Image(systemName: "gearshape").font(.system(size: 17)).frame(width: 24, height: 24) + Image(systemName: "gearshape").font(.system(size: model.isCompact ? 21 : 17)).frame(width: 24, height: 24) } .menuStyle(.borderlessButton) .menuIndicator(.hidden) @@ -1738,10 +1738,15 @@ private struct PlayerContent: View { .font(.system(size: max(16, min(28, geometry.size.width / 32)), weight: .semibold)) .foregroundStyle(.white) .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + .frame(maxWidth: .infinity) .padding(12) - .background(PlayerGlass().clipShape(RoundedRectangle(cornerRadius: 12)).allowsHitTesting(false)) - .padding(.horizontal, 24) - .padding(.bottom, model.controlsVisible ? 110 : 24) + .background(PlayerGlass().opacity(0.6) + .clipShape(RoundedRectangle(cornerRadius: 12)).allowsHitTesting(false)) + .padding(.horizontal, 8) + .padding(.bottom, model.controlsVisible + ? (model.isCompact ? compactControlHeight + 20 : 110) + : (model.isCompact ? 12 : 24)) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom) .allowsHitTesting(false) }