feat(quickshell): "Take your time." glows and flickers

A new GlowText component lights a line from inside: a soft two-layer
halo that breathes slowly, and every 4-14 seconds a short, uneven
stutter like a tube catching. Only opacity and the halo move, so the
glyphs stay on the pixel grid. The lock screen and the power menu use
it; the power menu's stops while it is closed.
This commit is contained in:
nvrl committed 2026-10-04 23:12:50 +02:00
1 parent c36a74e861
commit e9735d1129
4 files changed
+124 -16

No files matched your search

+2 -1
View File
@@ -252,7 +252,8 @@ session.
It is the wallpaper, barely softened, with the face set in the dark of the It is the wallpaper, barely softened, with the face set in the dark of the
porthole: the mark, the time in light display type, the date, "Take your porthole: the mark, the time in light display type, the date, "Take your
time.", a status log time." glowing faintly with the odd flicker (`components/GlowText.qml`), a
status log
(battery, network, what is playing, unread notices), and a rounded field that (battery, network, what is playing, unread notices), and a rounded field that
fills with dots as you type. The dots breathe while PAM checks; a wrong fills with dots as you type. The dots breathe while PAM checks; a wrong
password turns them and the field's rim to rust and nudges the field once. password turns them and the field's rim to rust and nudges the field once.
+110
View File
@@ -0,0 +1,110 @@
import QtQuick
import QtQuick.Effects
import "root:/config"
// A line of type lit from inside, like a tube in a dark console: a soft halo
// that breathes slowly, and every so often a short, uneven stutter where the
// light catches — two or three dips, then steady again for a while. The
// stutters come at random intervals so it never settles into a rhythm you
// could count.
//
// Only the halo and the opacity move, never the geometry, so the glyphs stay
// on the pixel grid. Set `active` false while the text is off screen to stop
// the animation.
Item {
id: root
property string text: ""
property color color: Theme.accent
property color glowColor: root.color
property string family: Theme.fontDisplay
property int pixelSize: Theme.fsLarge
property int weight: Font.Light
property real letterSpacing: 0
property bool active: true
// 0..1, how lit the tube is at this instant.
property real lit: 1
// Slow breathing on the halo alone, between `low` and 1.
property real breath: 1
implicitWidth: label.implicitWidth
implicitHeight: label.implicitHeight
Text {
id: label
visible: false
text: root.text
color: root.color
font.family: root.family
font.pixelSize: root.pixelSize
font.weight: root.weight
font.letterSpacing: root.letterSpacing
// Greyscale antialiasing: the glyphs are drawn into the effect's
// texture, and native subpixel rendering there leaves colour fringes.
renderType: Text.CurveRendering
}
// A wide, faint halo under a tight, brighter one, so the light both
// spreads into the dark and hugs the strokes.
MultiEffect {
source: label
anchors.fill: label
autoPaddingEnabled: true
opacity: root.lit
shadowEnabled: true
shadowColor: root.glowColor
shadowHorizontalOffset: 0
shadowVerticalOffset: 0
shadowBlur: 1.0
blurMax: Math.max(32, Math.round(root.pixelSize * 2))
shadowOpacity: 0.7 * root.breath
shadowScale: 1.02
}
MultiEffect {
source: label
anchors.fill: label
autoPaddingEnabled: true
opacity: 0.55 + 0.45 * root.lit
shadowEnabled: true
shadowColor: root.glowColor
shadowHorizontalOffset: 0
shadowVerticalOffset: 0
shadowBlur: 1.0
blurMax: Math.max(8, Math.round(root.pixelSize * 0.35))
shadowOpacity: 0.9 * root.lit
}
SequentialAnimation on breath {
running: root.active
loops: Animation.Infinite
NumberAnimation { to: 0.6; duration: 2600; easing.type: Easing.InOutSine }
NumberAnimation { to: 1.0; duration: 2600; easing.type: Easing.InOutSine }
}
// One stutter: uneven dips of uneven length, then back to full.
SequentialAnimation {
id: stutter
NumberAnimation { target: root; property: "lit"; to: 0.25; duration: 40 }
NumberAnimation { target: root; property: "lit"; to: 0.9; duration: 60 }
PauseAnimation { duration: 70 }
NumberAnimation { target: root; property: "lit"; to: 0.4; duration: 30 }
NumberAnimation { target: root; property: "lit"; to: 1.0; duration: 180; easing.type: Easing.OutCubic }
}
Timer {
id: nextStutter
running: root.active
repeat: true
interval: 6000
onTriggered: {
stutter.restart();
nextStutter.interval = 4000 + Math.random() * 10000;
}
}
onActiveChanged: if (!root.active) { stutter.stop(); root.lit = 1; }
}
+6 -7
View File
@@ -132,15 +132,14 @@ Item {
} }
// The one line the face says to you. Nothing here is in a hurry. // The one line the face says to you. Nothing here is in a hurry.
Text { Item { width: 1; height: Math.round(Theme.padL * surface.uiScale) }
topPadding: Math.round(Theme.padL * surface.uiScale)
leftPadding: Math.round(4 * surface.uiScale) GlowText {
x: Math.round(4 * surface.uiScale)
text: "Take your time." text: "Take your time."
color: Theme.subtext color: Theme.subtext
font.family: Theme.fontDisplay glowColor: Theme.accent
font.pixelSize: Math.round(Theme.fsLarge * surface.uiScale) pixelSize: Math.round(Theme.fsLarge * surface.uiScale)
font.weight: Font.Light
renderType: Text.NativeRendering
} }
Item { width: 1; height: Math.round(Theme.padXL * surface.uiScale) } Item { width: 1; height: Math.round(Theme.padXL * surface.uiScale) }
+6 -8
View File
@@ -98,15 +98,13 @@ PanelWindow {
size: 26 size: 26
} }
Text { Item { width: 1; height: Theme.padM }
topPadding: Theme.padM
GlowText {
text: "Take your time." text: "Take your time."
color: Theme.accent pixelSize: Theme.fsHuge
font.family: Theme.fontDisplay letterSpacing: 1
font.pixelSize: Theme.fsHuge active: root.open
font.weight: Font.Light
font.letterSpacing: 1
renderType: Text.NativeRendering
} }
Text { Text {