Files
Panama/config/dot/quickshell/modules/notifications/Toasts.qml
T
Gabriel Brown 2528edfddc Stop every popover double-counting the bar height
The control center fix applies to five more surfaces: the date menu,
the clipboard panel, the activity panel, notification toasts, and the
signal glass all opened 48 pixels under the bar while their own
expression asked for 12.

wlr-layer-shell has three behaviors and only the middle one is subtle.
A positive exclusiveZone reserves space; a negative one ignores what
others reserved; zero reserves nothing but RESPECTS what others
reserved. Every popover here uses zero, so the compositor had already
placed them below the bar before their own margin applied, and adding
Theme.barHeight counted the bar twice.

It is not a crash or a warning -- the surface simply opens lower than
written -- so a contract now holds the rule mechanically: a surface
with exclusiveZone 0 may not name Theme.barHeight in a margin. It also
checks the premise it rests on, and fails if the bar ever stops
reserving its own height rather than quietly checking the wrong thing.

Measured after: date menu and clipboard at 12px, control center at 2px,
each matching what its code asks for.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-19 11:31:53 -04:00

74 lines
2.5 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
// The gap ALONE, not the bar height plus the gap. exclusiveZone 0 means
// "reserve nothing, but respect what others reserved", so this surface
// already begins below the bar's zone -- adding the bar height here counted
// it twice and left the surface floating 48px under the bar instead of 12.
margins.top: 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
}
}
}
}