Files
qbz/crates/qbz-qt/qml/miniplayer/TBtn.qml
T
vicrodh 6b17db90dd feat(qt): the mini footer, the hover capsule and always-on-top
Block B3. The owner's verdict on the four blocks that landed before it was
that the miniplayer was "no draggeable, no hay controles de ningun tipo. Ni
de reproducción, ni para cambiar entre modos, no es pinneable." All of that
lived here.

qml/miniplayer/ — MiniFooter with its three modes, and the five primitives
the reference declares as inner components: MiniSeek, MiniTransport,
MiniVolume, TBtn and CapBtn, one file each because a 396-line Slint footer
folded into one QML file lands well past the size rule. MiniWindowControls
is the hover capsule: five surface tabs, a separator, then expand, pin,
backdrop, drag and close, plus the collapsed square trigger.

The transport in full and compact is centred by a PHANTOM SPACER the width
of the volume button, not by a centred layout — micro has no volume and is
the one mode genuinely centred. The seek carries the reference's pending
machine: the -1.0 sentinel, the 0.02 convergence window and the 8s watchdog.
The capsule is a conditional MOUNT, so leaving the card destroys its
expanded flag and its 250ms timer with it.

ALWAYS-ON-TOP is new work, not a port: three comments in the Slint tree
promise it and no code implements it. The owner asked for it back because
Tauri had it and Slint structurally cannot. Qt.WindowStaysOnTopHint, a pin
button, and a persisted pref that defaults ON.

COLOUR LITERALS: six were carried over from the reference verbatim and every
one of them was WRONG. Slint writes #RRGGBBAA; Qt parses #AARRGGBB, so
#ffffff33 is not white at 20% but opaque pale yellow — which is exactly how
the capsule rendered until it was driven and looked at. This is the second
time this trap has landed in this port. Each converted literal now carries
the reference form beside it. One of the six is MiniShell's, from B2.

mini_qt.rs — surface 0 leaves the temporary clamp, because the micro footer
IS the card and now exists. Surfaces 3 and 4 stay clamped until B4.

Driven and seen, not inferred: the footer in artwork and micro, the capsule
revealing on hover and collapsing on leave, the micro card at 380x57 with
its centred transport, and the pin persisting mini_always_on_top across a
toggle. The drag handle is wired to startSystemMove on pressed; the VNC
platform cannot move a window, so that one is owed a real compositor.

cc
2026-08-04 08:43:02 -06:00

76 lines
3.0 KiB
QML

// TBtn — the miniplayer footer's transport/volume button (2026-08-03
// miniplayer/tray contract A-29, §4.3.3), port of the inner
// `component TBtn inherits Rectangle` at
// `crates/qbz-ui/ui/miniplayer/MiniFooter.slint:16-49`.
//
// A file of its own rather than a QML inline component: the rule is A-29's and
// it is a SIZE rule, not a language rule — a 396-line MiniFooter.slint whose
// port folded five inner types in would land well past TRACK-RULES' ~500-line
// line. (QML does have `component X: Rectangle { … }`, and this tree uses it at
// qml/immersive/QueueTabsPanel.qml:135; nothing in this diff qualifies.)
//
// TWO details that look like bugs and are not:
//
// - THE ICON TINT DOES NOT CHECK `enabled` (§12-P7). A disabled button still
// lightens its glyph under the cursor; only the root's 0.3 opacity conveys
// disabled. That is the reference's behaviour (:36-38 has no enabled arm
// where :28 does), and it is reproduced rather than "fixed".
// - The hover BACKGROUND does check it (:28).
//
// The idle glyph is `Theme.alpha-70`, which is not a name in QbzIcon's tint
// vocabulary — the vocabulary carries theme TOKENS, not the alpha ramp. It is
// spelled here as textPrimary at 0.70 opacity, which is what alpha-70 IS: the
// ramp is white-based on dark themes and black-based on light ones
// (QbzTheme.qml's alphaTier), and textPrimary follows the same polarity. Using
// "secondary" instead — the QbzIconButton idle tint — would be a different
// colour (#cccccc, 80 %) and would not track the ramp on light themes.
//
// `btnEnabled`, not `enabled`: `enabled` is a QQuickItem property and
// shadowing it disarms the whole subtree. Same spelling as QbzIconButton.
import QtQuick
import "../theme"
Rectangle {
id: root
/// Icon file stem, e.g. "shuffle" (QbzIcon appends ".svg").
property string name: ""
property int iconSize: 16
/// The square edge; the radius is half of it, so the hover fill is a disc.
property int btn: 30
property bool active: false
property bool btnEnabled: true
signal clicked()
QbzTheme { id: theme }
width: root.btn
height: root.btn
radius: root.btn / 2
antialiasing: true
opacity: root.btnEnabled ? 1.0 : 0.3
color: (ta.containsMouse && root.btnEnabled) ? theme.alphaTier(8) : "transparent"
QbzIcon {
name: root.name
width: root.iconSize
height: root.iconSize
// Pixel-snapped centring, 1:1 with the reference's
// `Math.round((parent.width - self.width) / 2 / 1px) * 1px` (:34-35):
// a half-pixel offset on a 13 px glyph is visible as a blur.
x: Math.round((root.width - width) / 2)
y: Math.round((root.height - height) / 2)
tintName: root.active ? "accent" : "textPrimary"
opacity: (root.active || ta.containsMouse) ? 1.0 : 0.70
}
MouseArea {
id: ta
anchors.fill: parent
hoverEnabled: true
cursorShape: root.btnEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: if (root.btnEnabled) root.clicked()
}
}