Files
attention-required/Banner.qml
T
alan e5053de6a7 Wider ranges, an agent guide, patch and effects commands
Every effect's sliders and clamps now reach far (a nudge from a tremor
to an earthquake, a banner that stays minutes). AGENTS.md documents the
file schema, every option and range, the CLI, verification and worked
scenarios for scripts and coding agents; attention-required effects
prints the catalog as JSON, patch merges keys into a rule, docs prints
the guide.
2026-09-08 17:21:44 +01:00

146 lines
4.6 KiB
QML

import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Commons
import "Rules.js" as Rules
// A big card with the message that slides in, stays, and slides out. On
// every screen, click-through, reserves no space.
//
// Options: duration (seconds it stays, 3), intensity (size, 1), speed
// (slide speed, 4), position (top | center | bottom), color (accent |
// urgent | foreground or any CSS color), text ("{summary}" template).
Item {
id: root
width: 0
height: 0
visible: false
property bool active: false
property real progress: 0
property string title: ""
property string body: ""
property string position: "top"
property real size: 1
property color glow: Color.accent
property int slideMs: 250
property int holdMs: 3000
function number(value, fallback, min, max) {
var n = Number(value)
if (!isFinite(n)) return fallback
return Math.max(min, Math.min(max, n))
}
function resolveColor(value) {
var v = String(value || "").trim().toLowerCase()
if (!v || v === "accent") return Color.accent
if (v === "urgent") return Color.urgent
if (v === "foreground" || v === "text") return Color.foreground
var c = Qt.color(value)
return c.valid === false ? Color.accent : c
}
function trigger(opts, notif, rule) {
opts = opts || {}
var custom = String(opts.text || "").trim()
title = Rules.renderTemplate(custom, notif, rule)
body = custom ? "" : Rules.stripTags(notif ? notif.body : "")
position = opts.position === "center" || opts.position === "bottom" ? String(opts.position) : "top"
size = number(opts.intensity, 1, 0.2, 6)
slideMs = Math.round(1000 / number(opts.speed, 4, 0.2, 50))
holdMs = Math.round(number(opts.duration, 3, 0.1, 300) * 1000)
glow = resolveColor(opts.color)
off.stop()
hide.stop()
active = true
progress = 1
hide.interval = holdMs
hide.restart()
}
Behavior on progress { NumberAnimation { duration: root.slideMs; easing.type: Easing.OutCubic } }
Timer {
id: hide
onTriggered: {
root.progress = 0
off.interval = root.slideMs + 60
off.restart()
}
}
Timer {
id: off
onTriggered: root.active = false
}
Variants {
model: Quickshell.screens
PanelWindow {
id: window
required property var modelData
screen: modelData
visible: root.active
color: "transparent"
anchors { top: true; bottom: true; left: true; right: true }
exclusionMode: ExclusionMode.Ignore
WlrLayershell.namespace: "attention-required-banner"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
mask: Region {}
readonly property int margin: Style.space(24)
Rectangle {
id: card
readonly property int pad: Math.round(Style.space(18) * root.size)
width: Math.min(window.width * 0.7, column.implicitWidth + pad * 2)
height: column.implicitHeight + pad * 2
anchors.horizontalCenter: parent.horizontalCenter
y: root.position === "top" ? Math.round(-height + root.progress * (height + window.margin))
: root.position === "bottom" ? Math.round(window.height - root.progress * (height + window.margin))
: Math.round((window.height - height) / 2)
opacity: root.position === "center" ? root.progress : 1
color: Color.popups.background
radius: Style.cornerRadius
border.width: Math.max(2, Style.space(2))
border.color: root.glow
Column {
id: column
anchors.centerIn: parent
width: Math.min(window.width * 0.7 - card.pad * 2, Math.max(titleText.implicitWidth, bodyText.visible ? bodyText.implicitWidth : 0))
spacing: Math.round(Style.space(6) * root.size)
Text {
id: titleText
width: parent.width
text: root.title
color: Color.popups.text
font.family: Style.font.family
font.pixelSize: Math.round(Style.font.display * 1.4 * root.size)
font.weight: Font.DemiBold
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
}
Text {
id: bodyText
visible: root.body.length > 0
width: parent.width
text: root.body
color: Color.popups.text
opacity: 0.8
font.family: Style.font.family
font.pixelSize: Math.round(Style.font.body * 1.2 * root.size)
wrapMode: Text.Wrap
maximumLineCount: 4
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}
}