diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index 2fff9d21..7f9f4a3a 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -150,6 +150,7 @@ Singleton { property int mangoLayoutBorderSize: -1 property int firstDayOfWeek: -1 + property bool showWeekNumber: false property bool use24HourClock: true property bool showSeconds: false property bool padHours12Hour: false diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index 51350b20..4bffbfbb 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -33,6 +33,7 @@ var SPEC = { mangoLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" }, firstDayOfWeek: { def: -1 }, + showWeekNumber: { def: false }, use24HourClock: { def: true }, showSeconds: { def: false }, padHours12Hour: { def: false }, diff --git a/quickshell/Modules/DankDash/DankDashPopout.qml b/quickshell/Modules/DankDash/DankDashPopout.qml index 03600971..97a86554 100644 --- a/quickshell/Modules/DankDash/DankDashPopout.qml +++ b/quickshell/Modules/DankDash/DankDashPopout.qml @@ -12,7 +12,7 @@ DankPopout { property var triggerScreen: null property int currentTabIndex: 0 - popupWidth: 700 + popupWidth: SettingsData.showWeekNumber ? 736 : 700 popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500 triggerWidth: 80 screen: triggerScreen @@ -168,6 +168,7 @@ DankPopout { LayoutMirroring.enabled: I18n.isRtl LayoutMirroring.childrenInherit: true + implicitWidth: Math.max(700, pages.implicitWidth + (Theme.spacingM * 2)) implicitHeight: contentColumn.height + Theme.spacingM * 2 color: "transparent" focus: true @@ -316,6 +317,7 @@ DankPopout { id: pages width: parent.width height: implicitHeight + implicitWidth: currentItem && currentItem.implicitWidth > 0 ? currentItem.implicitWidth : (700 - Theme.spacingM * 2) implicitHeight: { if (root.currentTabIndex === 0) return overviewLoader.item?.implicitHeight ?? 410; diff --git a/quickshell/Modules/DankDash/MediaPlayerTab.qml b/quickshell/Modules/DankDash/MediaPlayerTab.qml index e65966d8..17a33fc8 100644 --- a/quickshell/Modules/DankDash/MediaPlayerTab.qml +++ b/quickshell/Modules/DankDash/MediaPlayerTab.qml @@ -105,7 +105,7 @@ Item { return Math.max(0, Math.min(1, calculatedRatio)); } - implicitWidth: 700 + implicitWidth: SettingsData.showWeekNumber ? 736 : 700 implicitHeight: playerContent.height + playerContent.anchors.topMargin * 2 Connections { diff --git a/quickshell/Modules/DankDash/Overview/CalendarOverviewCard.qml b/quickshell/Modules/DankDash/Overview/CalendarOverviewCard.qml index a401422f..1972f065 100644 --- a/quickshell/Modules/DankDash/Overview/CalendarOverviewCard.qml +++ b/quickshell/Modules/DankDash/Overview/CalendarOverviewCard.qml @@ -7,6 +7,8 @@ import qs.Widgets Rectangle { id: root + implicitWidth: SettingsData.showWeekNumber ? 736 : 700 + property bool showEventDetails: false property date selectedDate: systemClock.date property var selectedDateEvents: [] @@ -41,6 +43,40 @@ Rectangle { return d; } + function getWeekNumber(dateObj) { + // Set time to noon to avoid potential Daylight Saving Time related bugs + const weekStartDay = startOfWeek(dateObj); + weekStartDay.setHours(12, 0, 0, 0); + + let week1Start; + + if (weekStartJs() === 1) { + // ISO 8601 Standard, week start on Monday + // A week belongs to the year its Thursday falls in + // So we have to get the yearTarget from weekStartDay instead of dateObj + let yearTarget = weekStartDay; + yearTarget.setDate(yearTarget.getDate() + 3); // Monday + 3 = Thursday + + // Week 1 is the week containing Jan 4th + const jan4 = new Date(yearTarget.getFullYear(), 0, 4); + week1Start = startOfWeek(jan4); + } else { + // Traditional / US Standard, week start on Sunday + // A week belongs to the year its Sunday falls in + let yearTarget = weekStartDay; + yearTarget.setDate(yearTarget.getDate() + 6); // Monday + 6 = Sunday + + // Week 1 is the week containing Jan 1st + const jan1 = new Date(yearTarget.getFullYear(), 0, 1); + week1Start = startOfWeek(jan1); + } + + week1Start.setHours(12, 0, 0, 0); + + const diffDays = Math.round((weekStartDay.getTime() - week1Start.getTime()) / 86400000); // Number of miliseconds in a day + return Math.floor(diffDays / 7) + 1; + } + function updateSelectedDateEvents() { if (CalendarService && CalendarService.khalAvailable) { const events = CalendarService.getEventsForDate(selectedDate); @@ -151,6 +187,7 @@ Rectangle { elide: Text.ElideRight } } + Row { width: parent.width height: 28 @@ -224,120 +261,172 @@ Rectangle { Row { width: parent.width - height: 18 + height: parent.height - 28 - Theme.spacingS visible: !showEventDetails + spacing: SettingsData.showWeekNumber ? Theme.spacingS : 0 - Repeater { - model: { - const days = []; - const qtFirst = weekStartQt(); - for (let i = 0; i < 7; ++i) { - const qtDay = ((qtFirst - 1 + i) % 7) + 1; - days.push(I18n.locale().dayName(qtDay, Locale.ShortFormat)); - } - return days; - } + Column { + id: weekNumberColumn + visible: SettingsData.showWeekNumber + width: SettingsData.showWeekNumber ? 28 : 0 + height: parent.height + spacing: Theme.spacingS - Rectangle { - width: parent.width / 7 + Item { + width: parent.width height: 18 - color: "transparent" + } - StyledText { - anchors.centerIn: parent - text: modelData - font.pixelSize: Theme.fontSizeSmall - color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6) - font.weight: Font.Medium + Grid { + width: parent.width + height: parent.height - 18 - Theme.spacingS + columns: 1 + rows: 6 + + Repeater { + model: 6 + Rectangle { + width: parent.width + height: parent.height / 6 + color: "transparent" + + StyledText { + anchors.centerIn: parent + text: { + const rowDate = new Date(calendarGrid.firstDay); + rowDate.setDate(rowDate.getDate() + index * 7); + return root.getWeekNumber(rowDate); + } + font.pixelSize: Theme.fontSizeSmall + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6) + font.weight: Font.Medium + } + } } } } - } - Grid { - id: calendarGrid - visible: !showEventDetails + Column { + width: SettingsData.showWeekNumber ? (parent.width - weekNumberColumn.width - parent.spacing) : parent.width + height: parent.height + spacing: Theme.spacingS - property date displayDate: systemClock.date - property date selectedDate: systemClock.date + Row { + width: parent.width + height: 18 - readonly property date firstDay: { - const firstOfMonth = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1); - return startOfWeek(firstOfMonth); - } - - width: parent.width - height: parent.height - 28 - 18 - Theme.spacingS * 2 - columns: 7 - rows: 6 - - Repeater { - model: 42 - - Rectangle { - readonly property date dayDate: { - const date = new Date(parent.firstDay); - date.setDate(date.getDate() + index); - return date; - } - readonly property bool isCurrentMonth: dayDate.getMonth() === calendarGrid.displayDate.getMonth() - readonly property bool isToday: dayDate.toDateString() === new Date().toDateString() - readonly property bool isSelected: dayDate.toDateString() === calendarGrid.selectedDate.toDateString() - - width: parent.width / 7 - height: parent.height / 6 - color: "transparent" - - Rectangle { - anchors.centerIn: parent - width: Math.min(parent.width - 4, parent.height - 4, 32) - height: width - color: isToday ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : dayArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent" - radius: Theme.cornerRadius - - StyledText { - anchors.centerIn: parent - text: dayDate.getDate() - font.pixelSize: Theme.fontSizeSmall - color: isToday ? Theme.primary : isCurrentMonth ? Theme.surfaceText : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4) - font.weight: isToday ? Font.Medium : Font.Normal + Repeater { + model: { + const days = []; + const qtFirst = weekStartQt(); + for (let i = 0; i < 7; ++i) { + const qtDay = ((qtFirst - 1 + i) % 7) + 1; + days.push(I18n.locale().dayName(qtDay, Locale.ShortFormat)); + } + return days; } Rectangle { - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottomMargin: 4 - width: 12 - height: 2 - radius: Theme.cornerRadius - visible: CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate) - color: isToday ? Qt.lighter(Theme.primary, 1.3) : Theme.primary - opacity: isToday ? 0.9 : 0.7 + width: parent.width / 7 + height: 18 + color: "transparent" - Behavior on opacity { - NumberAnimation { - duration: Theme.shortDuration - easing.type: Theme.standardEasing + StyledText { + anchors.centerIn: parent + text: modelData + font.pixelSize: Theme.fontSizeSmall + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6) + font.weight: Font.Medium + } + } + } + } + + Grid { + id: calendarGrid + width: parent.width + height: parent.height - 18 - Theme.spacingS + columns: 7 + rows: 6 + + property date displayDate: systemClock.date + property date selectedDate: systemClock.date + + readonly property date firstDay: { + const firstOfMonth = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1); + return startOfWeek(firstOfMonth); + } + + Repeater { + model: 42 + + Rectangle { + readonly property date dayDate: { + const date = new Date(parent.firstDay); + date.setDate(date.getDate() + index); + return date; + } + readonly property bool isCurrentMonth: dayDate.getMonth() === calendarGrid.displayDate.getMonth() + readonly property bool isToday: dayDate.toDateString() === new Date().toDateString() + readonly property bool isSelected: dayDate.toDateString() === calendarGrid.selectedDate.toDateString() + + width: parent.width / 7 + height: parent.height / 6 + color: "transparent" + + Rectangle { + anchors.centerIn: parent + width: Math.min(parent.width - 4, parent.height - 4, 32) + height: width + color: isToday ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : dayArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent" + radius: Theme.cornerRadius + + StyledText { + anchors.centerIn: parent + text: dayDate.getDate() + font.pixelSize: Theme.fontSizeSmall + color: isToday ? Theme.primary : isCurrentMonth ? Theme.surfaceText : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4) + font.weight: isToday ? Font.Medium : Font.Normal + } + + Rectangle { + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottomMargin: 4 + width: 12 + height: 2 + radius: Theme.cornerRadius + visible: CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate) + color: isToday ? Qt.lighter(Theme.primary, 1.3) : Theme.primary + opacity: isToday ? 0.9 : 0.7 + + Behavior on opacity { + NumberAnimation { + duration: Theme.shortDuration + easing.type: Theme.standardEasing + } + } + } + } + + MouseArea { + id: dayArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + if (CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate)) { + root.selectedDate = dayDate; + root.showEventDetails = true; + } } } } } - - MouseArea { - id: dayArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - if (CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate)) { - root.selectedDate = dayDate; - root.showEventDetails = true; - } - } - } } } } + DankListView { width: parent.width - Theme.spacingS * 2 height: parent.height - (showEventDetails ? 40 : 28 + 18) - Theme.spacingS diff --git a/quickshell/Modules/DankDash/OverviewTab.qml b/quickshell/Modules/DankDash/OverviewTab.qml index 4176277d..de7ea352 100644 --- a/quickshell/Modules/DankDash/OverviewTab.qml +++ b/quickshell/Modules/DankDash/OverviewTab.qml @@ -8,7 +8,7 @@ Item { LayoutMirroring.enabled: I18n.isRtl LayoutMirroring.childrenInherit: true - implicitWidth: 700 + implicitWidth: SettingsData.showWeekNumber ? 736 : 700 implicitHeight: 410 signal switchToWeatherTab diff --git a/quickshell/Modules/DankDash/WallpaperTab.qml b/quickshell/Modules/DankDash/WallpaperTab.qml index 9cc859bb..2f55e5c8 100644 --- a/quickshell/Modules/DankDash/WallpaperTab.qml +++ b/quickshell/Modules/DankDash/WallpaperTab.qml @@ -12,7 +12,7 @@ Item { LayoutMirroring.enabled: I18n.isRtl LayoutMirroring.childrenInherit: true - implicitWidth: 700 + implicitWidth: SettingsData.showWeekNumber ? 736 : 700 implicitHeight: 410 property string wallpaperDir: "" diff --git a/quickshell/Modules/DankDash/WeatherTab.qml b/quickshell/Modules/DankDash/WeatherTab.qml index f7be2c6e..21ea656a 100644 --- a/quickshell/Modules/DankDash/WeatherTab.qml +++ b/quickshell/Modules/DankDash/WeatherTab.qml @@ -11,7 +11,7 @@ Item { LayoutMirroring.enabled: I18n.isRtl LayoutMirroring.childrenInherit: true - implicitWidth: 700 + implicitWidth: SettingsData.showWeekNumber ? 736 : 700 implicitHeight: 410 property bool syncing: false property bool showHourly: false diff --git a/quickshell/Modules/Settings/TimeWeatherTab.qml b/quickshell/Modules/Settings/TimeWeatherTab.qml index 006b7b38..61f4d43c 100644 --- a/quickshell/Modules/Settings/TimeWeatherTab.qml +++ b/quickshell/Modules/Settings/TimeWeatherTab.qml @@ -85,6 +85,16 @@ Item { settingKey: "dateFormat" iconName: "calendar_today" + SettingsToggleRow { + tab: "time" + tags: ["show", "week"] + settingKey: "showWeekNumber" + text: I18n.tr("Show Week Number") + description: I18n.tr("Show week number in the calendar") + checked: SettingsData.showWeekNumber + onToggled: checked => SettingsData.set("showWeekNumber", checked) + } + SettingsDropdownRow { tab: "time" tags: ["first", "day", "week"]