import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Wayland import "../../config" as Cfg import "../../components" ModuleChip { id: root chipColor: t.clockBg chipColorHovered: t.clockBgHover chipBorderColor: t.clockBorder chipBorderColorHovered: t.clockBorderHover chipHoverEnabled: true chipTapAction: () => calendarPopup.toggle() chipAnimWidth: true width: clockLayout.implicitWidth + 24 radius: panelRadius Item { id: timeManager property date now: new Date() property int lastSecond: -1 Timer { interval: 10; running: true; repeat: true onTriggered: { let d = new Date() let s = d.getSeconds() if (s !== timeManager.lastSecond) { timeManager.lastSecond = s timeManager.now = d } } } } RowLayout { id: clockLayout anchors.centerIn: parent spacing: 0 Text { text: "󱑂" color: root.t.clockIcon font.pixelSize: 14 Layout.alignment: Qt.AlignVCenter Layout.rightMargin: 8 } Text { text: TimezoneProvider.toTz(timeManager.now).toLocaleTimeString(Qt.locale(), "HH:mm") color: root.t.clockTime font.pixelSize: 13 font.bold: true verticalAlignment: Text.AlignVCenter } Text { id: secondsText text: ":" + TimezoneProvider.toTz(timeManager.now).toLocaleTimeString(Qt.locale(), "ss") color: root.t.clockSeconds font.pixelSize: 13 font.bold: true verticalAlignment: Text.AlignVCenter opacity: (root.hovered || calendarPopup.popupOpen) && Cfg.Config.clockExtendEnabled ? 1.0 : 0.0 visible: opacity > 0 Layout.preferredWidth: (root.hovered || calendarPopup.popupOpen) && Cfg.Config.clockExtendEnabled ? implicitWidth : 0 clip: true Behavior on opacity { NumberAnimation { duration: 200 } } Behavior on Layout.preferredWidth { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } } } } StandardPopup { id: calendarPopup ns: "calendar" chipRoot: root cardWidth: 284 cardColor: t.calCardBg cardBorderColor: t.calCardBorder cardHeight: cardCol.implicitHeight + 28 component MonthNavButton: Rectangle { id: navBtn required property int direction width: 26; height: 26; radius: 6 color: btnMouse.containsMouse ? (btnMouse.pressed ? root.t.calArrowBgPress : root.t.calArrowBgHover) : root.t.calArrowBg scale: btnMouse.pressed ? 0.88 : 1.0 Behavior on color { ColorAnimation { duration: 120 } } Behavior on scale { NumberAnimation { duration: 100; easing.type: Easing.OutCubic } } MouseArea { id: btnMouse anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor function navigate() { if (slideAnim.running) return cal.slideDir = navBtn.direction let m = cal.displayMonth + navBtn.direction let y = cal.displayYear if (m > 11) { m = 0; y += 1 } if (m < 0) { m = 11; y -= 1 } cal.pendingMonth = m cal.pendingYear = y slideAnim.restart() } onPressed: { navigate(); holdDelay.restart() } onReleased: { holdDelay.stop(); holdRepeat.stop() } onCanceled: { holdDelay.stop(); holdRepeat.stop() } Timer { id: holdDelay; interval: 400; repeat: false; onTriggered: holdRepeat.restart() } Timer { id: holdRepeat; interval: 200; repeat: true; onTriggered: btnMouse.navigate() } } Text { anchors.centerIn: parent text: navBtn.direction < 0 ? "‹" : "›" color: btnMouse.containsMouse ? root.t.calArrowIconHover : root.t.calArrowIcon font.pixelSize: 18; font.bold: true Behavior on color { ColorAnimation { duration: 120 } } } } ColumnLayout { id: cardCol anchors { left: parent.left; right: parent.right; top: parent.top } anchors.margins: 16 anchors.topMargin: 18 spacing: 14 ColumnLayout { spacing: 2 Layout.fillWidth: true Text { text: TimezoneProvider.toTz(timeManager.now).toLocaleDateString(Qt.locale(), "dddd") color: root.t.clockPopupDim font.pixelSize: 12 font.capitalization: Font.AllUppercase Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter } Text { text: TimezoneProvider.toTz(timeManager.now).toLocaleDateString(Qt.locale(), "d MMMM yyyy") color: root.t.clockPopupHeader font.pixelSize: 22 font.bold: true Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter } } Rectangle { Layout.fillWidth: true; height: 1; color: root.t.calSeparator } RowLayout { Layout.fillWidth: true MonthNavButton { direction: -1 } Text { text: new Date(cal.displayYear, cal.displayMonth, 1).toLocaleDateString(Qt.locale(), "MMMM yyyy") color: root.t.clockPopupText font.pixelSize: 13; font.bold: true Layout.fillWidth: true; horizontalAlignment: Text.AlignHCenter } MonthNavButton { direction: 1 } } Item { id: cal Layout.fillWidth: true implicitHeight: gridCol.implicitHeight property int displayYear: TimezoneProvider.toTz(timeManager.now).getFullYear() property int displayMonth: TimezoneProvider.toTz(timeManager.now).getMonth() property int pendingYear: displayYear property int pendingMonth: displayMonth property int slideDir: 1 Connections { target: calendarPopup function onPopupOpenChanged() { if (calendarPopup.popupOpen) { let tzd = TimezoneProvider.toTz(timeManager.now) cal.displayYear = tzd.getFullYear() cal.displayMonth = tzd.getMonth() } } } SequentialAnimation { id: slideAnim NumberAnimation { target: gridCol; property: "x" to: -cal.slideDir * cal.width duration: 80; easing.type: Easing.InCubic } ScriptAction { script: { cal.displayMonth = cal.pendingMonth cal.displayYear = cal.pendingYear gridCol.x = cal.slideDir * cal.width } } NumberAnimation { target: gridCol; property: "x" to: 0 duration: 100; easing.type: Easing.OutCubic } } Item { id: calGridClip anchors.fill: parent clip: true ColumnLayout { id: gridCol width: parent.width spacing: 2 Row { spacing: 0 Repeater { model: ["Mo","Tu","We","Th","Fr","Sa","Su"] Text { width: (calendarPopup.card.width - 32) / 7 text: modelData; color: root.t.clockPopupDim font.pixelSize: 11; horizontalAlignment: Text.AlignHCenter } } } Repeater { model: 6 Row { id: weekRow property int weekIndex: index Repeater { model: 7 delegate: Item { width: (calendarPopup.card.width - 32) / 7; height: 26 readonly property int dayNum: (weekRow.weekIndex * 7 + index) - ((new Date(cal.displayYear, cal.displayMonth, 1).getDay() + 6) % 7) + 1 readonly property bool inMonth: dayNum >= 1 && dayNum <= new Date(cal.displayYear, cal.displayMonth + 1, 0).getDate() readonly property bool isToday: { let tzd = TimezoneProvider.toTz(timeManager.now) return inMonth && dayNum === tzd.getDate() && cal.displayYear === tzd.getFullYear() && cal.displayMonth === tzd.getMonth() } Rectangle { anchors.centerIn: parent; width: 22; height: 22; radius: 11 color: isToday ? root.t.accent : "transparent" border.color: isToday ? root.t.borderToday : "transparent" border.width: isToday ? 2 : 0 visible: inMonth } Text { anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter text: inMonth ? dayNum : "" color: isToday ? root.t.todayText : (index >= 5 ? root.t.clockPopupDim : root.t.clockPopupText) font.pixelSize: 12; font.bold: isToday } } } } } } } } Rectangle { Layout.fillWidth: true; height: 1; color: root.t.calSeparator } RowLayout { Layout.fillWidth: true Layout.leftMargin: 6 Layout.rightMargin: 6 Text { text: "UTC " + (TimezoneProvider.tzOffsetHours() >= 0 ? "+" : "") + TimezoneProvider.tzOffsetHours() color: root.t.clockPopupUtc font.pixelSize: 13 font.bold: true font.family: "monospace" } Item { Layout.fillWidth: true } Text { text: { let d = TimezoneProvider.toTz(timeManager.now) let month = d.getMonth() + 1 let day = d.getDate() let year = d.getFullYear() if (Cfg.Config.dateFormat === "DMY") return day + "/" + month + "/" + year return month + "/" + day + "/" + year } color: root.t.clockPopupDate font.pixelSize: 14 font.bold: true font.family: "monospace" } } Item { height: 4 } } } }