ci / bun-nix (pull_request) Successful in 49s
ci / web (pull_request) Successful in 1m3s
ci / docs-site (pull_request) Successful in 1m16s
apple / swift (pull_request) Successful in 1m33s
apple / screenshots (pull_request) Skipped
ci / rust-arm64 (pull_request) Successful in 4m38s
ci / rust (pull_request) Successful in 16m9s
Four reworks from the first palette-era on-glass review, all iOS-facing: - Surfaces carry the palette now, not just the text on them: ConsoleGlass washes every tier (Liquid Glass tint, pre-26 material, tvOS material) with ink.glass — the same colour the desktop console fills its panels with — and the close buttons move to an ink-aware consoleGlassBackground. The pre-26 branch also gains the focus tint it had silently dropped. Stray literals follow: ConnectOverlay text rides ink in the console takeover, card shadows soften on pale fields, the focused keycap reads onAccent. The online pip stays status-green on purpose. - The header breathes: title top padding 4/10 -> 10/18 plus shared header-spacing and title-bottom helpers mapped from the console shell's rhythm, applied to the launcher, settings and add-host alike, with the add-host close X re-anchored to the title row. - Settings, Add Host and the Library present IN PLACE on iOS: one persistent aurora whose calm is chased (the console's bg_mix), screens as transparent layers with the console's 0.26 s ease-out-cubic push/pop, an input drop for the transition, and the controller handed off through isActive — no more opaque bottom-up covers, no backdrop teardown. macOS keeps its sheets, tvOS its focus-engine covers. - The settings select is a real band: choice rows mount GamepadOptionBand, a spring-driven drum (Animatable body, ring-distance wrap, neighbours gated by focus and flight) whose retargeting spring accumulates rapid steps into one continuous spin. Reduce Motion falls back to a plain crossfade; toggles keep the quiet 14 pt slip. Verified: swift build (macOS), swift build --triple arm64-apple-ios17.0, swift test 208 passed / 0 failed. On-glass QA still owed: palette sweep on a pale palette, transition compositing over materials, drum feel on device.
102 lines
4.7 KiB
Swift
102 lines
4.7 KiB
Swift
// The ink the gamepad UI draws with under the chosen background palette.
|
|
//
|
|
// The console screens were white-on-dark throughout with the brand violet hardcoded as the
|
|
// accent. Both had to become palette-derived at once: a pale field needs dark text or it is
|
|
// unreadable, and a violet focus wash on a copper field is exactly the clash this exists to fix.
|
|
//
|
|
// Handed down the view tree as an environment value rather than passed to each screen, so a
|
|
// leaf (a row, a hint pill, a card) can ask for the right colour without every caller in between
|
|
// knowing about palettes. `pf-console-ui` does the same thing with a thread-local `Ink`.
|
|
|
|
import PunktfunkShared
|
|
import SwiftUI
|
|
|
|
#if os(iOS) || os(macOS) || os(tvOS)
|
|
|
|
struct GamepadInk: Equatable, Sendable {
|
|
/// Primary text/glyph colour.
|
|
let fg: Color
|
|
/// Focus wash, selected tab pill, switch track, caret — the palette's own accent.
|
|
let accent: Color
|
|
/// What reads ON the accent (a filled pill's label).
|
|
let onAccent: Color
|
|
/// The base fill every glass surface starts from.
|
|
let glass: Color
|
|
/// What a wash laid UNDER text tends toward: black on a dark field, white on a pale one.
|
|
let shade: Color
|
|
/// How hard those washes go. A pale field needs far less — mixing toward white at the dark
|
|
/// field's strength bleaches the chroma straight out of the gradient.
|
|
let shadeScale: Double
|
|
/// True when the field is pale, for the few places that need to branch rather than blend
|
|
/// (a material's `colorScheme`, a shadow's presence).
|
|
let isLight: Bool
|
|
|
|
/// The foreground at `alpha`.
|
|
func fg(_ alpha: Double) -> Color { fg.opacity(alpha) }
|
|
/// The accent at `alpha`.
|
|
func accent(_ alpha: Double) -> Color { accent.opacity(alpha) }
|
|
/// A wash under text: `alpha` is the dark-field strength, scaled for a pale one.
|
|
func shade(_ alpha: Double) -> Color { shade.opacity(alpha * shadeScale) }
|
|
/// The glass base at `alpha` — what a surface's material is washed with so it carries the
|
|
/// palette's hue (the console fills its panels with exactly this colour).
|
|
func glass(_ alpha: Double) -> Color { glass.opacity(alpha) }
|
|
/// A drop shadow: always black — a white shadow is not a shadow — but softened on a pale
|
|
/// field, where full-strength black under every card reads as a smear rather than depth.
|
|
func shadow(_ alpha: Double) -> Color { .black.opacity(alpha * (isLight ? 0.4 : 1)) }
|
|
|
|
static func of(_ p: GamepadPalette) -> GamepadInk {
|
|
let accent = Color(red: p.accent.x, green: p.accent.y, blue: p.accent.z)
|
|
let accentLuma = 0.2126 * p.accent.x + 0.7152 * p.accent.y + 0.0722 * p.accent.z
|
|
// Chosen by luminance, not by `light`: an accent is picked for contrast against the
|
|
// GLASS, not against the field.
|
|
let onAccent: Color = accentLuma > 0.55 ? .black : .white
|
|
guard p.light else {
|
|
return GamepadInk(
|
|
fg: .white, accent: accent, onAccent: onAccent,
|
|
glass: Color(red: 0.086, green: 0.086, blue: 0.125),
|
|
shade: .black, shadeScale: 1, isLight: false)
|
|
}
|
|
return GamepadInk(
|
|
// Tinted toward the palette's own ground so it doesn't read as a foreign grey.
|
|
fg: Color(red: p.ground.x * 0.16, green: p.ground.y * 0.14, blue: p.ground.z * 0.20),
|
|
accent: accent, onAccent: onAccent,
|
|
glass: .white,
|
|
shade: .white, shadeScale: 0.45, isLight: true)
|
|
}
|
|
|
|
/// The shipped dark look — what a preview or a test composition gets.
|
|
static let dark = GamepadInk.of(GamepadPalette.named("violet"))
|
|
|
|
/// The online pip — deliberately NOT palette-derived: a status colour must not change
|
|
/// meaning with the wallpaper (the console's rule; this is its `ONLINE_GREEN` verbatim).
|
|
static let onlineGreen = Color(red: 0.20, green: 0.84, blue: 0.29)
|
|
}
|
|
|
|
private struct GamepadInkKey: EnvironmentKey {
|
|
static let defaultValue = GamepadInk.dark
|
|
}
|
|
|
|
extension EnvironmentValues {
|
|
/// The ink of the palette currently drawing. Set once, high up (see `GamepadInkModifier`).
|
|
var gamepadInk: GamepadInk {
|
|
get { self[GamepadInkKey.self] }
|
|
set { self[GamepadInkKey.self] = newValue }
|
|
}
|
|
}
|
|
|
|
extension View {
|
|
/// Resolve the stored `ui_palette` and publish its ink to everything below. Applied by the
|
|
/// gamepad screens' common root so no individual view has to read the setting.
|
|
func gamepadPaletteInk() -> some View { modifier(GamepadInkModifier()) }
|
|
}
|
|
|
|
private struct GamepadInkModifier: ViewModifier {
|
|
@AppStorage(DefaultsKey.uiPalette) private var paletteID = "violet"
|
|
|
|
func body(content: Content) -> some View {
|
|
content.environment(\.gamepadInk, GamepadInk.of(GamepadPalette.named(paletteID)))
|
|
}
|
|
}
|
|
|
|
#endif
|