diff --git a/Config/BarConfig.qml b/Config/BarConfig.qml index e9a74f4..407edaf 100644 --- a/Config/BarConfig.qml +++ b/Config/BarConfig.qml @@ -1,55 +1,56 @@ import Quickshell.Io JsonObject { - property Popouts popouts: Popouts {} + property Popouts popouts: Popouts {} - property list entries: [ - { - id: "workspaces", - enabled: true - }, - { - id: "audio", - enabled: true - }, - { - id: "resources", - enabled: true - }, - { - id: "updates", - enabled: true - }, - { - id: "spacer", - enabled: true - }, - { - id: "activeWindow", - enabled: true - }, - { - id: "spacer", - enabled: true - }, - { - id: "tray", - enabled: true - }, - { - id: "clock", - enabled: true - }, - { - id: "notifBell", - enabled: true - }, - ] + property list entries: [ + { + id: "workspaces", + enabled: true + }, + { + id: "audio", + enabled: true + }, + { + id: "resources", + enabled: true + }, + { + id: "updates", + enabled: true + }, + { + id: "spacer", + enabled: true + }, + { + id: "activeWindow", + enabled: true + }, + { + id: "spacer", + enabled: true + }, + { + id: "tray", + enabled: true + }, + { + id: "clock", + enabled: true + }, + { + id: "notifBell", + enabled: true + }, + ] - component Popouts: JsonObject { - property bool tray: true - property bool audio: true - property bool activeWindow: false - property bool resources: true - } + component Popouts: JsonObject { + property bool tray: true + property bool audio: true + property bool activeWindow: false + property bool resources: true + property bool clock: true + } } diff --git a/Helpers/Calendar.qml b/Helpers/Calendar.qml new file mode 100644 index 0000000..585d93a --- /dev/null +++ b/Helpers/Calendar.qml @@ -0,0 +1,99 @@ +pragma Singleton + +import Quickshell +import qs.Helpers + +Singleton { + id: root + + property int displayMonth: new Date().getMonth() + property int displayYear: new Date().getFullYear() + readonly property int weekStartDay: 1 // 0 = Sunday, 1 = Monday + + function getWeeksForMonth(month: int, year: int): var { + const firstDayOfMonth = new Date(year, month, 1); + const lastDayOfMonth = new Date(year, month + 1, 0); + + const days = []; + let currentDate = new Date(year, month, 1); + + // Back up to the start of the first week (Sunday or Monday based on locale) + const dayOfWeek = firstDayOfMonth.getDay(); + const daysToBackup = (dayOfWeek - root.weekStartDay + 7) % 7; + currentDate.setDate(currentDate.getDate() - daysToBackup); + + // Collect all days, including padding from previous/next month to complete the weeks + while (true) { + days.push({ + day: currentDate.getDate(), + month: currentDate.getMonth(), + year: currentDate.getFullYear(), + isCurrentMonth: currentDate.getMonth() === month, + isToday: isDateToday(currentDate) + }); + + currentDate.setDate(currentDate.getDate() + 1); + + // Stop after we've completed a full week following the last day of the month + if (currentDate > lastDayOfMonth && days.length % 7 === 0) { + break; + } + } + + return days; + } + + function getWeekNumbers(month: int, year: int): var { + const days = getWeeksForMonth(month, year); + const weekNumbers = []; + let lastWeekNumber = -1; + + for (let i = 0; i < days.length; i++) { + // Only add week numbers for days that belong to the current month + if (days[i].isCurrentMonth) { + const dayDate = new Date(days[i].year, days[i].month, days[i].day); + const weekNumber = getISOWeekNumber(dayDate); + + // Only push if this is a new week + if (weekNumber !== lastWeekNumber) { + weekNumbers.push(weekNumber); + lastWeekNumber = weekNumber; + } + } + } + + return weekNumbers; + } + + function getISOWeekNumber(date: var): int { + const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); + const dayNum = d.getUTCDay() || 7; + d.setUTCDate(d.getUTCDate() + 4 - dayNum); + const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + return Math.ceil((((d - yearStart) / 86400000) + 1) / 7); + } + + function isDateToday(date: var): bool { + const today = new Date(); + return date.getDate() === today.getDate() && + date.getMonth() === today.getMonth() && + date.getFullYear() === today.getFullYear(); + } + + function getWeekStartIndex(month: int, year: int): int { + const today = new Date(); + if (today.getMonth() !== month || today.getFullYear() !== year) { + return 0; + } + + const days = getWeeksForMonth(month, year); + for (let i = 0; i < days.length; i++) { + if (days[i].isToday) { + // Return the start index of the week containing today + return Math.floor(i / 7) * 7; + } + } + + return 0; + } +} diff --git a/Helpers/Year.qml b/Helpers/Year.qml new file mode 100644 index 0000000..03ad712 --- /dev/null +++ b/Helpers/Year.qml @@ -0,0 +1,420 @@ +import Quickshell.Io + +JsonObject { + property list week_0: [ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_1: [ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_2: [ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_3: [ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_4: [ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_5: [ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_6: [ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_7: [ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_8: [ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_9: [ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_10:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_11:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_12:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_13:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_14:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_15:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_16:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_17:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_18:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_19:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_20:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_21:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_22:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_23:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_24:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_25:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_26:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_27:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_28:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_29:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_30:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_31:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_32:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_33:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_34:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_35:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_36:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_37:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_38:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_39:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_40:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_41:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_42:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_43:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_44:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_45:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_46:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_47:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_48:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_49:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_50:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] + property list week_51:[ + 0, + 0, + 0, + 0, + 0, + 0, + ] +} diff --git a/Modules/Bar/BarLoader.qml b/Modules/Bar/BarLoader.qml index f893a3a..bff7bab 100644 --- a/Modules/Bar/BarLoader.qml +++ b/Modules/Bar/BarLoader.qml @@ -9,147 +9,153 @@ import qs.Helpers import qs.Daemons RowLayout { - id: root - anchors.fill: parent + id: root + anchors.fill: parent - readonly property int vPadding: 6 - required property Wrapper popouts - required property PanelWindow bar + readonly property int vPadding: 6 + required property Wrapper popouts + required property PanelWindow bar - function checkPopout(x: real): void { - const ch = childAt(x, height / 2) as WrappedLoader; + function checkPopout(x: real): void { + const ch = childAt(x, height / 2) as WrappedLoader; - if (!ch) { - popouts.hasCurrent = false; - return; - } + if (!ch) { + popouts.hasCurrent = false; + return; + } - const id = ch.id; - const top = ch.x; - const item = ch.item; - const itemWidth = item.implicitWidth; + const id = ch.id; + const top = ch.x; + const item = ch.item; + const itemWidth = item.implicitWidth; - if (id === "audio" && Config.barConfig.popouts.audio) { - popouts.currentName = "audio"; - popouts.currentCenter = Qt.binding( () => item.mapToItem(root, itemWidth / 2, 0 ).x ); - popouts.hasCurrent = true; - } else if ( id === "resources" && Config.barConfig.popouts.resources ) { - popouts.currentName = "resources"; - popouts.currentCenter = Qt.binding( () => item.mapToItem( root, itemWidth / 2, 0 ).x ); - popouts.hasCurrent = true; - } else if ( id === "tray" && Config.barConfig.popouts.tray ) { - const index = Math.floor((( x - top ) / item.implicitWidth ) * item.items.count ); - const trayItem = item.items.itemAt( index ); - if ( trayItem ) { - popouts.currentName = `traymenu${ index }`; - popouts.currentCenter = Qt.binding( () => trayItem.mapToItem( root, trayItem.implicitWidth / 2, 0 ).x ); - popouts.hasCurrent = true; - } else { - popouts.hasCurrent = false; - } - } - } + if (id === "audio" && Config.barConfig.popouts.audio) { + popouts.currentName = "audio"; + popouts.currentCenter = Qt.binding( () => item.mapToItem(root, itemWidth / 2, 0 ).x ); + popouts.hasCurrent = true; + } else if ( id === "resources" && Config.barConfig.popouts.resources ) { + popouts.currentName = "resources"; + popouts.currentCenter = Qt.binding( () => item.mapToItem( root, itemWidth / 2, 0 ).x ); + popouts.hasCurrent = true; + } else if ( id === "tray" && Config.barConfig.popouts.tray ) { + const index = Math.floor((( x - top ) / item.implicitWidth ) * item.items.count ); + const trayItem = item.items.itemAt( index ); + if ( trayItem ) { + popouts.currentName = `traymenu${ index }`; + popouts.currentCenter = Qt.binding( () => trayItem.mapToItem( root, trayItem.implicitWidth / 2, 0 ).x ); + popouts.hasCurrent = true; + } else { + popouts.hasCurrent = false; + } + } else if ( id === "clock" && Config.barConfig.popouts.clock ) { + Calendar.displayYear = new Date().getFullYear(); + Calendar.displayMonth = new Date().getMonth(); + popouts.currentName = "calendar"; + popouts.currentCenter = Qt.binding( () => item.mapToItem( root, itemWidth / 2, 0 ).x ); + popouts.hasCurrent = true; + } + } - Repeater { - id: repeater - model: Config.barConfig.entries + Repeater { + id: repeater + model: Config.barConfig.entries - DelegateChooser { - role: "id" + DelegateChooser { + role: "id" - DelegateChoice { - roleValue: "spacer" - delegate: WrappedLoader { - Layout.fillWidth: true - } - } - DelegateChoice { - roleValue: "workspaces" - delegate: WrappedLoader { - sourceComponent: Workspaces { - bar: root.bar - } - } - } - DelegateChoice { - roleValue: "audio" - delegate: WrappedLoader { - sourceComponent: AudioWidget {} - } - } - DelegateChoice { - roleValue: "tray" - delegate: WrappedLoader { - sourceComponent: TrayWidget { - bar: root.bar - } - } - } - DelegateChoice { - roleValue: "resources" - delegate: WrappedLoader { - sourceComponent: Resources {} - } - } - DelegateChoice { - roleValue: "updates" - delegate: WrappedLoader { - sourceComponent: UpdatesWidget {} - } - } - DelegateChoice { - roleValue: "notifBell" - delegate: WrappedLoader { - sourceComponent: NotifBell {} - } - } - DelegateChoice { - roleValue: "clock" - delegate: WrappedLoader { - sourceComponent: Clock {} - } - } - DelegateChoice { - roleValue: "activeWindow" - delegate: WrappedLoader { - sourceComponent: WindowTitle {} - } - } - } - } + DelegateChoice { + roleValue: "spacer" + delegate: WrappedLoader { + Layout.fillWidth: true + } + } + DelegateChoice { + roleValue: "workspaces" + delegate: WrappedLoader { + sourceComponent: Workspaces { + bar: root.bar + } + } + } + DelegateChoice { + roleValue: "audio" + delegate: WrappedLoader { + sourceComponent: AudioWidget {} + } + } + DelegateChoice { + roleValue: "tray" + delegate: WrappedLoader { + sourceComponent: TrayWidget { + bar: root.bar + } + } + } + DelegateChoice { + roleValue: "resources" + delegate: WrappedLoader { + sourceComponent: Resources {} + } + } + DelegateChoice { + roleValue: "updates" + delegate: WrappedLoader { + sourceComponent: UpdatesWidget {} + } + } + DelegateChoice { + roleValue: "notifBell" + delegate: WrappedLoader { + sourceComponent: NotifBell {} + } + } + DelegateChoice { + roleValue: "clock" + delegate: WrappedLoader { + sourceComponent: Clock {} + } + } + DelegateChoice { + roleValue: "activeWindow" + delegate: WrappedLoader { + sourceComponent: WindowTitle {} + } + } + } + } - component WrappedLoader: Loader { - required property bool enabled - required property string id - required property int index + component WrappedLoader: Loader { + required property bool enabled + required property string id + required property int index - Layout.alignment: Qt.AlignVCenter - Layout.fillHeight: true + Layout.alignment: Qt.AlignVCenter + Layout.fillHeight: true - function findFirstEnabled(): Item { - const count = repeater.count; - for (let i = 0; i < count; i++) { - const item = repeater.itemAt(i); - if (item?.enabled) - return item; - } - return null; - } + function findFirstEnabled(): Item { + const count = repeater.count; + for (let i = 0; i < count; i++) { + const item = repeater.itemAt(i); + if (item?.enabled) + return item; + } + return null; + } - function findLastEnabled(): Item { - for (let i = repeater.count - 1; i >= 0; i--) { - const item = repeater.itemAt(i); - if (item?.enabled) - return item; - } - return null; - } + function findLastEnabled(): Item { + for (let i = repeater.count - 1; i >= 0; i--) { + const item = repeater.itemAt(i); + if (item?.enabled) + return item; + } + return null; + } - Layout.leftMargin: findFirstEnabled() === this ? root.vPadding : 0 - Layout.rightMargin: findLastEnabled() === this ? root.vPadding : 0 + Layout.leftMargin: findFirstEnabled() === this ? root.vPadding : 0 + Layout.rightMargin: findLastEnabled() === this ? root.vPadding : 0 - visible: enabled - active: enabled - } + visible: enabled + active: enabled + } } diff --git a/Modules/Calendar/CalendarHeader.qml b/Modules/Calendar/CalendarHeader.qml new file mode 100644 index 0000000..14f5fe0 --- /dev/null +++ b/Modules/Calendar/CalendarHeader.qml @@ -0,0 +1,73 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config +import qs.Helpers + +RowLayout { + spacing: 12 + + Rectangle { + Layout.preferredWidth: 40 + Layout.preferredHeight: 40 + color: "transparent" + radius: 1000 + + CustomText { + text: "◀" + font.pointSize: 12 + color: DynamicColors.palette.m3onSurface + anchors.centerIn: parent + } + + StateLayer { + onClicked: { + if (Calendar.displayMonth === 0) { + Calendar.displayMonth = 11; + Calendar.displayYear -= 1; + } else { + Calendar.displayMonth -= 1; + } + } + } + } + + CustomText { + text: new Date(Calendar.displayYear, Calendar.displayMonth, 1).toLocaleDateString( + Qt.locale(), + "MMMM yyyy" + ) + font.weight: 600 + font.pointSize: 14 + color: DynamicColors.palette.m3onSurface + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + + Rectangle { + Layout.preferredWidth: 40 + Layout.preferredHeight: 40 + color: "transparent" + radius: 1000 + + CustomText { + text: "▶" + font.pointSize: 12 + color: DynamicColors.palette.m3onSurface + anchors.centerIn: parent + } + + StateLayer { + onClicked: { + if (Calendar.displayMonth === 11) { + Calendar.displayMonth = 0; + Calendar.displayYear += 1; + } else { + Calendar.displayMonth += 1; + } + } + } + } +} diff --git a/Modules/Calendar/CalendarPopup.qml b/Modules/Calendar/CalendarPopup.qml new file mode 100644 index 0000000..a25db29 --- /dev/null +++ b/Modules/Calendar/CalendarPopup.qml @@ -0,0 +1,77 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config +import qs.Helpers + +Item { + id: root + + required property Item wrapper + + implicitWidth: layout.childrenRect.width + layout.anchors.margins * 2 + implicitHeight: layout.childrenRect.height + layout.anchors.margins * 2 + + ColumnLayout { + id: layout + + anchors.centerIn: parent + anchors.margins: 16 + spacing: 16 + + // Header with month/year and navigation + CalendarHeader { + Layout.fillWidth: true + Layout.preferredHeight: childrenRect.height + } + + // Calendar grid + RowLayout { + Layout.fillWidth: true + Layout.preferredHeight: childrenRect.height + spacing: 12 + + ColumnLayout { + Layout.alignment: Qt.AlignTop + Layout.preferredHeight: childrenRect.height + Layout.preferredWidth: weekNumberColumn.width + spacing: 8 + + Item { + Layout.preferredHeight: dayOfWeekRow.height + } + + WeekNumberColumn { + id: weekNumberColumn + + Layout.alignment: Qt.AlignTop + Layout.preferredHeight: weekNumbers.values.length * 44 + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + Layout.preferredHeight: childrenRect.height + spacing: 8 + + DayOfWeekRow { + id: dayOfWeekRow + locale: Qt.locale() + Layout.fillWidth: true + Layout.preferredHeight: 30 + } + + MonthGrid { + locale: Qt.locale() + + wrapper: root.wrapper + Layout.preferredWidth: childrenRect.width + Layout.preferredHeight: childrenRect.height + } + } + } + } +} diff --git a/Modules/Calendar/DayOfWeekRow.qml b/Modules/Calendar/DayOfWeekRow.qml new file mode 100644 index 0000000..39505e5 --- /dev/null +++ b/Modules/Calendar/DayOfWeekRow.qml @@ -0,0 +1,45 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config +import qs.Helpers + +RowLayout { + id: root + + required property var locale + + spacing: 4 + + Repeater { + model: 7 + + Item { + required property int index + + Layout.fillWidth: true + Layout.preferredHeight: 30 + + readonly property string dayName: { + // Get the day name for this column + const dayIndex = (index + Calendar.weekStartDay) % 7; + return root.locale.dayName(dayIndex, Locale.ShortFormat); + } + + CustomText { + anchors.centerIn: parent + + text: parent.dayName + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: DynamicColors.palette.m3onSurfaceVariant + opacity: 0.8 + font.weight: 500 + font.pointSize: 11 + } + } + } +} + diff --git a/Modules/Calendar/MonthGrid.qml b/Modules/Calendar/MonthGrid.qml new file mode 100644 index 0000000..d1e61c3 --- /dev/null +++ b/Modules/Calendar/MonthGrid.qml @@ -0,0 +1,117 @@ +pragma ComponentBehavior: Bound + +import Quickshell +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config +import qs.Helpers + +GridLayout { + id: root + + required property var locale + required property Item wrapper + + columns: 7 + rowSpacing: 4 + columnSpacing: 4 + uniformCellWidths: true + uniformCellHeights: true + + component Anim: NumberAnimation { + target: root + duration: MaterialEasing.expressiveEffectsTime + easing.bezierCurve: MaterialEasing.expressiveEffects + } + + Repeater { + id: repeater + model: ScriptModel { + values: Calendar.getWeeksForMonth(Calendar.displayMonth, Calendar.displayYear) + + Behavior on values { + SequentialAnimation { + id: switchAnim + ParallelAnimation { + Anim { + property: "opacity" + from: 1.0 + to: 0.0 + } + Anim { + property: "scale" + from: 1.0 + to: 0.8 + } + } + PropertyAction {} + ParallelAnimation { + Anim { + property: "opacity" + from: 0.0 + to: 1.0 + } + Anim { + property: "scale" + from: 0.8 + to: 1.0 + } + } + } + } + } + + Rectangle { + + required property var modelData + required property int index + + Layout.preferredWidth: 40 + Layout.preferredHeight: width + + + radius: 1000 + color: { + if (modelData.isToday) { + console.log(width); + return DynamicColors.palette.m3primaryContainer; + } + return "transparent"; + } + + Behavior on color { + ColorAnimation { duration: 200 } + } + + CustomText { + anchors.centerIn: parent + + text: parent.modelData.day.toString() + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + opacity: parent.modelData.isCurrentMonth ? 1.0 : 0.4 + color: { + if (parent.modelData.isToday) { + return DynamicColors.palette.m3onPrimaryContainer; + } + return DynamicColors.palette.m3onSurface; + } + + Behavior on color { + ColorAnimation { duration: 200 } + } + Behavior on opacity { + NumberAnimation { duration: 200 } + } + } + + StateLayer { + color: DynamicColors.palette.m3onSurface + onClicked: { + console.log(`Selected date: ${parent.modelData.day}/${parent.modelData.month + 1}/${parent.modelData.year}`); + } + } + } + } +} diff --git a/Modules/Calendar/WeekNumberColumn.qml b/Modules/Calendar/WeekNumberColumn.qml new file mode 100644 index 0000000..8fdf5c1 --- /dev/null +++ b/Modules/Calendar/WeekNumberColumn.qml @@ -0,0 +1,45 @@ +pragma ComponentBehavior: Bound + +import Quickshell +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config +import qs.Helpers + +ColumnLayout { + id: root + + spacing: 4 + + readonly property var weekNumbers: Calendar.getWeekNumbers(Calendar.displayMonth, Calendar.displayYear) + + Repeater { + model: ScriptModel { + values: root.weekNumbers + } + + Item { + id: weekItem + Layout.preferredHeight: 40 + Layout.preferredWidth: 20 + Layout.alignment: Qt.AlignHCenter + + required property int index + required property var modelData + + CustomText { + id: weekText + + anchors.centerIn: parent + + text: weekItem.modelData + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: DynamicColors.palette.m3onSurfaceVariant + opacity: 0.5 + font.pointSize: 10 + } + } + } +} diff --git a/Modules/Content.qml b/Modules/Content.qml index 91c58c3..e239665 100644 --- a/Modules/Content.qml +++ b/Modules/Content.qml @@ -4,6 +4,7 @@ import Quickshell import Quickshell.Services.SystemTray import QtQuick import qs.Config +import qs.Modules.Calendar Item { id: root @@ -69,6 +70,13 @@ Item { } } } + + Popout { + name: "calendar" + sourceComponent: CalendarPopup { + wrapper: root.wrapper + } + } } component Popout: Loader { diff --git a/Modules/Lock/LockSurface.qml b/Modules/Lock/LockSurface.qml index 657d35a..faaa180 100644 --- a/Modules/Lock/LockSurface.qml +++ b/Modules/Lock/LockSurface.qml @@ -62,7 +62,6 @@ WlSessionLockSurface { onTextChanged: text = "" } - ScreencopyView { id: background @@ -84,14 +83,11 @@ WlSessionLockSurface { } } - Image { + CachingImage { id: backgroundImage anchors.fill: parent asynchronous: false - cache: false - source: WallpaperPath.currentWallpaperPath - sourceSize.width: root.screen.width - sourceSize.height: root.screen.height + path: WallpaperPath.currentWallpaperPath visible: Config.lock.useWallpaper Component.onCompleted: { diff --git a/scripts/generate_calendar_cache.py b/scripts/generate_calendar_cache.py new file mode 100644 index 0000000..3514115 --- /dev/null +++ b/scripts/generate_calendar_cache.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +""" +Calendar cache generator for z-bar-qt +Generates a JSON file containing date numbers for all 52 weeks of 3 years (last, current, next) +Structure: { "2024": { "week_0": [1,2,3,4,5,6,7], ... }, "2025": {...}, ... } +""" + +import json +import sys +from datetime import datetime, timedelta +from pathlib import Path + + +def get_week_start_day(): + """Returns the first day of the week (0=Sunday, 1=Monday, etc.) - hardcoded to Monday""" + return 1 # Monday + + +def get_weeks_for_year(year, week_start_day=1): + """ + Generate week data for a given year. + Returns a dict with 52 weeks, each containing 7 date numbers. + """ + weeks = {} + + # Find the first day of the year + jan_1 = datetime(year, 1, 1) + + # Find the first week's start date (adjust based on week_start_day) + first_date = jan_1 - timedelta(days=(jan_1.weekday() - week_start_day) % 7) + + # Generate 52 weeks + for week_num in range(52): + week_dates = [] + week_start = first_date + timedelta(weeks=week_num) + + for day_offset in range(7): + current_date = week_start + timedelta(days=day_offset) + week_dates.append(current_date.day) + + weeks[f"week_{week_num}"] = week_dates + + return weeks + + +def generate_calendar_cache(year=None): + """Generate cache for last year, current year, and next year""" + if year is None: + year = datetime.now().year + + cache = {} + for offset_year in [-1, 0, 1]: + target_year = year + offset_year + cache[str(target_year)] = get_weeks_for_year(target_year) + + return cache + + +def write_cache_file(cache_data): + """Write cache to the same location as Paths.cache in QML""" + import os + + # Use XDG_CACHE_HOME or ~/.cache, then add /zshell (matching Paths singleton) + xdg_cache_home = os.environ.get("XDG_CACHE_HOME") + if xdg_cache_home: + cache_dir = Path(xdg_cache_home) / "zshell" + else: + cache_dir = Path.home() / ".cache" / "zshell" + + cache_dir.mkdir(parents=True, exist_ok=True) + + cache_file = cache_dir / "calendar_cache.json" + + with open(cache_file, "w") as f: + json.dump(cache_data, f, indent=2) + + print(f"Calendar cache written to: {cache_file}") + return cache_file + + +def main(): + try: + # Generate cache for current year and ±1 year + cache = generate_calendar_cache() + + # Write to file + cache_file = write_cache_file(cache) + + print("Cache structure:") + print(" - Keys: year (e.g., '2024', '2025', '2026')") + print(" - Values: dict with 52 weeks") + print(" - Each week: array of 7 date numbers") + print(f"\nExample (first week of 2025):") + print(f" {cache['2025']['week_0']}") + + return 0 + except Exception as e: + print(f"Error generating calendar cache: {e}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + sys.exit(main())