diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index 8fedb3b9..c6bd73ec 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -10,28 +10,154 @@ import qs.Widgets NPanel { id: root - preferredWidth: Settings.data.location.showWeekNumberInCalendar ? 320 : 300 - preferredHeight: 300 + preferredWidth: Settings.data.location.showWeekNumberInCalendar ? 340 : 320 + preferredHeight: 380 - // Main Column panelContent: ColumnLayout { id: content anchors.fill: parent - anchors.margins: Style.marginM * scaling - spacing: Style.marginXS * scaling + anchors.margins: Style.marginL * scaling + spacing: Style.marginM * scaling readonly property int firstDayOfWeek: Qt.locale().firstDayOfWeek - // Header: Month/Year with navigation + // Current day + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 80 * scaling + radius: Style.radiusL * scaling + color: Color.mPrimary + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginL * scaling + anchors.topMargin: 12 * scaling + spacing: Style.marginM * scaling + + // Month, Year and Day + RowLayout { + Layout.alignment: Qt.AlignVCenter + Layout.fillHeight: true + spacing: Style.marginM * scaling + + // Big day of the month + NText { + text: Time.date.getDate() + pointSize: Style.fontSizeXXXL * 1.5 * scaling + font.weight: Style.fontWeightBold + color: Color.mOnPrimary + visible: (Time.date.getMonth() == grid.month) + Layout.preferredWidth: visible ? implicitWidth : 0 + Layout.fillHeight: true + } + + // Month and Year + ColumnLayout { + spacing: 0 + + NText { + text: Qt.locale().monthName(grid.month, Locale.LongFormat).toUpperCase() + pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnPrimary + } + + NText { + text: grid.year + pointSize: Style.fontSizeM * scaling + color: Qt.alpha(Color.mOnPrimary, 0.8) + } + } + } + + Item { + Layout.fillWidth: true + } + + // Digital clock with circular progress + Item { + width: Style.fontSizeXXXL * 2 * scaling + height: Style.fontSizeXXXL * 2 * scaling + + // Seconds circular progress + Canvas { + id: secondsProgress + anchors.fill: parent + + property real progress: (Time.date.getSeconds() + Time.date.getMilliseconds() / 1000) / 60 + + onProgressChanged: requestPaint() + + onPaint: { + var ctx = getContext("2d") + var centerX = width / 2 + var centerY = height / 2 + var radius = Math.min(width, height) / 2 - 4 * scaling + + ctx.reset() + + // Background circle + ctx.beginPath() + ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI) + ctx.lineWidth = 3 * scaling + ctx.strokeStyle = Qt.alpha(Color.mOnPrimary, 0.15) + ctx.stroke() + + // Progress arc + ctx.beginPath() + ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI) + ctx.lineWidth = 3 * scaling + ctx.strokeStyle = Color.mOnPrimary + ctx.lineCap = "round" + ctx.stroke() + } + + Connections { + target: Time + function onDateChanged() { + secondsProgress.progress = (Time.date.getSeconds() + Time.date.getMilliseconds() / 1000) / 60 + } + } + } + + // Digital clock + ColumnLayout { + anchors.centerIn: parent + spacing: -2 * scaling + + NText { + text: Qt.formatTime(Time.date, "HH") + pointSize: Style.fontSizeL * 0.7 * scaling + font.weight: Style.fontWeightBold + color: Color.mOnPrimary + family: Settings.data.ui.fontFixed + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: Qt.formatTime(Time.date, "mm") + pointSize: Style.fontSizeL * 0.7 * scaling + font.weight: Style.fontWeightBold + color: Color.mOnPrimary + family: Settings.data.ui.fontFixed + Layout.alignment: Qt.AlignHCenter + } + } + } + } + } + + // Navigation and divider RowLayout { Layout.fillWidth: true - Layout.leftMargin: Style.marginM * scaling - Layout.rightMargin: Style.marginM * scaling spacing: Style.marginS * scaling + NDivider { + Layout.fillWidth: true + } + NIconButton { icon: "chevron-left" - tooltipText: I18n.tr("tooltips.previous-month") onClicked: { let newDate = new Date(grid.year, grid.month - 1, 1) grid.year = newDate.getFullYear() @@ -39,18 +165,16 @@ NPanel { } } - NText { - text: grid.title - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - pointSize: Style.fontSizeM * scaling - font.weight: Style.fontWeightBold - color: Color.mPrimary + NIconButton { + icon: "calendar" + onClicked: { + grid.month = Time.date.getMonth() + grid.year = Time.date.getFullYear() + } } NIconButton { icon: "chevron-right" - tooltipText: I18n.tr("tooltips.next-month") onClicked: { let newDate = new Date(grid.year, grid.month + 1, 1) grid.year = newDate.getFullYear() @@ -59,40 +183,18 @@ NPanel { } } - // Divider between header and weekdays - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginS * scaling - Layout.bottomMargin: Style.marginL * scaling - } - - // Columns label (respects locale's first day of week) + // Names of days of the week RowLayout { Layout.fillWidth: true - Layout.leftMargin: Style.marginS * scaling // Align with grid - Layout.rightMargin: Style.marginS * scaling - Layout.bottomMargin: Style.marginM * scaling spacing: 0 - // Week header spacer or label (same width as week number column) Item { visible: Settings.data.location.showWeekNumberInCalendar - Layout.preferredWidth: visible ? Style.baseWidgetSize * scaling : 0 - - NText { - anchors.centerIn: parent - text: I18n.tr("calendar.panel.week") - color: Color.mOutline - pointSize: Style.fontSizeXS * scaling - font.weight: Style.fontWeightRegular - horizontalAlignment: Text.AlignHCenter - } + Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0 } - // Day name headers - now properly aligned with calendar grid GridLayout { Layout.fillWidth: true - Layout.fillHeight: true columns: 7 rows: 1 columnSpacing: 0 @@ -103,17 +205,17 @@ NPanel { Item { Layout.fillWidth: true - Layout.fillHeight: true - Layout.preferredWidth: Style.baseWidgetSize * scaling + Layout.preferredHeight: Style.baseWidgetSize * 0.6 * scaling NText { anchors.centerIn: parent text: { let dayIndex = (content.firstDayOfWeek + index) % 7 - return Qt.locale().dayName(dayIndex, Locale.ShortFormat) + const dayNames = ["S", "M", "T", "W", "T", "F", "S"] + return dayNames[dayIndex] } - color: Color.mSecondary - pointSize: Style.fontSizeM * scaling + color: Color.mPrimary + pointSize: Style.fontSizeS * scaling font.weight: Style.fontWeightBold horizontalAlignment: Text.AlignHCenter } @@ -122,82 +224,51 @@ NPanel { } } - // Grids: days with optional week numbers + // Grid with weeks and days RowLayout { Layout.fillWidth: true Layout.fillHeight: true - Layout.leftMargin: Style.marginS * scaling - Layout.rightMargin: Style.marginS * scaling spacing: 0 - // Week numbers column (only visible when enabled) + // Columna de números de semana ColumnLayout { visible: Settings.data.location.showWeekNumberInCalendar - Layout.preferredWidth: visible ? Style.baseWidgetSize * scaling : 0 + Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0 Layout.fillHeight: true spacing: 0 Repeater { - model: 6 // Maximum 6 weeks in a month view + model: 6 Item { Layout.fillWidth: true Layout.fillHeight: true - Layout.preferredHeight: Style.baseWidgetSize * scaling NText { anchors.centerIn: parent color: Color.mOutline - pointSize: Style.fontSizeXS * scaling - font.weight: Style.fontWeightBold + pointSize: Style.fontSizeXXS * scaling + font.weight: Style.fontWeightMedium text: { - // Calculate the date shown in the first column of this row - // MonthGrid always shows 42 days (6 weeks × 7 days) - - // First, find the first day of the month let firstOfMonth = new Date(grid.year, grid.month, 1) - - // Calculate how many days before the 1st to start the grid - // This depends on the locale's first day of week let firstDayOfWeek = content.firstDayOfWeek let firstOfMonthDayOfWeek = firstOfMonth.getDay() - - // Calculate offset: how many days before the 1st should the grid start? let daysBeforeFirst = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7 - - // MonthGrid typically shows the previous month's days to fill the first week - // If the 1st is already on the first day of week, show the previous week if (daysBeforeFirst === 0) { daysBeforeFirst = 7 } - - // Calculate the start date of the grid let gridStartDate = new Date(grid.year, grid.month, 1 - daysBeforeFirst) - - // Calculate the date for this specific row (week) let rowStartDate = new Date(gridStartDate) rowStartDate.setDate(gridStartDate.getDate() + (index * 7)) - - // For ISO week numbers, we need to find the Thursday of this week - // ISO 8601 week numbering: week with year's first Thursday is week 1 - // The week number is determined by the Thursday - - // Find the Thursday of this row's week - // If firstDayOfWeek is Monday (1), Thursday is +3 days - // If firstDayOfWeek is Sunday (0), we need to adjust let thursday = new Date(rowStartDate) if (firstDayOfWeek === 0) { - // Sunday start: Thursday is 4 days after Sunday thursday.setDate(rowStartDate.getDate() + 4) } else if (firstDayOfWeek === 1) { - // Monday start: Thursday is 3 days after Monday thursday.setDate(rowStartDate.getDate() + 3) } else { - // Other start days: calculate offset to Thursday let daysToThursday = (4 - firstDayOfWeek + 7) % 7 thursday.setDate(rowStartDate.getDate() + daysToThursday) } - return `${getISOWeekNumber(thursday)}` } } @@ -205,32 +276,43 @@ NPanel { } } - // The actual calendar grid + // Days Grid MonthGrid { id: grid Layout.fillWidth: true Layout.fillHeight: true - spacing: 0 + spacing: Style.marginXXS * scaling month: Time.date.getMonth() year: Time.date.getFullYear() locale: Qt.locale() delegate: Item { - Rectangle { - width: Style.baseWidgetSize * scaling - height: Style.baseWidgetSize * scaling - radius: width / 2 - color: model.today ? Color.mPrimary : Color.transparent + width: Style.baseWidgetSize * 0.9 * scaling + height: Style.baseWidgetSize * 0.9 * scaling + anchors.centerIn: parent + radius: Style.radiusM * scaling + + color: { + if (model.today) + return Color.mSecondary + return Color.transparent + } NText { anchors.centerIn: parent text: model.day - color: model.today ? Color.mOnPrimary : Color.mOnSurface - opacity: model.month === grid.month ? Style.opacityHeavy : Style.opacityLight + color: { + if (model.today) + return Color.mOnSecondary + if (model.month === grid.month) + return Color.mOnSurface + return Color.mOnSurfaceVariant + } + opacity: model.month === grid.month ? 1.0 : 0.4 pointSize: Style.fontSizeM * scaling - font.weight: model.today ? Style.fontWeightBold : Style.fontWeightRegular + font.weight: model.today ? Style.fontWeightBold : Style.fontWeightMedium } Behavior on color { @@ -238,33 +320,38 @@ NPanel { duration: Style.animationFast } } + + // Hover effect + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onEntered: { + if (!model.today) { + parent.color = Qt.alpha(Color.mSecondary, 0.2) + } + } + + onExited: { + if (!model.today) { + parent.color = Color.transparent + } + } + } } } } } } - // ISO 8601 week number calculation - // This is locale-independent and always uses Monday as first day of week function getISOWeekNumber(date) { - // Create a copy and set to nearest Thursday (current date + 4 - current day number) - // ISO week starts on Monday (1) to Sunday (7) const target = new Date(date.getTime()) target.setHours(0, 0, 0, 0) - - // Get day of week where Monday = 1, Sunday = 7 const dayOfWeek = target.getDay() || 7 - - // Set to nearest Thursday (which determines the week number) target.setDate(target.getDate() + 4 - dayOfWeek) - - // Get first day of year const yearStart = new Date(target.getFullYear(), 0, 1) - - // Calculate full weeks between yearStart and target - // Add 1 because we're counting weeks, not week differences const weekNumber = Math.ceil(((target - yearStart) / 86400000 + 1) / 7) - return weekNumber } }