Files
qbz/crates/qbz-qt/qml/controls/QbzToolButton.qml
T
vicrodh ed7fa588ea fix(qt): elevated controls lift on hover instead of darkening
Buttons that rest on surfaceElevated used surfaceHover for their hover
fill: a 6% white wash meant for rows that rest transparent. Over an
elevated button it lands DARKER than the rest fill — on a modal's dark
ground the Cancel/Save/footer buttons went near-black under the pointer.

QbzTheme gains surfaceElevatedHover (a lift of surfaceElevated, derived
so every palette has it), its ambient half-alpha twin, and the
elevatedFill / elevatedHoverFill pair an ambient-aware control paints.
Every site with the old pairing (65 across modals, toolbars, selects,
circle actions, chips and cards) now hovers to the lift.

cc
2026-09-14 00:49:54 -06:00

125 lines
5.2 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// QbzToolButton — the MyQBZ detail toolbar's `ToolBtn`
// (myqbz/MixtapeDetailView.slint:85-151): a 30px bordered chip that is
// icon-only, icon+label, or icon+label+badge, with TWO distinct active looks.
//
// Why not controls/BrowseGenreButton.qml: that one is hard-bound to
// `QbzBridge.genreFilterJson` and builds its own label out of it, so a generic
// label/badge/active-mode contract cannot be a parameter of it.
//
// THE TWO ACTIVE MODES are the whole point, and they are not interchangeable:
// * `fillActive: true` — active paints an ACCENT FILL (the Filter button,
// which is labelled and carries the filter count).
// * `fillActive: false` — active paints an accent BORDER on the normal
// elevated fill (the icon-only Select / Reset buttons). Never a fill.
//
// Numbers: height 30, r6, width = the row's own padded width (:97-99, so the
// padding centres the content rather than an absolute x offset), 1px border;
// padding 8/side icon-only and 10/side labelled (:108-109); row spacing 6;
// glyph 15x15; label 12px; badge 16x16 r8 with 10px semibold text.
//
// Where the .slint hardcodes `#ffffff` on the accent fill (:120, :127, :137)
// this uses `theme.accentGlyphTint` / `theme.accentGlyphColor` — the measured
// on-accent selector (theme/QbzTheme.qml, "ON AN ACCENT FILL"). The badge's
// non-filled text stays `accentText` because there it sits on the accent disc,
// which is what that token is for.
import QtQuick
import com.blitzfc.qbz
import "../theme"
Rectangle {
id: root
property string name: ""
property string label: ""
/// > 0 renders the trailing count disc.
property int badge: 0
property bool active: false
/// Active = accent FILL (labelled buttons) vs accent BORDER (icon-only).
property bool fillActive: false
/// THE SECOND SCALE — the Playlist Manager toolbar's `PmToolButton` /
/// `PmIconButton` (playlist/PlaylistManagerView.slint:162-230), which is the
/// same control 4 px taller with more air. The port's established idiom for
/// a second size on one control (`QbzLineEdit.sm`, `QbzSelect.sm`,
/// `QbzToggleButton.sm`), rather than a 90 %-identical fork.
///
/// Under `large`: height 34, padH 11 (the reference's `row { x: 11 }` and
/// `width: row.preferred-width + 22`), row spacing 7, label 13, NO border
/// (except the accent-border arm, which the PM never uses), and an
/// icon-only button is a 34 × 34 square with a 16 px glyph — the 15 → 16
/// delta between the labelled and icon-only forms IS the reference
/// (`:180-184` vs `:216-222`); do not unify it.
///
/// The idle GLYPH tint also changes, and it is the largest of the deltas:
/// this control tints idle glyphs `muted` (#888888) where the reference
/// paints `Theme.text-secondary` (#cccccc) on five of its six toolbar
/// controls. Defaults are untouched, so the MyQBZ toolbars cannot regress.
property bool large: false
signal clicked()
QbzTheme { id: theme }
readonly property bool filled: root.active && root.fillActive
readonly property bool iconOnly: root.label === ""
readonly property int padH: root.large ? 11 : (root.iconOnly ? 8 : 10)
height: root.large ? 34 : 30
implicitWidth: (root.large && root.iconOnly)
? 34
: 2 * root.padH + row.implicitWidth
radius: 6
color: root.filled ? theme.accent
: (toolArea.containsMouse ? theme.surfaceElevatedHover : theme.surfaceElevated)
border.width: (root.large && !(root.active && !root.fillActive)) ? 0 : 1
border.color: (root.active && !root.fillActive) ? theme.accent : theme.borderSubtle
Row {
id: row
anchors.centerIn: parent
spacing: root.large ? 7 : 6
QbzIcon {
anchors.verticalCenter: parent.verticalCenter
name: root.name
width: (root.large && root.iconOnly) ? 16 : 15
height: (root.large && root.iconOnly) ? 16 : 15
tintName: root.filled ? theme.accentGlyphTint
: (root.active ? "textPrimary" : (root.large ? "secondary" : "muted"))
}
Text {
visible: root.label !== ""
anchors.verticalCenter: parent.verticalCenter
text: root.label
color: root.filled ? theme.accentGlyphColor
: (root.active ? theme.textPrimary : theme.textSecondary)
font.pixelSize: root.large ? 13 : 12
verticalAlignment: Text.AlignVCenter
}
Rectangle {
visible: root.badge > 0
anchors.verticalCenter: parent.verticalCenter
width: root.badge > 0 ? 16 : 0
height: 16
radius: 8
color: root.filled ? theme.accentGlyphColor : theme.accent
Text {
anchors.centerIn: parent
text: root.badge
color: root.filled ? theme.accent : theme.accentText
font.pixelSize: 10
font.weight: theme.weightSemibold
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}
MouseArea {
id: toolArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.clicked()
}
}