// Copyright (C) 2026 Danilo M. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. import Quickshell import QtQuick import "../.." import "../appearance" import "../../island/calendar/Cal.js" as Cal // How the island shows time and dates: the clock pill, the calendar's day // headings, agenda times and hour picker. Module { id: mod name: "island-clock" label: "Date and time" // nf-md-clock_outline, present in Inconsolata Nerd Font. icon: "\u{f0150}" settings: true group: "Island bar" keywords: ["island", "clock", "time", "date", "format", "12-hour", "24-hour", "am", "pm", "day", "month"] searchItems: [{ text: "Clock", key: "h12" }, { text: "Date format", key: "dateFormat" }] property bool h12: Prefs.island.h12 property string dateFormat: Prefs.island.dateFormat dirty: h12 !== Prefs.island.h12 || dateFormat !== Prefs.island.dateFormat function refresh() { if (!mod.dirty) mod.discard(); } function discard() { mod.h12 = Prefs.island.h12; mod.dateFormat = Prefs.island.dateFormat; } page: Component { Page { title: "Date and time" actions: Component { SaveBar { dirty: mod.dirty onSave: Prefs.setIsland({ h12: mod.h12, dateFormat: mod.dateFormat }) onDiscard: mod.discard() } } Flow { id: body width: parent.width spacing: 32 SystemClock { id: now; precision: SystemClock.Minutes } readonly property string today: Cal.today(now.date) // Two made-up events today, one each side of noon. readonly property var events: [ { title: "Standup", allDay: false, start: today + "T09:30", end: today + "T10:00", color: "blue" }, { title: "Dinner", allDay: false, start: today + "T20:30", end: today + "T22:00", color: "green" }] Column { id: controls width: 3 * 120 + 2 * 12 spacing: 6 Heading { text: "Clock" } Hint { text: "The clock pill, the agenda and the hour picker. Event times can also be typed with am and pm (9.30pm)." } Row { objectName: "h12" spacing: 6 Tab { text: "24-hour"; selected: !mod.h12; onClicked: mod.h12 = false } Tab { text: "12-hour"; selected: mod.h12; onClicked: mod.h12 = true } } Item { width: 1; height: 14 } Heading { text: "Date" } Hint { text: "The clock pill. The calendar spells the month out, in the same order." } Row { objectName: "dateFormat" spacing: 12 Repeater { model: Cal.DATE_GROUPS delegate: Column { required property var modelData width: 120 spacing: 2 Text { text: modelData.title color: Theme.subtext bottomPadding: 4 font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } } Repeater { model: modelData.formats delegate: Rectangle { required property string modelData readonly property bool on: mod.dateFormat === modelData width: 120 height: 26 radius: 6 color: on ? Qt.alpha(Theme.accent, 0.25) : pick.containsMouse ? Qt.alpha(Theme.surface, 0.8) : "transparent" border.width: on ? 1 : 0 border.color: Qt.alpha(Theme.accent, 0.5) Text { anchors { left: parent.left; leftMargin: 8; verticalCenter: parent.verticalCenter } text: Qt.formatDate(now.date, parent.modelData) color: parent.on ? Theme.text : Theme.subtext font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 } } MouseArea { id: pick anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: mod.dateFormat = parent.modelData } } } } } } } // Beside the controls when there is room, below them when not. Column { width: Math.max(220, Math.min(400, body.width - controls.width - body.spacing)) spacing: 6 Heading { text: "Preview" } Hint { text: "With the unsaved choice." } // A mock of the island: the clock pill at rest, and the // calendar panel's heading, agenda and hour picker below it. Rectangle { width: parent.width height: mock.implicitHeight + 32 radius: 12 color: Qt.alpha(Theme.surface, 0.35) Column { id: mock anchors { fill: parent; margins: 16 } spacing: 14 Rectangle { anchors.horizontalCenter: parent.horizontalCenter width: pillRow.implicitWidth + 28 height: 34 radius: 17 color: Qt.alpha(Theme.base, 0.92) border.width: 1 border.color: Qt.alpha(Theme.text, 0.12) Row { id: pillRow anchors.centerIn: parent spacing: 10 Text { anchors.verticalCenter: parent.verticalCenter text: Qt.formatDateTime(now.date, Cal.shortDateFmt(mod.dateFormat)) color: Theme.subtext font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } } Text { anchors.verticalCenter: parent.verticalCenter text: Qt.formatDateTime(now.date, Cal.timeFmt(mod.h12)) color: Theme.text font { family: Theme.fontFamily; pixelSize: Theme.fontSize; weight: Font.DemiBold } } } } Rectangle { width: parent.width height: panelCol.implicitHeight + 24 radius: 17 color: Qt.alpha(Theme.base, 0.92) border.width: 1 border.color: Qt.alpha(Theme.text, 0.12) Column { id: panelCol anchors { fill: parent; margins: 12 } spacing: 6 Text { text: Qt.formatDate(now.date, Cal.dayFmt(mod.dateFormat, true)) color: Theme.text font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2; bold: true } } Repeater { model: body.events delegate: Row { required property var modelData spacing: 8 Rectangle { width: 3; height: 16; radius: 1; color: Theme[modelData.color] } Text { width: mod.h12 ? 92 : 82 text: Cal.timeLabel(modelData, body.today, mod.h12) color: Theme.subtext font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 5 } } Text { text: modelData.title color: Theme.text font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } } } } Text { text: "When " + Qt.formatDate(now.date, Cal.dayFmt(mod.dateFormat, false)) color: Theme.subtext font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } } Row { spacing: 2 Repeater { model: [8, 9, 10, 11, 12] delegate: Rectangle { required property int modelData width: 34 height: 26 radius: 6 color: modelData === 9 ? Qt.alpha(Theme.accent, 0.35) : "transparent" Text { anchors.centerIn: parent text: Cal.hourLabel(parent.modelData, mod.h12) color: Theme.text font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } } } } } } } } } } } } } component Heading: Text { font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3; bold: true } color: Theme.text } component Hint: Text { width: parent ? parent.width : 0 wrapMode: Text.WordWrap color: Theme.overlay font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } } }