Add a native Slint front-end for the music player
The React UI is sluggish on the kiosk and the reasons are browser-shaped: a compositor deciding what gets its own layer, a requestAnimationFrame loop repainting the viewport, and a JPEG decode per album card per cold start. The last three commits chased that through the service worker and the cover pipeline. This is the other direction - the same backend, the same layout, the same German copy, without a browser. Browse and play only. The typing game, the room-lights page, parent mode and the IR-remote assignment stay web-only, and nothing here touches python-backend. Four things carry the performance claim, in rough order of how much they should matter: Covers are downscaled once, ever. The backend serves one size - 640px on the long edge, no ?size= - and a grid card is 180. src/covers.rs fetches each cover once on a worker thread, resizes it, and writes a JPEG thumbnail to ~/.cache/musicmouse-slint/covers/. Two tiers and only two, which is what makes the cache worth keeping: a per-widget pixel size would give each album a dozen near-identical files and a fresh decode for each. The cache is dropped when the websocket announces a rescan, because that is the one moment the backend rewrites the art behind an unchanged cover URL - the trap sw.js had to learn about the hard way. The grid is virtualized. Rust hands the UI the album list pre-chunked into rows and the view puts those in a ListView, which instantiates only what is on screen. A flat list of 660 cards gives it no rows to skip, hence the chunking. Same purpose as content-visibility: auto on .grid > .card. The progress bar animates between the 2 Hz pushes rather than running a clock, so interpolation costs a property evaluation per frame on the render side and there is no equivalent of usePlaybackClock. It is suppressed for the frame a track changes on, so a new track jumps instead of sliding across two unrelated positions. Nothing on screen animates by itself. The ambient canvas is not ported, for the reason lib/lowPower.ts already gives: a loop repainting the viewport is a floor you cannot get under while it runs at all. The device must not use FemtoVG. On Mesa V3D it draws every runtime-loaded Image as solid black (slint-ui/slint#11785, open), which here means every album cover; Skia and the software renderer are unaffected. So `kiosk` is linuxkms + Skia and FemtoVG stays the default only for desktop development. Both profiles are verified to build; the kiosk binary links libinput/libgbm/libdrm and no X11 or Wayland at all. lib/search.ts, lib/keyboard.ts and lib/format.ts were already pure functions with their own tests, so they port across as pure Rust with theirs: 43 tests, no window required. The .slint files are layout only - nothing in them formats a number or picks a word. Verified against the real 660-album library rather than a fixture. tools/headless-shots.sh renders the UI inside a nested headless compositor and grabs a frame per screen, which is how that was checked on a machine whose session was locked; a Slint window needs a real compositor, and under bare Xvfb nothing maps at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
65
slint-frontend/Cargo.toml
Normal file
65
slint-frontend/Cargo.toml
Normal file
@@ -0,0 +1,65 @@
|
||||
[package]
|
||||
name = "musicmouse-slint"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Native Slint front-end for the MusicMouse player"
|
||||
|
||||
[dependencies]
|
||||
# default-features off so the renderer is an explicit choice below, never an
|
||||
# accident: on a Pi 5 (Mesa V3D) the default FemtoVG renderer draws every runtime
|
||||
# loaded Image as solid black - see slint-ui/slint#11785 - which for this app means
|
||||
# every album cover. The `skia` feature is the device's way out.
|
||||
slint = { version = "1.15", default-features = false, features = [
|
||||
"compat-1-2",
|
||||
"std",
|
||||
] }
|
||||
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
# Blocking HTTP and blocking websockets, each on its own thread, rather than an async
|
||||
# runtime. The whole client makes one library request, one websocket connection and a
|
||||
# trickle of covers; tokio would be more machinery than the job has work.
|
||||
ureq = { version = "2.12", default-features = false, features = ["json", "gzip"] }
|
||||
tungstenite = { version = "0.24", default-features = false, features = ["handshake"] }
|
||||
image = { version = "0.25", default-features = false, features = ["jpeg", "png"] }
|
||||
dirs = "5"
|
||||
# Only for `normalize()`: folding "Grüffelo" to "gruffelo" needs a real NFD pass, which
|
||||
# is what the web client gets from String.prototype.normalize for free.
|
||||
unicode-normalization = "0.1"
|
||||
|
||||
[build-dependencies]
|
||||
slint-build = "1.15"
|
||||
|
||||
[features]
|
||||
default = ["desktop"]
|
||||
|
||||
# Development on a normal desktop session. FemtoVG is fine on x86/Mesa and builds in
|
||||
# seconds, which is what makes it the right default *here* and the wrong one on the Pi.
|
||||
# `renderer-software` rides along so `SLINT_BACKEND=winit-software` works without a
|
||||
# rebuild: it is how the UI can be rendered and screenshotted on a machine with no GPU
|
||||
# (or no session), and it is the fallback the Pi 5 FemtoVG bug leaves standing.
|
||||
desktop = ["slint/backend-winit", "slint/renderer-femtovg", "slint/renderer-software"]
|
||||
|
||||
# Same window, Skia instead - the combination the device runs, so visual checks here
|
||||
# match what ships. Costs a long first build while rust-skia bootstraps.
|
||||
skia = ["slint/backend-winit", "slint/renderer-skia"]
|
||||
|
||||
# The device: straight onto KMS/DRM with no X, no Wayland and no compositor, input
|
||||
# via libinput. (`backend-linuxkms-noseat` is deprecated upstream in favour of this.)
|
||||
#
|
||||
# Build it with `--no-default-features --features kiosk`: cargo features are additive,
|
||||
# so leaving `desktop` enabled links winit and linuxkms into the same binary.
|
||||
kiosk = ["slint/backend-linuxkms-libinput", "slint/renderer-skia"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
# The UI is laid out and hit-tested in debug builds too; an unoptimised image resize
|
||||
# on a 343-album first run is otherwise the slowest thing in the app by far.
|
||||
[profile.dev.package.image]
|
||||
opt-level = 3
|
||||
[profile.dev.package.slint]
|
||||
opt-level = 2
|
||||
Reference in New Issue
Block a user