dashboard/calendar: add scroll

This commit is contained in:
2 * r + 2 * t 2025-09-03 22:28:18 +10:00
parent 02c59fbd38
commit 958d07a3ff

View file

@ -2,13 +2,14 @@ pragma ComponentBehavior: Bound
import qs.components import qs.components
import qs.components.effects import qs.components.effects
import qs.components.controls
import qs.services import qs.services
import qs.config import qs.config
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
Column { CustomMouseArea {
id: root id: root
required property var state required property var state
@ -18,221 +19,233 @@ Column {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
padding: Appearance.padding.large implicitHeight: inner.implicitHeight + inner.anchors.margins * 2
spacing: Appearance.spacing.small
RowLayout { acceptedButtons: Qt.MiddleButton
id: monthNavigationRow onClicked: root.state.currentDate = new Date()
anchors.left: parent.left function onWheel(event: WheelEvent): void {
anchors.right: parent.right if (event.angleDelta.y > 0)
anchors.margins: parent.padding root.state.currentDate = new Date(root.currYear, root.currMonth - 1, 1);
else if (event.angleDelta.y < 0)
root.state.currentDate = new Date(root.currYear, root.currMonth + 1, 1);
}
ColumnLayout {
id: inner
anchors.fill: parent
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.small spacing: Appearance.spacing.small
Item { RowLayout {
implicitWidth: implicitHeight id: monthNavigationRow
implicitHeight: prevMonthText.implicitHeight + Appearance.padding.small * 2
StateLayer { Layout.fillWidth: true
id: prevMonthStateLayer spacing: Appearance.spacing.small
radius: Appearance.rounding.full Item {
implicitWidth: implicitHeight
implicitHeight: prevMonthText.implicitHeight + Appearance.padding.small * 2
function onClicked(): void { StateLayer {
root.state.currentDate = new Date(root.currYear, root.currMonth - 1, 1); id: prevMonthStateLayer
radius: Appearance.rounding.full
function onClicked(): void {
root.state.currentDate = new Date(root.currYear, root.currMonth - 1, 1);
}
}
MaterialIcon {
id: prevMonthText
anchors.centerIn: parent
text: "chevron_left"
color: Colours.palette.m3tertiary
font.pointSize: Appearance.font.size.normal
font.weight: 700
} }
} }
MaterialIcon { Item {
id: prevMonthText Layout.fillWidth: true
anchors.centerIn: parent implicitWidth: monthYearDisplay.implicitWidth + Appearance.padding.small * 2
text: "chevron_left" implicitHeight: monthYearDisplay.implicitHeight + Appearance.padding.small * 2
color: Colours.palette.m3tertiary
font.pointSize: Appearance.font.size.normal StateLayer {
font.weight: 700 anchors.fill: monthYearDisplay
anchors.margins: -Appearance.padding.small
anchors.leftMargin: -Appearance.padding.normal
anchors.rightMargin: -Appearance.padding.normal
radius: Appearance.rounding.full
disabled: {
const now = new Date();
return root.currMonth === now.getMonth() && root.currYear === now.getFullYear();
}
function onClicked(): void {
root.state.currentDate = new Date();
}
}
StyledText {
id: monthYearDisplay
anchors.centerIn: parent
text: grid.title
color: Colours.palette.m3primary
font.pointSize: Appearance.font.size.normal
font.weight: 500
font.capitalization: Font.Capitalize
}
}
Item {
implicitWidth: implicitHeight
implicitHeight: nextMonthText.implicitHeight + Appearance.padding.small * 2
StateLayer {
id: nextMonthStateLayer
radius: Appearance.rounding.full
function onClicked(): void {
root.state.currentDate = new Date(root.currYear, root.currMonth + 1, 1);
}
}
MaterialIcon {
id: nextMonthText
anchors.centerIn: parent
text: "chevron_right"
color: Colours.palette.m3tertiary
font.pointSize: Appearance.font.size.normal
font.weight: 700
}
}
}
DayOfWeekRow {
id: daysRow
Layout.fillWidth: true
locale: grid.locale
delegate: StyledText {
required property var model
horizontalAlignment: Text.AlignHCenter
text: model.shortName
font.weight: 500
color: (model.day === 0 || model.day === 6) ? Colours.palette.m3secondary : Colours.palette.m3onSurfaceVariant
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: grid.implicitHeight
implicitWidth: monthYearDisplay.implicitWidth + Appearance.padding.small * 2 MonthGrid {
implicitHeight: monthYearDisplay.implicitHeight + Appearance.padding.small * 2 id: grid
StateLayer { month: root.currMonth
anchors.fill: monthYearDisplay year: root.currYear
anchors.margins: -Appearance.padding.small
anchors.leftMargin: -Appearance.padding.normal
anchors.rightMargin: -Appearance.padding.normal
radius: Appearance.rounding.full anchors.fill: parent
disabled: root.state.currentDate.toDateString() == new Date().toDateString()
function onClicked(): void { spacing: 3
root.state.currentDate = new Date(); locale: Qt.locale()
}
}
StyledText { delegate: Item {
id: monthYearDisplay id: dayItem
anchors.centerIn: parent required property var model
text: grid.title
color: Colours.palette.m3primary
font.pointSize: Appearance.font.size.normal
font.weight: 500
font.capitalization: Font.Capitalize
}
}
Item { implicitWidth: implicitHeight
implicitWidth: implicitHeight implicitHeight: text.implicitHeight + Appearance.padding.small * 2
implicitHeight: nextMonthText.implicitHeight + Appearance.padding.small * 2
StateLayer { StyledText {
id: nextMonthStateLayer id: text
radius: Appearance.rounding.full anchors.centerIn: parent
function onClicked(): void { horizontalAlignment: Text.AlignHCenter
root.state.currentDate = new Date(root.currYear, root.currMonth + 1, 1); text: grid.locale.toString(dayItem.model.day)
} color: {
} const dayOfWeek = dayItem.model.date.getUTCDay();
if (dayOfWeek === 0 || dayOfWeek === 6)
return Colours.palette.m3secondary;
MaterialIcon { return Colours.palette.m3onSurfaceVariant;
id: nextMonthText }
opacity: dayItem.model.today || dayItem.model.month === grid.month ? 1 : 0.4
anchors.centerIn: parent font.pointSize: Appearance.font.size.normal
text: "chevron_right" font.weight: 500
color: Colours.palette.m3tertiary
font.pointSize: Appearance.font.size.normal
font.weight: 700
}
}
}
DayOfWeekRow {
id: daysRow
locale: grid.locale
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: parent.padding
delegate: StyledText {
required property var model
horizontalAlignment: Text.AlignHCenter
text: model.shortName
font.weight: 500
color: (model.day === 0 || model.day === 6) ? Colours.palette.m3secondary : Colours.palette.m3onSurfaceVariant
}
}
Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: parent.padding
implicitHeight: grid.implicitHeight
MonthGrid {
id: grid
month: root.currMonth
year: root.currYear
anchors.fill: parent
spacing: 3
locale: Qt.locale()
delegate: Item {
id: dayItem
required property var model
implicitWidth: implicitHeight
implicitHeight: text.implicitHeight + Appearance.padding.small * 2
StyledText {
id: text
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: grid.locale.toString(dayItem.model.day)
color: {
const dayOfWeek = dayItem.model.date.getUTCDay();
if (dayOfWeek === 0 || dayOfWeek === 6)
return Colours.palette.m3secondary;
return Colours.palette.m3onSurfaceVariant;
} }
opacity: dayItem.model.today || dayItem.model.month === grid.month ? 1 : 0.4
font.pointSize: Appearance.font.size.normal
font.weight: 500
}
}
}
StyledRect {
id: todayIndicator
readonly property Item todayItem: grid.contentItem.children.find(c => c.model.today) ?? null
property Item today
onTodayItemChanged: {
if (todayItem)
today = todayItem;
}
x: today ? today.x + (today.width - implicitWidth) / 2 : 0
y: today?.y ?? 0
implicitWidth: today?.implicitWidth ?? 0
implicitHeight: today?.implicitHeight ?? 0
clip: true
radius: Appearance.rounding.full
color: Colours.palette.m3primary
opacity: todayItem ? 1 : 0
scale: todayItem ? 1 : 0.7
Colouriser {
x: -todayIndicator.x
y: -todayIndicator.y
implicitWidth: grid.width
implicitHeight: grid.height
source: grid
sourceColor: Colours.palette.m3onSurface
colorizationColor: Colours.palette.m3onPrimary
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {}
}
Behavior on x {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
} }
} }
Behavior on y { StyledRect {
Anim { id: todayIndicator
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial readonly property Item todayItem: grid.contentItem.children.find(c => c.model.today) ?? null
property Item today
onTodayItemChanged: {
if (todayItem)
today = todayItem;
}
x: today ? today.x + (today.width - implicitWidth) / 2 : 0
y: today?.y ?? 0
implicitWidth: today?.implicitWidth ?? 0
implicitHeight: today?.implicitHeight ?? 0
clip: true
radius: Appearance.rounding.full
color: Colours.palette.m3primary
opacity: todayItem ? 1 : 0
scale: todayItem ? 1 : 0.7
Colouriser {
x: -todayIndicator.x
y: -todayIndicator.y
implicitWidth: grid.width
implicitHeight: grid.height
source: grid
sourceColor: Colours.palette.m3onSurface
colorizationColor: Colours.palette.m3onPrimary
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {}
}
Behavior on x {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on y {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
} }
} }
} }