// Right-side Queue panel. // // Rendered as a layout sibling of the content area (see AppShell), so it // shrinks the content rather than overlaying it. Closing sets // ShellState.right-panel = 0. The body is a structural placeholder; // queue contents are wired in a later milestone. import { Theme } from "../foundation/semantic-colors.slint"; import { Spacing } from "../foundation/spacing.slint"; import { Typography } from "../foundation/typography.slint"; import { Radius } from "../foundation/radius.slint"; import { QbzIcon } from "../primitives/QbzIcon.slint"; import { ShellState, QueueState } from "../state.slint"; component CloseButton inherits Rectangle { callback clicked(); width: 28px; height: 28px; border-radius: Radius.sm; background: ta.has-hover ? Theme.surface-hover : transparent; QbzIcon { source: @image-url("../assets/icons/x.svg"); width: 15px; height: 15px; 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(); } } } // One queue entry row — thumbnail, title, artist, playing indicator. component QueueRow inherits Rectangle { in property title; in property artist; in property artwork; in property playing: false; callback clicked(); height: 56px; background: ta.has-hover ? Theme.surface-hover : transparent; HorizontalLayout { padding-left: Spacing.md; padding-right: Spacing.md; padding-top: 6px; padding-bottom: 6px; spacing: 10px; // Small album thumbnail. Rectangle { width: 44px; height: 44px; border-radius: 4px; background: Theme.surface-elevated; clip: true; if root.artwork.width > 0: Image { source: root.artwork; width: 100%; height: 100%; image-fit: cover; } } // Title + artist. VerticalLayout { horizontal-stretch: 1; alignment: center; spacing: 2px; Text { text: root.title; color: root.playing ? Theme.accent : Theme.text-primary; font-size: Typography.body; font-weight: root.playing ? Typography.semibold : Typography.regular; overflow: elide; } Text { text: root.artist; color: Theme.text-muted; font-size: 11px; overflow: elide; } } // Now-playing indicator. VerticalLayout { alignment: center; if root.playing: QbzIcon { source: @image-url("../assets/icons/audio-lines.svg"); width: 15px; height: 15px; tint: Theme.accent; } } } ta := TouchArea { mouse-cursor: pointer; clicked => { root.clicked(); } } } export component QueueSidebar inherits Rectangle { // Always present; the width animates 0 <-> 320 so the panel slides // in smoothly, matching the left sidebar's collapse animation. width: ShellState.right-panel == 1 ? 320px : 0px; clip: true; animate width { duration: 160ms; easing: ease-in-out; } background: Theme.surface-card; VerticalLayout { spacing: 0px; // Header row — title + close button. HorizontalLayout { padding-left: Spacing.md; padding-right: Spacing.sm; padding-top: Spacing.sm; padding-bottom: Spacing.sm; height: 44px; Text { text: @tr("Queue"); color: Theme.text-primary; font-size: Typography.heading; font-weight: Typography.semibold; vertical-alignment: center; horizontal-stretch: 1; } VerticalLayout { alignment: center; CloseButton { clicked => { ShellState.right-panel = 0; } } } } Rectangle { height: 1px; background: Theme.border-subtle; } // Body — the live queue, or an empty-state placeholder. Rectangle { vertical-stretch: 1; if QueueState.items.length == 0: Text { text: @tr("Nothing queued"); color: Theme.text-muted; font-size: Typography.body; } if QueueState.items.length > 0: Flickable { viewport-width: self.width; viewport-height: rows.preferred-height; rows := VerticalLayout { spacing: 0px; for item[idx] in QueueState.items: QueueRow { title: item.title; artist: item.artist; artwork: item.artwork; playing: item.playing; clicked => { QueueState.play-index(idx); } } } } } } }