Clock: new approach to bar clock display based on tokens.

This commit is contained in:
LemmyCook
2025-09-19 23:18:59 -04:00
parent 2cd73c265d
commit f3f8b82fdd
8 changed files with 790 additions and 213 deletions
-5
View File
@@ -142,10 +142,6 @@ Singleton {
adapter.location.use12hourFormat = widget.use12HourClock
delete widget.use12HourClock
}
if (widget.reverseDayMonth !== undefined) {
adapter.location.monthBeforeDay = widget.reverseDayMonth
delete widget.reverseDayMonth
}
break
}
@@ -331,7 +327,6 @@ Singleton {
property string name: defaultLocation
property bool useFahrenheit: false
property bool use12hourFormat: false
property bool monthBeforeDay: false
property bool showWeekNumberInCalendar: false
}
+1 -30
View File
@@ -15,36 +15,7 @@ Singleton {
return Math.floor(date / 1000)
}
function formatDate(monthBeforeDay = true) {
let now = date
let dayName = now.toLocaleDateString(Qt.locale(), "ddd")
dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1)
let day = now.getDate()
let suffix
if (day > 3 && day < 21)
suffix = 'th'
else
switch (day % 10) {
case 1:
suffix = "st"
break
case 2:
suffix = "nd"
break
case 3:
suffix = "rd"
break
default:
suffix = "th"
}
let month = now.toLocaleDateString(Qt.locale(), "MMMM")
let year = now.toLocaleDateString(Qt.locale(), "yyyy")
return `${dayName}, ` + (monthBeforeDay ? `${month} ${day}${suffix} ${year}` : `${day}${suffix} ${month} ${year}`)
}
/**
/**
* Formats a Date object into a YYYYMMDD-HHMMSS string.
* @param {Date} [date=new Date()] - The date to format. Defaults to the current date and time.
* @returns {string} The formatted date string.
+55 -145
View File
@@ -29,167 +29,77 @@ Rectangle {
}
readonly property string barPosition: Settings.data.bar.position
readonly property bool isBarVertical: barPosition === "left" || barPosition === "right"
readonly property bool compact: (Settings.data.bar.density === "compact")
readonly property bool use12h: Settings.data.location.use12hourFormat
readonly property bool monthBeforeDay: Settings.data.location.monthBeforeDay
readonly property var now: Time.date
// Resolve settings: try user settings or defaults from BarWidgetRegistry
readonly property string displayFormat: widgetSettings.displayFormat !== undefined ? widgetSettings.displayFormat : widgetMetadata.displayFormat
readonly property bool usePrimaryColor: widgetSettings.usePrimaryColor !== undefined ? widgetSettings.usePrimaryColor : widgetMetadata.usePrimaryColor
property bool useMonospacedFont: widgetSettings.useMonospacedFont !== undefined ? widgetSettings.useMonospacedFont : widgetMetadata.useMonospacedFont
readonly property string line1: widgetSettings.line1 !== undefined ? widgetSettings.line1 : widgetMetadata.line1
readonly property string line2: widgetSettings.line2 !== undefined ? widgetSettings.line2 : widgetMetadata.line2
readonly property string line3: widgetSettings.line3 !== undefined ? widgetSettings.line3 : widgetMetadata.line3
readonly property string line4: widgetSettings.line4 !== undefined ? widgetSettings.line4 : widgetMetadata.line4
// Use compact mode for vertical bars
readonly property bool verticalMode: barPosition === "left" || barPosition === "right"
implicitWidth: verticalMode ? Math.round(Style.capsuleHeight * scaling) : Math.round(layout.implicitWidth + Style.marginM * 2 * scaling)
implicitHeight: verticalMode ? Math.round(Style.capsuleHeight * 2.5 * scaling) : Math.round(Style.capsuleHeight * scaling) // Match BarPill
implicitWidth: isBarVertical ? Math.round(Style.capsuleHeight * scaling) : Math.round(layout.implicitWidth + Style.marginM * 2 * scaling)
implicitHeight: isBarVertical ? Math.round(Style.capsuleHeight * 2.5 * scaling) : Math.round(Style.capsuleHeight * scaling) // Match BarPill
radius: Math.round(Style.radiusS * scaling)
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
Item {
id: clockContainer
anchors.fill: parent
anchors.margins: compact ? 0 : Style.marginXS * scaling
anchors.centerIn: parent
// anchors.margins: compact ? 0 : Style.marginXS * scaling
ColumnLayout {
id: layout
anchors.centerIn: parent
spacing: verticalMode ? -2 * scaling : -3 * scaling
spacing: isBarVertical ? -2 * scaling : -3 * scaling
// Compact mode for vertical bars - Time section (HH, MM)
Repeater {
model: verticalMode ? 2 : 1
NText {
readonly property bool showSeconds: (displayFormat === "time-seconds")
readonly property bool inlineDate: (displayFormat === "time-date")
readonly property var now: Time.date
text: {
if (verticalMode) {
// Compact mode: time section (first 2 lines)
switch (index) {
case 0:
// Hours
if (use12h) {
const hours = now.getHours()
const displayHours = hours === 0 ? 12 : (hours > 12 ? hours - 12 : hours)
return displayHours.toString().padStart(2, '0')
} else {
return now.getHours().toString().padStart(2, '0')
}
case 1:
// Minutes
return now.getMinutes().toString().padStart(2, '0')
default:
return ""
}
} else {
// Normal mode: single line with time
let timeStr = ""
if (use12h) {
// 12-hour format with proper padding and consistent spacing
const hours = now.getHours()
const displayHours = hours === 0 ? 12 : (hours > 12 ? hours - 12 : hours)
const paddedHours = displayHours.toString().padStart(2, '0')
const minutes = now.getMinutes().toString().padStart(2, '0')
const ampm = hours < 12 ? 'AM' : 'PM'
if (showSeconds) {
const seconds = now.getSeconds().toString().padStart(2, '0')
timeStr = `${paddedHours}:${minutes}:${seconds} ${ampm}`
} else {
timeStr = `${paddedHours}:${minutes} ${ampm}`
}
} else {
// 24-hour format with padding
const hours = now.getHours().toString().padStart(2, '0')
const minutes = now.getMinutes().toString().padStart(2, '0')
if (showSeconds) {
const seconds = now.getSeconds().toString().padStart(2, '0')
timeStr = `${hours}:${minutes}:${seconds}`
} else {
timeStr = `${hours}:${minutes}`
}
}
// Add inline date if needed
if (inlineDate) {
let dayName = now.toLocaleDateString(Qt.locale(), "ddd")
dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1)
const day = now.getDate().toString().padStart(2, '0')
let month = now.toLocaleDateString(Qt.locale(), "MMM")
timeStr += " - " + (monthBeforeDay ? `${dayName}, ${month} ${day}` : `${dayName}, ${day} ${month}`)
}
return timeStr
}
}
font.family: Settings.data.ui.fontFixed
font.pointSize: verticalMode ? Style.fontSizeXXS * scaling : Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold
color: Color.mPrimary
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
}
}
// Separator line for compact mode (between time and date)
Rectangle {
visible: verticalMode
Layout.preferredWidth: 20 * scaling
Layout.preferredHeight: 2 * scaling
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 3 * scaling
Layout.bottomMargin: 3 * scaling
color: Color.mPrimary
opacity: 0.3
radius: 1 * scaling
}
// Compact mode for vertical bars - Date section (DD, MM)
Repeater {
model: verticalMode ? 2 : 0
NText {
readonly property var now: Time.date
text: {
if (verticalMode) {
// Compact mode: date section (last 2 lines)
switch (index) {
case 0:
return monthBeforeDay ? (now.getMonth() + 1).toString().padStart(2, '0') : now.getDate().toString().padStart(2, '0')
case 1:
return monthBeforeDay ? now.getDate().toString().padStart(2, '0') : (now.getMonth() + 1).toString().padStart(2, '0')
default:
return ""
}
}
return ""
}
font.pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightBold
color: Color.mPrimary
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
}
}
// Second line for normal mode (date)
NText {
visible: !verticalMode && (displayFormat === "time-date-short")
text: {
const now = Time.date
const day = now.getDate().toString().padStart(2, '0')
const month = (now.getMonth() + 1).toString().padStart(2, '0')
return monthBeforeDay ? `${month}/${day}` : `${day}/${month}`
}
visible: text !== ""
text: Qt.formatDateTime(now, line1.trim())
font.family: useMonospacedFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault
font.pointSize: isBarVertical ? Style.fontSizeS * scaling : Style.fontSizeS * scaling
font.weight: Style.fontWeightBold
color: usePrimaryColor ? Color.mPrimary : Color.mOnSurface
wrapMode: Text.WordWrap
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
}
// Enable fixed-width font for consistent spacing
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightRegular
color: Color.mPrimary
NText {
visible: text !== ""
text: Qt.formatDateTime(now, line2.trim())
font.family: useMonospacedFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault
font.pointSize: isBarVertical ? Style.fontSizeS * scaling : Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold
color: usePrimaryColor ? Color.mPrimary : Color.mOnSurface
wrapMode: Text.WordWrap
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
}
NText {
visible: text !== "" && isBarVertical
text: Qt.formatDateTime(now, line3.trim())
font.family: useMonospacedFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault
font.pointSize: isBarVertical ? Style.fontSizeS * scaling : Style.fontSizeS * scaling
font.weight: Style.fontWeightBold
color: usePrimaryColor ? Color.mPrimary : Color.mOnSurface
wrapMode: Text.WordWrap
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.topMargin: visible ? Style.marginXS * scaling : 0
}
NText {
visible: text !== "" && isBarVertical
text: Qt.formatDateTime(now, line4.trim())
font.family: useMonospacedFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault
font.pointSize: isBarVertical ? Style.fontSizeS * scaling : Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold
color: usePrimaryColor ? Color.mPrimary : Color.mOnSurface
wrapMode: Text.WordWrap
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
}
}
@@ -197,7 +107,7 @@ Rectangle {
NTooltip {
id: tooltip
text: `${Time.formatDate(monthBeforeDay)}.`
text: "Open calendar"
target: clockContainer
positionAbove: Settings.data.bar.position === "bottom"
}
@@ -19,7 +19,7 @@ Popup {
x: (parent.width - width) * 0.5
y: (parent.height - height) * 0.5
width: 500 * scaling
width: Math.max(content.implicitWidth + padding * 2, 500 * scaling)
height: content.implicitHeight + padding * 2
padding: Style.marginXL * scaling
modal: true
@@ -8,42 +8,327 @@ import qs.Services
ColumnLayout {
id: root
spacing: Style.marginM * scaling
width: 700 * scaling
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
// Local state
property string valueDisplayFormat: widgetData.displayFormat !== undefined ? widgetData.displayFormat : widgetMetadata.displayFormat
property bool valueUsePrimaryColor: widgetData.usePrimaryColor !== undefined ? widgetData.usePrimaryColor : widgetMetadata.usePrimaryColor
property bool valueUseMonospacedFont: widgetData.useMonospacedFont !== undefined ? widgetData.useMonospacedFont : widgetMetadata.useMonospacedFont
property string valueLine1: widgetData.line1 !== undefined ? widgetData.line1 : widgetMetadata.line1
property string valueLine2: widgetData.line2 !== undefined ? widgetData.line2 : widgetMetadata.line2
property string valueLine3: widgetData.line3 !== undefined ? widgetData.line3 : widgetMetadata.line3
property string valueLine4: widgetData.line4 !== undefined ? widgetData.line4 : widgetMetadata.line4
// Track the currently focused input field
property var focusedInput: null
property int focusedLineIndex: -1
readonly property var now: Time.date
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.displayFormat = valueDisplayFormat
settings.usePrimaryColor = valueUsePrimaryColor
settings.useMonospacedFont = valueUseMonospacedFont
settings.line1 = valueLine1.trim()
settings.line2 = valueLine2.trim()
settings.line3 = valueLine3.trim()
settings.line4 = valueLine4.trim()
return settings
}
NComboBox {
label: "Display format"
model: ListModel {
ListElement {
key: "time"
name: "HH:mm"
}
ListElement {
key: "time-seconds"
name: "HH:mm:ss"
}
ListElement {
key: "time-date"
name: "HH:mm + Date"
}
ListElement {
key: "time-date-short"
name: "HH:mm + Short date"
// Function to insert token at cursor position in the focused input
function insertToken(token) {
if (!focusedInput || !focusedInput.inputItem) {
// If no input is focused, default to line 1
if (inputLine1.inputItem) {
inputLine1.inputItem.focus = true
focusedInput = inputLine1
focusedLineIndex = 1
}
}
currentKey: valueDisplayFormat
onSelected: key => valueDisplayFormat = key
minimumWidth: 230 * scaling
if (focusedInput && focusedInput.inputItem) {
var input = focusedInput.inputItem
var cursorPos = input.cursorPosition
var currentText = input.text
// Insert token at cursor position
var newText = currentText.substring(0, cursorPos) + token + currentText.substring(cursorPos)
input.text = newText + " "
// Move cursor after the inserted token
input.cursorPosition = cursorPos + token.length + 1
// Ensure the input keeps focus
input.focus = true
}
}
// Function to update the value property based on which line was edited
function updateLineValue(lineIndex, text) {
switch (lineIndex) {
case 1:
valueLine1 = text
break
case 2:
valueLine2 = text
break
case 3:
valueLine3 = text
break
case 4:
valueLine4 = text
break
}
}
NToggle {
Layout.fillWidth: true
label: "Use primary color"
description: "When enabled, this applies the primary color for emphasis."
checked: valueUsePrimaryColor
onToggled: checked => valueUsePrimaryColor = checked
}
NToggle {
Layout.fillWidth: true
label: "Use monospaced font"
description: "When enabled, the clock will use the monospaced font."
checked: valueUseMonospacedFont
onToggled: checked => valueUseMonospacedFont = checked
}
NDivider {
Layout.fillWidth: true
}
NHeader {
label: "Clock format"
description: "Build your clock display using the tokens below.\nAdditional lines (3 & 4) are only shown in the vertical bar layout.\nClick on any token to insert it into the selected input field."
}
RowLayout {
id: main
Layout.fillWidth: true
spacing: Style.marginL * scaling
ColumnLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1 // Equal sizing hint
spacing: Style.marginM * scaling
NTextInput {
id: inputLine1
Layout.fillWidth: true
label: "1st line"
placeholderText: "HH:mm"
text: valueLine1
onTextChanged: updateLineValue(1, text)
// Track focus state
Component.onCompleted: {
if (inputItem) {
inputItem.onActiveFocusChanged.connect(function () {
if (inputItem.activeFocus) {
root.focusedInput = inputLine1
root.focusedLineIndex = 1
}
})
}
}
}
NTextInput {
id: inputLine2
Layout.fillWidth: true
label: "2nd line"
placeholderText: "ddd MMM d"
text: valueLine2
onTextChanged: updateLineValue(2, text)
// Track focus state
Component.onCompleted: {
if (inputItem) {
inputItem.onActiveFocusChanged.connect(function () {
if (inputItem.activeFocus) {
root.focusedInput = inputLine2
root.focusedLineIndex = 2
}
})
}
}
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1 // Equal sizing hint
spacing: Style.marginM * scaling
NTextInput {
id: inputLine3
Layout.fillWidth: true
label: "3rd line"
placeholderText: ""
text: valueLine3
onTextChanged: updateLineValue(3, text)
// Track focus state
Component.onCompleted: {
if (inputItem) {
inputItem.onActiveFocusChanged.connect(function () {
if (inputItem.activeFocus) {
root.focusedInput = inputLine3
root.focusedLineIndex = 3
}
})
}
}
}
NTextInput {
id: inputLine4
Layout.fillWidth: true
label: "4th line"
placeholderText: ""
text: valueLine4
onTextChanged: updateLineValue(4, text)
// Track focus state
Component.onCompleted: {
if (inputItem) {
inputItem.onActiveFocusChanged.connect(function () {
if (inputItem.activeFocus) {
root.focusedInput = inputLine4
root.focusedLineIndex = 4
}
})
}
}
}
}
// --------------
// Preview
ColumnLayout {
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
Layout.fillWidth: false // Don't stretch this column
NLabel {
label: "Preview"
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
}
Rectangle {
Layout.preferredWidth: 240 * scaling
Layout.preferredHeight: 120 * scaling // Fixed height instead of fillHeight
color: Color.mSurfaceVariant
radius: Style.radiusM * scaling
border.color: focusedLineIndex > 0 ? Color.mSecondary : Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
Behavior on border.color {
ColorAnimation {
duration: Style.animationFast
}
}
ColumnLayout {
spacing: -2 * scaling
anchors.centerIn: parent
NText {
visible: text !== ""
text: Qt.formatDateTime(now, valueLine1.trim())
font.family: valueUseMonospacedFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: valueUsePrimaryColor ? Color.mPrimary : Color.mOnSurface
wrapMode: Text.WordWrap
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
NText {
visible: text !== ""
text: Qt.formatDateTime(now, valueLine2.trim())
font.family: valueUseMonospacedFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: valueUsePrimaryColor ? Color.mPrimary : Color.mOnSurface
wrapMode: Text.WordWrap
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
NText {
visible: text !== ""
text: Qt.formatDateTime(now, valueLine3.trim())
font.family: valueUseMonospacedFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: valueUsePrimaryColor ? Color.mPrimary : Color.mOnSurface
wrapMode: Text.WordWrap
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.topMargin: visible ? Style.marginXS * scaling : 0
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
NText {
visible: text !== ""
text: Qt.formatDateTime(now, valueLine4.trim())
font.family: valueUseMonospacedFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: valueUsePrimaryColor ? Color.mPrimary : Color.mOnSurface
wrapMode: Text.WordWrap
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
}
}
}
}
NDivider {
Layout.topMargin: Style.marginL * scaling
Layout.bottomMargin: Style.marginL * scaling
}
NHeader {
label: "Tokens"
description: focusedLineIndex > 0 ? "Click any token to add it to line " + focusedLineIndex : "Select an input field above, then click a token to insert it."
}
NDateTimeTokens {
Layout.fillWidth: true
height: 400 * scaling
// Connect to token clicked signal if NDateTimeTokens provides it
onTokenClicked: token => root.insertToken(token)
}
}
+1 -8
View File
@@ -93,19 +93,12 @@ ColumnLayout {
}
NToggle {
label: "Use 12-hour time format"
label: "Use 12-hour time format on the lock screen"
description: "On for AM/PM format (e.g., 8:00 PM), off for 24-hour format (e.g., 20:00)."
checked: Settings.data.location.use12hourFormat
onToggled: checked => Settings.data.location.use12hourFormat = checked
}
NToggle {
label: "Show month before day"
description: "On for 09/17/2025, off for 17/09/2025."
checked: Settings.data.location.monthBeforeDay
onToggled: checked => Settings.data.location.monthBeforeDay = checked
}
NToggle {
label: "Show week numbers"
description: "Displays the week of the year (e.g., Week 38) in the calendar."
+6 -1
View File
@@ -53,7 +53,12 @@ Singleton {
},
"Clock": {
"allowUserSettings": true,
"displayFormat": "time-date-short"
"usePrimaryColor": true,
"useMonospacedFont": true,
"line1": "HH:mm ddd, MMM dd",
"line2": "",
"line3": "",
"line4": ""
},
"CustomButton": {
"allowUserSettings": true,
+418
View File
@@ -0,0 +1,418 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
Rectangle {
id: root
color: Color.mSurfaceVariant
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
radius: Style.radiusM * scaling
property date sampleDate: new Date() // Dec 25, 2023, 2:30:45.123 PM
// Signal emitted when a token is clicked
signal tokenClicked(string token)
ColumnLayout {
id: column
anchors.fill: parent
anchors.margins: Style.marginL * scaling
spacing: Style.marginM * scaling
// Scrollable list of tokens
NScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
ListView {
id: tokensList
model: ListModel {
// Common format combinations
ListElement {
category: "Common"
token: "h:mm AP"
description: "12-hour time with minutes"
example: "2:30 PM"
}
ListElement {
category: "Common"
token: "HH:mm"
description: "24-hour time with minutes"
example: "14:30"
}
ListElement {
category: "Common"
token: "HH:mm:ss"
description: "24-hour time with seconds"
example: "14:30:45"
}
ListElement {
category: "Common"
token: "ddd MMM d"
description: "Weekday, month and day"
example: "Mon Dec 25"
}
ListElement {
category: "Common"
token: "yyyy-MM-dd"
description: "ISO date format"
example: "2023-12-25"
}
ListElement {
category: "Common"
token: "MM/dd/yyyy"
description: "US date format"
example: "12/25/2023"
}
ListElement {
category: "Common"
token: "dd.MM.yyyy"
description: "European date format"
example: "25.12.2023"
}
ListElement {
category: "Common"
token: "ddd, MMM dd"
description: "Weekday with date"
example: "Fri, Dec 12"
}
// Hour tokens
ListElement {
category: "Hour"
token: "h"
description: "Hour without leading zero (12-hour when used with AP/ap, otherwise 24-hour)"
example: "2 (needs AP/ap for 12hr)"
}
ListElement {
category: "Hour"
token: "hh"
description: "Hour with leading zero (12-hour when used with AP/ap, otherwise 24-hour)"
example: "02 (needs AP/ap for 12hr)"
}
ListElement {
category: "Hour"
token: "h AP"
description: "12-hour format with AM/PM"
example: "2 PM"
}
ListElement {
category: "Hour"
token: "hh AP"
description: "12-hour format with leading zero and AM/PM"
example: "02 PM"
}
ListElement {
category: "Hour"
token: "H"
description: "Hour without leading zero (0-23) - 24-hour format"
example: "14"
}
ListElement {
category: "Hour"
token: "HH"
description: "Hour with leading zero (00-23) - 24-hour format"
example: "14"
}
// Minute tokens
ListElement {
category: "Minute"
token: "m"
description: "Minute without leading zero (0-59)"
example: "30"
}
ListElement {
category: "Minute"
token: "mm"
description: "Minute with leading zero (00-59)"
example: "30"
}
// Second tokens
ListElement {
category: "Second"
token: "s"
description: "Second without leading zero (0-59)"
example: "45"
}
ListElement {
category: "Second"
token: "ss"
description: "Second with leading zero (00-59)"
example: "45"
}
// AM/PM tokens
ListElement {
category: "AM/PM"
token: "AP"
description: "AM/PM in uppercase"
example: "PM"
}
ListElement {
category: "AM/PM"
token: "ap"
description: "am/pm in lowercase"
example: "pm"
}
// Timezone tokens
ListElement {
category: "Timezone"
token: "t"
description: "Timezone abbreviation"
example: "UTC"
}
// Year tokens
ListElement {
category: "Year"
token: "yy"
description: "Year as two-digit number (00-99)"
example: "23"
}
ListElement {
category: "Year"
token: "yyyy"
description: "Year as four-digit number"
example: "2023"
}
// Month tokens
ListElement {
category: "Month"
token: "M"
description: "Month as number without leading zero (1-12)"
example: "12"
}
ListElement {
category: "Month"
token: "MM"
description: "Month as number with leading zero (01-12)"
example: "12"
}
ListElement {
category: "Month"
token: "MMM"
description: "Abbreviated month name"
example: "Dec"
}
ListElement {
category: "Month"
token: "MMMM"
description: "Full month name"
example: "December"
}
// Day tokens
ListElement {
category: "Day"
token: "d"
description: "Day without leading zero (1-31)"
example: "25"
}
ListElement {
category: "Day"
token: "dd"
description: "Day with leading zero (01-31)"
example: "25"
}
ListElement {
category: "Day"
token: "ddd"
description: "Abbreviated day name"
example: "Mon"
}
ListElement {
category: "Day"
token: "dddd"
description: "Full day name"
example: "Monday"
}
}
delegate: Rectangle {
id: tokenDelegate
width: tokensList.width
height: 50 * scaling
color: {
if (tokenMouseArea.containsMouse) {
return Qt.alpha(Color.mPrimary, 0.1)
}
return index % 2 === 0 ? Color.mSurface : Color.mSurfaceVariant
}
// Mouse area for the entire delegate
MouseArea {
id: tokenMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
// Emit the signal with the token
root.tokenClicked(model.token)
// Visual feedback
clickAnimation.start()
}
}
// Click animation
SequentialAnimation {
id: clickAnimation
PropertyAnimation {
target: tokenDelegate
property: "color"
to: Qt.alpha(Color.mPrimary, 0.3)
duration: 100
}
PropertyAnimation {
target: tokenDelegate
property: "color"
to: tokenMouseArea.containsMouse ? Qt.alpha(Color.mPrimary, 0.1) : (index % 2 === 0 ? Color.mSurface : Color.mSurfaceVariant)
duration: 200
}
}
RowLayout {
anchors.fill: parent
anchors.margins: Style.marginS * scaling
spacing: Style.marginM * scaling
// Category badge
Rectangle {
width: 70 * scaling
height: 28 * scaling
color: getCategoryColor(model.category)[0]
radius: Style.radiusS * scaling
opacity: tokenMouseArea.containsMouse ? 0.9 : 1.0
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
}
}
NText {
anchors.centerIn: parent
text: model.category
color: getCategoryColor(model.category)[1]
font.pointSize: Style.fontSizeXS * scaling
}
}
// Token - Made more prominent and clickable
Rectangle {
id: tokenButton
width: 100 * scaling
height: 28 * scaling
color: tokenMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurface
radius: Style.radiusS * scaling
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
NText {
anchors.centerIn: parent
text: model.token
color: tokenMouseArea.containsMouse ? Color.mOnPrimary : Color.mSurface
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightBold
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
}
// Description
NText {
Layout.fillWidth: true
text: model.description
color: tokenMouseArea.containsMouse ? Color.mOnSurface : Color.mOnSurfaceVariant
font.pointSize: Style.fontSizeS * scaling
wrapMode: Text.WordWrap
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
// Live example
Rectangle {
width: 90 * scaling
height: 28 * scaling
color: tokenMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurfaceVariant
radius: Style.radiusS * scaling
border.color: tokenMouseArea.containsMouse ? Color.mPrimary : Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
Behavior on border.color {
ColorAnimation {
duration: Style.animationFast
}
}
NText {
anchors.centerIn: parent
text: Qt.formatDateTime(root.sampleDate, model.token)
color: tokenMouseArea.containsMouse ? Color.mOnPrimary : Color.mSurfaceVariant
font.pointSize: Style.fontSizeS * scaling
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
}
}
}
}
}
}
function getCategoryColor(category) {
switch (category) {
case "Year":
return [Color.mPrimary, Color.mOnPrimary]
case "Month":
return [Color.mSecondary, Color.mOnSecondary]
case "Day":
return [Color.mTertiary, Color.mOnTertiary]
case "Hour":
return [Color.mPrimary, Color.mOnPrimary]
case "Minute":
return [Color.mSecondary, Color.mOnSecondary]
case "Second":
return [Color.mTertiary, Color.mOnTertiary]
case "AM/PM":
return [Color.mError, Color.mOnError]
case "Timezone":
return [Color.mOnSurface, Color.mSurface]
case "Common":
return [Color.mError, Color.mOnError]
default:
return [Color.mOnSurfaceVariant, Color.mSurfaceVariant]
}
}
}