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
76 lines
3.0 KiB
QML
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()
|
|
}
|
|
}
|