From 46f70f3836ec494979c024e9ff4ec544643c0a9e Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Fri, 14 Nov 2025 15:38:16 +0100 Subject: ... --- accounts/gkleen@sif/shell/quickshell/Bar.qml | 4 +- .../shell/quickshell/Services/Worktime.qml | 75 ++++++++ .../gkleen@sif/shell/quickshell/WorktimeWidget.qml | 193 +++++++++++++++------ home-modules/quickshell.nix | 3 + 4 files changed, 220 insertions(+), 55 deletions(-) create mode 100644 accounts/gkleen@sif/shell/quickshell/Services/Worktime.qml diff --git a/accounts/gkleen@sif/shell/quickshell/Bar.qml b/accounts/gkleen@sif/shell/quickshell/Bar.qml index c998c335..c0ff4dac 100644 --- a/accounts/gkleen@sif/shell/quickshell/Bar.qml +++ b/accounts/gkleen@sif/shell/quickshell/Bar.qml @@ -66,9 +66,7 @@ PanelWindow { anchors.verticalCenter: parent.verticalCenter spacing: 0 - // WorktimeWidget { command: "time"; } - - // WorktimeWidget { command: "today"; } + WorktimeWidget {} KeyboardLayout {} diff --git a/accounts/gkleen@sif/shell/quickshell/Services/Worktime.qml b/accounts/gkleen@sif/shell/quickshell/Services/Worktime.qml new file mode 100644 index 00000000..fdb45aa0 --- /dev/null +++ b/accounts/gkleen@sif/shell/quickshell/Services/Worktime.qml @@ -0,0 +1,75 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import QtQml + +Singleton { + id: root + + property alias time: timeState + property alias today: todayState + + CommandState { + id: timeState + command: "time" + } + CommandState { + id: todayState + command: "today" + } + + component CommandState : Scope { + id: commandState + + required property string command + property var state: null + + property bool strikeout: !strikeoutTimer.running + property alias running: process.running + property alias updating: updateTimer.running + + Process { + id: process + running: true + command: [ @worktime@, commandState.command, "--waybar" ] + stdout: StdioCollector { + id: processCollector + onStreamFinished: { + try { + commandState.state = JSON.parse(processCollector.text); + strikeoutTimer.restart(); + } catch (e) { + console.warn("Worktime: Failed to parse output:", processCollector.text, e); + } + } + } + } + + Timer { + id: updateTimer + running: commandState.state?.class == "running" || commandState.state?.class == "over" + interval: 60000 + repeat: true + onTriggered: process.running = true + } + + Timer { + id: strikeoutTimer + running: false + interval: 5 * updateTimer.interval + repeat: false + } + } + + Timer { + running: Boolean(timeState.state) && Boolean(todayState.state) && timeState.strikeout && todayState.strikeout + interval: 1000 + repeat: false + onTriggered: { + timeState.state = null; + todayState.state = null; + } + } +} diff --git a/accounts/gkleen@sif/shell/quickshell/WorktimeWidget.qml b/accounts/gkleen@sif/shell/quickshell/WorktimeWidget.qml index 04bcc581..0e07ff59 100644 --- a/accounts/gkleen@sif/shell/quickshell/WorktimeWidget.qml +++ b/accounts/gkleen@sif/shell/quickshell/WorktimeWidget.qml @@ -1,81 +1,125 @@ import QtQml import Quickshell -import Quickshell.Io import QtQuick import Quickshell.Widgets +import qs.Services Item { id: root - required property string command - property var state: null - height: parent.height - width: label.contentWidth + 8 + width: (timeWidget.visible ? timeWidget.label.contentWidth + 8 : 0) + (todayWidget.visible ? todayWidget.label.contentWidth + 8 : 0) + (icon.visible ? icon.implicitWidth + 8 : 0) anchors.verticalCenter: parent.verticalCenter - Process { - id: process - running: true - command: [ @worktime@, root.command, "--waybar" ] - stdout: StdioCollector { - id: processCollector - onStreamFinished: { - try { - root.state = JSON.parse(processCollector.text); - } catch (e) { - console.warn("Worktime: Failed to parse output:", processCollector.text, e); + component TextWidget : Item { + id: textWidget + + visible: textWidget.state.state?.text ?? false + + required property var state + property alias label: label + property alias mouseArea: mouseArea + + anchors.verticalCenter: parent.verticalCenter + implicitWidth: label.contentWidth + 8 + height: parent.height + + WrapperMouseArea { + id: mouseArea + + anchors.fill: parent + + enabled: true + hoverEnabled: true + acceptedButtons: Qt.NoButton + cursorShape: Qt.PointingHandCursor + + Item { + anchors.fill: parent + + Text { + id: label + + anchors.centerIn: parent + + text: textWidget.state.state?.text ?? "" + + font.pointSize: 10 + font.family: "Fira Sans" + font.strikeout: textWidget.state.strikeout + color: { + if (textWidget.state.state?.class == "running") + return "white"; + if (textWidget.state.state?.class == "over") + return "#f28a21"; + return "#555"; + } } } } } - Timer { - running: true - interval: 60 - repeat: true - onTriggered: process.running = true - } - WrapperMouseArea { id: mouseArea anchors.fill: parent - - enabled: true hoverEnabled: true - Item { - anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + Worktime.time.running = true; + Worktime.today.running = true; + } - Text { - id: label + Rectangle { + anchors.fill: parent + color: { + if (mouseArea.containsMouse) + return "#33808080"; + return "transparent"; + } + Row { + height: parent.height anchors.centerIn: parent + spacing: 0 + + TextWidget { + id: timeWidget + state: Worktime.time + } + + TextWidget { + id: todayWidget + state: Worktime.today + } + + MaterialDesignIcon { + id: icon + + anchors.verticalCenter: parent.verticalCenter - visible: root.state?.text ?? false - text: root.state?.text ?? "" - - font.pointSize: 10 - font.family: "Fira Sans" - color: { - if (root.state?.class == "running") - return "white"; - if (root.state?.class == "over") - return "#f28a21"; - return "#555"; + implicitSize: 14 + + visible: !timeWidget.visible && !todayWidget.visible + + icon: (Worktime.time.running || Worktime.today.running) ? "update" : "timer-off" + color: "#555" } } } } - PopupWindow { + component WorktimePopup : PopupWindow { id: tooltip - property bool nextVisible: Boolean(root.state?.tooltip ?? false) && (mouseArea.containsMouse || tooltipMouseArea.containsMouse) + required property var state + required property var mouseArea + + property bool nextVisible: (tooltipText.visible || tooltipIcon.visible) && (tooltip.mouseArea.containsMouse || tooltipMouseArea.containsMouse) anchor { - item: mouseArea + item: tooltip.mouseArea edges: Edges.Bottom | Edges.Left } visible: false @@ -88,8 +132,8 @@ Item { onTriggered: tooltip.visible = tooltip.nextVisible } - implicitWidth: tooltipText.contentWidth + 16 - implicitHeight: tooltipText.contentHeight + 16 + implicitWidth: (tooltipIcon.visible ? tooltipIcon.implicitWidth : 0) + (tooltipIcon.visible && tooltipText.visible ? 8 : 0) + (tooltipText.visible ? tooltipText.implicitWidth : 0) + 16 + implicitHeight: tooltipText.implicitHeight + 16 color: "black" WrapperMouseArea { @@ -103,18 +147,63 @@ Item { Item { anchors.fill: parent - Text { - id: tooltipText + Row { + id: tooltipLayout - anchors.centerIn: parent + anchors { + left: parent.left + top: parent.top + leftMargin: 8 + topMargin: 8 + verticalCenter: parent.verticalCenter + } - font.pointSize: 10 - font.family: "Fira Sans" - color: "white" + height: parent.height + width: childrenRect.width - text: root.state?.tooltip ?? "" + spacing: 0 + + MaterialDesignIcon { + id: tooltipIcon + + implicitSize: 14 + anchors.verticalCenter: parent.verticalCenter + + visible: tooltip.state.running || !tooltip.state.updating + + icon: tooltip.state.running ? "update" : "timer-off" + } + + Item { + visible: tooltipIcon.visible && tooltipText.visible + height: parent.height + width: 8 + } + + Text { + id: tooltipText + + visible: tooltip.state.state?.tooltip ?? false + + anchors.verticalCenter: parent.verticalCenter + + font.pointSize: 10 + font.family: "Fira Sans" + color: "white" + + text: tooltip.state.state?.tooltip ?? "" + } } } } } + + WorktimePopup { + state: Worktime.time + mouseArea: timeWidget.mouseArea + } + WorktimePopup { + state: Worktime.today + mouseArea: todayWidget.mouseArea + } } diff --git a/home-modules/quickshell.nix b/home-modules/quickshell.nix index dac7089f..79c33920 100644 --- a/home-modules/quickshell.nix +++ b/home-modules/quickshell.nix @@ -53,6 +53,9 @@ in { Documentation = "https://quickshell.org/docs/v${cfg.package.version}"; PartOf = [ "graphical-session.target" ]; After = [ "graphical-session-pre.target" ]; + X-Restart-Triggers = [ + "${config.xdg.configFile."quickshell".source}" + ]; }; Service = { -- cgit v1.2.3