// Compact, click-through system feedback. Repeated hardware-key presses update // this one surface in place, avoiding a stack of notification cards. import Quickshell import Quickshell.Wayland import QtQuick import qs.config import qs.services import qs.widgets PanelWindow { id: root property var modelData: null readonly property var presentation: OsdState.state readonly property bool belongsHere: !OsdState.monitorName || !root.modelData || root.modelData.name === OsdState.monitorName readonly property bool requestedVisible: OsdState.active && root.belongsHere readonly property int horizontalPadding: 18 readonly property int contentGap: 14 readonly property int progressWidth: 156 readonly property int progressLabelWidth: 48 readonly property int messageWidth: Math.min(300, Math.max(52, Math.ceil(labelMetrics.advanceWidth))) readonly property int desiredWidth: root.horizontalPadding * 2 + 24 + root.contentGap + (root.presentation.progress ? root.progressWidth + root.contentGap + root.progressLabelWidth : root.messageWidth) screen: root.modelData anchors.bottom: true margins.bottom: Theme.dockIconSize + Theme.dockPadding * 2 + Theme.barGap * 3 exclusiveZone: 0 exclusionMode: ExclusionMode.Ignore implicitWidth: root.desiredWidth implicitHeight: 64 color: "transparent" mask: Region {} WlrLayershell.namespace: "qs-popover-osd" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.None property bool mapped: false visible: root.mapped onRequestedVisibleChanged: { if (root.requestedVisible) { unmapTimer.stop(); root.mapped = true; entrance.restart(); } else if (root.mapped) { unmapTimer.restart(); } } Component.onCompleted: { if (root.requestedVisible) root.mapped = true; } Timer { id: unmapTimer interval: Theme.durNormal onTriggered: root.mapped = false } Rectangle { id: surface anchors.fill: parent radius: Theme.popoverRadius color: Theme.alpha(Theme.bgPopover, 0.9) border.width: 1 border.color: Theme.alpha(Theme.fg, 0.1) opacity: root.requestedVisible ? 1 : 0 Behavior on opacity { NumberAnimation { duration: Theme.durNormal; easing.type: Easing.OutCubic } } transform: Translate { id: slide y: root.requestedVisible ? 0 : 8 Behavior on y { NumberAnimation { duration: Theme.durNormal; easing.type: Easing.OutCubic } } } PrismEdge { anchors.top: parent.top anchors.topMargin: 1 anchors.left: parent.left anchors.right: parent.right inset: parent.radius } Row { anchors.centerIn: parent spacing: root.contentGap ThemedIcon { anchors.verticalCenter: parent.verticalCenter size: 24 icon: root.presentation.icon iconFallback: "dialog-information-symbolic" tint: Theme.fg } Item { visible: root.presentation.progress anchors.verticalCenter: parent.verticalCenter width: visible ? root.progressWidth : 0 height: 8 Rectangle { anchors.fill: parent radius: height / 2 color: Theme.alpha(Theme.fg, 0.14) } Rectangle { anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom width: parent.width * root.presentation.ratio radius: height / 2 color: Theme.accent Behavior on width { NumberAnimation { duration: Theme.durFast; easing.type: Easing.OutCubic } } } } Text { anchors.verticalCenter: parent.verticalCenter width: root.presentation.progress ? root.progressLabelWidth : root.messageWidth text: root.presentation.label color: Theme.fg elide: Text.ElideRight maximumLineCount: 1 horizontalAlignment: root.presentation.progress ? Text.AlignRight : Text.AlignLeft font.family: Theme.fontFamily font.features: Theme.tabularFigures font.pixelSize: Theme.fontSize font.weight: Font.DemiBold } } } TextMetrics { id: labelMetrics font.family: Theme.fontFamily font.pixelSize: Theme.fontSize font.weight: Font.DemiBold text: root.presentation.label } ParallelAnimation { id: entrance NumberAnimation { target: surface property: "opacity" from: 0 to: 1 duration: Theme.durNormal easing.type: Easing.OutCubic } NumberAnimation { target: slide property: "y" from: 8 to: 0 duration: Theme.durNormal easing.type: Easing.OutCubic } } }