// 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 } }