Files
omarchy/shell/Ui/PanelSlider.qml
T
David Heinemeier HanssonandClaude Opus 5.5 b18ab4952b Add a no-animations mode, on by default in VMs (#13550)
A machine without a GPU, like most VMs, renders through llvmpipe on the
CPU, where every animated frame and every translucent window costs. In a
VM, opening and closing a terminal took ~5s of CPU; a panel ~3.4s.

omarchy toggle animations (also under Toggle > Animations) places a Hyprland
flag that turns off animations, blur and shadows and makes windows opaque.
The shell follows Hyprland's animations:enabled, rereading it on every
config reload: its one-shot animations run for Style.duration(ms), which
is then 0, and its spinners, pulses and title marquee hold still. A new
install in a VM starts with the flag in place.

Measured in the ISO test VM (llvmpipe), CPU per interaction:
terminal open+close 5000ms -> 481ms, workspace switch 1670ms -> 409ms,
audio panel 3446ms -> 796ms, volume OSD 2324ms -> 584ms, menu 2028ms ->
1241ms.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 15:46:05 +02:00

150 lines
4.8 KiB
QML

import QtQuick
import qs.Commons
Item {
id: root
property QtObject bar: null
property real value: 0
property real minimum: 0
property real maximum: 1
property real step: 0.05
property bool integer: false
property color trackColor: bar ? Style.selectedFillFor(bar.foreground, Color.accent) : "#333"
property color fillColor: bar ? bar.foreground : Color.foreground
property color knobColor: bar ? bar.foreground : Color.foreground
property bool dragging: false
property real trackHeight: Math.max(4, Math.round(Style.spacing.controlHeight * 0.11))
property real knobSize: Math.max(14, Math.round(Style.spacing.controlHeight * 0.38))
property real liveValue: value
// macOS-style notches. When > 1, that many evenly-spaced tick marks are cut
// into the track (drawn in the panel background color, so only the part
// crossing the track shows). Purely visual — snapping is the caller's job via
// `integer`/`step` or an index-based value. Default 0 leaves the track plain.
property int tickCount: 0
property color tickColor: bar ? bar.background : Color.background
onValueChanged: if (!dragging) liveValue = value
signal moved(real value)
signal released(real value)
// Right-click is a secondary action on the whole track — audio uses it to
// mute the channel the slider belongs to. Dragging stays left-button only.
signal rightClicked()
implicitWidth: Style.space(200)
implicitHeight: Math.max(Style.space(22), knobSize + Style.spacing.md)
readonly property real range: Math.max(0.0001, maximum - minimum)
readonly property real progress: Math.max(0, Math.min(1, (liveValue - minimum) / range))
readonly property bool _hot: mouseArea.containsMouse || root.dragging
Rectangle {
id: track
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
height: root.trackHeight
radius: height / 2
color: root.trackColor
}
Rectangle {
id: fill
anchors.verticalCenter: track.verticalCenter
anchors.left: track.left
height: track.height
radius: track.radius
color: root.fillColor
width: track.width * root.progress
Behavior on width {
enabled: !root.dragging
NumberAnimation { duration: Style.duration(140); easing.type: Easing.OutCubic }
}
}
Repeater {
model: root.tickCount > 1 ? root.tickCount : 0
Rectangle {
required property int index
width: Math.max(1, Style.space(2))
height: root.trackHeight + Style.space(4)
radius: 1
color: root.tickColor
anchors.verticalCenter: track.verticalCenter
x: Math.max(0, Math.min(track.width - width,
track.width * (index / (root.tickCount - 1)) - width / 2))
}
}
BorderSurface {
id: knob
width: root.knobSize
height: root.knobSize
radius: root.knobSize / 2
color: root.knobColor
borderSpec: Border.flat(root.bar ? root.bar.background : "#101315", Math.max(1, Style.space(2)))
anchors.verticalCenter: track.verticalCenter
x: Math.max(0, Math.min(track.width - width, track.width * root.progress - width / 2))
scale: root._hot ? 1.15 : 1.0
Behavior on x {
enabled: !root.dragging
NumberAnimation { duration: Style.duration(140); easing.type: Easing.OutCubic }
}
Behavior on scale {
NumberAnimation { duration: Style.duration(110); easing.type: Easing.OutCubic }
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
function valueFromX(x) {
var clamped = Math.max(0, Math.min(track.width, x))
var raw = root.minimum + (clamped / track.width) * root.range
if (root.integer) raw = Math.round(raw)
return Math.max(root.minimum, Math.min(root.maximum, raw))
}
onPressed: function(mouse) {
if (mouse.button !== Qt.LeftButton) return
root.dragging = true
var next = valueFromX(mouse.x)
root.liveValue = next
root.moved(next)
}
onClicked: function(mouse) {
if (mouse.button === Qt.RightButton) root.rightClicked()
}
onPositionChanged: function(mouse) {
if (!root.dragging) return
var next = valueFromX(mouse.x)
root.liveValue = next
root.moved(next)
}
onReleased: function(mouse) {
if (mouse.button !== Qt.LeftButton) return
root.dragging = false
root.released(root.liveValue)
root.liveValue = root.value
}
onWheel: function(wheel) {
var delta = wheel.angleDelta.y > 0 ? root.step : -root.step
var next = Math.max(root.minimum, Math.min(root.maximum, root.liveValue + delta))
if (root.integer) next = Math.round(next)
root.liveValue = next
root.moved(next)
root.released(next)
}
}
}