Playlist Manager: list view renders compact rows with total play time; folder grid cards no longer clip the playlist count (content-driven height); list view shows compact folder chips; the move-to-folder menu gains a folder search and a 4-row scroll cap. Search: the most-popular track hero is resized to the artist-card height with the quality shown as centered text instead of a clipped badge. cc
242 lines
8.7 KiB
Plaintext
242 lines
8.7 KiB
Plaintext
// Most-popular hero card for a TRACK top result.
|
|
//
|
|
// Mirrors the Tauri SearchView `.popular-card` track case: a vertical
|
|
// card with a square artwork on top (hover scrim + favorite / play /
|
|
// more overlay buttons), then a tight title / artist stack alongside an
|
|
// icon-only quality badge. Sized to match the search-dashboard ArtistCard
|
|
// (160x220) so the Most-popular slot sits proportionate next to the
|
|
// Artists carousel cards. Tauri's `.most-popular-card` is 160x220 with a
|
|
// 142x142 artwork (`.popular-card-artwork`), padding 6px — replicated here.
|
|
//
|
|
// Reusable: takes a TrackItem and bubbles every interaction up through one
|
|
// media-action(kind, id, action) — "play" | "favorite" | the context-menu
|
|
// actions — keyed by "track".
|
|
|
|
import { Theme } from "../foundation/semantic-colors.slint";
|
|
import { Typography } from "../foundation/typography.slint";
|
|
import { Radius } from "../foundation/radius.slint";
|
|
import { Spacing } from "../foundation/spacing.slint";
|
|
import { TrackItem } from "../state.slint";
|
|
import { QbzIcon } from "QbzIcon.slint";
|
|
import { TrackContextMenu } from "TrackContextMenu.slint";
|
|
|
|
// Circular hover-overlay action button — identical visual to AlbumCard's
|
|
// OverlayButton so the hero cards share one look.
|
|
component OverlayButton inherits Rectangle {
|
|
in property <image> icon;
|
|
in property <bool> primary: false;
|
|
in property <length> diameter: 36px;
|
|
in property <length> icon-size: 16px;
|
|
out property <bool> hovered: ta.has-hover;
|
|
callback clicked();
|
|
|
|
width: root.diameter;
|
|
height: root.diameter;
|
|
border-radius: root.diameter / 2;
|
|
background: root.primary
|
|
? (ta.has-hover ? #ffffffd6 : #ffffff)
|
|
: (ta.has-hover ? #ffffff3d : #ffffff24);
|
|
border-width: root.primary ? 0px : 1.5px;
|
|
border-color: #ffffffcc;
|
|
|
|
QbzIcon {
|
|
source: root.icon;
|
|
width: root.icon-size;
|
|
height: root.icon-size;
|
|
x: (parent.width - self.width) / 2;
|
|
y: (parent.height - self.height) / 2;
|
|
tint: root.primary ? #000000 : #ffffff;
|
|
}
|
|
|
|
ta := TouchArea {
|
|
mouse-cursor: pointer;
|
|
clicked => {
|
|
root.clicked();
|
|
}
|
|
}
|
|
}
|
|
|
|
export component SearchTrackHero inherits Rectangle {
|
|
in property <TrackItem> track;
|
|
// Detailed quality string, e.g. "Hi-Res 24-bit / 192 kHz". Rendered as
|
|
// plain text below the artist (the icon badge was dropped — it clipped
|
|
// against the card edge). "" hides the line.
|
|
in property <string> quality-label;
|
|
// kind ("track"), track id, action ("play" | "favorite" | menu actions).
|
|
callback media-action(string, string, string);
|
|
|
|
// Match the search-dashboard ArtistCard footprint (160x220) so the
|
|
// Most-popular hero is proportionate next to the artist cards. Tauri's
|
|
// `.popular-card`: 160 wide, 6px padding, 142x142 artwork.
|
|
width: 160px;
|
|
height: 220px;
|
|
border-radius: 12px;
|
|
background: hover.has-hover || root.overlay-on
|
|
? Theme.surface-elevated
|
|
: Theme.surface-card;
|
|
animate background { duration: 150ms; }
|
|
clip: true;
|
|
|
|
// Overlay stays up while the pointer is over the artwork OR a button.
|
|
property <bool> overlay-on: hover.has-hover
|
|
|| fav-btn.hovered || play-btn.hovered || more-btn.hovered;
|
|
|
|
VerticalLayout {
|
|
padding: 6px;
|
|
alignment: start;
|
|
spacing: 0px;
|
|
|
|
// --- Artwork + hover overlay -------------------------------------
|
|
// Shrunk to 128px (from 142px) so the title / artist / quality-text
|
|
// stack fits cleanly below it without growing the 160x220 card.
|
|
// Centered horizontally within the inner width.
|
|
HorizontalLayout {
|
|
alignment: center;
|
|
Rectangle {
|
|
width: 128px;
|
|
height: 128px;
|
|
border-radius: Radius.sm;
|
|
background: Theme.surface-elevated;
|
|
clip: true;
|
|
|
|
// Cover art (track's album cover). Empty until the artwork
|
|
// pipeline resolves it, leaving the placeholder visible.
|
|
Image {
|
|
width: 100%;
|
|
height: 100%;
|
|
source: root.track.artwork;
|
|
image-fit: cover;
|
|
}
|
|
// Placeholder music glyph behind the cover.
|
|
if root.track.artwork.width == 0: QbzIcon {
|
|
source: @image-url("../assets/icons/music.svg");
|
|
width: 30px;
|
|
height: 30px;
|
|
x: (parent.width - self.width) / 2;
|
|
y: (parent.height - self.height) / 2;
|
|
tint: Theme.text-muted;
|
|
}
|
|
|
|
// Hover scrim.
|
|
Rectangle {
|
|
width: 100%;
|
|
height: 100%;
|
|
background: #000000;
|
|
opacity: root.overlay-on ? 0.6 : 0.0;
|
|
animate opacity { duration: 150ms; }
|
|
}
|
|
|
|
// Card-open (play) + hover detector. Declared before the action
|
|
// buttons so those, when interactive, win the pointer.
|
|
hover := TouchArea {
|
|
width: 100%;
|
|
height: 100%;
|
|
mouse-cursor: pointer;
|
|
clicked => {
|
|
root.media-action("track", root.track.id, "play");
|
|
}
|
|
}
|
|
|
|
// Hover action buttons — favorite / play / more. Centered in
|
|
// the 128px artwork.
|
|
HorizontalLayout {
|
|
x: 0px;
|
|
y: (128px - self.height) / 2;
|
|
width: 128px;
|
|
height: 38px;
|
|
alignment: center;
|
|
spacing: 8px;
|
|
opacity: root.overlay-on ? 1.0 : 0.0;
|
|
animate opacity { duration: 150ms; }
|
|
|
|
VerticalLayout {
|
|
alignment: center;
|
|
fav-btn := OverlayButton {
|
|
icon: @image-url("../assets/icons/heart.svg");
|
|
diameter: 30px;
|
|
icon-size: 14px;
|
|
clicked => {
|
|
root.media-action("track", root.track.id, "favorite");
|
|
}
|
|
}
|
|
}
|
|
play-btn := OverlayButton {
|
|
icon: @image-url("../assets/icons/play-fill.svg");
|
|
primary: true;
|
|
diameter: 38px;
|
|
icon-size: 16px;
|
|
clicked => {
|
|
root.media-action("track", root.track.id, "play");
|
|
}
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
more-btn := OverlayButton {
|
|
icon: @image-url("../assets/icons/ellipsis.svg");
|
|
diameter: 30px;
|
|
icon-size: 14px;
|
|
clicked => {
|
|
track-menu.show();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Context menu for the "more" button — the shared track menu.
|
|
track-menu := TrackContextMenu {
|
|
x: 20px;
|
|
y: 48px;
|
|
track-id: root.track.id;
|
|
media-action(k, i, a) => {
|
|
root.media-action(k, i, a);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle { height: 8px; }
|
|
|
|
// --- Title / artist / quality text -------------------------------
|
|
// The icon quality badge was dropped (it clipped against the card
|
|
// edge). Quality now reads as a plain text line below the artist,
|
|
// matching the album hero's quality string. Full inner width, elided.
|
|
VerticalLayout {
|
|
alignment: start;
|
|
spacing: 1px;
|
|
horizontal-stretch: 1;
|
|
|
|
Text {
|
|
text: root.track.title;
|
|
width: 100%;
|
|
horizontal-alignment: center;
|
|
height: 18px;
|
|
color: Theme.text-primary;
|
|
font-size: 13px;
|
|
font-weight: Typography.medium;
|
|
vertical-alignment: center;
|
|
overflow: elide;
|
|
}
|
|
if root.track.artist != "": Text {
|
|
text: root.track.artist;
|
|
width: 100%;
|
|
horizontal-alignment: center;
|
|
height: 16px;
|
|
color: Theme.text-muted;
|
|
font-size: 11px;
|
|
vertical-alignment: center;
|
|
overflow: elide;
|
|
}
|
|
if root.quality-label != "": Text {
|
|
text: root.quality-label;
|
|
width: 100%;
|
|
horizontal-alignment: center;
|
|
height: 15px;
|
|
color: Theme.accent;
|
|
font-size: 11px;
|
|
vertical-alignment: center;
|
|
overflow: elide;
|
|
}
|
|
}
|
|
}
|
|
}
|