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
70 lines
2.2 KiB
QML
70 lines
2.2 KiB
QML
// The banner stack: newest at the top, tucked under the bar on the right.
|
|
//
|
|
// The window is exactly as wide and tall as the stack, so everything outside
|
|
// it stays clickable; the mask trims it further to the cards themselves.
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import qs.config
|
|
import qs.services
|
|
|
|
PanelWindow {
|
|
id: root
|
|
|
|
visible: !Notifs.doNotDisturb && Notifs.popups.length > 0
|
|
color: "transparent"
|
|
|
|
anchors.top: true
|
|
anchors.right: true
|
|
margins.top: Theme.barHeight + Theme.barGap * 2
|
|
margins.right: Theme.barSideMargin
|
|
exclusiveZone: 0
|
|
|
|
implicitWidth: 400
|
|
implicitHeight: Math.max(1, stack.implicitHeight)
|
|
|
|
WlrLayershell.namespace: "qs-notifications"
|
|
WlrLayershell.layer: WlrLayer.Overlay
|
|
|
|
// Normally inert so banners never steal a keystroke. An open inline reply
|
|
// is the one case where the surface has to be typeable.
|
|
WlrLayershell.keyboardFocus: root.replyFocused ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
|
|
|
property bool replyFocused: false
|
|
|
|
mask: Region {
|
|
item: stack
|
|
}
|
|
|
|
Column {
|
|
id: stack
|
|
anchors.top: parent.top
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
spacing: Theme.itemSpacing
|
|
|
|
Repeater {
|
|
// Notifs.popups.slice() is a fresh array on every change (a new
|
|
// arrival, a dismissal, a sibling toast timing out). Handing that
|
|
// straight to Repeater would reset the model and rebuild every
|
|
// delegate each time, blowing away whichever toast has a reply
|
|
// field mid-typing. ScriptModel diffs by object identity
|
|
// (Notification instances are unique QObjects), so only genuinely
|
|
// added/removed notifications add/remove delegates — unrelated
|
|
// toasts, and their slide-in animations, are untouched.
|
|
model: ScriptModel {
|
|
values: Notifs.popups.slice(0, Settings.maxVisibleToasts)
|
|
}
|
|
|
|
Toast {
|
|
required property var modelData
|
|
|
|
width: stack.width
|
|
notification: modelData
|
|
onReplyFocusChanged: focused => root.replyFocused = focused
|
|
}
|
|
}
|
|
}
|
|
}
|