Files
Gabriel Brown e6b4d3c1a1 Stop lists and sliders from losing input under the user
WifiList and the notification toasts built their model from a plain
computed array, so any background property tick (a scan result, an
unrelated notification arriving) reassigned the whole array and the
Repeater destroyed and recreated every delegate -- including one with
an open, focused password field or an in-progress reply. Switched
both to a ScriptModel, which diffs by identity instead of resetting.

ValueSlider had its pointer-to-value mapping offset by 16px (the
hit-area margin was applied with the wrong sign), so 0% was
unreachable and every click landed to the right of where it was
placed -- affects every slider in the shell. SliderRow used -1 as a
sentinel for "nothing pending," which collides with legitimate
negative preference values like pointer sensitivity.

Dock intellihide read the globally focused workspace instead of each
monitor's own, so an empty workspace on one screen could hide the
dock on another; ActivityPanel rebuilt every row once a second during
a recording because the elapsed-time read lived in the model
construction instead of each row's own binding.

Claude-Session: https://claude.ai/code/session_01E6TJUAh41HaP25MVHWkhRZ
2026-08-18 21:22:58 -04:00

64 lines
1.9 KiB
QML

// A single banner: a notification card that slides in and times itself out.
import QtQuick
import qs.config
import qs.services
Item {
id: root
required property var notification
// Raised while the inline reply field has focus — Toasts.qml turns that
// into a keyboard focus request on the layer surface.
signal replyFocusChanged(bool focused)
// Mirrors the signal above so the dismiss timer below can read it.
property bool replyFocused: false
implicitHeight: card.implicitHeight
// Critical notifications stay until dismissed (the setting is 0). An app
// asking for 0 means "never expire" per the freedesktop spec; -1 means
// "server decides", which is our default. Shared with Notifs.qml, which
// gives a DND-hidden transient notification the same lifetime.
readonly property int timeoutMs: Notifs.notificationTimeoutMs(root.notification)
HoverHandler {
id: hover
}
Timer {
// Hovering, or actively typing a reply, holds the banner open; the
// countdown restarts (from the top, same as hover) once both let go.
running: root.timeoutMs > 0 && !hover.hovered && !root.replyFocused
interval: root.timeoutMs
onTriggered: Notifs.dropPopup(root.notification)
}
NotificationCard {
id: card
width: parent.width
notification: root.notification
onDismissed: Notifs.dropPopup(root.notification)
onReplyFocusChanged: focused => {
root.replyFocused = focused;
root.replyFocusChanged(focused);
}
// Slide in from the right edge. Runs once, on creation.
NumberAnimation on x {
from: 40
to: 0
duration: Theme.durNormal
easing.type: Easing.OutCubic
}
NumberAnimation on opacity {
from: 0
to: 1
duration: Theme.durNormal
}
}
}