Files
qbz/crates/qbz-qt/qml/controls/QbzSplitButton.qml
T
vicrodh 6d07dfdf4d feat(artist): sort for the in-library albums and a split play button on every album section
The "In library" Albums section gains the release buckets' sort select
(same five keys, persisted by artist_prefs under the pseudo release type
"library"; rows sorted in QML off the document's seed). Every album
section — each catalog bucket and the library grid — gains a split
button right of its sort (controls/QbzSplitButton.qml, sized like the
sm select): the body plays the whole section in its sort order as one
queue replacing the current one (`QbzPlayer.playAlbums` ->
`playback_qt::play_albums`, each album resolved through the single-album
path, failures skipped), the chevron offers Play random (one album of
the section) and Play selected, which arms a per-section select mode:
the cards show their check bottom-right inside the art
(`AlbumCard.selectMarkBottom`), the button reads "Play selected (n)" and
plays the checked albums in sort order, and the menu swaps that row for
Cancel selection. One section is armed at a time; changing artist or
tab disarms. No queue options on purpose.

cc
2026-09-04 13:10:42 -06:00

171 lines
5.5 KiB
QML

// QbzSplitButton — a toolbar button that LOOKS like a select: the body runs
// the primary action, the chevron cell opens a menu of alternatives.
//
// +--------------+-+
// | Play all |v|
// +--------------+-+
//
// Sized and painted like `QbzSelect { sm: true }` so it sits flush beside one
// in a section header (30 tall, r6, elevated fill, no outline, 12px label,
// 14px chevron): the album-section "Play all / Play random / Play selected"
// control on the artist page is the first host. The menu is the shared
// controls/QbzContextMenu opened below-right of the whole control, the
// QbzSelect / sort-menu convention.
//
// `menuItems` is `[{ label, action, icon? }]`; a pick closes the menu and
// emits `picked(action)`. The body emits `clicked()` and never opens the
// menu — the two halves are separate hit zones with their own hover fill,
// and the 1px hairline between them is the split.
import QtQuick
import com.blitzfc.qbz
import "../theme"
Rectangle {
id: root
property string label: ""
property var menuItems: []
property int menuWidth: 180
property bool btnEnabled: true
signal clicked()
signal picked(string action)
QbzTheme { id: theme }
readonly property int chevronWidth: 24
height: 30
radius: 6
implicitWidth: bodyLabel.implicitWidth + 24 + root.chevronWidth
width: implicitWidth
// Same resting fill as QbzSelect sm (translucent under the dynamic
// background); hover is painted per zone below.
color: theme.ambientOn ? theme.surfaceElevatedA50 : theme.surfaceElevated
opacity: root.btnEnabled ? 1.0 : 0.4
// --- Body: the primary action ------------------------------------------
Rectangle {
id: body
x: 0
y: 0
width: parent.width - root.chevronWidth
height: parent.height
radius: 6
color: bodyArea.containsMouse && root.btnEnabled ? theme.surfaceHover : "transparent"
// Square the inner corners so the hover fill meets the hairline.
Rectangle {
x: parent.width - 6
y: 0
width: 6
height: parent.height
color: parent.color
}
Text {
id: bodyLabel
anchors.centerIn: parent
text: root.label
color: theme.textPrimary
font.pixelSize: 12
verticalAlignment: Text.AlignVCenter
}
MouseArea {
id: bodyArea
anchors.fill: parent
enabled: root.btnEnabled
hoverEnabled: true
cursorShape: root.btnEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: root.clicked()
}
}
// --- The split ----------------------------------------------------------
Rectangle {
x: parent.width - root.chevronWidth
y: 7
width: 1
height: parent.height - 14
color: theme.borderSubtle
}
// --- Chevron: the menu --------------------------------------------------
Rectangle {
id: chevron
x: parent.width - root.chevronWidth
y: 0
width: root.chevronWidth
height: parent.height
radius: 6
color: chevronArea.containsMouse && root.btnEnabled ? theme.surfaceHover : "transparent"
Rectangle {
x: 0
y: 0
width: 6
height: parent.height
color: parent.color
}
QbzIcon {
name: "chevron-down"
width: 14
height: 14
anchors.centerIn: parent
tintName: chevronArea.containsMouse ? "textPrimary" : "secondary"
}
MouseArea {
id: chevronArea
anchors.fill: parent
enabled: root.btnEnabled
hoverEnabled: true
cursorShape: root.btnEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: menu.openBelowRight(root)
}
}
QbzContextMenu {
id: menu
menuWidth: root.menuWidth
Repeater {
model: root.menuItems
delegate: Rectangle {
required property var modelData
width: parent ? parent.width : 0
height: 33
radius: 5
color: itemArea.containsMouse ? theme.surfaceHover : "transparent"
Row {
anchors.fill: parent
anchors.leftMargin: 8
spacing: 8
QbzIcon {
visible: (modelData.icon || "") !== ""
name: modelData.icon || ""
width: visible ? 15 : 0
height: 15
anchors.verticalCenter: parent.verticalCenter
tintName: "secondary"
}
Text {
height: parent.height
width: parent.width - 23
text: modelData.label
color: theme.textSecondary
font.pixelSize: 13
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
}
MouseArea {
id: itemArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
menu.close()
root.picked(modelData.action)
}
}
}
}
}
}