Add a "Play next" ContextMenuItem above "Add to queue" in every menu that offers queueing: the album page track menu, the artist page track menu, and the discover album card menu. Play next uses the list-plus icon and Add to queue switches to list-end, matching Tauri's ListPlus / ListEnd icon mapping. The queue sidebar track menu is left untouched. cc
793 lines
31 KiB
Plaintext
793 lines
31 KiB
Plaintext
// Album detail page.
|
|
//
|
|
// Mirrors the Svelte AlbumDetailView: back button, an album header
|
|
// (artwork + title / artist / info line / action buttons), a divider,
|
|
// and the track list with a column header. Data comes from the
|
|
// AlbumState global; the action buttons are visual for now.
|
|
|
|
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 { AlbumState, AlbumTrackItem, AlbumActions, NavState, ContentView } from "../state.slint";
|
|
import { QbzIcon } from "../primitives/QbzIcon.slint";
|
|
import { ContextMenu } from "../primitives/ContextMenu.slint";
|
|
import { ContextMenuItem } from "../primitives/ContextMenuItem.slint";
|
|
import { QualityBadgeFull } from "../primitives/QualityBadgeFull.slint";
|
|
import { NavButtons } from "../shell/NavButtons.slint";
|
|
|
|
component CircleAction inherits Rectangle {
|
|
in property <image> icon;
|
|
in property <bool> primary: false;
|
|
in property <length> diameter: 40px;
|
|
callback clicked();
|
|
|
|
width: root.diameter;
|
|
height: root.diameter;
|
|
border-radius: root.diameter / 2;
|
|
// Ghost circle — translucent fill with a faint ring, never a solid
|
|
// accent fill (mirrors the Tauri transport controls).
|
|
background: ta.has-hover ? Theme.surface-hover : transparent;
|
|
border-width: 1px;
|
|
border-color: ta.has-hover ? Theme.text-disabled : Theme.border-subtle;
|
|
|
|
QbzIcon {
|
|
source: root.icon;
|
|
width: root.primary ? 19px : 17px;
|
|
height: root.primary ? 19px : 17px;
|
|
x: (parent.width - self.width) / 2;
|
|
y: (parent.height - self.height) / 2;
|
|
tint: ta.has-hover ? Theme.text-primary : Theme.text-secondary;
|
|
}
|
|
|
|
ta := TouchArea {
|
|
mouse-cursor: pointer;
|
|
clicked => {
|
|
root.clicked();
|
|
}
|
|
}
|
|
}
|
|
|
|
component TrackRow inherits Rectangle {
|
|
in property <AlbumTrackItem> track;
|
|
in property <int> index;
|
|
// kind ("track"), track id, action ("play" | "queue" | "favorite").
|
|
callback media-action(string, string, string);
|
|
|
|
height: 56px;
|
|
border-radius: 8px;
|
|
// The even-row tint is clearly fainter than the hover tint so the two
|
|
// never read as the same state.
|
|
background: ta.has-hover
|
|
? #ffffff16
|
|
: (Math.mod(root.index, 2) == 1 ? #ffffff07 : transparent);
|
|
|
|
// Row hover/background detector — declared before the content so the
|
|
// more-button keeps its own clicks.
|
|
ta := TouchArea {
|
|
mouse-cursor: default;
|
|
}
|
|
|
|
HorizontalLayout {
|
|
padding-left: 12px;
|
|
padding-right: 12px;
|
|
spacing: 16px;
|
|
|
|
Text {
|
|
text: root.track.number;
|
|
width: 32px;
|
|
color: Theme.text-muted;
|
|
font-size: Typography.body;
|
|
vertical-alignment: center;
|
|
}
|
|
|
|
VerticalLayout {
|
|
alignment: center;
|
|
horizontal-stretch: 1;
|
|
spacing: 2px;
|
|
|
|
// alignment: start packs the title and the explicit badge at
|
|
// their natural sizes, so the badge sits right after the name
|
|
// instead of being stretched out to a column position.
|
|
HorizontalLayout {
|
|
alignment: start;
|
|
spacing: 6px;
|
|
Text {
|
|
text: root.track.title;
|
|
color: Theme.text-primary;
|
|
font-size: Typography.body;
|
|
font-weight: Typography.medium;
|
|
vertical-alignment: center;
|
|
overflow: elide;
|
|
}
|
|
if root.track.explicit: VerticalLayout {
|
|
alignment: center;
|
|
Rectangle {
|
|
width: 16px;
|
|
height: 16px;
|
|
border-radius: 3px;
|
|
background: Theme.surface-elevated;
|
|
Text {
|
|
text: "E";
|
|
color: Theme.text-muted;
|
|
font-size: 9px;
|
|
font-weight: Typography.semibold;
|
|
horizontal-alignment: center;
|
|
vertical-alignment: center;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Text {
|
|
text: root.track.artist;
|
|
visible: root.track.artist != "";
|
|
color: Theme.text-muted;
|
|
font-size: Typography.link;
|
|
overflow: elide;
|
|
}
|
|
}
|
|
|
|
Text {
|
|
text: root.track.duration;
|
|
width: 80px;
|
|
color: Theme.text-muted;
|
|
font-size: Typography.body;
|
|
horizontal-alignment: center;
|
|
vertical-alignment: center;
|
|
}
|
|
Text {
|
|
text: root.track.quality-tier == "hires"
|
|
? "Hi-Res"
|
|
: (root.track.quality-tier == "cd" ? "CD" : "");
|
|
width: 80px;
|
|
color: Theme.text-muted;
|
|
font-size: Typography.legal;
|
|
horizontal-alignment: center;
|
|
vertical-alignment: center;
|
|
}
|
|
|
|
// Favorite button.
|
|
VerticalLayout {
|
|
alignment: center;
|
|
Rectangle {
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: Radius.sm;
|
|
background: fav-ta.has-hover ? Theme.surface-elevated : transparent;
|
|
QbzIcon {
|
|
source: @image-url("../assets/icons/heart.svg");
|
|
width: 16px;
|
|
height: 16px;
|
|
x: (parent.width - self.width) / 2;
|
|
y: (parent.height - self.height) / 2;
|
|
tint: fav-ta.has-hover ? Theme.text-primary : Theme.text-muted;
|
|
}
|
|
fav-ta := TouchArea {
|
|
mouse-cursor: pointer;
|
|
clicked => {
|
|
root.media-action("track", root.track.id, "favorite");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Offline-cache button.
|
|
VerticalLayout {
|
|
alignment: center;
|
|
Rectangle {
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: Radius.sm;
|
|
background: dl-ta.has-hover ? Theme.surface-elevated : transparent;
|
|
QbzIcon {
|
|
source: @image-url("../assets/icons/cloud-download.svg");
|
|
width: 16px;
|
|
height: 16px;
|
|
x: (parent.width - self.width) / 2;
|
|
y: (parent.height - self.height) / 2;
|
|
tint: dl-ta.has-hover ? Theme.text-primary : Theme.text-muted;
|
|
}
|
|
dl-ta := TouchArea {
|
|
mouse-cursor: pointer;
|
|
clicked => {
|
|
root.media-action("track", root.track.id, "download");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Context-menu button — always visible in the muted column color,
|
|
// a 32px square hit area that brightens on hover.
|
|
VerticalLayout {
|
|
alignment: center;
|
|
Rectangle {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: Radius.sm;
|
|
background: more-ta.has-hover ? Theme.surface-elevated : transparent;
|
|
QbzIcon {
|
|
source: @image-url("../assets/icons/ellipsis.svg");
|
|
width: 16px;
|
|
height: 16px;
|
|
x: (parent.width - self.width) / 2;
|
|
y: (parent.height - self.height) / 2;
|
|
tint: more-ta.has-hover ? Theme.text-primary : Theme.text-muted;
|
|
}
|
|
more-ta := TouchArea {
|
|
mouse-cursor: pointer;
|
|
clicked => {
|
|
track-menu.show();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
track-menu := PopupWindow {
|
|
x: parent.width - 208px;
|
|
y: 46px;
|
|
width: 196px;
|
|
height: 109px;
|
|
ContextMenu {
|
|
ContextMenuItem {
|
|
icon: @image-url("../assets/icons/play.svg");
|
|
label: @tr("Play");
|
|
clicked => {
|
|
root.media-action("track", root.track.id, "play");
|
|
}
|
|
}
|
|
ContextMenuItem {
|
|
icon: @image-url("../assets/icons/list-plus.svg");
|
|
label: @tr("Play next");
|
|
clicked => {
|
|
root.media-action("track", root.track.id, "play-next");
|
|
}
|
|
}
|
|
ContextMenuItem {
|
|
icon: @image-url("../assets/icons/list-end.svg");
|
|
label: @tr("Add to queue");
|
|
clicked => {
|
|
root.media-action("track", root.track.id, "queue");
|
|
}
|
|
}
|
|
ContextMenuItem {
|
|
icon: @image-url("../assets/icons/heart.svg");
|
|
label: @tr("Add to favorites");
|
|
clicked => {
|
|
root.media-action("track", root.track.id, "favorite");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// One label/award entry in the album sidebar — a round gradient avatar
|
|
// and a name.
|
|
component SidebarCard inherits Rectangle {
|
|
in property <image> icon;
|
|
in property <string> name;
|
|
in property <brush> avatar-bg;
|
|
callback clicked();
|
|
|
|
height: 56px;
|
|
border-radius: Radius.sm;
|
|
background: ta.has-hover ? Theme.surface-hover : transparent;
|
|
|
|
HorizontalLayout {
|
|
padding: 8px;
|
|
spacing: 10px;
|
|
VerticalLayout {
|
|
alignment: center;
|
|
Rectangle {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 18px;
|
|
background: root.avatar-bg;
|
|
QbzIcon {
|
|
source: root.icon;
|
|
width: 18px;
|
|
height: 18px;
|
|
x: (parent.width - self.width) / 2;
|
|
y: (parent.height - self.height) / 2;
|
|
tint: #ffffff;
|
|
}
|
|
}
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
horizontal-stretch: 1;
|
|
Text {
|
|
text: root.name;
|
|
color: Theme.text-secondary;
|
|
font-size: Typography.link;
|
|
font-weight: Typography.medium;
|
|
wrap: word-wrap;
|
|
}
|
|
}
|
|
}
|
|
|
|
ta := TouchArea {
|
|
mouse-cursor: pointer;
|
|
clicked => {
|
|
root.clicked();
|
|
}
|
|
}
|
|
}
|
|
|
|
component SidebarHeading inherits Text {
|
|
color: Theme.text-muted;
|
|
font-size: 10px;
|
|
font-weight: Typography.semibold;
|
|
letter-spacing: 1px;
|
|
}
|
|
|
|
export component AlbumPageView inherits Rectangle {
|
|
callback open-artist(string);
|
|
callback media-action(string, string, string);
|
|
|
|
// Whether the full-description modal is open.
|
|
property <bool> show-description: false;
|
|
|
|
background: Theme.surface-main;
|
|
|
|
Flickable {
|
|
viewport-width: self.width;
|
|
viewport-height: page.preferred-height;
|
|
|
|
// Artwork-tinted gradient behind the header; scrolls with content.
|
|
Rectangle {
|
|
x: 0;
|
|
y: 0;
|
|
width: parent.width;
|
|
height: 340px;
|
|
background: @linear-gradient(180deg,
|
|
AlbumState.header-color 0%,
|
|
AlbumState.header-color 16%,
|
|
transparent 100%);
|
|
}
|
|
|
|
page := VerticalLayout {
|
|
width: parent.width;
|
|
// padding matches Discover so the [<] [>] pair sits at the same
|
|
// top/left margin across every view.
|
|
padding-left: 32px;
|
|
padding-right: 32px;
|
|
padding-top: 11px;
|
|
padding-bottom: 100px;
|
|
spacing: 0px;
|
|
alignment: start;
|
|
|
|
// --- History navigation ------------------------------------------
|
|
HorizontalLayout {
|
|
alignment: start;
|
|
NavButtons { }
|
|
}
|
|
|
|
Rectangle { height: 22px; }
|
|
|
|
// --- Album header ------------------------------------------------
|
|
HorizontalLayout {
|
|
spacing: 32px;
|
|
|
|
Rectangle {
|
|
width: 224px;
|
|
height: 224px;
|
|
border-radius: 12px;
|
|
background: Theme.surface-elevated;
|
|
drop-shadow-blur: 32px;
|
|
drop-shadow-color: #00000080;
|
|
drop-shadow-offset-y: 8px;
|
|
clip: true;
|
|
Image {
|
|
width: 100%;
|
|
height: 100%;
|
|
source: AlbumState.artwork;
|
|
image-fit: cover;
|
|
}
|
|
}
|
|
|
|
VerticalLayout {
|
|
alignment: start;
|
|
padding-top: 4px;
|
|
spacing: 0px;
|
|
horizontal-stretch: 1;
|
|
|
|
Text {
|
|
text: AlbumState.title;
|
|
color: Theme.text-primary;
|
|
font-size: Typography.title;
|
|
font-weight: Typography.bold;
|
|
overflow: elide;
|
|
}
|
|
Rectangle { height: 4px; }
|
|
HorizontalLayout {
|
|
alignment: start;
|
|
artist-area := TouchArea {
|
|
width: artist-text.preferred-width;
|
|
height: artist-text.preferred-height;
|
|
mouse-cursor: pointer;
|
|
clicked => {
|
|
root.open-artist(AlbumState.artist-id);
|
|
}
|
|
artist-text := Text {
|
|
text: AlbumState.artist;
|
|
// Same color as the surrounding metadata,
|
|
// bold to read as a link.
|
|
color: artist-area.has-hover
|
|
? Theme.text-primary
|
|
: Theme.text-secondary;
|
|
font-size: Typography.heading;
|
|
font-weight: Typography.bold;
|
|
}
|
|
}
|
|
}
|
|
Rectangle { height: 10px; }
|
|
Text {
|
|
text: AlbumState.info-line;
|
|
color: Theme.text-secondary;
|
|
font-size: Typography.body;
|
|
overflow: elide;
|
|
}
|
|
|
|
// Editorial description — lives with the metadata and
|
|
// fills the width to the right of the artwork; truncated,
|
|
// with a Read more link to the full modal.
|
|
if AlbumState.description != "": Rectangle { height: 12px; }
|
|
if AlbumState.description != "": Text {
|
|
// Explicit width so the text fills the whole band
|
|
// to the right of the artwork instead of wrapping
|
|
// narrow and leaving dead space. Bound to root.width
|
|
// (not the parent layout) to avoid a binding loop:
|
|
// page padding 32+32, artwork 224, header gap 32.
|
|
width: root.width - 320px;
|
|
text: AlbumState.description-short;
|
|
color: Theme.text-secondary;
|
|
font-size: Typography.legal;
|
|
wrap: word-wrap;
|
|
}
|
|
if AlbumState.description != AlbumState.description-short: Rectangle { height: 4px; }
|
|
if AlbumState.description != AlbumState.description-short: read-more := TouchArea {
|
|
width: read-more-row.preferred-width;
|
|
height: 20px;
|
|
mouse-cursor: pointer;
|
|
clicked => {
|
|
root.show-description = true;
|
|
}
|
|
read-more-row := HorizontalLayout {
|
|
Text {
|
|
text: @tr("Read more");
|
|
color: read-more.has-hover ? Theme.accent-hover : Theme.accent;
|
|
font-size: Typography.legal;
|
|
vertical-alignment: center;
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle { height: 20px; }
|
|
HorizontalLayout {
|
|
alignment: start;
|
|
spacing: 12px;
|
|
CircleAction {
|
|
icon: @image-url("../assets/icons/play-fill.svg");
|
|
primary: true;
|
|
diameter: 44px;
|
|
clicked => {
|
|
root.media-action("album", AlbumState.id, "play");
|
|
}
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
CircleAction { icon: @image-url("../assets/icons/shuffle.svg"); }
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
CircleAction {
|
|
icon: @image-url("../assets/icons/heart.svg");
|
|
clicked => {
|
|
root.media-action("album", AlbumState.id, "favorite");
|
|
}
|
|
}
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
CircleAction { icon: @image-url("../assets/icons/radio.svg"); }
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
CircleAction { icon: @image-url("../assets/icons/user.svg"); }
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
CircleAction { icon: @image-url("../assets/icons/book-open.svg"); }
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
CircleAction { icon: @image-url("../assets/icons/cassette-tape.svg"); }
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
CircleAction { icon: @image-url("../assets/icons/square-check-big.svg"); }
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
CircleAction { icon: @image-url("../assets/icons/ellipsis.svg"); }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle { height: 20px; }
|
|
Rectangle {
|
|
height: 1px;
|
|
background: Theme.border-subtle;
|
|
}
|
|
Rectangle { height: 8px; }
|
|
|
|
// --- Track list + label/awards sidebar --------------------------
|
|
// No `alignment: start` here: it would defeat the track list's
|
|
// horizontal-stretch and leave the sidebar floating mid-row
|
|
// with dead space to its right. The track list stretches and
|
|
// the 254px sidebar is pushed flush to the right edge; when a
|
|
// main right sidebar opens, the whole content region narrows
|
|
// and the sidebar rides along with it.
|
|
HorizontalLayout {
|
|
spacing: 32px;
|
|
|
|
VerticalLayout {
|
|
horizontal-stretch: 1;
|
|
spacing: 0px;
|
|
alignment: start;
|
|
|
|
if AlbumState.loading && AlbumState.tracks.length == 0: Text {
|
|
text: @tr("Loading album...");
|
|
color: Theme.text-muted;
|
|
font-size: Typography.body;
|
|
}
|
|
|
|
// Toolbar — album quality badge and track search.
|
|
HorizontalLayout {
|
|
height: 52px;
|
|
spacing: 16px;
|
|
VerticalLayout {
|
|
alignment: center;
|
|
QualityBadgeFull {
|
|
tier: AlbumState.quality-tier;
|
|
detail: AlbumState.quality-detail;
|
|
}
|
|
}
|
|
Rectangle { horizontal-stretch: 1; }
|
|
VerticalLayout {
|
|
alignment: center;
|
|
Rectangle {
|
|
width: 280px;
|
|
height: 34px;
|
|
border-radius: 6px;
|
|
border-width: 1px;
|
|
border-color: Theme.border-subtle;
|
|
background: Theme.surface-elevated;
|
|
HorizontalLayout {
|
|
padding-left: 10px;
|
|
padding-right: 10px;
|
|
spacing: 7px;
|
|
VerticalLayout {
|
|
alignment: center;
|
|
QbzIcon {
|
|
source: @image-url("../assets/icons/search.svg");
|
|
width: 14px;
|
|
height: 14px;
|
|
tint: Theme.text-muted;
|
|
}
|
|
}
|
|
// Functional client-side filter.
|
|
Rectangle {
|
|
horizontal-stretch: 1;
|
|
track-search := TextInput {
|
|
width: 100%;
|
|
height: 100%;
|
|
color: Theme.text-primary;
|
|
font-size: 13px;
|
|
vertical-alignment: center;
|
|
single-line: true;
|
|
edited => {
|
|
AlbumActions.search(self.text);
|
|
}
|
|
}
|
|
Text {
|
|
visible: track-search.text == "";
|
|
text: @tr("Search tracks...");
|
|
// Span the box so the placeholder
|
|
// centers like the TextInput text.
|
|
width: 100%;
|
|
height: 100%;
|
|
color: Theme.text-muted;
|
|
font-size: 13px;
|
|
vertical-alignment: center;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Column header.
|
|
HorizontalLayout {
|
|
height: 40px;
|
|
padding-left: 12px;
|
|
padding-right: 12px;
|
|
spacing: 16px;
|
|
Text {
|
|
text: "#";
|
|
width: 32px;
|
|
color: Theme.text-muted;
|
|
font-size: Typography.legal;
|
|
letter-spacing: 0.5px;
|
|
vertical-alignment: center;
|
|
}
|
|
Text {
|
|
text: @tr("TITLE");
|
|
horizontal-stretch: 1;
|
|
color: Theme.text-muted;
|
|
font-size: Typography.legal;
|
|
letter-spacing: 0.5px;
|
|
vertical-alignment: center;
|
|
}
|
|
Text {
|
|
text: @tr("DURATION");
|
|
width: 80px;
|
|
color: Theme.text-muted;
|
|
font-size: Typography.legal;
|
|
letter-spacing: 0.5px;
|
|
horizontal-alignment: center;
|
|
vertical-alignment: center;
|
|
}
|
|
Text {
|
|
text: @tr("QUALITY");
|
|
width: 80px;
|
|
color: Theme.text-muted;
|
|
font-size: Typography.legal;
|
|
letter-spacing: 0.5px;
|
|
horizontal-alignment: center;
|
|
vertical-alignment: center;
|
|
}
|
|
// Favorite / offline-cache column headers.
|
|
Rectangle {
|
|
width: 28px;
|
|
QbzIcon {
|
|
source: @image-url("../assets/icons/heart.svg");
|
|
width: 14px;
|
|
height: 14px;
|
|
x: (parent.width - self.width) / 2;
|
|
y: (parent.height - self.height) / 2;
|
|
tint: Theme.text-muted;
|
|
}
|
|
}
|
|
Rectangle {
|
|
width: 28px;
|
|
QbzIcon {
|
|
source: @image-url("../assets/icons/cloud-download.svg");
|
|
width: 14px;
|
|
height: 14px;
|
|
x: (parent.width - self.width) / 2;
|
|
y: (parent.height - self.height) / 2;
|
|
tint: Theme.text-muted;
|
|
}
|
|
}
|
|
// Aligns with the per-row more-button slot.
|
|
Rectangle { width: 32px; }
|
|
}
|
|
|
|
for track[index] in AlbumState.tracks: TrackRow {
|
|
track: track;
|
|
index: index;
|
|
media-action(kind, id, action) => {
|
|
root.media-action(kind, id, action);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Label / awards sidebar.
|
|
if AlbumState.label != "" || AlbumState.awards.length > 0: VerticalLayout {
|
|
width: 254px;
|
|
spacing: 24px;
|
|
alignment: start;
|
|
|
|
if AlbumState.label != "": VerticalLayout {
|
|
spacing: 8px;
|
|
SidebarHeading { text: @tr("LABEL"); }
|
|
SidebarCard {
|
|
icon: @image-url("../assets/icons/disc.svg");
|
|
name: AlbumState.label;
|
|
avatar-bg: @linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
|
}
|
|
}
|
|
|
|
if AlbumState.awards.length > 0: VerticalLayout {
|
|
spacing: 8px;
|
|
SidebarHeading { text: @tr("AWARDS"); }
|
|
for award in AlbumState.awards: SidebarCard {
|
|
icon: @image-url("../assets/laurels.svg");
|
|
name: award;
|
|
avatar-bg: @linear-gradient(135deg, #b45309 0%, #eab308 100%);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Full-description modal --------------------------------------------
|
|
if root.show-description: Rectangle {
|
|
background: #000000bf;
|
|
|
|
// Scrim — a click anywhere outside the panel dismisses.
|
|
TouchArea {
|
|
mouse-cursor: default;
|
|
clicked => {
|
|
root.show-description = false;
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
width: Math.min(root.width - 80px, 560px);
|
|
height: Math.min(root.height - 120px, 460px);
|
|
x: (parent.width - self.width) / 2;
|
|
y: (parent.height - self.height) / 2;
|
|
border-radius: Radius.md;
|
|
background: Theme.surface-card;
|
|
border-width: 1px;
|
|
border-color: Theme.border-subtle;
|
|
drop-shadow-blur: 32px;
|
|
drop-shadow-color: Theme.card-shadow;
|
|
|
|
// Swallow clicks so they do not reach the scrim.
|
|
TouchArea { }
|
|
|
|
VerticalLayout {
|
|
padding: 24px;
|
|
spacing: 14px;
|
|
|
|
HorizontalLayout {
|
|
Text {
|
|
text: @tr("About this album");
|
|
color: Theme.text-primary;
|
|
font-size: Typography.heading;
|
|
font-weight: Typography.semibold;
|
|
horizontal-stretch: 1;
|
|
vertical-alignment: center;
|
|
}
|
|
close-x := TouchArea {
|
|
width: 28px;
|
|
height: 28px;
|
|
mouse-cursor: pointer;
|
|
clicked => {
|
|
root.show-description = false;
|
|
}
|
|
QbzIcon {
|
|
source: @image-url("../assets/icons/x.svg");
|
|
width: 18px;
|
|
height: 18px;
|
|
x: (parent.width - self.width) / 2;
|
|
y: (parent.height - self.height) / 2;
|
|
tint: close-x.has-hover ? Theme.text-primary : Theme.text-muted;
|
|
}
|
|
}
|
|
}
|
|
|
|
// vertical-stretch bounds the Flickable to the leftover
|
|
// panel height so a long description actually scrolls.
|
|
Flickable {
|
|
vertical-stretch: 1;
|
|
viewport-width: self.width;
|
|
viewport-height: desc-text.preferred-height;
|
|
desc-text := Text {
|
|
width: parent.width;
|
|
text: AlbumState.description;
|
|
color: Theme.text-secondary;
|
|
font-size: Typography.body;
|
|
wrap: word-wrap;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|