Files
qbz/crates/qbz-qt/qml/controls/CardMenu.qml
T
vicrodh b1dba198c9 feat(qt): kiosk profile release hardening
Integrate the Kiosk profile hardening round: 12 dedicated Kiosk views plus
22 shared opt-in hosts, shared navigation history with Back/Forward, Albums
as the default library tab, native paged Local Library (artists/tracks),
lightweight MyQBZ grid and detail, Ephemeral lifecycle, bounded lists and
artwork sized to the physical frame, static skeletons, 64px transport bar,
Settings touch reflow and a persistent Immersive exit.

Desktop keeps its prior behavior: every shared component defaults to the
desktop path and only opts into Kiosk behavior through explicit properties
(origin-relative ScrollMemory, viewport-bounded artwork requests).

Regression coverage: kiosk navigation and artwork Node tests added to the
runtime gate, scroll-origin fixtures for fixed and grouped rows, i18n
strings for all eight locales.

Verified by 750 release tests, six QML audits, Kiosk smokes at 800x480 and
1280x800 and a Desktop A/B with seven pixel-identical captures.

cc
2026-09-07 08:23:11 -06:00

110 lines
4.3 KiB
QML

// CardMenu — THE shared ⋯ / right-click menu surface (promoted from
// LibraryView in phase 21, brought up to primitives/ContextMenuItem.slint
// in the menu-parity round).
//
// Driven by an `entries` model; emits picked(action). Entry shape:
//
// { label, icon, action } a normal row (33px)
// { ..., enabled: false } muted row, ignores clicks
// (ContextMenuItem.slint `enabled`)
// { ..., danger: true } destructive row, red label
// (ContextMenuItem.slint `danger`)
// { sep: true } 1px group separator + 3px air
// (AlbumContextMenu.slint's
// `Rectangle { height: 1px; }`)
//
// Row metrics are ContextMenuItem.slint's: 33px, icon 15px, label 13px,
// and BOTH go text-primary on hover (the .slint tints icon + label
// together; the old POC row kept them permanently secondary).
//
// DEVIATION, deliberate: a `danger` row reddens the LABEL only. Slint
// tints the icon with Theme.danger at render time; QML tinting here is
// pre-baked per tint directory (assets/icons/<tint>/) and there is no
// `danger` bake — inventing one needs a new asset set + build.rs glue, and
// a missing pre-baked file renders NOTHING at runtime. The red label
// carries the signal on its own.
import QtQuick
import com.blitzfc.qbz
import "../theme"
QbzContextMenu {
id: cmRoot
property var entries: []
signal picked(string action)
QbzTheme { id: theme }
Repeater {
model: cmRoot.entries
delegate: Rectangle {
id: row
required property var modelData
readonly property bool isSep: modelData.sep === true
readonly property bool rowEnabled: modelData.enabled !== false
readonly property bool isDanger: modelData.danger === true
readonly property bool hot: cmiArea.containsMouse && rowEnabled
width: parent ? parent.width : 0
height: isSep ? 7 : (cmRoot.kioskHost ? 44 : 33)
radius: isSep ? 0 : 5
color: hot ? theme.surfaceHover : "transparent"
// ContextMenuItem.slint: `opacity: enabled ? 1.0 : 0.4`.
opacity: (isSep || rowEnabled) ? 1.0 : 0.4
// Separator: a 1px rule centred in the 7px band.
Rectangle {
visible: row.isSep
y: 3
width: parent.width
height: 1
color: theme.borderSubtle
}
Row {
visible: !row.isSep
anchors.fill: parent
anchors.leftMargin: 8
spacing: 8
QbzIcon {
name: row.modelData.icon || ""
width: 15
height: 15
anchors.verticalCenter: parent.verticalCenter
// Host is `surfaceHover`/transparent over a theme surface,
// so the glyph tracks the sibling label (textPrimary/
// textSecondary below) rather than a fixed white.
tintName: row.hot ? "textPrimary" : "secondary"
}
Text {
height: parent.height
width: parent.width - 23
text: row.modelData.label || ""
color: row.isDanger ? theme.danger
: (row.hot ? theme.textPrimary : theme.textSecondary)
font.pixelSize: cmRoot.kioskHost ? 16 : 13
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
}
MouseArea {
id: cmiArea
anchors.fill: parent
// A separator and a muted row take no input at all — a
// disabled MouseArea also reports no hover, so neither
// highlights.
enabled: !row.isSep && row.rowEnabled
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
cmRoot.close()
cmRoot.picked(row.modelData.action)
}
}
}
}
}