fixed some bugs

This commit is contained in:
2026-08-15 02:11:03 +02:00
parent 9cf510e93b
commit 9e73c1338f
9 changed files with 169 additions and 22 deletions
-1
View File
@@ -1 +0,0 @@
monitor=eDP-1, disable
+6 -1
View File
@@ -20,7 +20,12 @@ M.external = {
{ output = "desc:Samsung Electric Company LC27G7xT H4ZRA00734", mode = "2560x1440@240", position = "-2560x0", scale = 1 }, { output = "desc:Samsung Electric Company LC27G7xT H4ZRA00734", mode = "2560x1440@240", position = "-2560x0", scale = 1 },
} }
M.builtin = { output = M.laptop, mode = "1920x1080@60", position = "0x0", scale = 1 } -- `disabled = false` is load-bearing, not decoration. hl.monitor() merges into
-- whatever rule the output already has, so a spec that simply omits the flag
-- leaves an earlier `disabled = true` in place: the panel stays dark and the
-- mode/position are applied to a monitor nobody can see. Opening the lid while
-- docked hit exactly that.
M.builtin = { output = M.laptop, mode = "1920x1080@60", position = "0x0", scale = 1, disabled = false }
-- Anything not declared above falls back to Hyprland's auto placement, so a -- Anything not declared above falls back to Hyprland's auto placement, so a
-- borrowed projector or a monitor at the office still lights up. -- borrowed projector or a monitor at the office still lights up.
+9 -2
View File
@@ -115,10 +115,17 @@ PanelWindow {
implicitWidth: Theme.padM * 2 implicitWidth: Theme.padM * 2
implicitHeight: Theme.barHeight implicitHeight: Theme.barHeight
// A leaning hairline needs a box wide enough to hold the lean: at 20px
// tall the top edge sits 20 * skew ≈ 5px right of the bottom one, so a
// 1px-wide Skew folded into a bowtie instead. Width is the lean plus the
// stroke the rule should actually read as.
readonly property real ruleWeight: 1.5
readonly property real ruleHeight: 20
Skew { Skew {
anchors.centerIn: parent anchors.centerIn: parent
width: 1 width: parent.ruleHeight * Theme.skew + parent.ruleWeight
height: 20 height: parent.ruleHeight
color: Theme.alpha(Theme.outline, 0.9) color: Theme.alpha(Theme.outline, 0.9)
} }
} }
+8 -2
View File
@@ -37,9 +37,15 @@ Item {
// crimson line struck through its bottom edge on hover. Reserving the band // crimson line struck through its bottom edge on hover. Reserving the band
// and centring content in what is left is what keeps the rule under the // and centring content in what is left is what keeps the rule under the
// module instead of across it. // module instead of across it.
// The band reserved the full rule plus its inset — 7px off a 40px module,
// all of it taken from the bottom — so every module's content was centred
// 3.5px above the slab's own centre and the whole bar read as top-heavy.
// The rule is pushed closer to the edge now and the band keeps only enough
// clearance to stop the tallest content (the media pill's 24px art) from
// touching it, so content sits within a pixel of the slab's centre line.
readonly property real ruleHeight: 2 readonly property real ruleHeight: 2
readonly property real ruleInset: 5 readonly property real ruleInset: 3
readonly property real ruleBand: root.standalone ? 0 : root.ruleHeight + root.ruleInset readonly property real ruleBand: root.standalone ? 0 : 2
// Modules with a notion of progress (the player's track position) draw it in // Modules with a notion of progress (the player's track position) draw it in
// the same band, so it can never be struck through the content. A module // the same band, so it can never be struck through the content. A module
+37 -1
View File
@@ -50,18 +50,54 @@ BarPill {
} }
} }
// Declared with room for its own lean, so the slash sits inside the box
// the Row gave it instead of hanging several px into the date column.
Skew { Skew {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 2 width: 22 * Theme.skew + 3
height: 22 height: 22
color: Theme.alpha(Theme.accent, 0.6) color: Theme.alpha(Theme.accent, 0.6)
} }
// Archivo Black's digits are not all the same width, so a clock sized to
// its own text changed width as the minutes rolled over — 11:11 is
// several px narrower than 08:48 — and shoved the rest of the bar
// around. Reserve the widest possible reading and centre inside it.
TextMetrics {
id: timeMetrics
text: "88:88"
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsTitle
font.weight: Theme.weightDisplay
font.italic: true
font.letterSpacing: 0.5
}
// Archivo Black reserves descender room the time never uses — "12:04"
// is all cap height — so centring the line box left the digits sitting
// above everything beside them. Centre the ink instead.
// `tightBoundingRect` is measured from the baseline, so the ink's top
// inside the line box is `ascent + ink.y`.
readonly property real timeInkOffset: {
const ink = timeMetrics.tightBoundingRect;
if (ink.height <= 0) return 0;
const inkCentre = timeFont.ascent + ink.y + ink.height / 2;
return Math.round(timeMetrics.boundingRect.height / 2 - inkCentre);
}
FontMetrics {
id: timeFont
font: timeMetrics.font
}
SplitText { SplitText {
id: timeText id: timeText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: layout.timeInkOffset
width: timeMetrics.advanceWidth
text: Qt.formatDateTime(clock.date, "HH:mm") text: Qt.formatDateTime(clock.date, "HH:mm")
pixelSize: Theme.fsTitle pixelSize: Theme.fsTitle
horizontalAlignment: Text.AlignHCenter
split: 1.4 split: 1.4
} }
+11 -2
View File
@@ -89,6 +89,7 @@ PanelWindow {
id: volIcon id: volIcon
icon: Audio.icon icon: Audio.icon
readout: Audio.muted ? "" : Audio.volumePercent + "%" readout: Audio.muted ? "" : Audio.volumePercent + "%"
reserveReadout: true
label: "Volume" label: "Volume"
statusText: Audio.muted ? "Muted" : Audio.volumePercent + "% · " + Audio.sinkName statusText: Audio.muted ? "Muted" : Audio.volumePercent + "% · " + Audio.sinkName
hint: "Scroll to adjust" hint: "Scroll to adjust"
@@ -162,6 +163,7 @@ PanelWindow {
readout: Notifs.unread > 0 readout: Notifs.unread > 0
? (Notifs.unread > 9 ? "9+" : String(Notifs.unread)) ? (Notifs.unread > 9 ? "9+" : String(Notifs.unread))
: "" : ""
reserveReadout: true
dim: Notifs.dnd dim: Notifs.dnd
alert: Notifs.hasUnread && !Notifs.dnd alert: Notifs.hasUnread && !Notifs.dnd
suppressTooltip: notifPopout.detailActive suppressTooltip: notifPopout.detailActive
@@ -187,10 +189,17 @@ PanelWindow {
implicitWidth: Theme.railWidth implicitWidth: Theme.railWidth
implicitHeight: Theme.padM + 4 implicitHeight: Theme.padM + 4
// Same correction as the bar's divider, on the other axis: a 22px rule
// rises 22 * skew ≈ 5px across its run, so the box has to be that tall
// plus the weight of the stroke. At height 1 the polygon inverted and
// the rule rendered as a stubby wedge sitting left of centre.
readonly property real ruleWeight: 1.5
readonly property real ruleWidth: 22
Skew { Skew {
anchors.centerIn: parent anchors.centerIn: parent
width: 22 width: parent.ruleWidth
height: 1 height: parent.ruleWidth * Theme.skew + parent.ruleWeight
vertical: true vertical: true
color: Theme.alpha(Theme.outline, 0.9) color: Theme.alpha(Theme.outline, 0.9)
} }
+42 -6
View File
@@ -21,6 +21,13 @@ Item {
property bool dim: false property bool dim: false
property bool pulsing: false property bool pulsing: false
// Hold the readout line open even while there is nothing to put in it.
// Volume drops its percentage when muted and the bell only carries a count
// when something is unread, so without this the slot collapsed from 42px to
// 32px and every icon below it jumped a third of its own height.
property bool reserveReadout: false
readonly property bool hasReadout: root.readout !== "" || root.reserveReadout
readonly property bool hovered: hover.hovered readonly property bool hovered: hover.hovered
// Clearance between the wipe and the rail's left edge, chosen so the bar // Clearance between the wipe and the rail's left edge, chosen so the bar
@@ -31,16 +38,42 @@ Item {
signal scrolled(real delta) signal scrolled(real delta)
implicitWidth: Theme.railWidth implicitWidth: Theme.railWidth
implicitHeight: root.readout !== "" ? 42 : 32 implicitHeight: root.hasReadout ? root.glyphBand + readoutLine.height + 1 : root.glyphBand
Column { // The glyph always sits centred in a fixed band at the top of the slot and
// the readout hangs below it, rather than the pair being centred together.
// Centring the pair meant the icon slid up and down by a few px whenever its
// number appeared or vanished — the icons stopped lining up with each other
// exactly when something was changing and you were looking at them.
readonly property real glyphBand: 32
// Nerd Font's status glyphs are not all centred inside their own advance —
// the muted-speaker and muted-mic marks carry their cross out to the right,
// so centring the advance box left them visibly off-axis next to the wifi
// and bluetooth glyphs. Centre the ink instead, capped so a glyph with an
// odd bounding box cannot slide far off the rail's axis.
readonly property real opticalShift: {
const ink = glyphMetrics.tightBoundingRect;
if (ink.width <= 0) return 0;
const off = glyphMetrics.advanceWidth / 2 - (ink.x + ink.width / 2);
return Math.max(-4, Math.min(4, off));
}
TextMetrics {
id: glyphMetrics
text: root.icon
font.family: Theme.fontIcon
font.pixelSize: Theme.fsLarge
}
Item {
id: layout id: layout
anchors.centerIn: parent anchors.fill: parent
spacing: 1
Icon { Icon {
id: glyph id: glyph
anchors.horizontalCenter: parent.horizontalCenter x: (root.width - width) / 2 + root.opticalShift
y: (root.glyphBand - height) / 2
text: root.icon text: root.icon
pulsing: root.pulsing pulsing: root.pulsing
font.pixelSize: Theme.fsLarge font.pixelSize: Theme.fsLarge
@@ -57,7 +90,10 @@ Item {
} }
Text { Text {
id: readoutLine
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: root.glyphBand + 1
visible: root.readout !== "" visible: root.readout !== ""
text: root.readout text: root.readout
color: root.dim ? Theme.muted : Theme.subtext color: root.dim ? Theme.muted : Theme.subtext
@@ -88,7 +124,7 @@ Item {
anchors.leftMargin: root.wipeInset anchors.leftMargin: root.wipeInset
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 2 width: 2
height: root.hovered ? layout.implicitHeight : 0 height: root.hovered ? root.height - 4 : 0
color: Theme.glow color: Theme.glow
Behavior on height { Behavior on height {
+41 -6
View File
@@ -29,6 +29,7 @@ BarPill {
label: "CPU" label: "CPU"
value: Sys.cpuUsage value: Sys.cpuUsage
readout: Math.round(Sys.cpuUsage) + "%" readout: Math.round(Sys.cpuUsage) + "%"
widest: "100%"
} }
Divider {} Divider {}
@@ -37,6 +38,7 @@ BarPill {
label: "MEM" label: "MEM"
value: Sys.memPercent value: Sys.memPercent
readout: Sys.memUsed.toFixed(1) + "G" readout: Sys.memUsed.toFixed(1) + "G"
widest: "88.8G"
} }
Divider { visible: Sys.gpuAvailable } Divider { visible: Sys.gpuAvailable }
@@ -46,15 +48,24 @@ BarPill {
label: "GPU" label: "GPU"
value: Sys.gpuUsage value: Sys.gpuUsage
readout: Math.round(Sys.gpuUsage) + "%" readout: Math.round(Sys.gpuUsage) + "%"
widest: "100%"
} }
} }
component Divider: Skew { component Divider: Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 1 implicitWidth: rule.width
height: 18 implicitHeight: 18
// Sized to hold its own lean — see the note on the bar's divider.
Skew {
id: rule
anchors.centerIn: parent
width: parent.height * Theme.skew + 1
height: parent.height
color: Theme.alpha(Theme.outline, 0.8) color: Theme.alpha(Theme.outline, 0.8)
} }
}
// A tiny fixed label makes every number self-describing without turning the // A tiny fixed label makes every number self-describing without turning the
// compact meter cluster back into three separate cards. // compact meter cluster back into three separate cards.
@@ -65,14 +76,35 @@ BarPill {
property real value: 0 property real value: 0
property string readout: "" property string readout: ""
// The widest reading this metric will ever show. The number column is
// sized from this rather than from the live text, because sizing to the
// live text is what made the whole right-hand group breathe in and out:
// "9%" and "100%" are ~14px apart in Archivo Black, so every tick of the
// CPU sampler nudged the metrics, the media pill and the clock sideways.
// Reserving the maximum costs a few px of blank and buys a bar that
// never moves.
property string widest: "100%"
readonly property real numberWidth: Math.max(gauge.width, metrics.advanceWidth)
readonly property real headWidth: labelText.implicitWidth + head.spacing + numberWidth
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: 2 spacing: 2
width: Math.max(headWidth, 44)
TextMetrics {
id: metrics
text: metric.widest
font: gauge.font
}
Row { Row {
id: head
spacing: 4 spacing: 4
Text { Text {
anchors.baseline: readoutText.baseline id: labelText
anchors.baseline: gauge.baseline
text: metric.label text: metric.label
color: Theme.muted color: Theme.muted
font.family: Theme.fontMono font.family: Theme.fontMono
@@ -83,7 +115,7 @@ BarPill {
} }
Text { Text {
id: readoutText id: gauge
text: metric.readout text: metric.readout
color: Theme.heat(metric.value) color: Theme.heat(metric.value)
font.family: Theme.fontDisplay font.family: Theme.fontDisplay
@@ -99,12 +131,15 @@ BarPill {
} }
} }
// Ends flush with the reading above it instead of stopping short at a
// fixed 43px, so each metric reads as one block.
MeterBar { MeterBar {
value: metric.value value: metric.value
segments: 9 segments: 9
segmentWidth: 3
spacing: 2 spacing: 2
height: 5 height: 5
width: metric.width
segmentWidth: (width - 8 * spacing) / 9
} }
} }
+14
View File
@@ -31,6 +31,20 @@ Shape {
// rail slab. // rail slab.
property bool vertical: false property bool vertical: false
// NOTE ON SIZING. The lean shifts one edge by `lean * height` (or by
// `lean * width` when vertical), and the piece that actually gets painted is
// what is left: a Skew declared 1px wide and 20px tall does not draw a 1px
// rule at an angle, it draws a rule |1 - 20 * 0.249| = 4px thick that hangs
// 4px to the *left* of the box it was given. That is why the bar's and the
// rail's hairlines came out fat and off-centre. A thin leaning rule has to be
// declared with room for its own lean — `height * Theme.skew + weight` — and
// the call sites that draw one now do exactly that.
//
// Zero-sized pieces still have a lean offset, so a collapsed hover wipe or a
// meter at 0 drew a sub-pixel wedge of pure accent — the stray red dot under
// the metrics. Nothing with no extent should paint at all.
visible: root.width > 0 && root.height > 0
preferredRendererType: Shape.CurveRenderer preferredRendererType: Shape.CurveRenderer
asynchronous: false asynchronous: false