Route the interactive Panama date menu

This commit is contained in:
Gabriel Brown
2026-08-17 11:32:57 -04:00
parent fb27c5b309
commit 6a1bec849d
6 changed files with 112 additions and 32 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ Pill {
return date + time;
}
onActivated: ShellState.toggle("notifications")
onActivated: ShellState.toggleDateMenu("agenda")
Text {
anchors.verticalCenter: parent.verticalCenter
@@ -4,17 +4,20 @@
// months and the panel below it never jumps. Days belonging to the neighbouring
// months fill the edges, dimmed. Today wears the accent.
//
// Deliberately non-interactive apart from the header: there is no calendar
// backend behind this, so a day cell that highlighted on hover would be
// promising a click that does nothing.
// Days become real navigation targets now that Evolution Data Server backs the
// panel. Up to three source-coloured markers show whether a date has events.
import Quickshell
import QtQuick
import qs.config
import qs.services
Item {
id: root
signal dateActivated(date value)
signal visibleMonthChanged(int year, int month)
implicitHeight: body.implicitHeight
// Cells divide the available width evenly; the height is fixed so the grid
@@ -37,6 +40,7 @@ Item {
// panel opens, so it never comes back showing wherever you paged off to.
property int viewYear: root.todayYear
property int viewMonth: root.todayMonth
property date selectedDate: CalendarAgenda.selectedDate
readonly property bool onCurrentMonth: root.viewYear === root.todayYear && root.viewMonth === root.todayMonth
@@ -53,30 +57,29 @@ Item {
root.viewMonth = d.getMonth();
}
function activateCell(model: var): void {
if (!model.current) {
root.viewYear = model.date.getFullYear();
root.viewMonth = model.date.getMonth();
}
root.dateActivated(model.date);
}
onViewYearChanged: root.visibleMonthChanged(root.viewYear, root.viewMonth)
onViewMonthChanged: root.visibleMonthChanged(root.viewYear, root.viewMonth)
readonly property var cells: {
const first = new Date(root.viewYear, root.viewMonth, 1);
const offset = first.getDay(); // 0 = Sunday, matching the header row
const inThisMonth = new Date(root.viewYear, root.viewMonth + 1, 0).getDate();
const inPrevMonth = new Date(root.viewYear, root.viewMonth, 0).getDate();
const out = [];
for (let i = 0; i < 42; i++) {
const n = i - offset + 1;
if (n < 1)
out.push({
day: inPrevMonth + n,
current: false
});
else if (n > inThisMonth)
out.push({
day: n - inThisMonth,
current: false
});
else
out.push({
day: n,
current: true
});
const date = new Date(root.viewYear, root.viewMonth, i - offset + 1);
out.push({
date: date,
day: date.getDate(),
current: date.getMonth() === root.viewMonth && date.getFullYear() === root.viewYear
});
}
return out;
}
@@ -198,6 +201,8 @@ Item {
required property var modelData
readonly property bool isToday: cell.modelData.current && cell.modelData.day === root.todayDay && root.onCurrentMonth
readonly property bool isSelected: CalendarAgenda.dateKey(cell.modelData.date) === CalendarAgenda.dateKey(root.selectedDate)
readonly property var markers: CalendarAgenda.markersForDate(cell.modelData.date).slice(0, 3)
width: root.cellWidth
height: root.cellHeight
@@ -208,8 +213,18 @@ Item {
height: root.cellHeight - 4
radius: width / 2
border.width: 0
visible: cell.isToday
color: Theme.accent
visible: cell.isToday || cell.isSelected || cellMouse.containsMouse
color: {
if (cell.isToday)
return Theme.accent;
if (cell.isSelected)
return Theme.alpha(Theme.accent, 0.20);
return Theme.alpha(Theme.fg, 0.08);
}
Behavior on color {
ColorAnimation { duration: Theme.durFast }
}
}
Text {
@@ -223,11 +238,44 @@ Item {
color: {
if (cell.isToday)
return Theme.bgDark;
if (cell.isSelected)
return Theme.accent;
return cell.modelData.current ? Theme.fg : Theme.fgMuted;
}
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 1
spacing: 2
Repeater {
model: cell.markers
Rectangle {
required property var modelData
width: 3
height: 3
radius: 2
border.width: 0
color: cell.isToday ? Theme.bgDark : modelData.color
}
}
}
MouseArea {
id: cellMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.activateCell(cell.modelData)
}
}
}
}
}
Component.onCompleted: root.visibleMonthChanged(root.viewYear, root.viewMonth)
}
@@ -35,6 +35,10 @@ Singleton {
readonly property bool anyOverlayOpen: activeOverlay !== ""
// The date menu is one surface with two deliberate entry points. The clock
// opens the Daybook; Super+B and notification actions open message history.
property string dateMenuPage: "agenda"
// 0 means "use the currently focused workspace". A positive value lets a
// contextual surface, such as Focus, open Mission Control at its target.
property int overviewWorkspaceId: 0
@@ -48,6 +52,21 @@ Singleton {
root.activeOverlay = name;
}
function openDateMenu(page: string): void {
root.dateMenuPage = page === "notifications" ? "notifications" : "agenda";
root.activeOverlay = "notifications";
}
function toggleDateMenu(page: string): void {
const target = page === "notifications" ? "notifications" : "agenda";
if (root.activeOverlay === "notifications" && root.dateMenuPage === target) {
root.activeOverlay = "";
return;
}
root.dateMenuPage = target;
root.activeOverlay = "notifications";
}
function openOverview(workspaceId: int): void {
root.overviewWorkspaceId = Math.max(0, workspaceId);
root.overviewQuery = "";
+5 -2
View File
@@ -253,7 +253,8 @@ ShellRoot {
IpcHandler {
target: "notifications"
function toggle(): void { ShellState.toggle("notifications"); }
function toggle(): void { ShellState.toggleDateMenu("notifications"); }
function open(): void { ShellState.openDateMenu("notifications"); }
function clear(): void { Notifs.dismissAll(); }
function dnd(): bool {
Notifs.doNotDisturb = !Notifs.doNotDisturb;
@@ -266,13 +267,15 @@ ShellRoot {
function fixture(name: string): void { CalendarAgenda.applyFixture(name); }
function reset(): void { CalendarAgenda.clearFixture(); }
function refresh(): void { CalendarAgenda.refresh(); }
function open(): void { ShellState.open("notifications"); }
function open(): void { ShellState.openDateMenu("agenda"); }
function close(): void { ShellState.close(); }
function select(date: string): void { CalendarAgenda.selectIsoDate(date); }
function status(): string {
return JSON.stringify({
phase: CalendarAgenda.phase,
fixture: CalendarAgenda.fixtureMode,
open: ShellState.notificationsOpen,
page: ShellState.dateMenuPage,
sourceCount: CalendarAgenda.sources.length,
eventCount: CalendarAgenda.events.length,
selectedDate: CalendarAgenda.selectedDateKey,