feat(slint): build out the queue sidebar UI
Port the Tauri QueuePanel into QueueSidebar.slint: a Queue/History tab switch, a highlighted NOW PLAYING card with a favorite heart toggle, a paginated UP NEXT list with a Tauri-style counter (UP NEXT (N of M, K left)) and prev/next page controls, four footer actions (clear, save as playlist, infinite play, search) plus an inline queue search field. Tabs and actions are plain text / plain icon buttons with no pill backgrounds, per the QBZ design rule. All strings go through @tr. The panel-open width animation is preserved; opening the panel fires panel-opened so the controller pulls a fresh snapshot. cc
This commit is contained in:
1 file changed
+532
-55
@@ -2,16 +2,21 @@
|
||||
//
|
||||
// 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.
|
||||
// ShellState.right-panel = 0.
|
||||
//
|
||||
// Ported from the Tauri QueuePanel.svelte. Two tabs (Queue / History), a
|
||||
// NOW PLAYING card with a favorite toggle, a paginated UP NEXT list
|
||||
// (40 tracks per page), and a footer with four actions plus an inline
|
||||
// queue search field.
|
||||
|
||||
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";
|
||||
import { ShellState, QueueState, QueueItem } from "../state.slint";
|
||||
|
||||
// Close button — mirrors the X control used by LyricsSidebar.
|
||||
component CloseButton inherits Rectangle {
|
||||
callback clicked();
|
||||
width: 28px;
|
||||
@@ -36,33 +41,118 @@ component CloseButton inherits Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
// One queue entry row — thumbnail, title, artist, playing indicator.
|
||||
component QueueRow inherits Rectangle {
|
||||
in property <string> title;
|
||||
in property <string> artist;
|
||||
in property <image> artwork;
|
||||
in property <bool> playing: false;
|
||||
// One tab — plain text, no pill background (QBZ design rule). The active
|
||||
// tab is the brightest; inactive tabs dim and brighten on hover.
|
||||
component QueueTab inherits TouchArea {
|
||||
in property <string> label;
|
||||
in property <bool> active: false;
|
||||
mouse-cursor: pointer;
|
||||
horizontal-stretch: 0;
|
||||
|
||||
Text {
|
||||
text: root.label;
|
||||
font-size: Typography.body;
|
||||
font-weight: Typography.semibold;
|
||||
color: root.active
|
||||
? Theme.text-primary
|
||||
: (root.has-hover ? Theme.text-secondary : Theme.text-muted);
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
// A small footer / control icon button — plain icon, no pill background.
|
||||
component IconButton inherits Rectangle {
|
||||
in property <image> icon;
|
||||
in property <length> icon-size: 17px;
|
||||
in property <bool> active: false;
|
||||
in property <bool> enabled: true;
|
||||
callback clicked();
|
||||
|
||||
height: 56px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: Radius.sm;
|
||||
background: (ta.has-hover && root.enabled) ? Theme.surface-hover : transparent;
|
||||
|
||||
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.enabled
|
||||
? Theme.text-disabled
|
||||
: (root.active
|
||||
? Theme.accent
|
||||
: (ta.has-hover ? Theme.text-primary : Theme.text-secondary));
|
||||
}
|
||||
|
||||
ta := TouchArea {
|
||||
mouse-cursor: root.enabled ? pointer : default;
|
||||
clicked => {
|
||||
if (root.enabled) {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Explicit "E" badge — mirrors the album-track-list badge.
|
||||
component ExplicitBadge inherits 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;
|
||||
}
|
||||
}
|
||||
|
||||
// One UP NEXT / History row — index or artwork, title, artist, duration,
|
||||
// and (upcoming only) a remove control on hover.
|
||||
component QueueRow inherits Rectangle {
|
||||
in property <QueueItem> item;
|
||||
in property <int> index; // zero-based position in its list
|
||||
in property <bool> show-number: true; // true = UP NEXT, false = History
|
||||
in property <bool> removable: false;
|
||||
callback clicked();
|
||||
callback remove();
|
||||
|
||||
height: 52px;
|
||||
background: ta.has-hover ? Theme.surface-hover : transparent;
|
||||
border-radius: Radius.sm;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: Spacing.md;
|
||||
padding-right: Spacing.md;
|
||||
padding-left: Spacing.sm;
|
||||
padding-right: Spacing.xs;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
spacing: 10px;
|
||||
|
||||
// Small album thumbnail.
|
||||
Rectangle {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
// Leading: track number (UP NEXT) or thumbnail (History).
|
||||
if root.show-number: VerticalLayout {
|
||||
alignment: center;
|
||||
width: 26px;
|
||||
Text {
|
||||
text: (root.index + 1);
|
||||
color: Theme.text-muted;
|
||||
font-size: 13px;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
}
|
||||
if !root.show-number: Rectangle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 4px;
|
||||
background: Theme.surface-elevated;
|
||||
clip: true;
|
||||
if root.artwork.width > 0: Image {
|
||||
source: root.artwork;
|
||||
y: (parent.height - self.height) / 2;
|
||||
if root.item.artwork.width > 0: Image {
|
||||
source: root.item.artwork;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
image-fit: cover;
|
||||
@@ -74,34 +164,71 @@ component QueueRow inherits Rectangle {
|
||||
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;
|
||||
HorizontalLayout {
|
||||
spacing: 5px;
|
||||
Text {
|
||||
text: root.item.title;
|
||||
color: Theme.text-primary;
|
||||
font-size: 13px;
|
||||
font-weight: Typography.medium;
|
||||
overflow: elide;
|
||||
}
|
||||
if root.item.explicit: VerticalLayout {
|
||||
alignment: center;
|
||||
ExplicitBadge {}
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: root.artist;
|
||||
text: root.item.artist;
|
||||
color: Theme.text-muted;
|
||||
font-size: 11px;
|
||||
overflow: elide;
|
||||
}
|
||||
}
|
||||
|
||||
// Now-playing indicator.
|
||||
// Duration (hidden on hover when the row is removable, so the
|
||||
// remove control can take its place).
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
if root.playing: QbzIcon {
|
||||
source: @image-url("../assets/icons/audio-lines.svg");
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
tint: Theme.accent;
|
||||
Text {
|
||||
text: root.item.duration;
|
||||
color: Theme.text-muted;
|
||||
font-size: 11px;
|
||||
visible: !(root.removable && ta.has-hover);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove control — only on UP NEXT rows, only on hover.
|
||||
if root.removable: VerticalLayout {
|
||||
alignment: center;
|
||||
width: 24px;
|
||||
visible: ta.has-hover;
|
||||
remove-btn := Rectangle {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: Radius.sm;
|
||||
background: rta.has-hover ? Theme.surface-hover : transparent;
|
||||
QbzIcon {
|
||||
source: @image-url("../assets/icons/x.svg");
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
tint: rta.has-hover ? Theme.text-primary : Theme.text-muted;
|
||||
}
|
||||
rta := TouchArea {
|
||||
mouse-cursor: pointer;
|
||||
clicked => {
|
||||
root.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ta := TouchArea {
|
||||
// The remove control sits above this area and consumes its own
|
||||
// clicks, so a row click never doubles as a remove.
|
||||
mouse-cursor: pointer;
|
||||
clicked => {
|
||||
root.clicked();
|
||||
@@ -117,24 +244,62 @@ export component QueueSidebar inherits Rectangle {
|
||||
animate width { duration: 160ms; easing: ease-in-out; }
|
||||
background: Theme.surface-card;
|
||||
|
||||
// True when the Queue tab has nothing to show at all.
|
||||
property <bool> queue-empty:
|
||||
!QueueState.has-current && QueueState.upcoming-total == 0;
|
||||
|
||||
// Pull a fresh queue snapshot whenever the panel slides open — the
|
||||
// queue may have changed (or been restored) while it was closed.
|
||||
property <int> panel-state: ShellState.right-panel;
|
||||
changed panel-state => {
|
||||
if (self.panel-state == 1) {
|
||||
QueueState.panel-opened();
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 0px;
|
||||
|
||||
// Header row — title + close button.
|
||||
// --- Header: tabs + 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;
|
||||
spacing: 14px;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
QueueTab {
|
||||
label: @tr("Queue");
|
||||
active: QueueState.tab == 0;
|
||||
clicked => {
|
||||
QueueState.set-tab(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
Text {
|
||||
text: "|";
|
||||
color: Theme.text-disabled;
|
||||
font-size: Typography.body;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
QueueTab {
|
||||
label: @tr("History");
|
||||
active: QueueState.tab == 1;
|
||||
clicked => {
|
||||
QueueState.set-tab(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Spacer pushes the close button to the right edge.
|
||||
Rectangle { horizontal-stretch: 1; }
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
CloseButton {
|
||||
@@ -149,29 +314,341 @@ export component QueueSidebar inherits Rectangle {
|
||||
background: Theme.border-subtle;
|
||||
}
|
||||
|
||||
// Body — the live queue, or an empty-state placeholder.
|
||||
// --- Body --------------------------------------------------------
|
||||
Rectangle {
|
||||
vertical-stretch: 1;
|
||||
|
||||
if QueueState.items.length == 0: Text {
|
||||
text: @tr("Nothing queued");
|
||||
color: Theme.text-muted;
|
||||
font-size: Typography.body;
|
||||
// ===== Queue tab =====
|
||||
if QueueState.tab == 0: Flickable {
|
||||
viewport-width: self.width;
|
||||
viewport-height: queue-body.preferred-height;
|
||||
|
||||
queue-body := VerticalLayout {
|
||||
padding: Spacing.md;
|
||||
spacing: Spacing.md;
|
||||
|
||||
// NOW PLAYING section.
|
||||
if QueueState.has-current: VerticalLayout {
|
||||
spacing: 10px;
|
||||
Text {
|
||||
text: @tr("NOW PLAYING");
|
||||
color: Theme.text-muted;
|
||||
font-size: 11px;
|
||||
font-weight: Typography.semibold;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
// Highlighted current-track card.
|
||||
Rectangle {
|
||||
background: Theme.surface-elevated;
|
||||
border-radius: Radius.sm;
|
||||
height: 68px;
|
||||
HorizontalLayout {
|
||||
padding: 10px;
|
||||
spacing: 12px;
|
||||
// Artwork.
|
||||
Rectangle {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 4px;
|
||||
background: Theme.surface-card;
|
||||
clip: true;
|
||||
if QueueState.now-playing.artwork.width > 0: Image {
|
||||
source: QueueState.now-playing.artwork;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
image-fit: cover;
|
||||
}
|
||||
}
|
||||
// Title + artist.
|
||||
VerticalLayout {
|
||||
horizontal-stretch: 1;
|
||||
alignment: center;
|
||||
spacing: 3px;
|
||||
HorizontalLayout {
|
||||
spacing: 5px;
|
||||
Text {
|
||||
text: QueueState.now-playing.title;
|
||||
color: Theme.text-primary;
|
||||
font-size: Typography.body;
|
||||
font-weight: Typography.medium;
|
||||
overflow: elide;
|
||||
}
|
||||
if QueueState.now-playing.explicit: VerticalLayout {
|
||||
alignment: center;
|
||||
ExplicitBadge {}
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: QueueState.now-playing.artist;
|
||||
color: Theme.text-muted;
|
||||
font-size: 12px;
|
||||
overflow: elide;
|
||||
}
|
||||
}
|
||||
// Favorite (heart) toggle.
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
fav := Rectangle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: Radius.sm;
|
||||
background: fav-ta.has-hover
|
||||
? Theme.surface-hover
|
||||
: transparent;
|
||||
QbzIcon {
|
||||
source: QueueState.now-playing-favorite
|
||||
? @image-url("../assets/icons/heart-filled.svg")
|
||||
: @image-url("../assets/icons/heart.svg");
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
tint: QueueState.now-playing-favorite
|
||||
? Theme.favorite
|
||||
: (fav-ta.has-hover
|
||||
? Theme.text-primary
|
||||
: Theme.text-muted);
|
||||
}
|
||||
fav-ta := TouchArea {
|
||||
mouse-cursor: pointer;
|
||||
clicked => {
|
||||
QueueState.toggle-now-playing-favorite();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UP NEXT section.
|
||||
if QueueState.upcoming-total > 0: VerticalLayout {
|
||||
spacing: 8px;
|
||||
// Counter — "UP NEXT (20 of 40, 38 left)".
|
||||
Text {
|
||||
text: @tr("UP NEXT ({} of {}, {} left)",
|
||||
QueueState.page-end,
|
||||
QueueState.upcoming-total,
|
||||
QueueState.upcoming-remaining);
|
||||
color: Theme.text-muted;
|
||||
font-size: 11px;
|
||||
font-weight: Typography.semibold;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
// Page of upcoming rows.
|
||||
VerticalLayout {
|
||||
spacing: 0px;
|
||||
for item[idx] in QueueState.upcoming-page: QueueRow {
|
||||
item: item;
|
||||
index: QueueState.page-start - 1 + idx;
|
||||
show-number: true;
|
||||
removable: true;
|
||||
clicked => {
|
||||
QueueState.play-upcoming(idx);
|
||||
}
|
||||
remove => {
|
||||
QueueState.remove-upcoming(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Paginator — plain text controls, no pills. Only
|
||||
// shown when the upcoming list spans >1 page.
|
||||
if QueueState.page-count > 1: HorizontalLayout {
|
||||
padding-top: 4px;
|
||||
alignment: center;
|
||||
spacing: Spacing.md;
|
||||
IconButton {
|
||||
icon: @image-url("../assets/icons/chevron-left.svg");
|
||||
icon-size: 15px;
|
||||
enabled: QueueState.page > 0;
|
||||
clicked => {
|
||||
QueueState.prev-page();
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
Text {
|
||||
text: @tr("Page {} of {}",
|
||||
QueueState.page + 1,
|
||||
QueueState.page-count);
|
||||
color: Theme.text-muted;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
IconButton {
|
||||
icon: @image-url("../assets/icons/chevron-right.svg");
|
||||
icon-size: 15px;
|
||||
enabled: QueueState.page < QueueState.page-count - 1;
|
||||
clicked => {
|
||||
QueueState.next-page();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty state — no current track and no upcoming.
|
||||
if root.queue-empty: VerticalLayout {
|
||||
padding-top: 48px;
|
||||
spacing: 6px;
|
||||
Text {
|
||||
text: @tr("Your queue is empty");
|
||||
color: Theme.text-secondary;
|
||||
font-size: Typography.body;
|
||||
font-weight: Typography.medium;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
Text {
|
||||
text: @tr("Play an album or track to get started");
|
||||
color: Theme.text-muted;
|
||||
font-size: 12px;
|
||||
horizontal-alignment: center;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
// No-results state — a search is active but nothing matched.
|
||||
if !root.queue-empty
|
||||
&& QueueState.search-query != ""
|
||||
&& QueueState.upcoming-total == 0: VerticalLayout {
|
||||
padding-top: 32px;
|
||||
Text {
|
||||
text: @tr("No tracks match your search");
|
||||
color: Theme.text-muted;
|
||||
font-size: 12px;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if QueueState.items.length > 0: Flickable {
|
||||
// ===== History tab =====
|
||||
if QueueState.tab == 1: Flickable {
|
||||
viewport-width: self.width;
|
||||
viewport-height: rows.preferred-height;
|
||||
viewport-height: history-body.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);
|
||||
history-body := VerticalLayout {
|
||||
padding: Spacing.md;
|
||||
spacing: 8px;
|
||||
|
||||
Text {
|
||||
text: @tr("RECENTLY PLAYED");
|
||||
color: Theme.text-muted;
|
||||
font-size: 11px;
|
||||
font-weight: Typography.semibold;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
if QueueState.history.length > 0: VerticalLayout {
|
||||
spacing: 0px;
|
||||
for item[idx] in QueueState.history: QueueRow {
|
||||
item: item;
|
||||
index: idx;
|
||||
show-number: false;
|
||||
removable: false;
|
||||
clicked => {
|
||||
QueueState.play-history(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
if QueueState.history.length == 0: VerticalLayout {
|
||||
padding-top: 48px;
|
||||
Text {
|
||||
text: @tr("Nothing played yet");
|
||||
color: Theme.text-muted;
|
||||
font-size: Typography.body;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Footer: four actions + inline search (Queue tab only) ------
|
||||
if QueueState.tab == 0 && !root.queue-empty: Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.border-subtle;
|
||||
}
|
||||
if QueueState.tab == 0 && !root.queue-empty: HorizontalLayout {
|
||||
padding-left: Spacing.md;
|
||||
padding-right: Spacing.md;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
spacing: Spacing.xs;
|
||||
|
||||
// Action 1 — Clear queue.
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
IconButton {
|
||||
icon: @image-url("../assets/icons/list-x.svg");
|
||||
clicked => {
|
||||
QueueState.clear-queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Action 2 — Save the queue as a playlist.
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
IconButton {
|
||||
icon: @image-url("../assets/icons/list-plus.svg");
|
||||
clicked => {
|
||||
QueueState.save-as-playlist();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Action 3 — Toggle infinite play.
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
IconButton {
|
||||
icon: @image-url("../assets/icons/infinity.svg");
|
||||
active: QueueState.infinite-play;
|
||||
clicked => {
|
||||
QueueState.toggle-infinite-play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer.
|
||||
Rectangle { horizontal-stretch: 1; }
|
||||
|
||||
// Action 4 — inline queue search field.
|
||||
Rectangle {
|
||||
width: 150px;
|
||||
height: 30px;
|
||||
border-radius: Radius.sm;
|
||||
background: Theme.surface-elevated;
|
||||
HorizontalLayout {
|
||||
padding-left: 9px;
|
||||
padding-right: 7px;
|
||||
spacing: 6px;
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
QbzIcon {
|
||||
source: @image-url("../assets/icons/search.svg");
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
tint: Theme.text-muted;
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
horizontal-stretch: 1;
|
||||
search-input := TextInput {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: Theme.text-primary;
|
||||
font-size: 12px;
|
||||
vertical-alignment: center;
|
||||
single-line: true;
|
||||
text: QueueState.search-query;
|
||||
edited => {
|
||||
QueueState.search-query = self.text;
|
||||
QueueState.search-changed();
|
||||
}
|
||||
}
|
||||
Text {
|
||||
visible: search-input.text == "";
|
||||
text: @tr("Search queue");
|
||||
color: Theme.text-muted;
|
||||
font-size: 12px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user