Build the Prism calendar Daybook
This commit is contained in:
@@ -1,9 +1,5 @@
|
||||
// The contents of the date menu, in GNOME's order: date header, notifications,
|
||||
// calendar, weather.
|
||||
//
|
||||
// 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.
|
||||
// The approved A · Daybook: month context on the left, the selected day's
|
||||
// schedule or grouped notification history on the right.
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
@@ -14,38 +10,38 @@ import qs.widgets
|
||||
Item {
|
||||
id: root
|
||||
|
||||
implicitWidth: 480
|
||||
implicitHeight: content.implicitHeight + Theme.popoverPadding * 2
|
||||
implicitWidth: 760
|
||||
implicitHeight: frame.implicitHeight + Theme.popoverPadding * 2
|
||||
|
||||
// Called by the window when it opens and closes, so the panel is always in
|
||||
// a known state rather than wherever the last visit left it.
|
||||
function prepare(): void {
|
||||
function prepare(page: string): void {
|
||||
calendar.showToday();
|
||||
CalendarAgenda.selectDate(clock.date);
|
||||
CalendarAgenda.setVisibleMonth(clock.date.getFullYear(), clock.date.getMonth());
|
||||
if (page === "notifications")
|
||||
Notifs.markAllRead();
|
||||
}
|
||||
|
||||
function reset(): void {
|
||||
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 {
|
||||
id: clock
|
||||
precision: SystemClock.Minutes
|
||||
}
|
||||
|
||||
Column {
|
||||
id: content
|
||||
id: frame
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Theme.popoverPadding
|
||||
spacing: Theme.sectionSpacing
|
||||
spacing: 0
|
||||
|
||||
// ── Header ──────────────────────────────────────────────────────────
|
||||
Item {
|
||||
id: header
|
||||
width: parent.width
|
||||
height: 34
|
||||
height: 42
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
@@ -57,7 +53,6 @@ Item {
|
||||
color: Theme.fg
|
||||
elide: Text.ElideRight
|
||||
font.family: Theme.fontFamily
|
||||
// The day number changes in place at midnight.
|
||||
font.features: Theme.tabularFigures
|
||||
font.pixelSize: Theme.fontSizeTitle
|
||||
font.weight: Font.DemiBold
|
||||
@@ -69,153 +64,210 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 6
|
||||
|
||||
// Labelled rather than icon-only: an unexplained crossed-out
|
||||
// bell is exactly the kind of thing a normal person has to
|
||||
// click to find out about.
|
||||
Rectangle {
|
||||
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
|
||||
}
|
||||
HeaderPill {
|
||||
visible: ShellState.dateMenuPage === "notifications"
|
||||
text: "← Back to agenda"
|
||||
onClicked: ShellState.dateMenuPage = "agenda"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: clearPill
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: clearLabel.implicitWidth + 20
|
||||
height: 28
|
||||
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)
|
||||
HeaderPill {
|
||||
text: "Do Not Disturb"
|
||||
active: Notifs.doNotDisturb
|
||||
icon: "notifications-disabled-symbolic"
|
||||
onClicked: Notifs.doNotDisturb = !Notifs.doNotDisturb
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: clearMouse.containsMouse ? Theme.durFast : Theme.durNormal
|
||||
}
|
||||
}
|
||||
HeaderPill {
|
||||
visible: ShellState.dateMenuPage === "agenda"
|
||||
text: "Open Calendar ↗"
|
||||
onClicked: CalendarAgenda.openCalendar(CalendarAgenda.selectedDate)
|
||||
}
|
||||
|
||||
Text {
|
||||
id: clearLabel
|
||||
anchors.centerIn: parent
|
||||
text: "Clear all"
|
||||
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()
|
||||
}
|
||||
HeaderPill {
|
||||
visible: ShellState.dateMenuPage === "notifications" && Notifs.hasNotifications
|
||||
text: "Clear all"
|
||||
onClicked: Notifs.dismissAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Notifications ───────────────────────────────────────────────────
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
border.width: 0
|
||||
color: Theme.alpha(Theme.fg, 0.1)
|
||||
color: Theme.alpha(Theme.fg, 0.10)
|
||||
}
|
||||
|
||||
MediaCard {
|
||||
id: media
|
||||
Row {
|
||||
id: body
|
||||
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 {
|
||||
width: parent.width
|
||||
height: 1
|
||||
border.width: 0
|
||||
visible: media.visible
|
||||
color: Theme.alpha(Theme.fg, 0.1)
|
||||
Behavior on color { ColorAnimation { duration: Theme.durFast } }
|
||||
|
||||
Row {
|
||||
id: pillRow
|
||||
anchors.centerIn: parent
|
||||
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 {
|
||||
id: notifications
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
// ── Calendar ────────────────────────────────────────────────────────
|
||||
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
|
||||
MouseArea {
|
||||
id: pillMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: pill.clicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user