3ba19f28a2
apple / swift (push) Waiting to run
apple / screenshots (push) Blocked by required conditions
release / apple (push) Waiting to run
ci / rust (push) Failing after 50s
ci / web (push) Successful in 57s
decky / build-publish (push) Successful in 14s
ci / docs-site (push) Successful in 1m2s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 29s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 6s
flatpak / build-publish (push) Failing after 2m14s
ci / bench (push) Successful in 5m34s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5m48s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5m46s
docker / deploy-docs (push) Successful in 23s
windows-host / package (push) Successful in 11m40s
arch / build-publish (push) Successful in 18m10s
deb / build-publish (push) Successful in 18m9s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 4m21s
android / android (push) Successful in 21m4s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 17m2s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 19m37s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 5m38s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m3s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 1m2s
The console UI now runs on tvOS through the NATIVE focus engine: carousel cards and settings rows are focusable Buttons (Siri Remote and pads both navigate; imperative scrollTo replaces the drop-prone scrollPosition binding), while iOS/macOS keep the 60 Hz poll untouched - on tvOS it carries only what focus has no concept of: X/Y screen actions and left/right value adjust with the poll's dominant-axis feel (onMoveCommand proved input-source-dependent: keyboard intercepted, pad dpad not -> double steps). Text entry uses the system fullscreen keyboard (TVTextEntry); pairing + library present as covers under the launcher; the game library defaults ON; settings values slide a quiet 14 pt in the step's direction. Session controls: controller/remote input routes EXCLUSIVELY through GameController during a stream (GCEventViewController, interaction disabled) - a pad's B no longer doubles as a UIKit menu press that ended sessions mid-game. Deliberate exits only: the cross-client escape chord (hold L1+R1+Start+Select 1.5 s - pf-client-core's contract, now implemented on all Apple platforms) and holding the remote's Back >= 1 s; the start-of-stream banner (now also on tvOS) teaches both. The Siri Remote's touch surface drives the host pointer - press = left click, Play/Pause = right click, release-tail jumps gated so motion stays truly relative. tvOS 26 regressions fixed at the root: the app-wide brand tint rendered every unfocused control as a blank pill (tint dropped on tvOS) and the 17 pt root font shrank the whole platform (29 pt there), plus 10-foot sizing across host cards, the gamepad screens, and the stats HUD (whose misleading "Press Menu" hint is gone). Acknowledgements scrolls by focus-sized chunks and Menu pops instead of suspending; full-width focusSections make the home actions reachable from any column. The presenter defaults to stage-3 glass pacing on tvOS (a 60 Hz panel fed a 60 fps stream is the sticky-FIFO worst case behind the 50 ms display stage) and is pickable from the gamepad settings; HDR capability advertises from AVPlayer.eligibleForHDRPlayback instead of the current mode's EDR headroom, so an SDR home screen no longer hides an HDR TV. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
136 lines
5.7 KiB
Swift
136 lines
5.7 KiB
Swift
import PunktfunkKit
|
|
import SwiftUI
|
|
|
|
/// Open-source acknowledgements: Punktfunk's own license (MIT OR Apache-2.0) followed by the
|
|
/// third-party software notices. Used as a pushed view on iOS/tvOS and a preferences tab on macOS.
|
|
struct AcknowledgementsView: View {
|
|
private var version: String? {
|
|
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
|
|
}
|
|
|
|
// TV-legible sizes for the explicitly-sized text; the in-hand sizes elsewhere. (The license
|
|
// walls use relative system styles, which already scale per platform.)
|
|
#if os(tvOS)
|
|
private static let titleFont: CGFloat = 36
|
|
private static let headlineFont: CGFloat = 26
|
|
private static let captionFont: CGFloat = 20
|
|
#else
|
|
private static let titleFont: CGFloat = 22
|
|
private static let headlineFont: CGFloat = 17
|
|
private static let captionFont: CGFloat = 12
|
|
#endif
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
// Top-level LazyVStack so the third-party-notices chunks (Licenses.thirdPartyNoticesChunks,
|
|
// ~885 KB total) load lazily as they scroll into view — a single Text that large overshoots
|
|
// the text-rendering height limit (blank below the limit + very slow). spacing 0 keeps the
|
|
// notice chunks visually continuous; the header block carries its own spacing + bottom pad.
|
|
LazyVStack(alignment: .leading, spacing: 0) {
|
|
VStack(alignment: .leading, spacing: 18) {
|
|
Text("Punktfunk")
|
|
.font(.geist(Self.titleFont, .bold, relativeTo: .title2))
|
|
if let version {
|
|
Text("Version \(version)")
|
|
.font(.geist(Self.captionFont, relativeTo: .caption))
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
LicenseWall(text: Licenses.appLicense)
|
|
.font(.caption.monospaced())
|
|
|
|
Divider()
|
|
|
|
Text("Bundled font")
|
|
.font(.geist(Self.headlineFont, .semibold, relativeTo: .headline))
|
|
Text("Punktfunk ships the Geist typeface (Geist Sans), "
|
|
+ "© The Geist Project Authors / Vercel, used under the SIL Open Font "
|
|
+ "License 1.1.")
|
|
.font(.geist(Self.captionFont, relativeTo: .caption))
|
|
.foregroundStyle(.secondary)
|
|
if !Licenses.fontLicense.isEmpty {
|
|
LicenseWall(text: Licenses.fontLicense)
|
|
.font(.caption2.monospaced())
|
|
}
|
|
|
|
Divider()
|
|
|
|
Text("Third-party software")
|
|
.font(.geist(Self.headlineFont, .semibold, relativeTo: .headline))
|
|
Text(
|
|
"Punktfunk uses the open-source components below, each under its own license. "
|
|
+ "On some platforms FFmpeg is additionally bundled under the LGPL v2.1+ "
|
|
+ "(dynamically linked, replaceable)."
|
|
)
|
|
.font(.geist(Self.captionFont, relativeTo: .caption))
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding(.bottom, 18)
|
|
|
|
ForEach(Licenses.thirdPartyNoticesChunks.indices, id: \.self) { i in
|
|
Text(Licenses.thirdPartyNoticesChunks[i])
|
|
.font(.caption2.monospaced())
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.modifier(SelectableText())
|
|
.modifier(TVFocusable())
|
|
}
|
|
}
|
|
.frame(maxWidth: 900, alignment: .leading)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding()
|
|
#if os(tvOS)
|
|
.padding(40)
|
|
#endif
|
|
}
|
|
.navigationTitle("Acknowledgements")
|
|
}
|
|
}
|
|
|
|
/// `textSelection(.enabled)` is unavailable on tvOS, so apply it only where it exists.
|
|
private struct SelectableText: ViewModifier {
|
|
func body(content: Content) -> some View {
|
|
#if os(tvOS)
|
|
content
|
|
#else
|
|
content.textSelection(.enabled)
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// Focus IS scrolling on tvOS: with nothing focusable in this pushed screen the license wall
|
|
/// couldn't move at all, and a Menu press had nothing inside the NavigationStack to route
|
|
/// through — it suspended the whole app instead of popping. Plain (non-interactive) focusability
|
|
/// on every license/notice chunk fixes both; a chunk is sized to about two thirds of a screen
|
|
/// (see Licenses.chunked), so each focus step reads as a page turn. The chunks must be SMALL
|
|
/// focus stops all the way down — one tall focusable block would strand focus at its top and the
|
|
/// next stop could sit past the LazyVStack's instantiation window.
|
|
private struct TVFocusable: ViewModifier {
|
|
func body(content: Content) -> some View {
|
|
#if os(tvOS)
|
|
content.focusable()
|
|
#else
|
|
content
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// One license wall: a single selectable Text on touch/desktop; on tvOS, focus-page-sized
|
|
/// chunks (see TVFocusable). The caller's `.font` cascades into either form.
|
|
private struct LicenseWall: View {
|
|
let text: String
|
|
|
|
var body: some View {
|
|
#if os(tvOS)
|
|
let chunks = Licenses.chunked(text)
|
|
ForEach(chunks.indices, id: \.self) { i in
|
|
Text(chunks[i])
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.modifier(TVFocusable())
|
|
}
|
|
#else
|
|
Text(text)
|
|
.modifier(SelectableText())
|
|
#endif
|
|
}
|
|
}
|