// A GNOME-style slider: a thick rounded track with an inline leading icon, // used for volume and brightness in the quick settings panel. // // Deliberately not QtQuick.Controls.Slider — that pulls in a style plugin and // fights the theme. This is ~50 lines and does exactly what's needed. import QtQuick import Quickshell import Quickshell.Widgets import qs.config Item { id: root property real value: 0 // 0.0 .. 1.0 property string icon: "" property bool enabled: true // Emitted continuously while dragging. signal moved(real value) implicitWidth: 240 implicitHeight: 32 opacity: enabled ? 1.0 : 0.45 // ThemedIcon, not a bare IconImage — see the note in Toggle.qml: Adwaita // symbolic icons have a hardcoded near-black fill that Qt will not recolor. ThemedIcon { id: leadingIcon anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter size: 18 icon: root.icon tint: Theme.fg visible: root.icon !== "" } Rectangle { id: track anchors.left: leadingIcon.visible ? leadingIcon.right : parent.left anchors.leftMargin: leadingIcon.visible ? 10 : 0 anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter height: 10 radius: height / 2 color: Theme.alpha(Theme.fg, 0.12) border.width: 0 Rectangle { id: fill anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom width: parent.width * Math.max(0, Math.min(1, root.value)) radius: parent.radius border.width: 0 // The gradient is anchored to the full track, not to the fill, so // the color at a given position never moves as the value changes — // dragging reveals the prism rather than squashing it. gradient: Gradient { orientation: Gradient.Horizontal GradientStop { position: 0.0 color: Theme.accent } GradientStop { position: 1.0 color: Theme.mix(Theme.accent, Theme.accentSecondary, Math.max(0.001, Math.min(1, root.value))) } } // Only animates when the value changes from outside (e.g. a media // key); dragging sets it directly and reads as instant. Behavior on width { enabled: !drag.pressed NumberAnimation { duration: Theme.durFast easing.type: Easing.OutQuad } } } Rectangle { id: knob width: 16 height: 16 radius: 8 border.width: 0 color: Theme.fg anchors.verticalCenter: parent.verticalCenter x: Math.max(0, Math.min(parent.width - width, fill.width - width / 2)) visible: drag.containsMouse || drag.pressed } MouseArea { id: drag anchors.fill: parent anchors.margins: -8 // easier to grab than a 10px track enabled: root.enabled hoverEnabled: true cursorShape: Qt.PointingHandCursor function apply(mouseX) { // mouseX is local to this MouseArea, whose anchors.margins: -8 // pushes its own origin 8px before the track's left edge. So // MouseArea-local x=8 is track-local x=0 -- subtract the // margin to land back in the track's own coordinate space // before turning it into a fraction. const ratio = Math.max(0, Math.min(1, (mouseX - 8) / track.width)); root.moved(ratio); } onPressed: event => apply(event.x) onPositionChanged: event => { if (pressed) apply(event.x); } onWheel: event => { const step = event.angleDelta.y > 0 ? 0.05 : -0.05; root.moved(Math.max(0, Math.min(1, root.value + step))); } } } }