feat(quickshell): add dense telemetry bar #4

Open
luna wants to merge 18 commits from feat/quickshell-dense-bar into master
2 changed files with 175 additions and 0 deletions
Showing only changes of commit 7eb7b948e8 - Show all commits
@@ -0,0 +1,162 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Wayland
import QtQuick
import qs.hyprchrome.theme
// Full-screen scrim behind the bar: dims the desktop and lays the dense bar's
// drafting grid over it while the rail is expanded.
//
// Sits on the TOP layer while the bar itself is on OVERLAY. Two surfaces on the
// same layer stack by creation order, which is not something to rely on; one
// layer apart is a guarantee — above ordinary windows, below the bar.
//
// It reserves nothing and takes no input: the mask is an empty Region, so
// clicks land on whatever is underneath rather than on the scrim.
PanelWindow {
id: backdrop
property bool active: true
property real dim: 0.55
property int gridSpacing: 120
// The dense bar drew this grid at 0.018 against its own near-black panel.
// Over a 55% scrim on top of lit windows that is invisible, so it is a
// knob rather than a constant.
property real gridOpacity: 0.1
// The accent with its saturation pulled back: warm enough to read as part
// of the palette, not so loud that a full-screen grid competes with the
// bar. Derived rather than a literal so it tracks a palette change.
// Registration crosses sit on every other intersection of the grid.
property color crossColor: Theme.muted
property real crossOpacity: 0.35
property int crossSize: 20
// Thickness in STEPS, not pixels: 1 -> 1px, 2 -> 3px, 3 -> 5px. Only odd
// widths can straddle a 1px rule symmetrically, so an even pixel count
// would push every mark half a pixel off the grid it registers against.
property int crossThickness: 2
readonly property int crossWeight: Math.max(1, backdrop.crossThickness) * 2 - 1
property color gridColor: Qt.hsla(Theme.accent.hslHue,
Theme.accent.hslSaturation * 0.45,
Theme.accent.hslLightness,
1)
visible: scrim.opacity > 0
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: ExclusionMode.Ignore
color: "transparent"
readonly property int crossColumns: Math.ceil(backdrop.width / (backdrop.gridSpacing * 2)) + 1
readonly property int crossRows: Math.ceil(backdrop.height / (backdrop.gridSpacing * 2)) + 1
anchors {
top: true
left: true
right: true
bottom: true
}
mask: Region {}
Item {
id: scrim
anchors.fill: parent
opacity: backdrop.active ? 1 : 0
// Matched to the bar's own collapse so the scrim and the panels resolve
// together rather than one trailing the other.
Behavior on opacity {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
Rectangle {
anchors.fill: parent
color: Theme.surface
opacity: backdrop.dim
}
// Faint drafting grid; no gradient and deliberately subordinate to
// whatever is showing through it.
Repeater {
model: Math.ceil(scrim.width / backdrop.gridSpacing)
Rectangle {
required property int index
x: index * backdrop.gridSpacing
width: 1
height: scrim.height
color: backdrop.gridColor
opacity: backdrop.gridOpacity
}
}
Repeater {
model: Math.ceil(scrim.height / backdrop.gridSpacing)
Rectangle {
required property int index
y: index * backdrop.gridSpacing
width: scrim.width
height: 1
color: backdrop.gridColor
opacity: backdrop.gridOpacity
}
}
// Register marks on every other line, so they land on a 2x grid rather
// than on every crossing — sparse enough to read as drafting registration
// rather than as texture.
Repeater {
model: backdrop.crossColumns * backdrop.crossRows
Item {
required property int index
readonly property int column: index % backdrop.crossColumns
readonly property int row: Math.floor(index / backdrop.crossColumns)
// Marks on every other rule in both directions, so they line up
// in columns as well as rows. Both offsets are whole multiples of
// gridSpacing, so every mark lands on a real intersection.
//
// Math.floor, not /2: the item offset and the bars inside it must
// round the same way, or an even crossSize sits half a pixel off the
// rule it marks.
x: column * backdrop.gridSpacing * 2 - Math.floor(backdrop.crossSize / 2)
y: row * backdrop.gridSpacing * 2 - Math.floor(backdrop.crossSize / 2)
width: backdrop.crossSize
height: backdrop.crossSize
opacity: backdrop.crossOpacity
Rectangle {
// Placed with the same Math.floor the item's own offset uses.
// anchors.verticalCenter halves the height unfloored, so an even
// crossSize put the 1px bar half a pixel off the rule it marks.
y: Math.floor(backdrop.crossSize / 2) - Math.floor(backdrop.crossWeight / 2)
width: parent.width
height: backdrop.crossWeight
color: backdrop.crossColor
}
Rectangle {
x: Math.floor(backdrop.crossSize / 2) - Math.floor(backdrop.crossWeight / 2)
width: backdrop.crossWeight
height: parent.height
color: backdrop.crossColor
}
}
}
}
}
@@ -3,6 +3,7 @@ import QtQuick.Shapes
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland
import qs.hyprchrome.widgets
import qs.hyprchrome.widgets.host
import qs.hyprchrome.widgets.vitals
@@ -45,12 +46,24 @@ Scope {
onPressed: root.toggle()
}
// Scrim first: it is a layer below the bar, so stacking does not depend on
// creation order, but keeping the declaration order the same as the visual
// order costs nothing.
ChromeBackdrop {
screen: root.targetScreen
active: root.expanded
}
PanelWindow {
id: window
screen: root.targetScreen
visible: root.targetScreen !== null
// One layer above the scrim, so the stacking is guaranteed rather than
// dependent on surface creation order. See ChromeBackdrop.
WlrLayershell.layer: WlrLayer.Overlay
property int margin: 12
// Where the panels will SETTLE, not where they are mid-transition. Binding