Build the Prism calendar Daybook
This commit is contained in:
@@ -0,0 +1,128 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.config
|
||||||
|
import qs.services
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var eventData
|
||||||
|
property bool past: false
|
||||||
|
|
||||||
|
signal activated
|
||||||
|
signal joinRequested
|
||||||
|
|
||||||
|
readonly property var sourceData: CalendarAgenda.sourceForId(eventData.sourceId)
|
||||||
|
readonly property color eventColor: sourceData?.color ?? Theme.accent
|
||||||
|
readonly property string timeText: eventData.allDay
|
||||||
|
? "All day"
|
||||||
|
: Qt.formatTime(new Date(Number(eventData.start) * 1000), Settings.use24Hour ? "HH:mm" : "h:mm AP")
|
||||||
|
readonly property string detailText: {
|
||||||
|
const source = root.sourceData?.name ?? "Calendar";
|
||||||
|
const location = String(root.eventData.location ?? "").trim();
|
||||||
|
return location === "" ? source : source + " · " + location;
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitHeight: 58
|
||||||
|
opacity: past ? 0.52 : 1
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: 9
|
||||||
|
border.width: 0
|
||||||
|
color: rowMouse.containsMouse ? Theme.alpha(Theme.fg, 0.07) : "transparent"
|
||||||
|
|
||||||
|
Behavior on color { ColorAnimation { duration: Theme.durFast } }
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: rowMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.activated()
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: timeLabel
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 56
|
||||||
|
text: root.timeText
|
||||||
|
color: Theme.fgDim
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.features: Theme.tabularFigures
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: sourceRail
|
||||||
|
anchors.left: timeLabel.right
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 3
|
||||||
|
height: 34
|
||||||
|
radius: 2
|
||||||
|
border.width: 0
|
||||||
|
color: root.eventColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.left: sourceRail.right
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
anchors.right: joinPill.visible ? joinPill.left : parent.right
|
||||||
|
anchors.rightMargin: joinPill.visible ? 10 : 4
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 3
|
||||||
|
|
||||||
|
Text {
|
||||||
|
width: parent.width
|
||||||
|
text: root.eventData.summary || "Untitled event"
|
||||||
|
color: Theme.fg
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSize
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
width: parent.width
|
||||||
|
text: root.detailText
|
||||||
|
color: Theme.fgDim
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: joinPill
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 4
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: String(root.eventData.joinUrl ?? "").startsWith("https://")
|
||||||
|
width: joinLabel.implicitWidth + 18
|
||||||
|
height: 26
|
||||||
|
radius: Theme.pillRadius
|
||||||
|
border.width: 0
|
||||||
|
color: joinMouse.containsMouse ? Theme.alpha(Theme.accent, 0.28) : Theme.alpha(Theme.accent, 0.14)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: joinLabel
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "Join"
|
||||||
|
color: Theme.accent
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: joinMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.joinRequested()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.config
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.quicksettings
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
signal requestNotifications
|
||||||
|
|
||||||
|
readonly property bool isToday: CalendarAgenda.selectedDateKey === CalendarAgenda.dateKey(clock.date)
|
||||||
|
readonly property var upNext: CalendarAgenda.nextEventForSelectedDate
|
||||||
|
readonly property var listedEvents: CalendarAgenda.selectedEvents.filter(event => !root.upNext || event.id !== root.upNext.id)
|
||||||
|
readonly property string selectedLabel: root.isToday
|
||||||
|
? "Today"
|
||||||
|
: Qt.formatDate(CalendarAgenda.selectedDate, "dddd, MMMM d")
|
||||||
|
|
||||||
|
implicitHeight: content.implicitHeight
|
||||||
|
|
||||||
|
SystemClock {
|
||||||
|
id: clock
|
||||||
|
precision: SystemClock.Minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: content
|
||||||
|
width: parent.width
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 3
|
||||||
|
visible: CalendarAgenda.phase === "loading"
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "CALENDAR"
|
||||||
|
color: Theme.fgMuted
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
font.letterSpacing: 1.1
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: "Loading calendars…"
|
||||||
|
color: Theme.fgDim
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 10
|
||||||
|
visible: CalendarAgenda.phase === "unavailable"
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Calendar data is unavailable"
|
||||||
|
color: Theme.fg
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: "Notifications, weather, and the month grid are still available. GNOME Calendar may have more detail."
|
||||||
|
color: Theme.fgDim
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSize
|
||||||
|
}
|
||||||
|
ActionPill {
|
||||||
|
text: "Open Calendar"
|
||||||
|
onClicked: CalendarAgenda.openCalendar(CalendarAgenda.selectedDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: nextCard
|
||||||
|
width: parent.width
|
||||||
|
height: visible ? 88 : 0
|
||||||
|
visible: CalendarAgenda.available && root.isToday && root.upNext !== null
|
||||||
|
radius: Theme.cardRadius
|
||||||
|
border.width: 1
|
||||||
|
border.color: Theme.alpha(Theme.accent, 0.18)
|
||||||
|
color: Theme.alpha(Theme.accent, 0.105)
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: -24
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: -34
|
||||||
|
width: 110
|
||||||
|
height: 110
|
||||||
|
radius: 55
|
||||||
|
border.width: 0
|
||||||
|
color: Theme.alpha(Theme.accentSecondary, 0.08)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 14
|
||||||
|
anchors.right: nextJoin.visible ? nextJoin.left : parent.right
|
||||||
|
anchors.rightMargin: nextJoin.visible ? 10 : 14
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "UP NEXT · " + root.relativeStart(root.upNext)
|
||||||
|
color: Theme.accent
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.features: Theme.tabularFigures
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Bold
|
||||||
|
font.letterSpacing: 0.8
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
width: parent.width
|
||||||
|
text: root.upNext?.summary ?? ""
|
||||||
|
color: Theme.fg
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
width: parent.width
|
||||||
|
text: root.eventDetail(root.upNext)
|
||||||
|
color: Theme.fgDim
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionPill {
|
||||||
|
id: nextJoin
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: 12
|
||||||
|
visible: String(root.upNext?.joinUrl ?? "").startsWith("https://")
|
||||||
|
text: "Join"
|
||||||
|
accent: true
|
||||||
|
onClicked: CalendarAgenda.join(root.upNext)
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
z: -1
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: CalendarAgenda.openEvent(root.upNext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: 34
|
||||||
|
visible: CalendarAgenda.available
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: root.selectedLabel.toUpperCase()
|
||||||
|
color: Theme.fgMuted
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
font.letterSpacing: 0.9
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: "Your schedule"
|
||||||
|
color: Theme.fg
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
text: CalendarAgenda.selectedEvents.length + (CalendarAgenda.selectedEvents.length === 1 ? " event" : " events")
|
||||||
|
color: Theme.fgDim
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.features: Theme.tabularFigures
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: visible ? 92 : 0
|
||||||
|
visible: CalendarAgenda.available && CalendarAgenda.selectedEvents.length === 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: "Nothing scheduled"
|
||||||
|
color: Theme.fgDim
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSize
|
||||||
|
}
|
||||||
|
ActionPill {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: "Open Calendar"
|
||||||
|
onClicked: CalendarAgenda.openCalendar(CalendarAgenda.selectedDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollColumn {
|
||||||
|
width: parent.width
|
||||||
|
maxHeight: 286
|
||||||
|
spacing: 0
|
||||||
|
visible: CalendarAgenda.available && root.listedEvents.length > 0
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.listedEvents
|
||||||
|
|
||||||
|
AgendaEvent {
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
eventData: modelData
|
||||||
|
past: Number(modelData.end) <= CalendarAgenda.nowEpoch
|
||||||
|
onActivated: CalendarAgenda.openEvent(modelData)
|
||||||
|
onJoinRequested: CalendarAgenda.join(modelData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationSummary {
|
||||||
|
width: parent.width
|
||||||
|
onActivated: root.requestNotifications()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function relativeStart(event: var): string {
|
||||||
|
if (!event)
|
||||||
|
return "";
|
||||||
|
const seconds = Number(event.start) - CalendarAgenda.nowEpoch;
|
||||||
|
if (seconds <= 0)
|
||||||
|
return "NOW";
|
||||||
|
if (seconds < 3600)
|
||||||
|
return "IN " + Math.ceil(seconds / 60) + " MINUTES";
|
||||||
|
return Qt.formatTime(new Date(Number(event.start) * 1000), Settings.use24Hour ? "HH:mm" : "h:mm AP");
|
||||||
|
}
|
||||||
|
|
||||||
|
function eventDetail(event: var): string {
|
||||||
|
if (!event)
|
||||||
|
return "";
|
||||||
|
const source = CalendarAgenda.sourceForId(event.sourceId)?.name ?? "Calendar";
|
||||||
|
const location = String(event.location ?? "").trim();
|
||||||
|
return location === "" ? source : source + " · " + location;
|
||||||
|
}
|
||||||
|
|
||||||
|
component ActionPill: Rectangle {
|
||||||
|
id: action
|
||||||
|
|
||||||
|
required property string text
|
||||||
|
property bool accent: false
|
||||||
|
signal clicked
|
||||||
|
|
||||||
|
width: actionLabel.implicitWidth + 20
|
||||||
|
height: 28
|
||||||
|
radius: Theme.pillRadius
|
||||||
|
border.width: 0
|
||||||
|
color: actionMouse.containsMouse
|
||||||
|
? Theme.alpha(action.accent ? Theme.accent : Theme.fg, 0.24)
|
||||||
|
: Theme.alpha(action.accent ? Theme.accent : Theme.fg, action.accent ? 0.14 : 0.07)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: actionLabel
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: action.text
|
||||||
|
color: action.accent ? Theme.accent : Theme.fg
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
id: actionMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: action.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ PanelWindow {
|
|||||||
margins.top: Theme.barHeight + Theme.barGap * 2
|
margins.top: Theme.barHeight + Theme.barGap * 2
|
||||||
exclusiveZone: 0
|
exclusiveZone: 0
|
||||||
|
|
||||||
implicitWidth: panel.implicitWidth
|
implicitWidth: 760
|
||||||
implicitHeight: panel.implicitHeight
|
implicitHeight: panel.implicitHeight
|
||||||
|
|
||||||
// The `qs-popover` prefix is matched by a layerrule in hypr/rules.lua.
|
// The `qs-popover` prefix is matched by a layerrule in hypr/rules.lua.
|
||||||
@@ -41,15 +41,24 @@ PanelWindow {
|
|||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (root.visible) {
|
if (root.visible) {
|
||||||
// Opening the tray is what marks everything read, same as GNOME.
|
CalendarAgenda.refresh();
|
||||||
Notifs.markAllRead();
|
if (ShellState.dateMenuPage === "notifications")
|
||||||
panel.prepare();
|
Notifs.markAllRead();
|
||||||
|
panel.prepare(ShellState.dateMenuPage);
|
||||||
openAnim.restart();
|
openAnim.restart();
|
||||||
} else {
|
} else {
|
||||||
panel.reset();
|
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.
|
// Closes the panel when a click lands anywhere else on the desktop.
|
||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
windows: [root]
|
windows: [root]
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
// The contents of the date menu, in GNOME's order: date header, notifications,
|
// The approved A · Daybook: month context on the left, the selected day's
|
||||||
// calendar, weather.
|
// schedule or grouped notification history on the right.
|
||||||
//
|
|
||||||
// Kept separate from DateMenu.qml (the PanelWindow) so it can be dropped into a
|
|
||||||
// FloatingWindow for testing — layer-shell surfaces cannot map outside a
|
|
||||||
// wlroots compositor.
|
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -14,38 +10,38 @@ import qs.widgets
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
implicitWidth: 480
|
implicitWidth: 760
|
||||||
implicitHeight: content.implicitHeight + Theme.popoverPadding * 2
|
implicitHeight: frame.implicitHeight + Theme.popoverPadding * 2
|
||||||
|
|
||||||
// Called by the window when it opens and closes, so the panel is always in
|
function prepare(page: string): void {
|
||||||
// a known state rather than wherever the last visit left it.
|
|
||||||
function prepare(): void {
|
|
||||||
calendar.showToday();
|
calendar.showToday();
|
||||||
|
CalendarAgenda.selectDate(clock.date);
|
||||||
|
CalendarAgenda.setVisibleMonth(clock.date.getFullYear(), clock.date.getMonth());
|
||||||
|
if (page === "notifications")
|
||||||
|
Notifs.markAllRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset(): void {
|
function reset(): void {
|
||||||
notifications.reset();
|
notifications.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minutes precision: the header only shows a date, so this exists purely to
|
|
||||||
// roll it over at midnight without a per-second wake.
|
|
||||||
SystemClock {
|
SystemClock {
|
||||||
id: clock
|
id: clock
|
||||||
precision: SystemClock.Minutes
|
precision: SystemClock.Minutes
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: content
|
id: frame
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.margins: Theme.popoverPadding
|
anchors.margins: Theme.popoverPadding
|
||||||
spacing: Theme.sectionSpacing
|
spacing: 0
|
||||||
|
|
||||||
// ── Header ──────────────────────────────────────────────────────────
|
|
||||||
Item {
|
Item {
|
||||||
|
id: header
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 34
|
height: 42
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -57,7 +53,6 @@ Item {
|
|||||||
color: Theme.fg
|
color: Theme.fg
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
font.family: Theme.fontFamily
|
font.family: Theme.fontFamily
|
||||||
// The day number changes in place at midnight.
|
|
||||||
font.features: Theme.tabularFigures
|
font.features: Theme.tabularFigures
|
||||||
font.pixelSize: Theme.fontSizeTitle
|
font.pixelSize: Theme.fontSizeTitle
|
||||||
font.weight: Font.DemiBold
|
font.weight: Font.DemiBold
|
||||||
@@ -69,153 +64,210 @@ Item {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: 6
|
spacing: 6
|
||||||
|
|
||||||
// Labelled rather than icon-only: an unexplained crossed-out
|
HeaderPill {
|
||||||
// bell is exactly the kind of thing a normal person has to
|
visible: ShellState.dateMenuPage === "notifications"
|
||||||
// click to find out about.
|
text: "← Back to agenda"
|
||||||
Rectangle {
|
onClicked: ShellState.dateMenuPage = "agenda"
|
||||||
id: dndPill
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: dndRow.implicitWidth + 20
|
|
||||||
height: 28
|
|
||||||
radius: Theme.pillRadius
|
|
||||||
border.width: 0
|
|
||||||
|
|
||||||
color: {
|
|
||||||
if (Notifs.doNotDisturb)
|
|
||||||
return dndMouse.containsMouse ? Theme.alpha(Theme.accent, 0.42) : Theme.alpha(Theme.accent, 0.28);
|
|
||||||
return dndMouse.containsMouse ? Theme.alpha(Theme.fg, 0.16) : Theme.alpha(Theme.fg, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: dndMouse.containsMouse ? Theme.durFast : Theme.durNormal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: dndRow
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
ThemedIcon {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
size: 14
|
|
||||||
icon: "notifications-disabled-symbolic"
|
|
||||||
iconFallback: "user-invisible-symbolic"
|
|
||||||
tint: Notifs.doNotDisturb ? Theme.accent : Theme.fgDim
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: "Do Not Disturb"
|
|
||||||
color: Notifs.doNotDisturb ? Theme.accent : Theme.fg
|
|
||||||
font.family: Theme.fontFamily
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: dndMouse
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: Notifs.doNotDisturb = !Notifs.doNotDisturb
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
HeaderPill {
|
||||||
id: clearPill
|
text: "Do Not Disturb"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
active: Notifs.doNotDisturb
|
||||||
width: clearLabel.implicitWidth + 20
|
icon: "notifications-disabled-symbolic"
|
||||||
height: 28
|
onClicked: Notifs.doNotDisturb = !Notifs.doNotDisturb
|
||||||
radius: Theme.pillRadius
|
}
|
||||||
border.width: 0
|
|
||||||
// Hidden rather than disabled: there is nothing ambiguous
|
|
||||||
// about an empty tray, so the control has no job.
|
|
||||||
visible: Notifs.hasNotifications
|
|
||||||
color: clearMouse.containsMouse ? Theme.alpha(Theme.fg, 0.16) : Theme.alpha(Theme.fg, 0.08)
|
|
||||||
|
|
||||||
Behavior on color {
|
HeaderPill {
|
||||||
ColorAnimation {
|
visible: ShellState.dateMenuPage === "agenda"
|
||||||
duration: clearMouse.containsMouse ? Theme.durFast : Theme.durNormal
|
text: "Open Calendar ↗"
|
||||||
}
|
onClicked: CalendarAgenda.openCalendar(CalendarAgenda.selectedDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
HeaderPill {
|
||||||
id: clearLabel
|
visible: ShellState.dateMenuPage === "notifications" && Notifs.hasNotifications
|
||||||
anchors.centerIn: parent
|
text: "Clear all"
|
||||||
text: "Clear all"
|
onClicked: Notifs.dismissAll()
|
||||||
color: Theme.fg
|
|
||||||
font.family: Theme.fontFamily
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: clearMouse
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: Notifs.dismissAll()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Notifications ───────────────────────────────────────────────────
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 1
|
height: 1
|
||||||
border.width: 0
|
border.width: 0
|
||||||
color: Theme.alpha(Theme.fg, 0.1)
|
color: Theme.alpha(Theme.fg, 0.10)
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaCard {
|
Row {
|
||||||
id: media
|
id: body
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
height: Math.min(620, Math.max(438, leftContent.implicitHeight + 28, rightContentHeight + 28))
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: leftColumn
|
||||||
|
width: 310
|
||||||
|
height: body.height
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: leftContent
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.margins: 14
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
CalendarGrid {
|
||||||
|
id: calendar
|
||||||
|
width: parent.width
|
||||||
|
selectedDate: CalendarAgenda.selectedDate
|
||||||
|
onDateActivated: date => CalendarAgenda.selectDate(date)
|
||||||
|
onVisibleMonthChanged: (year, month) => CalendarAgenda.setVisibleMonth(year, month)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
border.width: 0
|
||||||
|
visible: media.visible || Weather.available
|
||||||
|
color: Theme.alpha(Theme.fg, 0.08)
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaCard {
|
||||||
|
id: media
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
WeatherCard {
|
||||||
|
width: parent.width
|
||||||
|
visible: Weather.available
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 1
|
||||||
|
height: body.height
|
||||||
|
border.width: 0
|
||||||
|
color: Theme.alpha(Theme.fg, 0.10)
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: rightColumn
|
||||||
|
width: body.width - leftColumn.width - 1
|
||||||
|
height: body.height
|
||||||
|
|
||||||
|
readonly property real availableWidth: width - 28
|
||||||
|
|
||||||
|
AgendaPane {
|
||||||
|
id: agenda
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.margins: 14
|
||||||
|
visible: ShellState.dateMenuPage === "agenda"
|
||||||
|
onRequestNotifications: {
|
||||||
|
ShellState.dateMenuPage = "notifications";
|
||||||
|
Notifs.markAllRead();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: notificationPane
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.margins: 14
|
||||||
|
spacing: 10
|
||||||
|
visible: ShellState.dateMenuPage === "notifications"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: 28
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: "Notifications"
|
||||||
|
color: Theme.fg
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: Notifs.history.length + (Notifs.history.length === 1 ? " item" : " items")
|
||||||
|
color: Theme.fgDim
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.features: Theme.tabularFigures
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationList {
|
||||||
|
id: notifications
|
||||||
|
width: parent.width
|
||||||
|
maxHeight: body.height - 76
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property real rightContentHeight: ShellState.dateMenuPage === "notifications"
|
||||||
|
? notificationPane.implicitHeight
|
||||||
|
: agenda.implicitHeight
|
||||||
|
|
||||||
|
component HeaderPill: Rectangle {
|
||||||
|
id: pill
|
||||||
|
|
||||||
|
required property string text
|
||||||
|
property string icon: ""
|
||||||
|
property bool active: false
|
||||||
|
signal clicked
|
||||||
|
|
||||||
|
width: pillRow.implicitWidth + 20
|
||||||
|
height: 28
|
||||||
|
radius: Theme.pillRadius
|
||||||
|
border.width: 0
|
||||||
|
color: {
|
||||||
|
if (pill.active)
|
||||||
|
return pillMouse.containsMouse ? Theme.alpha(Theme.accent, 0.38) : Theme.alpha(Theme.accent, 0.24);
|
||||||
|
return pillMouse.containsMouse ? Theme.alpha(Theme.fg, 0.15) : Theme.alpha(Theme.fg, 0.07);
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Behavior on color { ColorAnimation { duration: Theme.durFast } }
|
||||||
width: parent.width
|
|
||||||
height: 1
|
Row {
|
||||||
border.width: 0
|
id: pillRow
|
||||||
visible: media.visible
|
anchors.centerIn: parent
|
||||||
color: Theme.alpha(Theme.fg, 0.1)
|
spacing: 6
|
||||||
|
|
||||||
|
ThemedIcon {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: pill.icon !== ""
|
||||||
|
size: visible ? 14 : 0
|
||||||
|
icon: pill.icon
|
||||||
|
tint: pill.active ? Theme.accent : Theme.fgDim
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: pill.text
|
||||||
|
color: pill.active ? Theme.accent : Theme.fg
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationList {
|
MouseArea {
|
||||||
id: notifications
|
id: pillMouse
|
||||||
width: parent.width
|
anchors.fill: parent
|
||||||
}
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
// ── Calendar ────────────────────────────────────────────────────────
|
onClicked: pill.clicked()
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
border.width: 0
|
|
||||||
color: Theme.alpha(Theme.fg, 0.1)
|
|
||||||
}
|
|
||||||
|
|
||||||
CalendarGrid {
|
|
||||||
id: calendar
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Weather ─────────────────────────────────────────────────────────
|
|
||||||
// Separator and card share one condition so the panel does not end on a
|
|
||||||
// rule with nothing under it.
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
border.width: 0
|
|
||||||
visible: Weather.available
|
|
||||||
color: Theme.alpha(Theme.fg, 0.1)
|
|
||||||
}
|
|
||||||
|
|
||||||
WeatherCard {
|
|
||||||
width: parent.width
|
|
||||||
visible: Weather.available
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,15 @@ import qs.widgets
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
readonly property bool compact: width < 360
|
||||||
|
|
||||||
readonly property MprisPlayer player: {
|
readonly property MprisPlayer player: {
|
||||||
const players = Mpris.players ? Mpris.players.values : [];
|
const players = Mpris.players ? Mpris.players.values : [];
|
||||||
return players.find(p => p.isPlaying) ?? players.find(p => p.trackTitle) ?? null;
|
return players.find(p => p.isPlaying) ?? players.find(p => p.trackTitle) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
visible: root.player !== null
|
visible: root.player !== null
|
||||||
implicitHeight: visible ? 76 : 0
|
implicitHeight: visible ? (compact ? 64 : 76) : 0
|
||||||
radius: Theme.cardRadius
|
radius: Theme.cardRadius
|
||||||
border.width: 0
|
border.width: 0
|
||||||
color: Theme.alpha(Theme.fg, 0.06)
|
color: Theme.alpha(Theme.fg, 0.06)
|
||||||
@@ -24,11 +26,11 @@ Rectangle {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: artwork
|
id: artwork
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 10
|
anchors.leftMargin: root.compact ? 8 : 10
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: 56
|
width: root.compact ? 46 : 56
|
||||||
height: 56
|
height: width
|
||||||
radius: 10
|
radius: root.compact ? 9 : 10
|
||||||
border.width: 0
|
border.width: 0
|
||||||
color: Theme.alpha(Theme.accentAlt, 0.16)
|
color: Theme.alpha(Theme.accentAlt, 0.16)
|
||||||
clip: true
|
clip: true
|
||||||
@@ -54,9 +56,9 @@ Rectangle {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.left: artwork.right
|
anchors.left: artwork.right
|
||||||
anchors.leftMargin: 12
|
anchors.leftMargin: root.compact ? 9 : 12
|
||||||
anchors.right: controls.left
|
anchors.right: controls.left
|
||||||
anchors.rightMargin: 10
|
anchors.rightMargin: root.compact ? 6 : 10
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: 3
|
spacing: 3
|
||||||
|
|
||||||
@@ -83,7 +85,7 @@ Rectangle {
|
|||||||
Row {
|
Row {
|
||||||
id: controls
|
id: controls
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 10
|
anchors.rightMargin: root.compact ? 7 : 10
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: 2
|
spacing: 2
|
||||||
|
|
||||||
@@ -114,7 +116,7 @@ Rectangle {
|
|||||||
property bool emphasized: false
|
property bool emphasized: false
|
||||||
signal clicked
|
signal clicked
|
||||||
|
|
||||||
width: emphasized ? 34 : 30
|
width: root.compact ? (emphasized ? 30 : 27) : (emphasized ? 34 : 30)
|
||||||
height: width
|
height: width
|
||||||
radius: width / 2
|
radius: width / 2
|
||||||
border.width: 0
|
border.width: 0
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.config
|
||||||
|
import qs.services
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
signal activated
|
||||||
|
|
||||||
|
visible: Notifs.hasNotifications
|
||||||
|
implicitHeight: visible ? 62 : 0
|
||||||
|
radius: Theme.cardRadius
|
||||||
|
border.width: 0
|
||||||
|
color: summaryMouse.containsMouse ? Theme.alpha(Theme.fg, 0.10) : Theme.alpha(Theme.fg, 0.055)
|
||||||
|
|
||||||
|
Behavior on color { ColorAnimation { duration: Theme.durFast } }
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 14
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 3
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: Notifs.history.length + (Notifs.history.length === 1 ? " notification" : " notifications")
|
||||||
|
color: Theme.fg
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSize
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Grouped quietly until you open them"
|
||||||
|
color: Theme.fgDim
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 14
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: "View →"
|
||||||
|
color: Theme.fgDim
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: summaryMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.activated()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
module qs.modules.datemenu
|
module qs.modules.datemenu
|
||||||
|
AgendaEvent 1.0 AgendaEvent.qml
|
||||||
|
AgendaPane 1.0 AgendaPane.qml
|
||||||
CalendarGrid 1.0 CalendarGrid.qml
|
CalendarGrid 1.0 CalendarGrid.qml
|
||||||
DateMenu 1.0 DateMenu.qml
|
DateMenu 1.0 DateMenu.qml
|
||||||
DateMenuPanel 1.0 DateMenuPanel.qml
|
DateMenuPanel 1.0 DateMenuPanel.qml
|
||||||
@@ -6,4 +8,5 @@ MediaCard 1.0 MediaCard.qml
|
|||||||
MonthArrow 1.0 MonthArrow.qml
|
MonthArrow 1.0 MonthArrow.qml
|
||||||
NotificationGroup 1.0 NotificationGroup.qml
|
NotificationGroup 1.0 NotificationGroup.qml
|
||||||
NotificationList 1.0 NotificationList.qml
|
NotificationList 1.0 NotificationList.qml
|
||||||
|
NotificationSummary 1.0 NotificationSummary.qml
|
||||||
WeatherCard 1.0 WeatherCard.qml
|
WeatherCard 1.0 WeatherCard.qml
|
||||||
|
|||||||
@@ -526,7 +526,7 @@ git commit -m "Route the interactive Panama date menu"
|
|||||||
- Consumes: `ShellState.dateMenuPage`, `Notifs.hasNotifications`, and existing `NotificationList`.
|
- Consumes: `ShellState.dateMenuPage`, `Notifs.hasNotifications`, and existing `NotificationList`.
|
||||||
- Produces: a 760-pixel two-column date menu with left width 310 and independently bounded right content.
|
- Produces: a 760-pixel two-column date menu with left width 310 and independently bounded right content.
|
||||||
|
|
||||||
- [ ] **Step 1: Add failing structural and live-surface assertions**
|
- [x] **Step 1: Add failing structural and live-surface assertions**
|
||||||
|
|
||||||
Extend the contract to assert the loaded layer and approved components:
|
Extend the contract to assert the loaded layer and approved components:
|
||||||
|
|
||||||
@@ -542,7 +542,7 @@ done
|
|||||||
|
|
||||||
Run the contract and expect failure on width or missing components.
|
Run the contract and expect failure on width or missing components.
|
||||||
|
|
||||||
- [ ] **Step 2: Implement focused event and summary components**
|
- [x] **Step 2: Implement focused event and summary components**
|
||||||
|
|
||||||
`AgendaEvent.qml` accepts `property var event`, `property bool past`, and signals `activated` and `joinRequested`. It renders a 48-pixel tabular time column, 3-pixel source-color rail, elided summary, source/location metadata, and optional Join pill. All-day rows replace time with `All day`.
|
`AgendaEvent.qml` accepts `property var event`, `property bool past`, and signals `activated` and `joinRequested`. It renders a 48-pixel tabular time column, 3-pixel source-color rail, elided summary, source/location metadata, and optional Join pill. All-day rows replace time with `All day`.
|
||||||
|
|
||||||
@@ -557,7 +557,7 @@ Run the contract and expect failure on width or missing components.
|
|||||||
|
|
||||||
The Up Next card is omitted for selected dates other than today and when no future timed event exists. Use `ScrollColumn` with a maximum content height derived from the panel's available height. Event clicks call `CalendarAgenda.openEvent(event)`; Join calls `CalendarAgenda.join(event)`.
|
The Up Next card is omitted for selected dates other than today and when no future timed event exists. Use `ScrollColumn` with a maximum content height derived from the panel's available height. Event clicks call `CalendarAgenda.openEvent(event)`; Join calls `CalendarAgenda.join(event)`.
|
||||||
|
|
||||||
- [ ] **Step 3: Recompose DateMenuPanel as Daybook**
|
- [x] **Step 3: Recompose DateMenuPanel as Daybook**
|
||||||
|
|
||||||
Keep the existing header styling and Prism container, but replace the single `Column` with this concrete structure:
|
Keep the existing header styling and Prism container, but replace the single `Column` with this concrete structure:
|
||||||
|
|
||||||
@@ -601,7 +601,7 @@ When Agenda's notification summary activates, set `ShellState.dateMenuPage = "no
|
|||||||
|
|
||||||
Resize MediaCard typography/controls responsively for the 310-pixel column; do not duplicate its playback logic.
|
Resize MediaCard typography/controls responsively for the 310-pixel column; do not duplicate its playback logic.
|
||||||
|
|
||||||
- [ ] **Step 4: Update DateMenu lifecycle and bounds**
|
- [x] **Step 4: Update DateMenu lifecycle and bounds**
|
||||||
|
|
||||||
Set `DateMenu.implicitWidth: 760`. Bound height to the active screen's available geometry, with the panel content choosing a maximum right-column scroll height instead of overflowing the monitor.
|
Set `DateMenu.implicitWidth: 760`. Bound height to the active screen's available geometry, with the panel content choosing a maximum right-column scroll height instead of overflowing the monitor.
|
||||||
|
|
||||||
@@ -616,7 +616,7 @@ panel.prepare(ShellState.dateMenuPage);
|
|||||||
|
|
||||||
Add a connection that marks notifications read when an already-open panel switches into Notifications. Opening from the clock must not mark them read.
|
Add a connection that marks notifications read when an already-open panel switches into Notifications. Opening from the clock must not mark them read.
|
||||||
|
|
||||||
- [ ] **Step 5: Verify fixture visuals on the real layer surface**
|
- [x] **Step 5: Verify fixture visuals on the real layer surface**
|
||||||
|
|
||||||
Run:
|
Run:
|
||||||
|
|
||||||
@@ -637,7 +637,7 @@ Capture a temporary full-screen screenshot with `grim`, inspect the Daybook at o
|
|||||||
|
|
||||||
Close the panel and reset fixture mode after inspection. Do not add screenshots to git.
|
Close the panel and reset fixture mode after inspection. Do not add screenshots to git.
|
||||||
|
|
||||||
- [ ] **Step 6: Commit the Daybook**
|
- [x] **Step 6: Commit the Daybook**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add config/dot/quickshell/modules/datemenu \
|
git add config/dot/quickshell/modules/datemenu \
|
||||||
|
|||||||
@@ -40,6 +40,15 @@ jq -e '.open == true and .page == "notifications"' \
|
|||||||
<<<"$(qs ipc call calendar-agenda status)" >/dev/null \
|
<<<"$(qs ipc call calendar-agenda status)" >/dev/null \
|
||||||
|| fail 'notification open did not route to Notifications'
|
|| fail 'notification open did not route to Notifications'
|
||||||
|
|
||||||
|
hyprctl layers | rg -q 'namespace: qs-popover-datemenu' \
|
||||||
|
|| fail 'Daybook layer did not map'
|
||||||
|
rg -q 'implicitWidth: 760' config/dot/quickshell/modules/datemenu/DateMenu.qml \
|
||||||
|
|| fail 'Daybook is not the approved width'
|
||||||
|
for component in AgendaPane AgendaEvent NotificationSummary; do
|
||||||
|
rg -q "${component} 1.0 ${component}.qml" config/dot/quickshell/modules/datemenu/qmldir \
|
||||||
|
|| fail "$component is not registered"
|
||||||
|
done
|
||||||
|
|
||||||
qs ipc call calendar-agenda reset >/dev/null
|
qs ipc call calendar-agenda reset >/dev/null
|
||||||
jq -e '.fixture == false' <<<"$(qs ipc call calendar-agenda status)" >/dev/null \
|
jq -e '.fixture == false' <<<"$(qs ipc call calendar-agenda status)" >/dev/null \
|
||||||
|| fail 'fixture reset did not restore live mode'
|
|| fail 'fixture reset did not restore live mode'
|
||||||
|
|||||||
Reference in New Issue
Block a user