import QtQuick import Quickshell import Quickshell.Wayland import "root:/config" import "root:/components" import "root:/services" // Volume / mic / brightness heads-up, bottom centre. Appears on change and // fades out on its own. PanelWindow { id: root required property var modelData screen: modelData readonly property bool primaryScreen: Quickshell.screens.length === 0 || Quickshell.screens[0] === modelData WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.namespace: "takemi-osd" WlrLayershell.keyboardFocus: WlrKeyboardFocus.None anchors { bottom: true } margins.bottom: 90 implicitWidth: 300 implicitHeight: 78 color: "transparent" exclusionMode: ExclusionMode.Ignore visible: primaryScreen && (shown || fade.running) property bool shown: false property string kind: "volume" // Suppress the burst of change signals that fires while services warm up. property bool armed: false Timer { interval: 1500 running: true onTriggered: root.armed = true } readonly property string glyph: { switch (root.kind) { case "mic": return Audio.micIcon; case "brightness": return Brightness.icon; default: return Audio.icon; } } readonly property string title: { switch (root.kind) { case "mic": return Audio.micMuted ? "Mic muted" : "Microphone"; case "brightness": return "Brightness"; default: return Audio.muted ? "Muted" : "Volume"; } } readonly property real level: { switch (root.kind) { case "mic": return Audio.micMuted ? 0 : Audio.micVolume; case "brightness": return Brightness.percent / 100; default: return Audio.muted ? 0 : Audio.volume; } } readonly property color tone: { switch (root.kind) { case "mic": return Audio.micMuted ? Theme.accent : Theme.blue; // Turning the screen up is not a warning. White, like volume. case "brightness": return Theme.text; default: return Audio.muted ? Theme.accent : Theme.primary; } } function flash(k: string) { if (!root.armed) return; root.kind = k; root.shown = true; glyphIcon.bump(); hold.restart(); } Timer { id: hold interval: 1600 onTriggered: root.shown = false } Connections { target: Audio function onVolumeBumped() { root.flash("volume"); } function onMicBumped() { root.flash("mic"); } } Connections { target: Brightness function onBumped() { root.flash("brightness"); } } P5Panel { id: panel anchors.fill: parent anchors.margins: 4 fill: Theme.mantle fillOpacity: 0.97 border: Theme.alpha(root.tone, 0.85) borderWidth: Theme.stroke slash: true slashColor: root.tone slashWidth: 5 halftone: true halftoneColor: root.tone halftoneOpacity: 0.08 sheen: true lean: 0.09 chop: 14 padding: Theme.padM opacity: root.shown ? 1 : 0 scale: root.shown ? 1 : 0.9 Behavior on opacity { NumberAnimation { id: fade; duration: Theme.durBase } } Behavior on scale { NumberAnimation { duration: Theme.durSlow; easing.type: Easing.OutBack } } Icon { id: glyphIcon anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter text: root.glyph color: root.tone font.pixelSize: 30 width: 36 } Column { anchors.left: glyphIcon.right anchors.leftMargin: Theme.padM anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter spacing: 4 // Title left, readout hard right. This used to be a Row with a // `parent.width - 90` spacer, which assumed the two labels never // needed more than 90px between them — "BRIGHTNESS" plus "100%" // does, and the percentage fell off the right edge. Anchoring the // readout to the right and letting the title elide into whatever // is left makes the widest case the safe one. Item { width: parent.width height: Math.max(titleLabel.implicitHeight, readout.implicitHeight) Text { id: titleLabel anchors.left: parent.left anchors.right: readout.left anchors.rightMargin: Theme.padS anchors.verticalCenter: parent.verticalCenter text: root.title.toUpperCase() color: Theme.subtext elide: Text.ElideRight font.family: Theme.fontMono font.pixelSize: Theme.fsMicro font.letterSpacing: 2 renderType: Text.NativeRendering } SplitText { id: readout anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter // Natural width, not a fixed one — SplitText offsets its // ghosts with left/right anchor margins, so pinning the // width and right-aligning the text would flatten the RGB // split away. The title's left edge is anchored, so a // widening readout only moves its own left edge. text: Math.round(root.level * 100) + "%" pixelSize: Theme.fsBody split: 1 splitOpacity: 0.5 } } MeterBar { width: parent.width height: 10 segments: 20 segmentWidth: (width - 19 * 3) / 20 spacing: 3 value: root.level * 100 activeColor: root.tone } } } }