Clicking an album card opens the album detail view: a back button, the album header (224px artwork, title, artist, and info line — year, label, genre, track count, duration — plus play / shuffle / favorite / more action buttons), and the track list with a column header (#, title, artist, duration, quality). - album.rs: fetches the album via QbzCore::get_album, maps it (info line, per-track quality tier, durations) onto the AlbumState global, and loads the header artwork through the shared image cache. - AlbumPageView.slint, with NavState driving the shell content region between Home and the album page. - Album clicks bubble AlbumCard -> Carousel -> HomeView -> AppShell -> AppWindow -> main; the back button returns to Home. cc
124 lines
3.9 KiB
Plaintext
124 lines
3.9 KiB
Plaintext
// Discover section carousel — a title, prev/next page controls, and a
|
||
// clipped, sliding track of album cards.
|
||
//
|
||
// Pagination by slice (no scrollbar), mirroring the Svelte
|
||
// DiscoverySection. Because there is no ScrollView the height is exactly
|
||
// the card height — nothing injects extra vertical space.
|
||
|
||
import { Theme } from "../foundation/semantic-colors.slint";
|
||
import { Typography } from "../foundation/typography.slint";
|
||
import { DiscoverSection } from "../state.slint";
|
||
import { AlbumCard } from "AlbumCard.slint";
|
||
|
||
component NavButton inherits Rectangle {
|
||
in property <string> glyph;
|
||
in property <bool> enabled: true;
|
||
callback clicked();
|
||
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 14px;
|
||
opacity: root.enabled ? 1.0 : 0.4;
|
||
background: ta.has-hover && root.enabled
|
||
? Theme.surface-hover
|
||
: Theme.surface-elevated;
|
||
|
||
Text {
|
||
text: root.glyph;
|
||
color: root.enabled ? Theme.text-primary : Theme.text-muted;
|
||
font-size: 16px;
|
||
horizontal-alignment: center;
|
||
vertical-alignment: center;
|
||
}
|
||
|
||
ta := TouchArea {
|
||
enabled: root.enabled;
|
||
mouse-cursor: root.enabled ? pointer : default;
|
||
clicked => {
|
||
root.clicked();
|
||
}
|
||
}
|
||
}
|
||
|
||
export component Carousel inherits VerticalLayout {
|
||
in property <DiscoverSection> section;
|
||
callback album-clicked(string);
|
||
|
||
property <length> card-width: 220px;
|
||
property <length> card-height: 266px;
|
||
property <length> gap: 32px;
|
||
|
||
property <int> count: root.section.albums.length;
|
||
property <int> per-page: Math.max(
|
||
1,
|
||
Math.floor((viewport.width + root.gap) / (root.card-width + root.gap)));
|
||
property <int> page-count: Math.max(1, Math.ceil(root.count / root.per-page));
|
||
property <int> page: 0;
|
||
// Self-correcting page index — stays valid when the window resizes.
|
||
property <int> current-page: Math.min(Math.max(0, root.page), root.page-count - 1);
|
||
property <length> step: root.per-page * (root.card-width + root.gap);
|
||
property <length> content-width:
|
||
root.count * root.card-width + Math.max(0, root.count - 1) * root.gap;
|
||
|
||
spacing: 12px;
|
||
|
||
// --- Header: title + page controls -----------------------------------
|
||
HorizontalLayout {
|
||
Text {
|
||
text: root.section.title;
|
||
color: Theme.text-primary;
|
||
font-size: 18px;
|
||
font-weight: Typography.semibold;
|
||
vertical-alignment: center;
|
||
horizontal-stretch: 1;
|
||
}
|
||
HorizontalLayout {
|
||
spacing: 4px;
|
||
VerticalLayout {
|
||
alignment: center;
|
||
NavButton {
|
||
glyph: "‹";
|
||
enabled: root.current-page > 0;
|
||
clicked => {
|
||
root.page = root.current-page - 1;
|
||
}
|
||
}
|
||
}
|
||
VerticalLayout {
|
||
alignment: center;
|
||
NavButton {
|
||
glyph: "›";
|
||
enabled: root.current-page < root.page-count - 1;
|
||
clicked => {
|
||
root.page = root.current-page + 1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- Track: clipped viewport, slides by page -------------------------
|
||
viewport := Rectangle {
|
||
height: root.card-height;
|
||
clip: true;
|
||
|
||
Rectangle {
|
||
x: -root.current-page * root.step;
|
||
y: 0px;
|
||
width: root.content-width;
|
||
height: root.card-height;
|
||
animate x { duration: 220ms; easing: ease-in-out; }
|
||
|
||
HorizontalLayout {
|
||
spacing: root.gap;
|
||
for album in root.section.albums: AlbumCard {
|
||
album: album;
|
||
clicked(id) => {
|
||
root.album-clicked(id);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|