140 lines
4.9 KiB
QML
140 lines
4.9 KiB
QML
import QtQuick
|
||
import "../components"
|
||
|
||
// Calendar grid – a plain Item so it can live inside an expanding section border.
|
||
Item {
|
||
id: root
|
||
|
||
property var now: new Date()
|
||
|
||
property int viewYear: now.getFullYear()
|
||
property int viewMonth: now.getMonth() // 0-based
|
||
|
||
readonly property var monthNames: [
|
||
"January","February","March","April","May","June",
|
||
"July","August","September","October","November","December"
|
||
]
|
||
readonly property int daysInMonth: new Date(viewYear, viewMonth + 1, 0).getDate()
|
||
// First weekday of month: Mon=0 … Sun=6
|
||
readonly property int startOffset: (new Date(viewYear, viewMonth, 1).getDay() + 6) % 7
|
||
readonly property int totalCells: Math.ceil((startOffset + daysInMonth) / 7) * 7
|
||
|
||
readonly property int todayYear: now.getFullYear()
|
||
readonly property int todayMonth: now.getMonth()
|
||
readonly property int todayDay: now.getDate()
|
||
|
||
// All widths derived from this constant — never from parent.width —
|
||
// to avoid Column polish loops when the popup is wider than the grid.
|
||
readonly property int cellW: 28
|
||
readonly property int cellGap: 2
|
||
readonly property int gridW: 7 * cellW + 6 * cellGap // 208
|
||
|
||
implicitWidth: gridW + 24
|
||
implicitHeight: calLayout.implicitHeight + 16
|
||
|
||
Column {
|
||
id: calLayout
|
||
// Centre the fixed-width grid inside the (potentially wider) popup.
|
||
// No left+right anchors → implicitWidth stays at gridW, no resize loop.
|
||
anchors { top: parent.top; topMargin: 8; horizontalCenter: parent.horizontalCenter }
|
||
spacing: 6
|
||
|
||
// Month navigation — all child widths are explicit constants
|
||
Row {
|
||
spacing: 0
|
||
|
||
Text {
|
||
text: "‹"
|
||
color: Theme.text
|
||
font.pixelSize: 16
|
||
width: 20
|
||
horizontalAlignment: Text.AlignHCenter
|
||
MouseArea {
|
||
anchors.fill: parent
|
||
cursorShape: Qt.PointingHandCursor
|
||
onClicked: {
|
||
if (root.viewMonth === 0) { root.viewMonth = 11; root.viewYear-- }
|
||
else root.viewMonth--
|
||
}
|
||
}
|
||
}
|
||
|
||
Text {
|
||
text: root.monthNames[root.viewMonth] + " " + root.viewYear
|
||
color: Theme.text
|
||
font.pixelSize: 13
|
||
font.bold: true
|
||
width: root.gridW - 40 // fixed constant, not parent.width
|
||
horizontalAlignment: Text.AlignHCenter
|
||
}
|
||
|
||
Text {
|
||
text: "›"
|
||
color: Theme.text
|
||
font.pixelSize: 16
|
||
width: 20
|
||
horizontalAlignment: Text.AlignHCenter
|
||
MouseArea {
|
||
anchors.fill: parent
|
||
cursorShape: Qt.PointingHandCursor
|
||
onClicked: {
|
||
if (root.viewMonth === 11) { root.viewMonth = 0; root.viewYear++ }
|
||
else root.viewMonth++
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Weekday headers
|
||
Row {
|
||
spacing: root.cellGap
|
||
Repeater {
|
||
model: ["Mo","Tu","We","Th","Fr","Sa","Su"]
|
||
Text {
|
||
text: modelData
|
||
color: Theme.textDim
|
||
font.pixelSize: 10
|
||
width: root.cellW
|
||
horizontalAlignment: Text.AlignHCenter
|
||
}
|
||
}
|
||
}
|
||
|
||
// Day grid
|
||
Grid {
|
||
columns: 7
|
||
spacing: root.cellGap
|
||
Repeater {
|
||
model: root.totalCells
|
||
delegate: Item {
|
||
width: root.cellW
|
||
height: 22
|
||
readonly property int dayNum: index - root.startOffset + 1
|
||
readonly property bool valid:
|
||
index >= root.startOffset &&
|
||
index < root.startOffset + root.daysInMonth
|
||
readonly property bool isToday:
|
||
valid &&
|
||
root.viewYear === root.todayYear &&
|
||
root.viewMonth === root.todayMonth &&
|
||
dayNum === root.todayDay
|
||
|
||
Rectangle {
|
||
anchors.fill: parent
|
||
radius: 3
|
||
color: isToday ? Theme.accent : "transparent"
|
||
visible: isToday
|
||
}
|
||
Text {
|
||
anchors.centerIn: parent
|
||
text: parent.valid ? String(parent.dayNum) : ""
|
||
color: parent.isToday ? Theme.text : Theme.textDim
|
||
font.pixelSize: 11
|
||
font.bold: parent.isToday
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|