feat(slint): release sections render as wrapping grids, not carousels

Tauri's artist page lays Discography / EPs & Singles / Live Albums /
Compilations / Others as grids that wrap into rows — never as
horizontally paginated carousels. The Slint port was reusing the
Discover Carousel here, which paginated horizontally and hid most of
the discography behind chevrons.

New `ui/artist/ReleaseGrid.slint`, modeled on `discover/GridSection`:
  - Same DiscoverSection input + album-clicked / media-action
    callbacks the Carousel exposed, so swapping is one import + one
    component name in ArtistPageView.
  - Card grid uses absolute positioning inside a sized Rectangle so a
    `for` can render a flow that Slint's layout primitives don't
    support directly. Columns recompute on resize:
      max(1, floor((width + gap) / (card_width + gap)))
    Card sizes match the Carousel for visual continuity (220x266 +
    24px gap), so the wider viewport lands ~5 cards per row, the
    narrower one ~2.
  - Header shows the section title and the count next to it
    (mirrors Tauri's `<count>` badge); no sort dropdown yet — per-
    section sorting can land in a follow-up.

cc
This commit is contained in:
vicrodh committed 2026-05-20 11:22:38 -06:00
1 parent 275d0e8121
commit 6642b5ef13
2 files changed
+75 -2

No files matched your search

@@ -22,7 +22,7 @@ import { ContextMenuItem } from "../primitives/ContextMenuItem.slint";
import { TrackPlayCell } from "../primitives/TrackPlayCell.slint";
import { TrackContextMenu } from "../primitives/TrackContextMenu.slint";
import { LoadingSpinner } from "../primitives/LoadingSpinner.slint";
import { Carousel } from "../discover/Carousel.slint";
import { ReleaseGrid } from "ReleaseGrid.slint";
import { NavButtons } from "../shell/NavButtons.slint";
component CircleAction inherits Rectangle {
@@ -569,7 +569,7 @@ export component ArtistPageView inherits Rectangle {
for section in ArtistState.release-sections: VerticalLayout {
spacing: 0px;
Rectangle { height: 32px; }
Carousel {
ReleaseGrid {
section: section;
album-clicked(id) => {
root.open-album(id);
@@ -0,0 +1,73 @@
// Wrapping grid of AlbumCards for the artist page's release sections
// (Discography, EPs & Singles, Live Albums, Compilations, Others).
//
// The Tauri artist view lays these as grids — never as horizontally
// paginated carousels — so a long discography wraps into rows and the
// whole shape stays scannable. Pattern mirrors `discover/GridSection`:
// absolute positioning inside a Rectangle whose height comes from the
// row count, computed once per resize.
import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint";
import { DiscoverSection } from "../state.slint";
import { AlbumCard } from "../discover/AlbumCard.slint";
export component ReleaseGrid inherits VerticalLayout {
in property <DiscoverSection> section;
callback album-clicked(string);
callback media-action(string, string, string);
property <length> card-width: 220px;
property <length> card-height: 266px;
property <length> gap: 24px;
property <int> count: root.section.albums.length;
spacing: 12px;
// --- Header: title + count -------------------------------------------
HorizontalLayout {
Text {
text: root.section.title;
color: Theme.text-primary;
font-size: Typography.heading;
font-weight: Typography.semibold;
vertical-alignment: center;
horizontal-stretch: 1;
}
if root.count > 0: Text {
text: " " + root.count;
color: Theme.text-muted;
font-size: Typography.body;
font-weight: Typography.regular;
vertical-alignment: center;
}
}
// --- Grid: absolute-positioned cards inside a sized Rectangle --------
grid := Rectangle {
// Columns recomputed per resize so the grid flows from 5 wide
// at large viewports down to 2 on narrow ones.
property <int> columns: Math.max(
1,
Math.floor((self.width + root.gap) / (root.card-width + root.gap)));
property <int> rows: Math.ceil(root.count / self.columns);
height: self.rows > 0
? self.rows * root.card-height + Math.max(0, self.rows - 1) * root.gap
: 0px;
for album[i] in root.section.albums: AlbumCard {
x: Math.mod(i, grid.columns) * (root.card-width + root.gap);
y: Math.floor(i / grid.columns) * (root.card-height + root.gap);
width: root.card-width;
height: root.card-height;
album: album;
clicked(id) => {
root.album-clicked(id);
}
media-action(kind, id, action) => {
root.media-action(kind, id, action);
}
}
}
}