From e1aa0a71dc95d9385d657107b42a3e1be0e7db78 Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Tue, 19 May 2026 20:04:53 -0600 Subject: [PATCH] feat(slint): full track context menu (13 items, ported from Tauri) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New shared TrackContextMenu primitive with every option the Tauri TrackMenu shows (flat — no submenus): Play now / Play next / Add to queue / Create radio / Add to library / Add to mixtape / Add to playlist / Share Qobuz link / Share Song.link / Make available offline / Go to album / Go to artist / Track info. Items already wired in the MVP (play / play-next / queue / favorite) fire as before; the rest emit their media-action and stay no-op until the backend lands. Used by SearchTrackRow, AlbumPageView TrackRow, and ArtistPageView TrackRow. cc --- crates/qbz-slint/ui/album/AlbumPageView.slint | 41 ++----- .../qbz-slint/ui/artist/ArtistPageView.slint | 39 +------ crates/qbz-slint/ui/assets/icons/link.svg | 1 + .../ui/primitives/TrackContextMenu.slint | 109 ++++++++++++++++++ .../qbz-slint/ui/search/SearchTrackRow.slint | 43 ++----- 5 files changed, 130 insertions(+), 103 deletions(-) create mode 100644 crates/qbz-slint/ui/assets/icons/link.svg create mode 100644 crates/qbz-slint/ui/primitives/TrackContextMenu.slint diff --git a/crates/qbz-slint/ui/album/AlbumPageView.slint b/crates/qbz-slint/ui/album/AlbumPageView.slint index 0228b91e9..5074f1e02 100644 --- a/crates/qbz-slint/ui/album/AlbumPageView.slint +++ b/crates/qbz-slint/ui/album/AlbumPageView.slint @@ -15,6 +15,7 @@ import { ContextMenu } from "../primitives/ContextMenu.slint"; import { ContextMenuItem } from "../primitives/ContextMenuItem.slint"; import { QualityBadgeFull } from "../primitives/QualityBadgeFull.slint"; import { TrackPlayCell } from "../primitives/TrackPlayCell.slint"; +import { TrackContextMenu } from "../primitives/TrackContextMenu.slint"; import { NavButtons } from "../shell/NavButtons.slint"; component CircleAction inherits Rectangle { @@ -239,40 +240,12 @@ component TrackRow inherits Rectangle { } } - 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"); - } - } + track-menu := TrackContextMenu { + x: parent.width - self.width - 12px; + y: 48px; + track-id: root.track.id; + media-action(k, i, a) => { + root.media-action(k, i, a); } } } diff --git a/crates/qbz-slint/ui/artist/ArtistPageView.slint b/crates/qbz-slint/ui/artist/ArtistPageView.slint index 6db78411b..8bd061cbb 100644 --- a/crates/qbz-slint/ui/artist/ArtistPageView.slint +++ b/crates/qbz-slint/ui/artist/ArtistPageView.slint @@ -13,6 +13,7 @@ import { QbzIcon } from "../primitives/QbzIcon.slint"; import { ContextMenu } from "../primitives/ContextMenu.slint"; import { ContextMenuItem } from "../primitives/ContextMenuItem.slint"; import { TrackPlayCell } from "../primitives/TrackPlayCell.slint"; +import { TrackContextMenu } from "../primitives/TrackContextMenu.slint"; import { Carousel } from "../discover/Carousel.slint"; import { NavButtons } from "../shell/NavButtons.slint"; @@ -147,40 +148,12 @@ component TrackRow inherits Rectangle { } } - track-menu := PopupWindow { - x: parent.width - 208px; + track-menu := TrackContextMenu { + x: parent.width - self.width - 12px; y: 44px; - 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"); - } - } + track-id: root.track.id; + media-action(k, i, a) => { + root.media-action(k, i, a); } } } diff --git a/crates/qbz-slint/ui/assets/icons/link.svg b/crates/qbz-slint/ui/assets/icons/link.svg new file mode 100644 index 000000000..f2d15541f --- /dev/null +++ b/crates/qbz-slint/ui/assets/icons/link.svg @@ -0,0 +1 @@ + diff --git a/crates/qbz-slint/ui/primitives/TrackContextMenu.slint b/crates/qbz-slint/ui/primitives/TrackContextMenu.slint new file mode 100644 index 000000000..32bacbec0 --- /dev/null +++ b/crates/qbz-slint/ui/primitives/TrackContextMenu.slint @@ -0,0 +1,109 @@ +// Track-row "more" context menu — flat, ported 1:1 from the Tauri +// TrackMenu component. Items that have no Slint wiring yet emit a +// media-action with an action string the runtime currently ignores +// (no-op until backend wiring lands). + +import { ContextMenu } from "ContextMenu.slint"; +import { ContextMenuItem } from "ContextMenuItem.slint"; + +export component TrackContextMenu inherits PopupWindow { + in property track-id; + callback media-action(string, string, string); + + width: 224px; + height: 392px; + + ContextMenu { + ContextMenuItem { + icon: @image-url("../assets/icons/play.svg"); + label: @tr("Play now"); + 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/radio.svg"); + label: @tr("Create radio"); + clicked => { + root.media-action("track", root.track-id, "create-radio"); + } + } + ContextMenuItem { + icon: @image-url("../assets/icons/heart.svg"); + label: @tr("Add to library"); + clicked => { + root.media-action("track", root.track-id, "favorite"); + } + } + ContextMenuItem { + icon: @image-url("../assets/icons/cassette-tape.svg"); + label: @tr("Add to mixtape"); + clicked => { + root.media-action("track", root.track-id, "add-to-mixtape"); + } + } + ContextMenuItem { + icon: @image-url("../assets/icons/list-music.svg"); + label: @tr("Add to playlist"); + clicked => { + root.media-action("track", root.track-id, "add-to-playlist"); + } + } + ContextMenuItem { + icon: @image-url("../assets/icons/link.svg"); + label: @tr("Share Qobuz link"); + clicked => { + root.media-action("track", root.track-id, "share-qobuz"); + } + } + ContextMenuItem { + icon: @image-url("../assets/icons/link.svg"); + label: @tr("Share Song.link"); + clicked => { + root.media-action("track", root.track-id, "share-songlink"); + } + } + ContextMenuItem { + icon: @image-url("../assets/icons/cloud-download.svg"); + label: @tr("Make available offline"); + clicked => { + root.media-action("track", root.track-id, "download"); + } + } + ContextMenuItem { + icon: @image-url("../assets/icons/disc-3.svg"); + label: @tr("Go to album"); + clicked => { + root.media-action("track", root.track-id, "go-to-album"); + } + } + ContextMenuItem { + icon: @image-url("../assets/icons/user.svg"); + label: @tr("Go to artist"); + clicked => { + root.media-action("track", root.track-id, "go-to-artist"); + } + } + ContextMenuItem { + icon: @image-url("../assets/icons/info.svg"); + label: @tr("Track info"); + clicked => { + root.media-action("track", root.track-id, "track-info"); + } + } + } +} diff --git a/crates/qbz-slint/ui/search/SearchTrackRow.slint b/crates/qbz-slint/ui/search/SearchTrackRow.slint index 6a24993f0..f216e6590 100644 --- a/crates/qbz-slint/ui/search/SearchTrackRow.slint +++ b/crates/qbz-slint/ui/search/SearchTrackRow.slint @@ -10,9 +10,8 @@ import { Typography } from "../foundation/typography.slint"; import { Radius } from "../foundation/radius.slint"; import { SearchTrackItem } from "../state.slint"; import { QbzIcon } from "../primitives/QbzIcon.slint"; -import { ContextMenu } from "../primitives/ContextMenu.slint"; -import { ContextMenuItem } from "../primitives/ContextMenuItem.slint"; import { TrackPlayCell } from "../primitives/TrackPlayCell.slint"; +import { TrackContextMenu } from "../primitives/TrackContextMenu.slint"; export component SearchTrackRow inherits Rectangle { in property track; @@ -153,40 +152,12 @@ export component SearchTrackRow inherits Rectangle { } } - 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"); - } - } + track-menu := TrackContextMenu { + x: parent.width - self.width - 12px; + y: 48px; + track-id: root.track.id; + media-action(k, i, a) => { + root.media-action(k, i, a); } } }