The three components were three takes on the same shape \u2014 a clickable rectangle with text/icon, a hot/hover state, an optional persistent border, and an optional 'selected' or 'active' highlight. CursorPill was a 30-line PillButton wrapper that added a hovered() signal; ChoiceButton was effectively PillButton with selected: bool painting an accent fill+border. Collapse them into a single qs.Ui.Button. State flags compose independently: hasCursor / hover hot fill active persistent foreground-tint fill selected accent fill + accent border bordered: true persistent 1px idle border (form primaries) focusable: true Tab focus paints the accent ring pressed pressed fill The hovered(bool) signal is now built-in, so CursorPill's wrapper is unnecessary. ButtonGroup wraps a Row+Repeater for the form-style 'pick one of N' pattern; panel-cursor-driven cases still compose Buttons directly in a Row with per-instance hasCursor wiring. Theme tokens move into a new [style] section in shell.toml: border-width = 1 focus-border-width = 3 idle-border-alpha = 0.4 hot-fill-alpha = 0.08 selected-fill-alpha = 0.18 pressed-fill-alpha = 0.22 focus-fill-alpha = 0.22 Style.qml parses these out of the same shell.toml [font] / [bar] already reads, and exposes pre-computed Style.hotFill / selectedFill / pressedFill / idleBorderColor / selectedAccentFill / borderWidth + the existing focusBorder* tokens. Themes that don't ship a [style] section get the previous defaults unchanged. Dev gallery consolidates three sections (PillButton, CursorPill, ChoiceButton) into Button + ButtonGroup, with the cursor model sections renamed accordingly.
201 lines
5.3 KiB
QML
201 lines
5.3 KiB
QML
import QtQuick
|
|
import QtQuick.Layouts
|
|
import Quickshell
|
|
import qs.Ui
|
|
import qs.Commons
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property QtObject bar: null
|
|
property string moduleName: "calendar"
|
|
property var settings: ({})
|
|
|
|
property date now: new Date()
|
|
property date viewMonth: new Date()
|
|
property bool popupOpen: false
|
|
|
|
function closePopout() { popupOpen = false }
|
|
|
|
function setting(name, fallback) {
|
|
var value = settings ? settings[name] : undefined
|
|
return value === undefined || value === null ? fallback : value
|
|
}
|
|
|
|
function formatLabel() {
|
|
if (!bar) return ""
|
|
var fmt = bar.vertical
|
|
? String(setting("verticalFormat", "HH\n—\nmm"))
|
|
: String(setting("format", "dddd HH:mm"))
|
|
return Qt.formatDateTime(now, fmt)
|
|
}
|
|
|
|
function shiftMonth(delta) {
|
|
var date = new Date(viewMonth)
|
|
date.setDate(1)
|
|
date.setMonth(date.getMonth() + delta)
|
|
viewMonth = date
|
|
}
|
|
|
|
function isoWeek(date) {
|
|
var d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()))
|
|
var day = d.getUTCDay() || 7
|
|
d.setUTCDate(d.getUTCDate() + 4 - day)
|
|
var yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1))
|
|
return Math.ceil((((d - yearStart) / 86400000) + 1) / 7)
|
|
}
|
|
|
|
function tooltipLabel() {
|
|
return Qt.formatDateTime(root.now, "dd MMMM yyyy") + " Week " + root.isoWeek(root.now)
|
|
}
|
|
|
|
implicitWidth: button.implicitWidth
|
|
implicitHeight: button.implicitHeight
|
|
|
|
SystemClock {
|
|
id: clockTimer
|
|
precision: SystemClock.Minutes
|
|
onDateChanged: root.now = clockTimer.date
|
|
}
|
|
|
|
WidgetButton {
|
|
id: button
|
|
anchors.fill: parent
|
|
bar: root.bar
|
|
text: root.formatLabel()
|
|
horizontalMargin: 8.75
|
|
verticalPadding: 8.75
|
|
tooltipText: root.tooltipLabel()
|
|
|
|
onPressed: function(b) {
|
|
if (b === Qt.RightButton) {
|
|
root.bar.run("omarchy-launch-floating-terminal-with-presentation omarchy-tz-select")
|
|
} else {
|
|
root.viewMonth = new Date(root.now)
|
|
root.popupOpen = !root.popupOpen
|
|
}
|
|
}
|
|
}
|
|
|
|
PopupCard {
|
|
id: popup
|
|
anchorItem: button
|
|
bar: root.bar
|
|
owner: root
|
|
open: root.popupOpen
|
|
contentWidth: 300
|
|
contentHeight: header.implicitHeight + grid.implicitHeight + 36
|
|
|
|
Column {
|
|
anchors.fill: parent
|
|
spacing: 8
|
|
|
|
Item {
|
|
id: header
|
|
width: parent.width
|
|
implicitHeight: 28
|
|
|
|
Button {
|
|
id: prevButton
|
|
anchors.left: parent.left
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
iconText: ""
|
|
foreground: root.bar.foreground
|
|
horizontalPadding: 8
|
|
verticalPadding: 4
|
|
onClicked: root.shiftMonth(-1)
|
|
}
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: Qt.formatDate(root.viewMonth, "MMMM yyyy")
|
|
color: root.bar.foreground
|
|
font.family: root.bar.fontFamily
|
|
font.pixelSize: Style.font.title
|
|
font.bold: true
|
|
}
|
|
|
|
Button {
|
|
id: nextButton
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
iconText: ""
|
|
foreground: root.bar.foreground
|
|
horizontalPadding: 8
|
|
verticalPadding: 4
|
|
onClicked: root.shiftMonth(1)
|
|
}
|
|
}
|
|
|
|
Grid {
|
|
id: grid
|
|
columns: 7
|
|
rowSpacing: 4
|
|
columnSpacing: 4
|
|
width: parent.width
|
|
|
|
Repeater {
|
|
model: ["S", "M", "T", "W", "T", "F", "S"]
|
|
|
|
Item {
|
|
required property string modelData
|
|
width: (grid.width - grid.columnSpacing * 6) / 7
|
|
height: 18
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: modelData
|
|
color: Qt.darker(root.bar.foreground, 1.6)
|
|
font.family: root.bar.fontFamily
|
|
font.pixelSize: Style.font.bodySmall
|
|
font.bold: true
|
|
}
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: 42
|
|
|
|
Rectangle {
|
|
required property int index
|
|
|
|
readonly property var startOfMonth: {
|
|
var d = new Date(root.viewMonth)
|
|
d.setDate(1)
|
|
return d
|
|
}
|
|
readonly property int firstDayOffset: startOfMonth.getDay()
|
|
readonly property var dayDate: {
|
|
var d = new Date(startOfMonth)
|
|
d.setDate(d.getDate() + index - firstDayOffset)
|
|
return d
|
|
}
|
|
readonly property bool inMonth: dayDate.getMonth() === root.viewMonth.getMonth()
|
|
readonly property bool isToday: {
|
|
var n = root.now
|
|
return dayDate.getDate() === n.getDate() && dayDate.getMonth() === n.getMonth() && dayDate.getFullYear() === n.getFullYear()
|
|
}
|
|
|
|
width: (grid.width - grid.columnSpacing * 6) / 7
|
|
height: 28
|
|
radius: 4
|
|
color: isToday ? root.bar.foreground : "transparent"
|
|
border.color: isToday ? root.bar.foreground : "transparent"
|
|
border.width: isToday ? 1 : 0
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: dayDate.getDate()
|
|
color: isToday ? root.bar.background : (inMonth ? root.bar.foreground : Qt.darker(root.bar.foreground, 2.2))
|
|
font.family: root.bar.fontFamily
|
|
font.pixelSize: Style.font.body
|
|
font.bold: isToday
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|