feat(kiosk): redesign Now Playing panel — dominant contain cover, compact meta, floating quality stamp
The cover is image-fit:contain filling the flex art area (no fragile parent-geometry math), so it is large and scales with the window. Toggle and meta rows get vertical-stretch:0 so only the art area takes the slack (nested layouts stretch greedily) — kills the dead space and the ~55px cover. Title toned to 20px, quality stamp floats on the transport row instead of owning a line. Cover<->Lyrics toggle; lyrics fill the panel width. cc
This commit is contained in:
1 file changed
+97
-88
@@ -129,12 +129,16 @@ export component KioskNowPlaying inherits Rectangle {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
horizontal-stretch: 1;
|
horizontal-stretch: 1;
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
padding: 26px;
|
padding: 18px;
|
||||||
spacing: 16px;
|
spacing: 12px;
|
||||||
|
|
||||||
// Cover / Lyrics toggle — pinned to the TOP so it lines up with
|
// Cover / Lyrics toggle — pinned to the TOP so it lines up with
|
||||||
// the Up Next / History tabs on the right panel.
|
// the Up Next / History tabs on the right panel. vertical-stretch:0
|
||||||
|
// is REQUIRED — a nested layout defaults to greedy stretch in
|
||||||
|
// Slint, so without it the tabs row eats vertical space and pushes
|
||||||
|
// the cover down (dead space above it).
|
||||||
HorizontalLayout {
|
HorizontalLayout {
|
||||||
|
vertical-stretch: 0;
|
||||||
alignment: start;
|
alignment: start;
|
||||||
spacing: 18px;
|
spacing: 18px;
|
||||||
QueueTab {
|
QueueTab {
|
||||||
@@ -158,98 +162,94 @@ export component KioskNowPlaying inherits Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cover/lyrics + metadata + transport fill the rest, vertically
|
// Flexible art/lyrics area — takes the slack (pins the toggle
|
||||||
// centered — the stretch absorbs the slack so the toggle above
|
// above and the transport below) and CLIPS, so on a short window
|
||||||
// stays pinned to the top (no dead space over it).
|
// (e.g. 1080x600) the cover shrinks instead of pushing the
|
||||||
VerticalLayout {
|
// controls off-screen.
|
||||||
|
art-area := Rectangle {
|
||||||
vertical-stretch: 1;
|
vertical-stretch: 1;
|
||||||
alignment: center;
|
clip: true;
|
||||||
spacing: 16px;
|
|
||||||
|
|
||||||
if !root.show-lyrics: HorizontalLayout {
|
// Cover: contained so it is as large as fits and scales with
|
||||||
alignment: center;
|
// the window WITHOUT reading parent geometry (the old
|
||||||
Rectangle {
|
// Math.min(art-area.height) collapsed it to ~55px). Fills the
|
||||||
width: 240px;
|
// dominant area — big on tall windows, shrinks on short ones.
|
||||||
height: 240px;
|
if !root.show-lyrics: Image {
|
||||||
border-radius: Radius.lg;
|
width: 100%;
|
||||||
background: Theme.surface-elevated;
|
height: 100%;
|
||||||
clip: true;
|
source: NowPlayingState.artwork-large;
|
||||||
if NowPlayingState.artwork-large.width > 0: Image {
|
image-fit: contain;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
source: NowPlayingState.artwork-large;
|
|
||||||
image-fit: cover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if root.show-lyrics: HorizontalLayout {
|
// Lyrics fill the panel width (no more right-edge clipping) and
|
||||||
alignment: center;
|
// the flexible height. Mount the karaoke view ONLY when lyrics
|
||||||
Rectangle {
|
// are READY (status 2 + lines): instantiating LyricsLinesView
|
||||||
width: 420px;
|
// against empty/loading state during the tap's own relayout
|
||||||
height: 320px;
|
// trips its geometry FOOTGUN (properties.rs:583 "Recursion
|
||||||
// Ask Rust for this track's lyrics when the surface
|
// detected"). status==2 lands async in a settled layout.
|
||||||
// appears (global callback — no geometry read, safe).
|
if root.show-lyrics: Rectangle {
|
||||||
// Mount the karaoke view ONLY when lyrics are READY
|
width: 100%;
|
||||||
// (status 2 + lines) and inside a FIXED-size box:
|
height: 100%;
|
||||||
// instantiating LyricsLinesView against empty/loading
|
init => {
|
||||||
// state or a still-resolving stretch width trips its
|
LyricsState.panel-opened();
|
||||||
// geometry FOOTGUN (properties.rs:583 "Recursion
|
}
|
||||||
// detected" — crash on tap 2026-07-12). status==2 lands
|
if LyricsState.status == 2 && LyricsState.lines.length > 0: LyricsLinesView {
|
||||||
// async in a clean turn; the fixed width keeps
|
width: 100%;
|
||||||
// `changed width` reading a constant. Mirrors the
|
height: 100%;
|
||||||
// desktop LyricsSidebar gate.
|
side-padding: 8px;
|
||||||
init => {
|
line-gap: 13px;
|
||||||
LyricsState.panel-opened();
|
size-inactive: 19px;
|
||||||
}
|
size-active: 23px;
|
||||||
if LyricsState.status == 2 && LyricsState.lines.length > 0: LyricsLinesView {
|
dimming-mode: LyricsState.dimming-mode;
|
||||||
width: 100%;
|
auto-follow: true;
|
||||||
height: 100%;
|
uppercase: LyricsState.uppercase;
|
||||||
side-padding: 10px;
|
show-attribution: false;
|
||||||
line-gap: 14px;
|
font-name: LyricsState.font-index == 1 ? "LINE Seed JP"
|
||||||
size-inactive: 20px;
|
: LyricsState.font-index == 2 ? "Montserrat"
|
||||||
size-active: 25px;
|
: LyricsState.font-index == 3 ? "Noto Sans"
|
||||||
dimming-mode: LyricsState.dimming-mode;
|
: LyricsState.font-index == 4 ? "Source Sans 3"
|
||||||
auto-follow: true;
|
: "";
|
||||||
uppercase: LyricsState.uppercase;
|
active-color: Theme.accent;
|
||||||
show-attribution: false;
|
}
|
||||||
font-name: LyricsState.font-index == 1 ? "LINE Seed JP"
|
if !(LyricsState.status == 2 && LyricsState.lines.length > 0): Text {
|
||||||
: LyricsState.font-index == 2 ? "Montserrat"
|
width: 100%;
|
||||||
: LyricsState.font-index == 3 ? "Noto Sans"
|
height: 100%;
|
||||||
: LyricsState.font-index == 4 ? "Source Sans 3"
|
text: LyricsState.status == 5 ? @tr("Lyrics unavailable offline")
|
||||||
: "";
|
: (LyricsState.status == 3 || LyricsState.status == 4) ? @tr("No lyrics available")
|
||||||
active-color: Theme.accent;
|
: @tr("Fetching lyrics...");
|
||||||
}
|
color: Theme.text-muted;
|
||||||
if !(LyricsState.status == 2 && LyricsState.lines.length > 0): Text {
|
font-size: 14px;
|
||||||
width: 100%;
|
horizontal-alignment: center;
|
||||||
height: 100%;
|
vertical-alignment: center;
|
||||||
text: LyricsState.status == 5 ? @tr("Lyrics unavailable offline")
|
|
||||||
: (LyricsState.status == 3 || LyricsState.status == 4) ? @tr("No lyrics available")
|
|
||||||
: @tr("Fetching lyrics...");
|
|
||||||
color: Theme.text-muted;
|
|
||||||
font-size: 14px;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pinned metadata + transport — always on screen even when the
|
||||||
|
// art area is squeezed on a short window. vertical-stretch:0 keeps
|
||||||
|
// it natural-height at the bottom (nested layouts stretch greedily
|
||||||
|
// by default) so ALL slack goes to the art area above.
|
||||||
|
VerticalLayout {
|
||||||
|
vertical-stretch: 0;
|
||||||
|
spacing: 8px;
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: NowPlayingState.title;
|
text: NowPlayingState.title;
|
||||||
color: Theme.text-primary;
|
color: Theme.text-primary;
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
wrap: word-wrap;
|
overflow: elide;
|
||||||
|
wrap: no-wrap;
|
||||||
}
|
}
|
||||||
artist-row := Rectangle {
|
artist-row := Rectangle {
|
||||||
height: 24px;
|
height: 18px;
|
||||||
HorizontalLayout {
|
HorizontalLayout {
|
||||||
alignment: center;
|
alignment: center;
|
||||||
Text {
|
Text {
|
||||||
text: NowPlayingState.artist;
|
text: NowPlayingState.artist;
|
||||||
color: Theme.text-secondary;
|
color: Theme.text-secondary;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,11 +260,6 @@ export component KioskNowPlaying inherits Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalLayout {
|
|
||||||
alignment: center;
|
|
||||||
AudioStamp { }
|
|
||||||
}
|
|
||||||
|
|
||||||
SeekBar {
|
SeekBar {
|
||||||
show-times: true;
|
show-times: true;
|
||||||
progress: NowPlayingState.progress;
|
progress: NowPlayingState.progress;
|
||||||
@@ -278,13 +273,27 @@ export component KioskNowPlaying inherits Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalLayout {
|
// Transport centered; the quality stamp floats at the right of
|
||||||
alignment: center;
|
// the same row instead of owning a whole line (saves vertical
|
||||||
TransportControls {
|
// space on short windows).
|
||||||
play-circle: true;
|
Rectangle {
|
||||||
classic-actions: true;
|
height: 46px;
|
||||||
media-action(k, i, a) => {
|
HorizontalLayout {
|
||||||
root.media-action(k, i, a);
|
alignment: center;
|
||||||
|
TransportControls {
|
||||||
|
play-circle: true;
|
||||||
|
classic-actions: true;
|
||||||
|
media-action(k, i, a) => {
|
||||||
|
root.media-action(k, i, a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HorizontalLayout {
|
||||||
|
alignment: end;
|
||||||
|
padding-right: 2px;
|
||||||
|
VerticalLayout {
|
||||||
|
alignment: center;
|
||||||
|
AudioStamp { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user