feat(slint): favorites Artists A-Z grouping + alpha strip

Add the optional A-Z group toggle to the Artists grid (Group: Off/A-Z
QbzSelect, default Off, persisted via favorites_prefs artists_group).
derive_artists now buckets the filtered set into FavArtistSection letter
groups (# first, then A-Z) and builds the alpha-jump model; the grouped grid
renders per-letter headers + sub-grids and an AlphaStrip on the right that
jumps the Flickable proportionally (mirrors the Albums alpha grouping).
Also renames the favorites view header title "Favorites" -> "Library" to
match Qobuz.

cc
This commit is contained in:
vicrodh committed 2026-05-27 15:32:15 -06:00
1 parent da683cd5d0
commit 41692012ee
4 files changed
+150 -22

No files matched your search

@@ -567,7 +567,7 @@ export component FavoritesView inherits Rectangle {
VerticalLayout {
alignment: center;
Text {
text: @tr("Favorites");
text: @tr("Library");
color: Theme.text-primary;
font-size: Typography.section;
font-weight: Typography.bold;
@@ -827,6 +827,21 @@ export component FavoritesView inherits Rectangle {
}
}
}
VerticalLayout {
alignment: center;
QbzSelect {
menu-width: 150px;
options: [
@tr("Group: Off"),
@tr("Group: A-Z"),
];
sm: true;
current-index: FavoritesState.artists-group-enabled ? 1 : 0;
selected(i) => {
FavoritesActions.artists-set-group(i == 0 ? "off" : "alpha");
}
}
}
}
}
@@ -1067,14 +1082,8 @@ export component FavoritesView inherits Rectangle {
}
// --- Artists tab ---
if FavoritesState.active-tab == "artists": artists-grid := Rectangle {
property <int> columns: Math.max(
1,
Math.floor((self.width + root.gap) / (root.artist-w + root.gap)));
property <int> rows: Math.ceil(FavoritesState.artists-visible.length / self.columns);
height: self.rows > 0
? self.rows * root.artist-h + Math.max(0, self.rows - 1) * root.gap
: 0px;
if FavoritesState.active-tab == "artists": VerticalLayout {
spacing: 0px;
if FavoritesState.artists.length == 0: Text {
text: @tr("No favorite artists yet.");
color: Theme.text-muted;
@@ -1086,11 +1095,49 @@ export component FavoritesView inherits Rectangle {
color: Theme.text-muted;
font-size: Typography.body;
}
for artist[i] in FavoritesState.artists-visible: FavArtistCard {
x: Math.mod(i, artists-grid.columns) * (root.artist-w + root.gap);
y: Math.floor(i / artists-grid.columns) * (root.artist-h + root.gap);
item: artist;
clicked(id) => { root.open-artist(id); }
// Flat grid (grouping off).
if !FavoritesState.artists-group-enabled: artists-grid := Rectangle {
property <int> columns: Math.max(
1,
Math.floor((self.width + root.gap) / (root.artist-w + root.gap)));
property <int> rows: Math.ceil(FavoritesState.artists-visible.length / self.columns);
height: self.rows > 0
? self.rows * root.artist-h + Math.max(0, self.rows - 1) * root.gap
: 0px;
for artist[i] in FavoritesState.artists-visible: FavArtistCard {
x: Math.mod(i, artists-grid.columns) * (root.artist-w + root.gap);
y: Math.floor(i / artists-grid.columns) * (root.artist-h + root.gap);
item: artist;
clicked(id) => { root.open-artist(id); }
}
}
// Grouped (A-Z) — sections with letter headers.
if FavoritesState.artists-group-enabled: VerticalLayout {
spacing: 10px;
for section[s] in FavoritesState.artists-grouped: VerticalLayout {
spacing: 6px;
Text {
text: section.title;
color: Theme.text-secondary;
font-size: Typography.legal;
font-weight: Typography.semibold;
}
section-grid := Rectangle {
property <int> columns: Math.max(
1,
Math.floor((self.width + root.gap) / (root.artist-w + root.gap)));
property <int> rows: Math.ceil(section.artists.length / self.columns);
height: self.rows > 0
? self.rows * root.artist-h + Math.max(0, self.rows - 1) * root.gap
: 0px;
for artist[i] in section.artists: FavArtistCard {
x: Math.mod(i, section-grid.columns) * (root.artist-w + root.gap);
y: Math.floor(i / section-grid.columns) * (root.artist-h + root.gap);
item: artist;
clicked(id) => { root.open-artist(id); }
}
}
}
}
}
@@ -1221,6 +1268,18 @@ export component FavoritesView inherits Rectangle {
* Math.max(0, (other-flick.viewport-height - other-flick.height) / 1px) * 1px;
}
}
// A-Z jump strip (artists grouping) — proportional jump.
if FavoritesState.active-tab == "artists"
&& FavoritesState.artists-group-enabled
&& FavoritesState.artists-alpha.length > 0: AlphaStrip {
x: parent.width - self.width - 20px;
height: 100%;
jumps: FavoritesState.artists-alpha;
jump(ord, idx) => {
other-flick.viewport-y = -(ord / Math.max(1, FavoritesState.artists-alpha.length - 1))
* Math.max(0, (other-flick.viewport-height - other-flick.height) / 1px) * 1px;
}
}
}
}
}