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
127 lines
4.0 KiB
QML
127 lines
4.0 KiB
QML
// GNOME's date/time menu: calendar, ongoing activity and messages in one panel,
|
|
// dropping from the top center because that is where GNOME put it and the
|
|
// muscle memory is the whole point.
|
|
//
|
|
// A layer-shell surface rather than a PopupWindow because the inline reply
|
|
// fields have to be typeable, and because the clock it hangs from is owned by a
|
|
// different module.
|
|
//
|
|
// Layer-shell centers a surface on whichever axis it is not anchored to, so
|
|
// anchoring only `top` is what puts it in the middle.
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import Quickshell.Hyprland
|
|
import qs.config
|
|
import qs.services
|
|
import qs.widgets
|
|
|
|
PanelWindow {
|
|
id: root
|
|
|
|
readonly property bool ongoingContentFits: panel.ongoingContentFits
|
|
|
|
// "notifications" remains the overlay key so existing keybinds keep their
|
|
// contract; the surface itself now has Agenda, Ongoing and Notifications.
|
|
visible: ShellState.notificationsOpen
|
|
color: "transparent"
|
|
|
|
anchors.top: 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
|
|
exclusiveZone: 0
|
|
|
|
implicitWidth: 760
|
|
implicitHeight: panel.implicitHeight
|
|
|
|
// The `qs-popover` prefix is matched by a layerrule in hypr/rules.lua.
|
|
WlrLayershell.namespace: "qs-popover-datemenu"
|
|
WlrLayershell.layer: WlrLayer.Overlay
|
|
// OnDemand rather than Exclusive: the panel must be able to take typing for
|
|
// inline replies, but must not steal it while merely visible.
|
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
|
|
|
onVisibleChanged: {
|
|
if (root.visible) {
|
|
CalendarAgenda.refresh();
|
|
if (ShellState.dateMenuPage === "notifications")
|
|
Notifs.markAllRead();
|
|
panel.prepare(ShellState.dateMenuPage);
|
|
openAnim.restart();
|
|
} else {
|
|
panel.reset();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: ShellState
|
|
function onDateMenuPageChanged(): void {
|
|
if (root.visible && ShellState.dateMenuPage === "notifications")
|
|
Notifs.markAllRead();
|
|
}
|
|
}
|
|
|
|
// Closes the panel when a click lands anywhere else on the desktop.
|
|
HyprlandFocusGrab {
|
|
windows: [root]
|
|
active: root.visible
|
|
onCleared: ShellState.close()
|
|
}
|
|
|
|
Rectangle {
|
|
id: surface
|
|
anchors.fill: parent
|
|
radius: Theme.popoverRadius
|
|
color: Theme.alpha(Theme.bgPopover, Theme.popoverAlpha)
|
|
border.width: 1
|
|
border.color: Theme.alpha(Theme.fg, 0.08)
|
|
|
|
// The prism edge — see widgets/PrismEdge.qml. Sits just inside the 1px
|
|
// border so the two don't fight for the same row of pixels.
|
|
PrismEdge {
|
|
anchors.top: parent.top
|
|
anchors.topMargin: 1
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
inset: parent.radius
|
|
}
|
|
|
|
// Grows out of the top edge it is anchored to, rather than out of its
|
|
// own center — the panel should look like it came from the bar.
|
|
transform: Scale {
|
|
id: openScale
|
|
origin.x: surface.width / 2
|
|
origin.y: 0
|
|
xScale: 1
|
|
yScale: 1
|
|
}
|
|
|
|
// Escape reaches here by propagating up from whatever holds focus
|
|
// (usually a reply field), so it works in every sub-state.
|
|
Item {
|
|
anchors.fill: parent
|
|
focus: true
|
|
Keys.onEscapePressed: ShellState.close()
|
|
|
|
DateMenuPanel {
|
|
id: panel
|
|
anchors.fill: parent
|
|
}
|
|
}
|
|
}
|
|
|
|
NumberAnimation {
|
|
id: openAnim
|
|
target: openScale
|
|
property: "yScale"
|
|
from: 0.94
|
|
to: 1.0
|
|
duration: Theme.durNormal
|
|
easing.type: Easing.OutCubic
|
|
}
|
|
}
|