Three views instantiated components the phase-23 directory move had left unimportable: PlaylistView and SearchView use TrackRow (now in rows/) and SectionRail uses AlbumCard (now in cards/). QML resolves lazily, so these survived cargo check, the test suite and the offscreen smoke — they only failed when the user opened a playlist. Adds a repo-wide audit of every instantiated component against the importing file's visible directories; the three above were all of them. The ambient field and the new dock band froze on lost focus. That is wrong on any tiling or mosaic desktop, where windows stay fully visible while unfocused — the field would stop while the user is looking at it. Both now gate on the window not being minimized or hidden, which is the real "nobody can see this". Freezing on not-visible stays. Dock band render cost, in descending order of impact: the drain publishes only the stream the active mode consumes (Bars mode was marshalling a 512-float waveform QList 30 times a second for nothing); each mode marshals its QList into JS once per tick instead of once per delegate; one shared Gradient replaces 28 per-delegate instances; and the per-bar Behaviors are gone since qbz-audio already smooths and clamps the amplitudes upstream. 18/18 tests; offscreen smoke clean; component-resolution audit clean. cc
605 lines
27 KiB
QML
605 lines
27 KiB
QML
// Playlist detail view — the QML port of playlist/PlaylistView.slint
|
|
// (phase 17). ONE JSON document (QbzBridge.playlistJson, playlist_qt.rs
|
|
// PlaylistDoc: header + track rows + ownership/follow/pin/sort/search).
|
|
//
|
|
// Header: 150px cover (server image, else first-track cover, else the
|
|
// placeholder glyph), "PLAYLIST" eyebrow, name, description-short + Read
|
|
// more (the shared AppShell text modal), owner • N tracks • duration,
|
|
// the action row: Play / Shuffle / heart (owned, wired) / pin (wired) /
|
|
// follow (foreign, subscribe API) / copy (foreign, create+add) / edit
|
|
// (owner: rename + delete modal). Right edge: in-playlist search + the
|
|
// sort dropdown (Default/Title/Artist/Album/Duration/Date added/Custom).
|
|
//
|
|
// Track list: the exact PlaylistView row (# / 36px art / title+artist /
|
|
// Album 220px link / Duration 70 / Quality 92 / heart / cloud reserve /
|
|
// ⋯ menu with Remove-from-playlist for owners), column header, empty and
|
|
// loading states. Per-row ⋯: Play / Play next / Play later / Add to
|
|
// queue / Go to artist / Go to album / Add|Remove from Library / Remove
|
|
// from playlist (owner).
|
|
//
|
|
// Drag & drop (issue #589): the row BODY is the drag source (press-drag
|
|
// >6px — the same gesture that drops onto sidebar playlists). A release
|
|
// INSIDE this list reorders (owner, switches the sort to Custom and
|
|
// persists the order); a release on a sidebar playlist row is the shared
|
|
// add-to-playlist drop (main.rs drag_end). The 2px accent line marks the
|
|
// insertion slot while dragging.
|
|
//
|
|
// POC-NOTEs (playlist_qt.rs has the full list): local playlists,
|
|
// custom-cover set/clear, multi-select + bulk bar, Suggested Songs,
|
|
// offline download, share — not ported.
|
|
|
|
import QtQuick
|
|
import QtQuick.Controls
|
|
import com.blitzfc.qbz
|
|
import "../controls"
|
|
import "../rows"
|
|
import "../theme"
|
|
|
|
Rectangle {
|
|
id: root
|
|
color: ambientOn ? "transparent" : theme.surfaceMain
|
|
readonly property bool ambientOn: QbzShell.ambientMode > 0 && QbzPlayer.npHasTrack
|
|
radius: 12
|
|
|
|
QbzTheme { id: theme }
|
|
|
|
readonly property var doc: parseDoc()
|
|
function parseDoc() {
|
|
try {
|
|
return JSON.parse(QbzBridge.playlistJson)
|
|
} catch (e) {
|
|
return ({})
|
|
}
|
|
}
|
|
readonly property var allTracks: doc.tracks || []
|
|
readonly property bool isOwner: doc.isOwner === true
|
|
readonly property bool loading: doc.loading === true
|
|
readonly property string sortField: doc.sortField || "default"
|
|
readonly property bool sortAsc: doc.sortAsc === true
|
|
readonly property string searchQuery: doc.search || ""
|
|
|
|
// Visible rows after the in-playlist search filter.
|
|
readonly property var tracks: {
|
|
if (searchQuery.trim() === "") return allTracks
|
|
const needle = searchQuery.trim().toLowerCase()
|
|
return allTracks.filter(function (t) {
|
|
return (t.title || "").toLowerCase().indexOf(needle) >= 0
|
|
|| (t.artist || "").toLowerCase().indexOf(needle) >= 0
|
|
|| (t.album || "").toLowerCase().indexOf(needle) >= 0
|
|
})
|
|
}
|
|
|
|
function sortLabel() {
|
|
return sortField === "title" ? QbzSession.tr("Title", QbzSession.trRev)
|
|
: sortField === "artist" ? QbzSession.tr("Artist", QbzSession.trRev)
|
|
: sortField === "album" ? QbzSession.tr("Album", QbzSession.trRev)
|
|
: sortField === "duration" ? QbzSession.tr("Duration", QbzSession.trRev)
|
|
: sortField === "added" ? QbzSession.tr("Date added", QbzSession.trRev)
|
|
: sortField === "custom" ? QbzSession.tr("Custom", QbzSession.trRev)
|
|
: QbzSession.tr("Default", QbzSession.trRev)
|
|
}
|
|
|
|
// --- Reorder state (the view-level half of the shared drag) ----------
|
|
property int reorderFrom: -1
|
|
property int reorderOver: -1
|
|
property string reorderDropPlaylist: ""
|
|
|
|
// The guarded mirror (PlaylistView.slint: Rust clears over-playlist-id
|
|
// in the same turn it clears active, so the value must be captured
|
|
// WHILE the drag is live).
|
|
Connections {
|
|
target: QbzShell
|
|
function onDragOverPlaylistIdChanged() {
|
|
if (QbzShell.dragActive) root.reorderDropPlaylist = QbzShell.dragOverPlaylistId
|
|
}
|
|
function onDragXChanged() { root.updateSlot() }
|
|
function onDragYChanged() { root.updateSlot() }
|
|
function onDragActiveChanged() {
|
|
if (QbzShell.dragActive) return
|
|
// Drag ended (main.rs drag_end already ran — it handled a
|
|
// sidebar-playlist drop, if any). A release INSIDE the list and
|
|
// NOT on a sidebar playlist reorders.
|
|
if (root.reorderFrom >= 0
|
|
&& root.reorderDropPlaylist === ""
|
|
&& root.pointerInList()
|
|
&& root.slotFromPointer() !== root.reorderFrom
|
|
&& root.slotFromPointer() !== root.reorderFrom + 1) {
|
|
QbzBridge.playlistReorder(root.reorderFrom, root.slotFromPointer())
|
|
}
|
|
root.reorderFrom = -1
|
|
root.reorderOver = -1
|
|
root.reorderDropPlaylist = ""
|
|
}
|
|
}
|
|
function pointerInList() {
|
|
if (!trackList) return false
|
|
const tl = trackList.mapToItem(null, 0, 0)
|
|
const br = trackList.mapToItem(null, trackList.width, trackList.height)
|
|
return QbzShell.dragX >= tl.x && QbzShell.dragX <= br.x
|
|
&& QbzShell.dragY >= tl.y && QbzShell.dragY <= br.y
|
|
}
|
|
function slotFromPointer() {
|
|
const tl = trackList.mapToItem(null, 0, 0)
|
|
return Math.max(0, Math.min(tracks.length,
|
|
Math.round((QbzShell.dragY - tl.y + trackList.contentY) / 50)))
|
|
}
|
|
function updateSlot() {
|
|
if (root.reorderFrom >= 0 && QbzShell.dragActive) {
|
|
root.reorderOver = root.pointerInList() ? root.slotFromPointer() : -1
|
|
}
|
|
}
|
|
|
|
// Read more → the shared AppShell text modal (phase 16 pattern).
|
|
function openDescription() {
|
|
var shell = root.parent
|
|
while (shell && shell.openTextModal === undefined) shell = shell.parent
|
|
if (shell) shell.openTextModal(doc.name || "", doc.description || "")
|
|
}
|
|
|
|
// --- Circular header action (CircleAction, on-surface variant) -------
|
|
|
|
|
|
// ============================ the view ================================
|
|
Column {
|
|
anchors.fill: parent
|
|
anchors.leftMargin: 32
|
|
anchors.rightMargin: 16
|
|
anchors.topMargin: 11
|
|
anchors.bottomMargin: 16
|
|
spacing: 0
|
|
|
|
Item { width: 1; height: 22 }
|
|
|
|
// --- Header ---------------------------------------------------------
|
|
Row {
|
|
width: parent.width
|
|
spacing: 24
|
|
|
|
// Cover.
|
|
Rectangle {
|
|
width: 150
|
|
height: 150
|
|
radius: theme.radiusMd
|
|
color: theme.surfaceElevated
|
|
clip: true
|
|
RoundedImage {
|
|
visible: (doc.coverPath || "") !== ""
|
|
anchors.fill: parent
|
|
source: doc.coverPath || ""
|
|
radius: theme.radiusMd
|
|
}
|
|
QbzIcon {
|
|
visible: (doc.coverPath || "") === ""
|
|
name: "list-music"
|
|
width: 56
|
|
height: 56
|
|
anchors.centerIn: parent
|
|
tintName: "muted"
|
|
}
|
|
}
|
|
|
|
// Metadata.
|
|
Column {
|
|
width: parent.width - 150 - 24
|
|
spacing: 0
|
|
Text {
|
|
text: QbzSession.tr("Playlist", QbzSession.trRev).toUpperCase()
|
|
color: theme.textMuted
|
|
font.pixelSize: 11
|
|
font.weight: theme.weightSemibold
|
|
font.letterSpacing: 1.5
|
|
}
|
|
Item { width: 1; height: 4 }
|
|
Text {
|
|
width: parent.width
|
|
text: doc.name || ""
|
|
color: theme.textPrimary
|
|
font.pixelSize: theme.fontSection
|
|
font.weight: theme.weightBold
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
Item { visible: (doc.descriptionShort || "") !== ""; width: 1; height: 6 }
|
|
Text {
|
|
visible: (doc.descriptionShort || "") !== ""
|
|
width: Math.min(parent.width, 700)
|
|
text: doc.descriptionShort || ""
|
|
color: theme.textSecondary
|
|
font.pixelSize: theme.fontLegal
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
Text {
|
|
visible: (doc.description || "") !== (doc.descriptionShort || "")
|
|
height: 18
|
|
text: QbzSession.tr("Read more", QbzSession.trRev)
|
|
color: rmArea.containsMouse ? theme.accentHover : theme.accent
|
|
font.pixelSize: theme.fontLegal
|
|
verticalAlignment: Text.AlignVCenter
|
|
MouseArea {
|
|
id: rmArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: root.openDescription()
|
|
}
|
|
}
|
|
Item { width: 1; height: 8 }
|
|
Text {
|
|
text: (doc.owner || "") + " • " + (doc.trackCount || 0) + " " + QbzSession.tr("tracks", QbzSession.trRev)
|
|
+ " • " + (doc.totalDuration || "")
|
|
color: theme.textSecondary
|
|
font.pixelSize: theme.fontLegal
|
|
}
|
|
Item { width: 1; height: 14 }
|
|
// Actions — buttons left, search + sort floating right.
|
|
Row {
|
|
width: parent.width
|
|
spacing: 12
|
|
QbzCircleAction {
|
|
name: "play-fill"
|
|
primary: true
|
|
btnEnabled: root.allTracks.length > 0
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
onClicked: QbzBridge.playlistPlayAll()
|
|
}
|
|
QbzCircleAction {
|
|
name: "shuffle"
|
|
btnEnabled: root.allTracks.length > 0
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
onClicked: QbzBridge.playlistShuffle()
|
|
}
|
|
QbzCircleAction {
|
|
name: (doc.isFavorite === true) ? "heart-filled" : "heart"
|
|
active: doc.isFavorite === true
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
onClicked: QbzBridge.playlistToggleFavorite()
|
|
}
|
|
QbzCircleAction {
|
|
name: (doc.pinned === true) ? "pin-filled" : "pin"
|
|
active: doc.pinned === true
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
onClicked: QbzBridge.playlistTogglePin()
|
|
}
|
|
QbzCircleAction {
|
|
visible: !root.isOwner
|
|
name: (doc.isFollowing === true) ? "check" : "user-plus"
|
|
active: doc.isFollowing === true
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
onClicked: QbzBridge.playlistToggleFollow()
|
|
}
|
|
QbzCircleAction {
|
|
visible: !root.isOwner && doc.isCopied !== true
|
|
name: "copy"
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
onClicked: QbzBridge.playlistCopy()
|
|
}
|
|
QbzCircleAction {
|
|
visible: root.isOwner
|
|
name: "pen-line"
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
onClicked: editModal.open()
|
|
}
|
|
|
|
Item { width: parent.width - 40 - 32 * 6 - 7 * 12 - 220 - 140; height: 1 }
|
|
|
|
// In-playlist search.
|
|
Rectangle {
|
|
width: 220
|
|
height: 30
|
|
radius: 6
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
color: theme.surfaceElevated
|
|
border.width: 1
|
|
border.color: theme.borderSubtle
|
|
Row {
|
|
anchors.fill: parent
|
|
anchors.leftMargin: 9
|
|
anchors.rightMargin: 9
|
|
spacing: 6
|
|
QbzIcon { name: "search"; width: 13; height: 13; anchors.verticalCenter: parent.verticalCenter; tintName: "muted" }
|
|
TextInput {
|
|
width: parent.width - 19
|
|
height: parent.height
|
|
color: theme.textPrimary
|
|
font.pixelSize: 13
|
|
verticalAlignment: Text.AlignVCenter
|
|
clip: true
|
|
onTextEdited: QbzBridge.playlistSetSearch(text)
|
|
Text {
|
|
visible: parent.text === ""
|
|
anchors.fill: parent
|
|
text: QbzSession.tr("Search tracks", QbzSession.trRev)
|
|
color: theme.textMuted
|
|
font.pixelSize: 13
|
|
verticalAlignment: Text.AlignVCenter
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Sort dropdown.
|
|
Rectangle {
|
|
width: 132
|
|
height: 34
|
|
radius: 6
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
color: sortArea.containsMouse ? theme.surfaceHover : theme.surfaceElevated
|
|
border.width: 1
|
|
border.color: theme.borderSubtle
|
|
Row {
|
|
anchors.fill: parent
|
|
anchors.leftMargin: 11
|
|
anchors.rightMargin: 8
|
|
spacing: 5
|
|
Text {
|
|
width: parent.width - 13 - 5
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: QbzSession.tr("Sort", QbzSession.trRev) + ": " + root.sortLabel()
|
|
color: theme.textSecondary
|
|
font.pixelSize: theme.fontLegal
|
|
elide: Text.ElideRight
|
|
}
|
|
QbzIcon { name: "chevron-down"; width: 13; height: 13; anchors.verticalCenter: parent.verticalCenter; tintName: "muted" }
|
|
}
|
|
MouseArea {
|
|
id: sortArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: sortMenu.openBelowRight(sortArea)
|
|
}
|
|
QbzContextMenu {
|
|
id: sortMenu
|
|
menuWidth: 172
|
|
Repeater {
|
|
model: [
|
|
{ "field": "default", "label": QbzSession.tr("Default", QbzSession.trRev) },
|
|
{ "field": "title", "label": QbzSession.tr("Title", QbzSession.trRev) },
|
|
{ "field": "artist", "label": QbzSession.tr("Artist", QbzSession.trRev) },
|
|
{ "field": "album", "label": QbzSession.tr("Album", QbzSession.trRev) },
|
|
{ "field": "duration", "label": QbzSession.tr("Duration", QbzSession.trRev) },
|
|
{ "field": "added", "label": QbzSession.tr("Date added", QbzSession.trRev) },
|
|
{ "field": "custom", "label": QbzSession.tr("Custom", QbzSession.trRev), "ownerOnly": true },
|
|
]
|
|
delegate: Rectangle {
|
|
required property var modelData
|
|
visible: modelData.ownerOnly !== true || root.isOwner
|
|
width: parent ? parent.width : 0
|
|
height: visible ? 33 : 0
|
|
radius: 5
|
|
color: soArea.containsMouse ? theme.surfaceHover : "transparent"
|
|
Row {
|
|
anchors.fill: parent
|
|
anchors.leftMargin: 8
|
|
spacing: 6
|
|
Text {
|
|
width: parent.width - 26
|
|
height: parent.height
|
|
text: modelData.label
|
|
color: theme.textSecondary
|
|
font.pixelSize: 13
|
|
font.weight: root.sortField === modelData.field ? theme.weightSemibold : theme.weightRegular
|
|
verticalAlignment: Text.AlignVCenter
|
|
}
|
|
QbzIcon {
|
|
visible: root.sortField === modelData.field
|
|
name: root.sortAsc ? "chevron-up" : "chevron-down"
|
|
width: 12
|
|
height: 12
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
tintName: "accent"
|
|
}
|
|
}
|
|
MouseArea {
|
|
id: soArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
sortMenu.close()
|
|
QbzBridge.playlistSetSort(modelData.field)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Item { width: 1; height: 18 }
|
|
|
|
// Loading / empty.
|
|
QbzSpinner {
|
|
visible: root.loading
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
size: 36
|
|
}
|
|
Text {
|
|
visible: !root.loading && root.allTracks.length === 0
|
|
text: QbzSession.tr("This playlist is empty.", QbzSession.trRev)
|
|
color: theme.textMuted
|
|
font.pixelSize: theme.fontBody
|
|
}
|
|
|
|
// --- Column header ---------------------------------------------------
|
|
Row {
|
|
visible: root.tracks.length > 0
|
|
width: parent.width
|
|
anchors.leftMargin: 12
|
|
spacing: 14
|
|
Text { text: "#"; width: 32; color: theme.textMuted; font.pixelSize: theme.fontLegal; horizontalAlignment: Text.AlignHCenter }
|
|
Rectangle { width: 36; height: 1; color: "transparent" }
|
|
Text { text: QbzSession.tr("Title", QbzSession.trRev); width: parent.width - 32 - 36 - 220 - 70 - 92 - 28 - 28 - 32 - 8 * 14; color: theme.textMuted; font.pixelSize: theme.fontLegal }
|
|
Text { text: QbzSession.tr("Album", QbzSession.trRev); width: 220; color: theme.textMuted; font.pixelSize: theme.fontLegal }
|
|
Text { text: QbzSession.tr("Duration", QbzSession.trRev); width: 70; color: theme.textMuted; font.pixelSize: theme.fontLegal; horizontalAlignment: Text.AlignHCenter }
|
|
Text { text: QbzSession.tr("Quality", QbzSession.trRev); width: 92; color: theme.textMuted; font.pixelSize: theme.fontLegal; horizontalAlignment: Text.AlignHCenter }
|
|
Rectangle { width: 28; height: 1; color: "transparent" }
|
|
Rectangle { width: 28; height: 1; color: "transparent" }
|
|
Rectangle { width: 32; height: 1; color: "transparent" }
|
|
}
|
|
Rectangle { visible: root.tracks.length > 0; width: 1; height: 3; color: "transparent" }
|
|
Rectangle { visible: root.tracks.length > 0; width: parent.width; height: 1; color: theme.borderSubtle }
|
|
Item { width: 1; height: 6 }
|
|
|
|
// --- Track list -------------------------------------------------------
|
|
Item {
|
|
width: parent.width
|
|
height: parent.height - 28 - 150 - 18 - 50
|
|
|
|
ListView {
|
|
id: trackList
|
|
anchors.fill: parent
|
|
anchors.rightMargin: 14
|
|
clip: true
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
cacheBuffer: 500
|
|
model: root.tracks
|
|
delegate: TrackRow {
|
|
required property var modelData
|
|
required property int index
|
|
width: parent ? parent.width : 0
|
|
item: modelData
|
|
number: index + 1
|
|
showArtwork: true
|
|
showAlbum: true
|
|
showDownload: true
|
|
menuShowRemove: root.isOwner
|
|
onPlayRequested: QbzBridge.playlistPlayTrack(item.id)
|
|
onEnqueueRequested: function (m) { QbzBridge.playlistEnqueueTrack(item.id, m) }
|
|
onRemoveRequested: QbzBridge.playlistRemoveTrack(item.playlistTrackId)
|
|
onBodyDragStarted: function (n) {
|
|
// #589: report the source index BEFORE the shared drag.
|
|
if (root.isOwner) {
|
|
root.reorderFrom = index
|
|
root.reorderOver = -1
|
|
root.reorderDropPlaylist = ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Drop indicator: a 2px accent line at the insertion slot,
|
|
// hidden on the no-op slots and while outside the list.
|
|
Rectangle {
|
|
visible: root.reorderFrom >= 0
|
|
&& root.reorderOver >= 0
|
|
&& root.reorderOver !== root.reorderFrom
|
|
&& root.reorderOver !== root.reorderFrom + 1
|
|
x: 0
|
|
y: Math.max(0, Math.min(parent.height - 2,
|
|
root.reorderOver * 50 - trackList.contentY - 1))
|
|
width: parent.width - 14
|
|
height: 2
|
|
radius: 1
|
|
color: theme.accent
|
|
}
|
|
|
|
QbzScrollBar {
|
|
target: trackList
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: 4
|
|
anchors.top: parent.top
|
|
anchors.bottom: parent.bottom
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Edit modal (rename + delete; owner) ---------------------------------
|
|
Popup {
|
|
id: editModal
|
|
parent: Overlay.overlay
|
|
anchors.centerIn: parent
|
|
width: 380
|
|
padding: 20
|
|
closePolicy: Popup.CloseOnEscape
|
|
background: Rectangle {
|
|
color: theme.surfaceCard
|
|
radius: theme.radiusMd
|
|
border.width: 1
|
|
border.color: theme.borderSubtle
|
|
}
|
|
contentItem: Column {
|
|
spacing: 14
|
|
Text {
|
|
text: QbzSession.tr("Edit playlist", QbzSession.trRev)
|
|
color: theme.textPrimary
|
|
font.pixelSize: theme.fontHeading
|
|
font.weight: theme.weightSemibold
|
|
}
|
|
Rectangle {
|
|
width: parent.width
|
|
height: 36
|
|
radius: 6
|
|
color: theme.surfaceElevated
|
|
border.width: 1
|
|
border.color: theme.borderSubtle
|
|
TextInput {
|
|
id: nameInput
|
|
anchors.fill: parent
|
|
anchors.leftMargin: 10
|
|
anchors.rightMargin: 10
|
|
text: root.doc.name || ""
|
|
color: theme.textPrimary
|
|
font.pixelSize: 14
|
|
verticalAlignment: Text.AlignVCenter
|
|
clip: true
|
|
}
|
|
}
|
|
Row {
|
|
spacing: 10
|
|
anchors.right: parent.right
|
|
Rectangle {
|
|
width: delText.implicitWidth + 28
|
|
height: 34
|
|
radius: 6
|
|
color: delArea.containsMouse ? "#33ef4444" : "transparent"
|
|
border.width: 1
|
|
border.color: "#66ef4444"
|
|
Text {
|
|
id: delText
|
|
anchors.centerIn: parent
|
|
text: QbzSession.tr("Delete", QbzSession.trRev)
|
|
color: "#ef4444"
|
|
font.pixelSize: 13
|
|
font.weight: theme.weightMedium
|
|
}
|
|
MouseArea {
|
|
id: delArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
editModal.close()
|
|
QbzBridge.playlistDelete()
|
|
}
|
|
}
|
|
}
|
|
Rectangle {
|
|
width: saveText.implicitWidth + 28
|
|
height: 34
|
|
radius: 6
|
|
color: saveArea.containsMouse ? theme.accentHover : theme.accent
|
|
Text {
|
|
id: saveText
|
|
anchors.centerIn: parent
|
|
text: QbzSession.tr("Save", QbzSession.trRev)
|
|
color: "#ffffff"
|
|
font.pixelSize: 13
|
|
font.weight: theme.weightMedium
|
|
}
|
|
MouseArea {
|
|
id: saveArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
editModal.close()
|
|
QbzBridge.playlistRename(nameInput.text)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|