Ephemeral folders: open / browse / play a folder without indexing it — album-grouped pane, CUE seek fast-path (no full re-decode per virtual track), folder-path persistence + rehydrate, "already playing" add-vs-replace choice dialog (ephemeral-only), and now-playing / queue / menu gating so an ephemeral track exposes no persistence actions. Folders tree brought 1:1 with Tauri: - Rail header: select-mode, collapse-all, open-ephemeral, tree search. - Resizable rail (default ~20% narrower; drag handle, min 50% default / max 50% of the content area). - Subfolder cover grid + grid/list toggle, recursive track count, name search; compact icon bulk bar (folder-check / play-next / queue / playlist / clear). - Tree is the default mode; circular checkboxes homologated with TrackRow; compact rows (smaller type + icon, no ambiguous trailing count). - Flat/Tree icon toggle (disc-album / folder-tree), floated right. Per-source TrackRow context menu (logic, not a blanket trim): Qobuz/offline = full; local = play/next/queue + add-to-playlist; plex/ephemeral = play/next/ queue only. App-wide standard: Math.round pixel-snap on icon centering across the UI to kill sub-pixel blur (CircleAction, header back/forward/collapse, and the rest). NowPlayingBar context icon -> turntable (frees disc-album for the Folders toggle). New assets: folder-check, folder-tree, turntable. cc
195 lines
6.7 KiB
Plaintext
195 lines
6.7 KiB
Plaintext
// Discover > For You "Spotlight" — port of Tauri's SpotlightLite.
|
|
// One favorite artist highlighted: a header, a hero (portrait +
|
|
// ARTIST + name + play-top-tracks / open-artist circle buttons), and
|
|
// a draggable row mixing a TOP TRACKS card, a RADIO card, and the
|
|
// artist's albums.
|
|
|
|
import { Theme } from "../foundation/semantic-colors.slint";
|
|
import { Typography } from "../foundation/typography.slint";
|
|
import { ForYouState, AlbumCardItem } from "../state.slint";
|
|
import { AlbumCard } from "AlbumCard.slint";
|
|
import { RadioCard } from "RadioCard.slint";
|
|
import { QbzIcon } from "../primitives/QbzIcon.slint";
|
|
|
|
component CircleButton inherits Rectangle {
|
|
in property <image> icon;
|
|
in property <bool> primary: false;
|
|
callback clicked();
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 22px;
|
|
background: root.primary
|
|
? (ta.has-hover ? #ffffffd6 : Theme.accent)
|
|
: (ta.has-hover ? Theme.surface-hover : Theme.surface-elevated);
|
|
QbzIcon {
|
|
source: root.icon;
|
|
width: root.primary ? 18px : 16px;
|
|
height: root.primary ? 18px : 16px;
|
|
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
|
|
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
|
|
tint: root.primary ? #ffffff : Theme.text-secondary;
|
|
}
|
|
ta := TouchArea {
|
|
mouse-cursor: pointer;
|
|
clicked => {
|
|
root.clicked();
|
|
}
|
|
}
|
|
}
|
|
|
|
export component Spotlight inherits VerticalLayout {
|
|
callback open-artist(string);
|
|
callback open-album(string);
|
|
callback play-top-tracks(string);
|
|
callback start-radio(string);
|
|
callback media-action(string, string, string);
|
|
|
|
property <length> gap: 24px;
|
|
// Row content width: TopTracks(200) + Radio(200) + albums(200 each)
|
|
// + gaps. has-top-tracks toggles the first card.
|
|
property <length> radio-cards-width:
|
|
(ForYouState.spotlight-has-top-tracks ? 200px + root.gap : 0px) + 200px + root.gap;
|
|
property <int> album-count: ForYouState.spotlight-albums.length;
|
|
property <length> content-width:
|
|
root.radio-cards-width
|
|
+ root.album-count * (200px + root.gap);
|
|
|
|
spacing: 12px;
|
|
|
|
// Header.
|
|
VerticalLayout {
|
|
spacing: 2px;
|
|
Text {
|
|
text: @tr("Spotlight");
|
|
color: Theme.text-primary;
|
|
font-size: 18px;
|
|
font-weight: Typography.semibold;
|
|
}
|
|
Text {
|
|
text: @tr("Shine a light on one of your favourite artists.");
|
|
color: Theme.text-muted;
|
|
font-size: Typography.legal;
|
|
}
|
|
}
|
|
|
|
// Hero.
|
|
HorizontalLayout {
|
|
spacing: 16px;
|
|
alignment: start;
|
|
Rectangle {
|
|
width: 96px;
|
|
height: 96px;
|
|
border-radius: 48px;
|
|
background: Theme.surface-elevated;
|
|
clip: true;
|
|
Image {
|
|
width: 100%;
|
|
height: 100%;
|
|
source: ForYouState.spotlight-image;
|
|
image-fit: cover;
|
|
}
|
|
if ForYouState.spotlight-image-url == "": QbzIcon {
|
|
source: @image-url("../assets/icons/user.svg");
|
|
width: 40px;
|
|
height: 40px;
|
|
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
|
|
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
|
|
tint: Theme.text-muted;
|
|
}
|
|
portrait-ta := TouchArea {
|
|
mouse-cursor: pointer;
|
|
clicked => {
|
|
root.open-artist(ForYouState.spotlight-artist-id);
|
|
}
|
|
}
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
spacing: 8px;
|
|
if ForYouState.spotlight-category != "": Text {
|
|
text: @tr("ARTIST");
|
|
color: Theme.text-muted;
|
|
font-size: 10px;
|
|
font-weight: Typography.semibold;
|
|
letter-spacing: 1.5px;
|
|
}
|
|
Text {
|
|
text: ForYouState.spotlight-name;
|
|
color: Theme.text-primary;
|
|
font-size: Typography.title;
|
|
font-weight: Typography.bold;
|
|
}
|
|
HorizontalLayout {
|
|
spacing: 10px;
|
|
alignment: start;
|
|
if ForYouState.spotlight-has-top-tracks: CircleButton {
|
|
icon: @image-url("../assets/icons/play-fill.svg");
|
|
primary: true;
|
|
clicked => {
|
|
root.play-top-tracks(ForYouState.spotlight-artist-id);
|
|
}
|
|
}
|
|
CircleButton {
|
|
icon: @image-url("../assets/icons/user.svg");
|
|
clicked => {
|
|
root.open-artist(ForYouState.spotlight-artist-id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle { height: 4px; }
|
|
|
|
// Content row — draggable (Flickable), like the other carousels.
|
|
Rectangle {
|
|
height: 270px;
|
|
clip: true;
|
|
Flickable {
|
|
width: 100%;
|
|
height: parent.height;
|
|
viewport-width: root.content-width;
|
|
viewport-height: self.height;
|
|
|
|
HorizontalLayout {
|
|
spacing: root.gap;
|
|
alignment: start;
|
|
|
|
if ForYouState.spotlight-has-top-tracks: RadioCard {
|
|
seed-title: @tr("Top tracks");
|
|
seed-subtitle: @tr("By {}", ForYouState.spotlight-name);
|
|
label: "TOP TRACKS";
|
|
artwork: ForYouState.spotlight-image;
|
|
clicked => {
|
|
root.play-top-tracks(ForYouState.spotlight-artist-id);
|
|
}
|
|
play => {
|
|
root.play-top-tracks(ForYouState.spotlight-artist-id);
|
|
}
|
|
}
|
|
RadioCard {
|
|
seed-title: ForYouState.spotlight-name;
|
|
seed-subtitle: @tr("Qobuz Radio Station");
|
|
label: "RADIO";
|
|
artwork: ForYouState.spotlight-image;
|
|
clicked => {
|
|
root.start-radio(ForYouState.spotlight-artist-id);
|
|
}
|
|
play => {
|
|
root.start-radio(ForYouState.spotlight-artist-id);
|
|
}
|
|
}
|
|
for album in ForYouState.spotlight-albums: AlbumCard {
|
|
album: album;
|
|
clicked(id) => {
|
|
root.open-album(id);
|
|
}
|
|
media-action(kind, id, action) => {
|
|
root.media-action(kind, id, action);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|