From 32c063aab89324838688a1d1623320cbbd71ad8f Mon Sep 17 00:00:00 2001 From: bbedward Date: Tue, 7 Apr 2026 15:22:46 -0400 Subject: [PATCH] Revert "qml: cut down on inline components for performance" This reverts commit f6e590a518b8cced36ce5ec2ce502c2a2fe57f23. --- quickshell/Common/Appearance.qml | 60 ++- quickshell/Common/AppearanceAnim.qml | 6 - quickshell/Common/AppearanceAnimCurves.qml | 13 - quickshell/Common/AppearanceAnimDurations.qml | 11 - quickshell/Common/AppearanceFontSize.qml | 9 - quickshell/Common/AppearanceRounding.qml | 9 - quickshell/Common/AppearanceSpacing.qml | 9 - quickshell/Modals/WindowRuleCheckboxRow.qml | 54 --- quickshell/Modals/WindowRuleInputField.qml | 17 - quickshell/Modals/WindowRuleModal.qml | 156 +++++-- quickshell/Modals/WindowRuleSectionHeader.qml | 15 - .../Modules/ProcessList/CircleGauge.qml | 161 ------- quickshell/Modules/ProcessList/InfoRow.qml | 29 -- .../Modules/ProcessList/PerformanceCard.qml | 160 ------- .../Modules/ProcessList/PerformanceView.qml | 160 ++++++- .../Modules/ProcessList/ProcessItem.qml | 334 --------------- .../Modules/ProcessList/ProcessListPopout.qml | 158 +++++++ .../Modules/ProcessList/ProcessesView.qml | 398 ++++++++++++++++++ .../Modules/ProcessList/SortableHeader.qml | 74 ---- quickshell/Modules/ProcessList/SystemView.qml | 25 ++ quickshell/Services/NotifWrapper.qml | 147 ------- quickshell/Services/NotificationService.qml | 144 +++++++ 22 files changed, 1051 insertions(+), 1098 deletions(-) delete mode 100644 quickshell/Common/AppearanceAnim.qml delete mode 100644 quickshell/Common/AppearanceAnimCurves.qml delete mode 100644 quickshell/Common/AppearanceAnimDurations.qml delete mode 100644 quickshell/Common/AppearanceFontSize.qml delete mode 100644 quickshell/Common/AppearanceRounding.qml delete mode 100644 quickshell/Common/AppearanceSpacing.qml delete mode 100644 quickshell/Modals/WindowRuleCheckboxRow.qml delete mode 100644 quickshell/Modals/WindowRuleInputField.qml delete mode 100644 quickshell/Modals/WindowRuleSectionHeader.qml delete mode 100644 quickshell/Modules/ProcessList/CircleGauge.qml delete mode 100644 quickshell/Modules/ProcessList/InfoRow.qml delete mode 100644 quickshell/Modules/ProcessList/PerformanceCard.qml delete mode 100644 quickshell/Modules/ProcessList/ProcessItem.qml delete mode 100644 quickshell/Modules/ProcessList/SortableHeader.qml delete mode 100644 quickshell/Services/NotifWrapper.qml diff --git a/quickshell/Common/Appearance.qml b/quickshell/Common/Appearance.qml index 4b08f893..3c225664 100644 --- a/quickshell/Common/Appearance.qml +++ b/quickshell/Common/Appearance.qml @@ -7,8 +7,60 @@ import Quickshell Singleton { id: root - readonly property AppearanceRounding rounding: AppearanceRounding {} - readonly property AppearanceSpacing spacing: AppearanceSpacing {} - readonly property AppearanceFontSize fontSize: AppearanceFontSize {} - readonly property AppearanceAnim anim: AppearanceAnim {} + readonly property Rounding rounding: Rounding {} + readonly property Spacing spacing: Spacing {} + readonly property FontSize fontSize: FontSize {} + readonly property Anim anim: Anim {} + + component Rounding: QtObject { + readonly property int small: 8 + readonly property int normal: 12 + readonly property int large: 16 + readonly property int extraLarge: 24 + readonly property int full: 1000 + } + + component Spacing: QtObject { + readonly property int small: 4 + readonly property int normal: 8 + readonly property int large: 12 + readonly property int extraLarge: 16 + readonly property int huge: 24 + } + + component FontSize: QtObject { + readonly property int small: 12 + readonly property int normal: 14 + readonly property int large: 16 + readonly property int extraLarge: 20 + readonly property int huge: 24 + } + + component AnimCurves: QtObject { + readonly property list standard: [0.2, 0, 0, 1, 1, 1] + readonly property list standardAccel: [0.3, 0, 1, 1, 1, 1] + readonly property list standardDecel: [0, 0, 0, 1, 1, 1] + readonly property list emphasized: [0.05, 0, 2 / 15, 0.06, 1 + / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] + readonly property list emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] + readonly property list emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1] + readonly property list expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1] + readonly property list expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1] + readonly property list expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1] + } + + component AnimDurations: QtObject { + readonly property int quick: 150 + readonly property int normal: 300 + readonly property int slow: 500 + readonly property int extraSlow: 1000 + readonly property int expressiveFastSpatial: 350 + readonly property int expressiveDefaultSpatial: 500 + readonly property int expressiveEffects: 200 + } + + component Anim: QtObject { + readonly property AnimCurves curves: AnimCurves {} + readonly property AnimDurations durations: AnimDurations {} + } } diff --git a/quickshell/Common/AppearanceAnim.qml b/quickshell/Common/AppearanceAnim.qml deleted file mode 100644 index 6bc6fec8..00000000 --- a/quickshell/Common/AppearanceAnim.qml +++ /dev/null @@ -1,6 +0,0 @@ -import QtQuick - -QtObject { - readonly property AppearanceAnimCurves curves: AppearanceAnimCurves {} - readonly property AppearanceAnimDurations durations: AppearanceAnimDurations {} -} diff --git a/quickshell/Common/AppearanceAnimCurves.qml b/quickshell/Common/AppearanceAnimCurves.qml deleted file mode 100644 index bf745ea9..00000000 --- a/quickshell/Common/AppearanceAnimCurves.qml +++ /dev/null @@ -1,13 +0,0 @@ -import QtQuick - -QtObject { - readonly property list standard: [0.2, 0, 0, 1, 1, 1] - readonly property list standardAccel: [0.3, 0, 1, 1, 1, 1] - readonly property list standardDecel: [0, 0, 0, 1, 1, 1] - readonly property list emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] - readonly property list emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] - readonly property list emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1] - readonly property list expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1] - readonly property list expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1] - readonly property list expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1] -} diff --git a/quickshell/Common/AppearanceAnimDurations.qml b/quickshell/Common/AppearanceAnimDurations.qml deleted file mode 100644 index eec095e2..00000000 --- a/quickshell/Common/AppearanceAnimDurations.qml +++ /dev/null @@ -1,11 +0,0 @@ -import QtQuick - -QtObject { - readonly property int quick: 150 - readonly property int normal: 300 - readonly property int slow: 500 - readonly property int extraSlow: 1000 - readonly property int expressiveFastSpatial: 350 - readonly property int expressiveDefaultSpatial: 500 - readonly property int expressiveEffects: 200 -} diff --git a/quickshell/Common/AppearanceFontSize.qml b/quickshell/Common/AppearanceFontSize.qml deleted file mode 100644 index f64154b5..00000000 --- a/quickshell/Common/AppearanceFontSize.qml +++ /dev/null @@ -1,9 +0,0 @@ -import QtQuick - -QtObject { - readonly property int small: 12 - readonly property int normal: 14 - readonly property int large: 16 - readonly property int extraLarge: 20 - readonly property int huge: 24 -} diff --git a/quickshell/Common/AppearanceRounding.qml b/quickshell/Common/AppearanceRounding.qml deleted file mode 100644 index a11707cb..00000000 --- a/quickshell/Common/AppearanceRounding.qml +++ /dev/null @@ -1,9 +0,0 @@ -import QtQuick - -QtObject { - readonly property int small: 8 - readonly property int normal: 12 - readonly property int large: 16 - readonly property int extraLarge: 24 - readonly property int full: 1000 -} diff --git a/quickshell/Common/AppearanceSpacing.qml b/quickshell/Common/AppearanceSpacing.qml deleted file mode 100644 index c25a1608..00000000 --- a/quickshell/Common/AppearanceSpacing.qml +++ /dev/null @@ -1,9 +0,0 @@ -import QtQuick - -QtObject { - readonly property int small: 4 - readonly property int normal: 8 - readonly property int large: 12 - readonly property int extraLarge: 16 - readonly property int huge: 24 -} diff --git a/quickshell/Modals/WindowRuleCheckboxRow.qml b/quickshell/Modals/WindowRuleCheckboxRow.qml deleted file mode 100644 index 9037eacb..00000000 --- a/quickshell/Modals/WindowRuleCheckboxRow.qml +++ /dev/null @@ -1,54 +0,0 @@ -import QtQuick -import qs.Common -import qs.Widgets - -Row { - id: checkboxRow - - property alias checked: checkbox.checked - property alias label: labelText.text - property bool indeterminate: false - - spacing: Theme.spacingS - height: 24 - - Rectangle { - id: checkbox - property bool checked: false - width: 20 - height: 20 - radius: 4 - color: checkboxRow.indeterminate ? Theme.surfaceVariant : (checked ? Theme.primary : "transparent") - border.color: checkboxRow.indeterminate ? Theme.outlineButton : (checked ? Theme.primary : Theme.outlineButton) - border.width: 2 - anchors.verticalCenter: parent.verticalCenter - - DankIcon { - anchors.centerIn: parent - name: checkboxRow.indeterminate ? "remove" : "check" - size: 12 - color: checkboxRow.indeterminate ? Theme.surfaceVariantText : Theme.background - visible: parent.checked || checkboxRow.indeterminate - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: { - if (checkboxRow.indeterminate) { - checkboxRow.indeterminate = false; - checkbox.checked = true; - } else { - checkbox.checked = !checkbox.checked; - } - } - } - } - - StyledText { - id: labelText - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter - } -} diff --git a/quickshell/Modals/WindowRuleInputField.qml b/quickshell/Modals/WindowRuleInputField.qml deleted file mode 100644 index 840832fa..00000000 --- a/quickshell/Modals/WindowRuleInputField.qml +++ /dev/null @@ -1,17 +0,0 @@ -import QtQuick -import qs.Common - -Rectangle { - id: inputFieldRect - - default property alias contentData: inputFieldRect.data - property bool hasFocus: false - property int fieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2 - - width: parent.width - height: fieldHeight - radius: Theme.cornerRadius - color: Theme.surfaceHover - border.color: hasFocus ? Theme.primary : Theme.outlineStrong - border.width: hasFocus ? 2 : 1 -} diff --git a/quickshell/Modals/WindowRuleModal.qml b/quickshell/Modals/WindowRuleModal.qml index d92c4d46..6c9583aa 100644 --- a/quickshell/Modals/WindowRuleModal.qml +++ b/quickshell/Modals/WindowRuleModal.qml @@ -297,6 +297,78 @@ FloatingWindow { } } + component SectionHeader: StyledText { + property string title + text: title + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.primary + topPadding: Theme.spacingM + bottomPadding: Theme.spacingXS + width: parent.width + horizontalAlignment: Text.AlignLeft + } + + component CheckboxRow: Row { + property alias checked: checkbox.checked + property alias label: labelText.text + property bool indeterminate: false + spacing: Theme.spacingS + height: 24 + + Rectangle { + id: checkbox + property bool checked: false + width: 20 + height: 20 + radius: 4 + color: parent.indeterminate ? Theme.surfaceVariant : (checked ? Theme.primary : "transparent") + border.color: parent.indeterminate ? Theme.outlineButton : (checked ? Theme.primary : Theme.outlineButton) + border.width: 2 + anchors.verticalCenter: parent.verticalCenter + + DankIcon { + anchors.centerIn: parent + name: parent.parent.indeterminate ? "remove" : "check" + size: 12 + color: parent.parent.indeterminate ? Theme.surfaceVariantText : Theme.background + visible: parent.checked || parent.parent.indeterminate + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + if (parent.parent.indeterminate) { + parent.parent.indeterminate = false; + parent.checked = true; + } else { + parent.checked = !parent.checked; + } + } + } + } + + StyledText { + id: labelText + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } + + component InputField: Rectangle { + id: inputFieldRect + default property alias contentData: inputFieldRect.data + property bool hasFocus: false + width: parent.width + height: root.inputFieldHeight + radius: Theme.cornerRadius + color: Theme.surfaceHover + border.color: hasFocus ? Theme.primary : Theme.outlineStrong + border.width: hasFocus ? 2 : 1 + } + FocusScope { anchors.fill: parent focus: true @@ -375,7 +447,7 @@ FloatingWindow { width: flickable.width - Theme.spacingM spacing: Theme.spacingXS - WindowRuleInputField { + InputField { hasFocus: nameInput.activeFocus DankTextField { id: nameInput @@ -388,11 +460,11 @@ FloatingWindow { } } - WindowRuleSectionHeader { + SectionHeader { title: I18n.tr("Match Criteria") } - WindowRuleInputField { + InputField { hasFocus: appIdInput.activeFocus DankTextField { id: appIdInput @@ -409,7 +481,7 @@ FloatingWindow { width: parent.width spacing: Theme.spacingS - WindowRuleInputField { + InputField { width: addTitleBtn.visible ? parent.width - addTitleBtn.width - Theme.spacingS : parent.width hasFocus: titleInput.activeFocus DankTextField { @@ -442,7 +514,7 @@ FloatingWindow { } } - WindowRuleSectionHeader { + SectionHeader { title: I18n.tr("Window Opening") } @@ -450,24 +522,24 @@ FloatingWindow { width: parent.width spacing: Theme.spacingL - WindowRuleCheckboxRow { + CheckboxRow { id: floatingToggle label: I18n.tr("Float") } - WindowRuleCheckboxRow { + CheckboxRow { id: maximizedToggle label: I18n.tr("Maximize") } - WindowRuleCheckboxRow { + CheckboxRow { id: fullscreenToggle label: I18n.tr("Fullscreen") } - WindowRuleCheckboxRow { + CheckboxRow { id: maximizedToEdgesToggle label: I18n.tr("Max to Edges") visible: isNiri } - WindowRuleCheckboxRow { + CheckboxRow { id: openFocusedToggle label: I18n.tr("Focus") visible: isNiri @@ -491,7 +563,7 @@ FloatingWindow { horizontalAlignment: Text.AlignLeft } - WindowRuleInputField { + InputField { width: parent.width hasFocus: outputInput.activeFocus DankTextField { @@ -518,7 +590,7 @@ FloatingWindow { horizontalAlignment: Text.AlignLeft } - WindowRuleInputField { + InputField { width: parent.width hasFocus: workspaceInput.activeFocus DankTextField { @@ -551,7 +623,7 @@ FloatingWindow { horizontalAlignment: Text.AlignLeft } - WindowRuleInputField { + InputField { width: parent.width hasFocus: columnWidthInput.activeFocus DankTextField { @@ -578,7 +650,7 @@ FloatingWindow { horizontalAlignment: Text.AlignLeft } - WindowRuleInputField { + InputField { width: parent.width hasFocus: windowHeightInput.activeFocus DankTextField { @@ -594,7 +666,7 @@ FloatingWindow { } } - WindowRuleSectionHeader { + SectionHeader { title: I18n.tr("Dynamic Properties") } @@ -602,7 +674,7 @@ FloatingWindow { width: parent.width spacing: Theme.spacingM - WindowRuleCheckboxRow { + CheckboxRow { id: opacityEnabled label: I18n.tr("Opacity") anchors.verticalCenter: parent.verticalCenter @@ -624,19 +696,19 @@ FloatingWindow { spacing: Theme.spacingL visible: isNiri - WindowRuleCheckboxRow { + CheckboxRow { id: vrrToggle label: I18n.tr("VRR On-Demand") } - WindowRuleCheckboxRow { + CheckboxRow { id: clipToGeometryToggle label: I18n.tr("Clip to Geometry") } - WindowRuleCheckboxRow { + CheckboxRow { id: tiledStateToggle label: I18n.tr("Tiled State") } - WindowRuleCheckboxRow { + CheckboxRow { id: drawBorderBgToggle label: I18n.tr("Border with BG") } @@ -697,7 +769,7 @@ FloatingWindow { spacing: Theme.spacingM visible: isNiri - WindowRuleCheckboxRow { + CheckboxRow { id: scrollFactorEnabled label: I18n.tr("Scroll Factor") anchors.verticalCenter: parent.verticalCenter @@ -718,7 +790,7 @@ FloatingWindow { width: parent.width spacing: Theme.spacingM - WindowRuleCheckboxRow { + CheckboxRow { id: cornerRadiusEnabled label: I18n.tr("Corner Radius") anchors.verticalCenter: parent.verticalCenter @@ -735,7 +807,7 @@ FloatingWindow { } } - WindowRuleSectionHeader { + SectionHeader { title: I18n.tr("Size Constraints") } @@ -755,7 +827,7 @@ FloatingWindow { horizontalAlignment: Text.AlignLeft } - WindowRuleInputField { + InputField { width: parent.width hasFocus: minWidthInput.activeFocus DankTextField { @@ -782,7 +854,7 @@ FloatingWindow { horizontalAlignment: Text.AlignLeft } - WindowRuleInputField { + InputField { width: parent.width hasFocus: maxWidthInput.activeFocus DankTextField { @@ -809,7 +881,7 @@ FloatingWindow { horizontalAlignment: Text.AlignLeft } - WindowRuleInputField { + InputField { width: parent.width hasFocus: minHeightInput.activeFocus DankTextField { @@ -836,7 +908,7 @@ FloatingWindow { horizontalAlignment: Text.AlignLeft } - WindowRuleInputField { + InputField { width: parent.width hasFocus: maxHeightInput.activeFocus DankTextField { @@ -852,7 +924,7 @@ FloatingWindow { } } - WindowRuleSectionHeader { + SectionHeader { title: I18n.tr("Hyprland Options") visible: isHyprland } @@ -862,43 +934,43 @@ FloatingWindow { spacing: Theme.spacingL visible: isHyprland - WindowRuleCheckboxRow { + CheckboxRow { id: tileToggle label: I18n.tr("Tile") } - WindowRuleCheckboxRow { + CheckboxRow { id: noFocusToggle label: I18n.tr("No Focus") } - WindowRuleCheckboxRow { + CheckboxRow { id: noBorderToggle label: I18n.tr("No Border") } - WindowRuleCheckboxRow { + CheckboxRow { id: noShadowToggle label: I18n.tr("No Shadow") } - WindowRuleCheckboxRow { + CheckboxRow { id: noDimToggle label: I18n.tr("No Dim") } - WindowRuleCheckboxRow { + CheckboxRow { id: noBlurToggle label: I18n.tr("No Blur") } - WindowRuleCheckboxRow { + CheckboxRow { id: noAnimToggle label: I18n.tr("No Anim") } - WindowRuleCheckboxRow { + CheckboxRow { id: noRoundingToggle label: I18n.tr("No Rounding") } - WindowRuleCheckboxRow { + CheckboxRow { id: pinToggle label: I18n.tr("Pin") } - WindowRuleCheckboxRow { + CheckboxRow { id: opaqueToggle label: I18n.tr("Opaque") } @@ -921,7 +993,7 @@ FloatingWindow { horizontalAlignment: Text.AlignLeft } - WindowRuleInputField { + InputField { width: parent.width hasFocus: sizeInput.activeFocus DankTextField { @@ -948,7 +1020,7 @@ FloatingWindow { horizontalAlignment: Text.AlignLeft } - WindowRuleInputField { + InputField { width: parent.width hasFocus: moveInput.activeFocus DankTextField { @@ -981,7 +1053,7 @@ FloatingWindow { horizontalAlignment: Text.AlignLeft } - WindowRuleInputField { + InputField { width: parent.width hasFocus: monitorInput.activeFocus DankTextField { @@ -1008,7 +1080,7 @@ FloatingWindow { horizontalAlignment: Text.AlignLeft } - WindowRuleInputField { + InputField { width: parent.width hasFocus: hyprWorkspaceInput.activeFocus DankTextField { diff --git a/quickshell/Modals/WindowRuleSectionHeader.qml b/quickshell/Modals/WindowRuleSectionHeader.qml deleted file mode 100644 index 4a77a761..00000000 --- a/quickshell/Modals/WindowRuleSectionHeader.qml +++ /dev/null @@ -1,15 +0,0 @@ -import QtQuick -import qs.Common -import qs.Widgets - -StyledText { - property string title - text: title - font.pixelSize: Theme.fontSizeMedium - font.weight: Font.Medium - color: Theme.primary - topPadding: Theme.spacingM - bottomPadding: Theme.spacingXS - width: parent.width - horizontalAlignment: Text.AlignLeft -} diff --git a/quickshell/Modules/ProcessList/CircleGauge.qml b/quickshell/Modules/ProcessList/CircleGauge.qml deleted file mode 100644 index d1072972..00000000 --- a/quickshell/Modules/ProcessList/CircleGauge.qml +++ /dev/null @@ -1,161 +0,0 @@ -import QtQuick -import qs.Common -import qs.Widgets - -Item { - id: gaugeRoot - - property real value: 0 - property string label: "" - property string sublabel: "" - property string detail: "" - property color accentColor: Theme.primary - property color detailColor: Theme.surfaceVariantText - - readonly property real thickness: Math.max(4, Math.min(width, height) / 15) - readonly property real glowExtra: thickness * 1.4 - readonly property real arcPadding: (thickness + glowExtra) / 2 - - readonly property real innerDiameter: width - (arcPadding + thickness + glowExtra) * 2 - readonly property real maxTextWidth: innerDiameter * 0.9 - readonly property real baseLabelSize: Math.round(width * 0.18) - readonly property real labelSize: Math.round(Math.min(baseLabelSize, maxTextWidth / Math.max(1, label.length * 0.65))) - readonly property real sublabelSize: Math.round(Math.min(width * 0.13, maxTextWidth / Math.max(1, sublabel.length * 0.7))) - readonly property real detailSize: Math.round(Math.min(width * 0.12, maxTextWidth / Math.max(1, detail.length * 0.65))) - - property real animValue: 0 - - onValueChanged: animValue = Math.min(1, Math.max(0, value)) - - Behavior on animValue { - NumberAnimation { - duration: Theme.mediumDuration - easing.type: Easing.OutCubic - } - } - - Component.onCompleted: animValue = Math.min(1, Math.max(0, value)) - - Canvas { - id: glowCanvas - anchors.fill: parent - onPaint: { - const ctx = getContext("2d"); - ctx.reset(); - const cx = width / 2; - const cy = height / 2; - const radius = (Math.min(width, height) / 2) - gaugeRoot.arcPadding; - const startAngle = -Math.PI * 0.5; - const endAngle = Math.PI * 1.5; - - ctx.lineCap = "round"; - - if (gaugeRoot.animValue > 0) { - const prog = startAngle + (endAngle - startAngle) * gaugeRoot.animValue; - ctx.beginPath(); - ctx.arc(cx, cy, radius, startAngle, prog); - ctx.strokeStyle = Qt.rgba(gaugeRoot.accentColor.r, gaugeRoot.accentColor.g, gaugeRoot.accentColor.b, 0.2); - ctx.lineWidth = gaugeRoot.thickness + gaugeRoot.glowExtra; - ctx.stroke(); - } - } - - Connections { - target: gaugeRoot - function onAnimValueChanged() { - glowCanvas.requestPaint(); - } - function onAccentColorChanged() { - glowCanvas.requestPaint(); - } - function onWidthChanged() { - glowCanvas.requestPaint(); - } - function onHeightChanged() { - glowCanvas.requestPaint(); - } - } - - Component.onCompleted: requestPaint() - } - - Canvas { - id: arcCanvas - anchors.fill: parent - onPaint: { - const ctx = getContext("2d"); - ctx.reset(); - const cx = width / 2; - const cy = height / 2; - const radius = (Math.min(width, height) / 2) - gaugeRoot.arcPadding; - const startAngle = -Math.PI * 0.5; - const endAngle = Math.PI * 1.5; - - ctx.lineCap = "round"; - - ctx.beginPath(); - ctx.arc(cx, cy, radius, startAngle, endAngle); - ctx.strokeStyle = Qt.rgba(gaugeRoot.accentColor.r, gaugeRoot.accentColor.g, gaugeRoot.accentColor.b, 0.1); - ctx.lineWidth = gaugeRoot.thickness; - ctx.stroke(); - - if (gaugeRoot.animValue > 0) { - const prog = startAngle + (endAngle - startAngle) * gaugeRoot.animValue; - ctx.beginPath(); - ctx.arc(cx, cy, radius, startAngle, prog); - ctx.strokeStyle = gaugeRoot.accentColor; - ctx.lineWidth = gaugeRoot.thickness; - ctx.stroke(); - } - } - - Connections { - target: gaugeRoot - function onAnimValueChanged() { - arcCanvas.requestPaint(); - } - function onAccentColorChanged() { - arcCanvas.requestPaint(); - } - function onWidthChanged() { - arcCanvas.requestPaint(); - } - function onHeightChanged() { - arcCanvas.requestPaint(); - } - } - - Component.onCompleted: requestPaint() - } - - Column { - anchors.centerIn: parent - spacing: 1 - - StyledText { - text: gaugeRoot.label - font.pixelSize: gaugeRoot.labelSize - font.family: SettingsData.monoFontFamily - font.weight: Font.Bold - color: Theme.surfaceText - anchors.horizontalCenter: parent.horizontalCenter - } - - StyledText { - text: gaugeRoot.sublabel - font.pixelSize: gaugeRoot.sublabelSize - font.weight: Font.Medium - color: gaugeRoot.accentColor - anchors.horizontalCenter: parent.horizontalCenter - } - - StyledText { - text: gaugeRoot.detail - font.pixelSize: gaugeRoot.detailSize - font.family: SettingsData.monoFontFamily - color: gaugeRoot.detailColor - anchors.horizontalCenter: parent.horizontalCenter - visible: gaugeRoot.detail.length > 0 - } - } -} diff --git a/quickshell/Modules/ProcessList/InfoRow.qml b/quickshell/Modules/ProcessList/InfoRow.qml deleted file mode 100644 index 115bc44e..00000000 --- a/quickshell/Modules/ProcessList/InfoRow.qml +++ /dev/null @@ -1,29 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Common -import qs.Widgets - -RowLayout { - property string label: "" - property string value: "" - - Layout.fillWidth: true - spacing: Theme.spacingS - - StyledText { - text: label + ":" - font.pixelSize: Theme.fontSizeSmall - font.weight: Font.Medium - color: Theme.surfaceVariantText - Layout.preferredWidth: 100 - } - - StyledText { - text: value - font.pixelSize: Theme.fontSizeSmall - font.family: SettingsData.monoFontFamily - color: Theme.surfaceText - Layout.fillWidth: true - elide: Text.ElideRight - } -} diff --git a/quickshell/Modules/ProcessList/PerformanceCard.qml b/quickshell/Modules/ProcessList/PerformanceCard.qml deleted file mode 100644 index 1cb990fa..00000000 --- a/quickshell/Modules/ProcessList/PerformanceCard.qml +++ /dev/null @@ -1,160 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Common -import qs.Widgets - -Rectangle { - id: card - - property string title: "" - property string icon: "" - property string value: "" - property string subtitle: "" - property color accentColor: Theme.primary - property var history: [] - property var history2: null - property real maxValue: 100 - property bool showSecondary: false - property string extraInfo: "" - property color extraInfoColor: Theme.surfaceVariantText - property int historySize: 60 - - radius: Theme.cornerRadius - color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) - border.color: Theme.outlineLight - border.width: 1 - - Canvas { - id: graphCanvas - anchors.fill: parent - anchors.margins: 4 - renderStrategy: Canvas.Cooperative - - property var hist: card.history - property var hist2: card.history2 - - onHistChanged: requestPaint() - onHist2Changed: requestPaint() - onWidthChanged: requestPaint() - onHeightChanged: requestPaint() - - onPaint: { - const ctx = getContext("2d"); - ctx.reset(); - ctx.clearRect(0, 0, width, height); - - if (!hist || hist.length < 2) - return; - - let max = card.maxValue; - if (max <= 0) { - max = 1; - for (let k = 0; k < hist.length; k++) - max = Math.max(max, hist[k]); - if (hist2) { - for (let l = 0; l < hist2.length; l++) - max = Math.max(max, hist2[l]); - } - max *= 1.1; - } - - const c = card.accentColor; - const grad = ctx.createLinearGradient(0, 0, 0, height); - grad.addColorStop(0, Qt.rgba(c.r, c.g, c.b, 0.25)); - grad.addColorStop(1, Qt.rgba(c.r, c.g, c.b, 0.02)); - - ctx.fillStyle = grad; - ctx.beginPath(); - ctx.moveTo(0, height); - for (let i = 0; i < hist.length; i++) { - const x = (width / (card.historySize - 1)) * i; - const y = height - (hist[i] / max) * height * 0.8; - ctx.lineTo(x, y); - } - ctx.lineTo((width / (card.historySize - 1)) * (hist.length - 1), height); - ctx.closePath(); - ctx.fill(); - - ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.8); - ctx.lineWidth = 2; - ctx.beginPath(); - for (let j = 0; j < hist.length; j++) { - const px = (width / (card.historySize - 1)) * j; - const py = height - (hist[j] / max) * height * 0.8; - j === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py); - } - ctx.stroke(); - - if (hist2 && hist2.length >= 2 && card.showSecondary) { - ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.4); - ctx.lineWidth = 1.5; - ctx.setLineDash([4, 4]); - ctx.beginPath(); - for (let m = 0; m < hist2.length; m++) { - const sx = (width / (card.historySize - 1)) * m; - const sy = height - (hist2[m] / max) * height * 0.8; - m === 0 ? ctx.moveTo(sx, sy) : ctx.lineTo(sx, sy); - } - ctx.stroke(); - ctx.setLineDash([]); - } - } - } - - ColumnLayout { - anchors.fill: parent - anchors.margins: Theme.spacingM - spacing: Theme.spacingXS - - RowLayout { - Layout.fillWidth: true - spacing: Theme.spacingS - - DankIcon { - name: card.icon - size: Theme.iconSize - color: card.accentColor - } - - StyledText { - text: card.title - font.pixelSize: Theme.fontSizeLarge - font.weight: Font.Bold - color: Theme.surfaceText - } - - Item { - Layout.fillWidth: true - } - - StyledText { - text: card.extraInfo - font.pixelSize: Theme.fontSizeSmall - font.family: SettingsData.monoFontFamily - color: card.extraInfoColor - visible: card.extraInfo.length > 0 - } - } - - Item { - Layout.fillHeight: true - } - - StyledText { - text: card.value - font.pixelSize: Theme.fontSizeXLarge - font.family: SettingsData.monoFontFamily - font.weight: Font.Bold - color: Theme.surfaceText - } - - StyledText { - text: card.subtitle - font.pixelSize: Theme.fontSizeSmall - font.family: SettingsData.monoFontFamily - color: Theme.surfaceVariantText - elide: Text.ElideRight - Layout.fillWidth: true - } - } -} diff --git a/quickshell/Modules/ProcessList/PerformanceView.qml b/quickshell/Modules/ProcessList/PerformanceView.qml index 1c4404fe..c793d92f 100644 --- a/quickshell/Modules/ProcessList/PerformanceView.qml +++ b/quickshell/Modules/ProcessList/PerformanceView.qml @@ -3,6 +3,7 @@ import QtQuick.Layouts import Quickshell import qs.Common import qs.Services +import qs.Widgets Item { id: root @@ -72,7 +73,6 @@ Item { PerformanceCard { Layout.fillWidth: true Layout.fillHeight: true - historySize: root.historySize title: "CPU" icon: "memory" value: DgopService.cpuUsage.toFixed(1) + "%" @@ -88,7 +88,6 @@ Item { PerformanceCard { Layout.fillWidth: true Layout.fillHeight: true - historySize: root.historySize title: I18n.tr("Memory") icon: "sd_card" value: DgopService.memoryUsage.toFixed(1) + "%" @@ -110,7 +109,6 @@ Item { PerformanceCard { Layout.fillWidth: true Layout.fillHeight: true - historySize: root.historySize title: I18n.tr("Network") icon: "swap_horiz" value: "↓ " + root.formatBytes(DgopService.networkRxRate) @@ -127,7 +125,6 @@ Item { PerformanceCard { Layout.fillWidth: true Layout.fillHeight: true - historySize: root.historySize title: I18n.tr("Disk") icon: "storage" value: "R: " + root.formatBytes(DgopService.diskReadRate) @@ -149,4 +146,159 @@ Item { } } } + + component PerformanceCard: Rectangle { + id: card + + property string title: "" + property string icon: "" + property string value: "" + property string subtitle: "" + property color accentColor: Theme.primary + property var history: [] + property var history2: null + property real maxValue: 100 + property bool showSecondary: false + property string extraInfo: "" + property color extraInfoColor: Theme.surfaceVariantText + + radius: Theme.cornerRadius + color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + border.color: Theme.outlineLight + border.width: 1 + + Canvas { + id: graphCanvas + anchors.fill: parent + anchors.margins: 4 + renderStrategy: Canvas.Cooperative + + property var hist: card.history + property var hist2: card.history2 + + onHistChanged: requestPaint() + onHist2Changed: requestPaint() + onWidthChanged: requestPaint() + onHeightChanged: requestPaint() + + onPaint: { + const ctx = getContext("2d"); + ctx.reset(); + ctx.clearRect(0, 0, width, height); + + if (!hist || hist.length < 2) + return; + + let max = card.maxValue; + if (max <= 0) { + max = 1; + for (let k = 0; k < hist.length; k++) + max = Math.max(max, hist[k]); + if (hist2) { + for (let l = 0; l < hist2.length; l++) + max = Math.max(max, hist2[l]); + } + max *= 1.1; + } + + const c = card.accentColor; + const grad = ctx.createLinearGradient(0, 0, 0, height); + grad.addColorStop(0, Qt.rgba(c.r, c.g, c.b, 0.25)); + grad.addColorStop(1, Qt.rgba(c.r, c.g, c.b, 0.02)); + + ctx.fillStyle = grad; + ctx.beginPath(); + ctx.moveTo(0, height); + for (let i = 0; i < hist.length; i++) { + const x = (width / (root.historySize - 1)) * i; + const y = height - (hist[i] / max) * height * 0.8; + ctx.lineTo(x, y); + } + ctx.lineTo((width / (root.historySize - 1)) * (hist.length - 1), height); + ctx.closePath(); + ctx.fill(); + + ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.8); + ctx.lineWidth = 2; + ctx.beginPath(); + for (let j = 0; j < hist.length; j++) { + const px = (width / (root.historySize - 1)) * j; + const py = height - (hist[j] / max) * height * 0.8; + j === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py); + } + ctx.stroke(); + + if (hist2 && hist2.length >= 2 && card.showSecondary) { + ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.4); + ctx.lineWidth = 1.5; + ctx.setLineDash([4, 4]); + ctx.beginPath(); + for (let m = 0; m < hist2.length; m++) { + const sx = (width / (root.historySize - 1)) * m; + const sy = height - (hist2[m] / max) * height * 0.8; + m === 0 ? ctx.moveTo(sx, sy) : ctx.lineTo(sx, sy); + } + ctx.stroke(); + ctx.setLineDash([]); + } + } + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: Theme.spacingM + spacing: Theme.spacingXS + + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacingS + + DankIcon { + name: card.icon + size: Theme.iconSize + color: card.accentColor + } + + StyledText { + text: card.title + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Bold + color: Theme.surfaceText + } + + Item { + Layout.fillWidth: true + } + + StyledText { + text: card.extraInfo + font.pixelSize: Theme.fontSizeSmall + font.family: SettingsData.monoFontFamily + color: card.extraInfoColor + visible: card.extraInfo.length > 0 + } + } + + Item { + Layout.fillHeight: true + } + + StyledText { + text: card.value + font.pixelSize: Theme.fontSizeXLarge + font.family: SettingsData.monoFontFamily + font.weight: Font.Bold + color: Theme.surfaceText + } + + StyledText { + text: card.subtitle + font.pixelSize: Theme.fontSizeSmall + font.family: SettingsData.monoFontFamily + color: Theme.surfaceVariantText + elide: Text.ElideRight + Layout.fillWidth: true + } + } + } } diff --git a/quickshell/Modules/ProcessList/ProcessItem.qml b/quickshell/Modules/ProcessList/ProcessItem.qml deleted file mode 100644 index 98b58146..00000000 --- a/quickshell/Modules/ProcessList/ProcessItem.qml +++ /dev/null @@ -1,334 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import Quickshell -import qs.Common -import qs.Services -import qs.Widgets - -Rectangle { - id: processItemRoot - - property var process: null - property bool isExpanded: false - property bool isSelected: false - property var contextMenu: null - - signal toggleExpand - signal clicked - signal contextMenuRequested(real mouseX, real mouseY) - - readonly property int processPid: process?.pid ?? 0 - readonly property real processCpu: process?.cpu ?? 0 - readonly property int processMemKB: process?.memoryKB ?? 0 - readonly property string processCmd: process?.command ?? "" - readonly property string processFullCmd: process?.fullCommand ?? processCmd - - height: isExpanded ? (44 + expandedRect.height + Theme.spacingXS) : 44 - radius: Theme.cornerRadius - color: { - if (isSelected) - return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15); - return processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent"; - } - border.color: { - if (isSelected) - return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3); - return processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"; - } - border.width: 1 - clip: true - - Behavior on height { - NumberAnimation { - duration: Theme.shortDuration - easing.type: Theme.standardEasing - } - } - - Behavior on color { - ColorAnimation { - duration: Theme.shortDuration - } - } - - MouseArea { - id: processMouseArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: mouse => { - if (mouse.button === Qt.RightButton) { - processItemRoot.contextMenuRequested(mouse.x, mouse.y); - return; - } - processItemRoot.clicked(); - processItemRoot.toggleExpand(); - } - } - - Column { - anchors.fill: parent - spacing: 0 - - Item { - width: parent.width - height: 44 - - RowLayout { - anchors.fill: parent - anchors.leftMargin: Theme.spacingS - anchors.rightMargin: Theme.spacingS - spacing: 0 - - Item { - Layout.fillWidth: true - Layout.minimumWidth: 200 - height: parent.height - - Row { - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingS - - DankIcon { - name: DgopService.getProcessIcon(processItemRoot.processCmd) - size: Theme.iconSize - 4 - color: { - if (processItemRoot.processCpu > 80) - return Theme.error; - if (processItemRoot.processCpu > 50) - return Theme.warning; - return Theme.surfaceText; - } - opacity: 0.8 - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: processItemRoot.processCmd - font.pixelSize: Theme.fontSizeSmall - font.family: SettingsData.monoFontFamily - font.weight: Font.Medium - color: Theme.surfaceText - elide: Text.ElideRight - width: Math.min(implicitWidth, 280) - anchors.verticalCenter: parent.verticalCenter - } - } - } - - Item { - Layout.preferredWidth: 100 - height: parent.height - - Rectangle { - anchors.centerIn: parent - width: 70 - height: 24 - radius: Theme.cornerRadius - color: { - if (processItemRoot.processCpu > 80) - return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.15); - if (processItemRoot.processCpu > 50) - return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12); - return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.06); - } - - StyledText { - anchors.centerIn: parent - text: DgopService.formatCpuUsage(processItemRoot.processCpu) - font.pixelSize: Theme.fontSizeSmall - font.family: SettingsData.monoFontFamily - font.weight: Font.Bold - color: { - if (processItemRoot.processCpu > 80) - return Theme.error; - if (processItemRoot.processCpu > 50) - return Theme.warning; - return Theme.surfaceText; - } - } - } - } - - Item { - Layout.preferredWidth: 100 - height: parent.height - - Rectangle { - anchors.centerIn: parent - width: 70 - height: 24 - radius: Theme.cornerRadius - color: { - if (processItemRoot.processMemKB > 2 * 1024 * 1024) - return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.15); - if (processItemRoot.processMemKB > 1024 * 1024) - return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12); - return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.06); - } - - StyledText { - anchors.centerIn: parent - text: DgopService.formatMemoryUsage(processItemRoot.processMemKB) - font.pixelSize: Theme.fontSizeSmall - font.family: SettingsData.monoFontFamily - font.weight: Font.Bold - color: { - if (processItemRoot.processMemKB > 2 * 1024 * 1024) - return Theme.error; - if (processItemRoot.processMemKB > 1024 * 1024) - return Theme.warning; - return Theme.surfaceText; - } - } - } - } - - Item { - Layout.preferredWidth: 80 - height: parent.height - - StyledText { - anchors.centerIn: parent - text: processItemRoot.processPid > 0 ? processItemRoot.processPid.toString() : "" - font.pixelSize: Theme.fontSizeSmall - font.family: SettingsData.monoFontFamily - color: Theme.surfaceVariantText - } - } - - Item { - Layout.preferredWidth: 40 - height: parent.height - - DankIcon { - anchors.centerIn: parent - name: processItemRoot.isExpanded ? "expand_less" : "expand_more" - size: Theme.iconSize - 4 - color: Theme.surfaceVariantText - } - } - } - } - - Rectangle { - id: expandedRect - width: parent.width - Theme.spacingM * 2 - height: processItemRoot.isExpanded ? (expandedContent.implicitHeight + Theme.spacingS * 2) : 0 - anchors.horizontalCenter: parent.horizontalCenter - radius: Theme.cornerRadius - 2 - color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, 0.6) - clip: true - visible: processItemRoot.isExpanded - - Behavior on height { - NumberAnimation { - duration: Theme.shortDuration - easing.type: Theme.standardEasing - } - } - - Column { - id: expandedContent - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.margins: Theme.spacingS - spacing: Theme.spacingXS - - RowLayout { - width: parent.width - spacing: Theme.spacingS - - StyledText { - id: cmdLabel - text: I18n.tr("Full Command:", "process detail label") - font.pixelSize: Theme.fontSizeSmall - 2 - font.weight: Font.Bold - color: Theme.surfaceVariantText - Layout.alignment: Qt.AlignVCenter - } - - StyledText { - id: cmdText - text: processItemRoot.processFullCmd - font.pixelSize: Theme.fontSizeSmall - 2 - font.family: SettingsData.monoFontFamily - color: Theme.surfaceText - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - elide: Text.ElideMiddle - } - - Rectangle { - id: copyBtn - Layout.preferredWidth: 24 - Layout.preferredHeight: 24 - Layout.alignment: Qt.AlignVCenter - radius: Theme.cornerRadius - 2 - color: copyMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15) : "transparent" - - DankIcon { - anchors.centerIn: parent - name: "content_copy" - size: 14 - color: copyMouseArea.containsMouse ? Theme.primary : Theme.surfaceVariantText - } - - MouseArea { - id: copyMouseArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - Quickshell.execDetached(["dms", "cl", "copy", processItemRoot.processFullCmd]); - } - } - } - } - - Row { - spacing: Theme.spacingL - - Row { - spacing: Theme.spacingXS - - StyledText { - text: "PPID:" - font.pixelSize: Theme.fontSizeSmall - 2 - font.weight: Font.Bold - color: Theme.surfaceVariantText - } - - StyledText { - text: (processItemRoot.process?.ppid ?? 0) > 0 ? processItemRoot.process.ppid.toString() : "--" - font.pixelSize: Theme.fontSizeSmall - 2 - font.family: SettingsData.monoFontFamily - color: Theme.surfaceText - } - } - - Row { - spacing: Theme.spacingXS - - StyledText { - text: "Mem:" - font.pixelSize: Theme.fontSizeSmall - 2 - font.weight: Font.Bold - color: Theme.surfaceVariantText - } - - StyledText { - text: (processItemRoot.process?.memoryPercent ?? 0).toFixed(1) + "%" - font.pixelSize: Theme.fontSizeSmall - 2 - font.family: SettingsData.monoFontFamily - color: Theme.surfaceText - } - } - } - } - } - } -} diff --git a/quickshell/Modules/ProcessList/ProcessListPopout.qml b/quickshell/Modules/ProcessList/ProcessListPopout.qml index c154c676..3adbe07d 100644 --- a/quickshell/Modules/ProcessList/ProcessListPopout.qml +++ b/quickshell/Modules/ProcessList/ProcessListPopout.qml @@ -374,4 +374,162 @@ DankPopout { } } } + + component CircleGauge: Item { + id: gaugeRoot + + property real value: 0 + property string label: "" + property string sublabel: "" + property string detail: "" + property color accentColor: Theme.primary + property color detailColor: Theme.surfaceVariantText + + readonly property real thickness: Math.max(4, Math.min(width, height) / 15) + readonly property real glowExtra: thickness * 1.4 + readonly property real arcPadding: (thickness + glowExtra) / 2 + + readonly property real innerDiameter: width - (arcPadding + thickness + glowExtra) * 2 + readonly property real maxTextWidth: innerDiameter * 0.9 + readonly property real baseLabelSize: Math.round(width * 0.18) + readonly property real labelSize: Math.round(Math.min(baseLabelSize, maxTextWidth / Math.max(1, label.length * 0.65))) + readonly property real sublabelSize: Math.round(Math.min(width * 0.13, maxTextWidth / Math.max(1, sublabel.length * 0.7))) + readonly property real detailSize: Math.round(Math.min(width * 0.12, maxTextWidth / Math.max(1, detail.length * 0.65))) + + property real animValue: 0 + + onValueChanged: animValue = Math.min(1, Math.max(0, value)) + + Behavior on animValue { + NumberAnimation { + duration: Theme.mediumDuration + easing.type: Easing.OutCubic + } + } + + Component.onCompleted: animValue = Math.min(1, Math.max(0, value)) + + Canvas { + id: glowCanvas + anchors.fill: parent + onPaint: { + const ctx = getContext("2d"); + ctx.reset(); + const cx = width / 2; + const cy = height / 2; + const radius = (Math.min(width, height) / 2) - gaugeRoot.arcPadding; + const startAngle = -Math.PI * 0.5; + const endAngle = Math.PI * 1.5; + + ctx.lineCap = "round"; + + if (gaugeRoot.animValue > 0) { + const prog = startAngle + (endAngle - startAngle) * gaugeRoot.animValue; + ctx.beginPath(); + ctx.arc(cx, cy, radius, startAngle, prog); + ctx.strokeStyle = Qt.rgba(gaugeRoot.accentColor.r, gaugeRoot.accentColor.g, gaugeRoot.accentColor.b, 0.2); + ctx.lineWidth = gaugeRoot.thickness + gaugeRoot.glowExtra; + ctx.stroke(); + } + } + + Connections { + target: gaugeRoot + function onAnimValueChanged() { + glowCanvas.requestPaint(); + } + function onAccentColorChanged() { + glowCanvas.requestPaint(); + } + function onWidthChanged() { + glowCanvas.requestPaint(); + } + function onHeightChanged() { + glowCanvas.requestPaint(); + } + } + + Component.onCompleted: requestPaint() + } + + Canvas { + id: arcCanvas + anchors.fill: parent + onPaint: { + const ctx = getContext("2d"); + ctx.reset(); + const cx = width / 2; + const cy = height / 2; + const radius = (Math.min(width, height) / 2) - gaugeRoot.arcPadding; + const startAngle = -Math.PI * 0.5; + const endAngle = Math.PI * 1.5; + + ctx.lineCap = "round"; + + ctx.beginPath(); + ctx.arc(cx, cy, radius, startAngle, endAngle); + ctx.strokeStyle = Qt.rgba(gaugeRoot.accentColor.r, gaugeRoot.accentColor.g, gaugeRoot.accentColor.b, 0.1); + ctx.lineWidth = gaugeRoot.thickness; + ctx.stroke(); + + if (gaugeRoot.animValue > 0) { + const prog = startAngle + (endAngle - startAngle) * gaugeRoot.animValue; + ctx.beginPath(); + ctx.arc(cx, cy, radius, startAngle, prog); + ctx.strokeStyle = gaugeRoot.accentColor; + ctx.lineWidth = gaugeRoot.thickness; + ctx.stroke(); + } + } + + Connections { + target: gaugeRoot + function onAnimValueChanged() { + arcCanvas.requestPaint(); + } + function onAccentColorChanged() { + arcCanvas.requestPaint(); + } + function onWidthChanged() { + arcCanvas.requestPaint(); + } + function onHeightChanged() { + arcCanvas.requestPaint(); + } + } + + Component.onCompleted: requestPaint() + } + + Column { + anchors.centerIn: parent + spacing: 1 + + StyledText { + text: gaugeRoot.label + font.pixelSize: gaugeRoot.labelSize + font.family: SettingsData.monoFontFamily + font.weight: Font.Bold + color: Theme.surfaceText + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: gaugeRoot.sublabel + font.pixelSize: gaugeRoot.sublabelSize + font.weight: Font.Medium + color: gaugeRoot.accentColor + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: gaugeRoot.detail + font.pixelSize: gaugeRoot.detailSize + font.family: SettingsData.monoFontFamily + color: gaugeRoot.detailColor + anchors.horizontalCenter: parent.horizontalCenter + visible: gaugeRoot.detail.length > 0 + } + } + } } diff --git a/quickshell/Modules/ProcessList/ProcessesView.qml b/quickshell/Modules/ProcessList/ProcessesView.qml index 274fe8bf..181cf15a 100644 --- a/quickshell/Modules/ProcessList/ProcessesView.qml +++ b/quickshell/Modules/ProcessList/ProcessesView.qml @@ -368,4 +368,402 @@ Item { } } } + + component SortableHeader: Item { + id: headerItem + + property string text: "" + property string sortKey: "" + property string currentSort: "" + property bool sortAscending: false + property int alignment: Text.AlignHCenter + + signal clicked + + readonly property bool isActive: sortKey === currentSort + + height: 36 + + Rectangle { + anchors.fill: parent + anchors.margins: 2 + radius: Theme.cornerRadius + color: headerItem.isActive ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (headerMouseArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.06) : "transparent") + + Behavior on color { + ColorAnimation { + duration: Theme.shortDuration + } + } + } + + RowLayout { + anchors.fill: parent + anchors.leftMargin: Theme.spacingS + anchors.rightMargin: Theme.spacingS + spacing: 4 + + Item { + Layout.fillWidth: headerItem.alignment === Text.AlignLeft + visible: headerItem.alignment !== Text.AlignLeft + } + + StyledText { + text: headerItem.text + font.pixelSize: Theme.fontSizeSmall + font.family: SettingsData.monoFontFamily + font.weight: headerItem.isActive ? Font.Bold : Font.Medium + color: headerItem.isActive ? Theme.primary : Theme.surfaceText + opacity: headerItem.isActive ? 1 : 0.8 + } + + DankIcon { + name: headerItem.sortAscending ? "arrow_upward" : "arrow_downward" + size: Theme.fontSizeSmall + color: Theme.primary + visible: headerItem.isActive + } + + Item { + Layout.fillWidth: headerItem.alignment !== Text.AlignLeft + visible: headerItem.alignment === Text.AlignLeft + } + } + + MouseArea { + id: headerMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: headerItem.clicked() + } + } + + component ProcessItem: Rectangle { + id: processItemRoot + + property var process: null + property bool isExpanded: false + property bool isSelected: false + property var contextMenu: null + + signal toggleExpand + signal clicked + signal contextMenuRequested(real mouseX, real mouseY) + + readonly property int processPid: process?.pid ?? 0 + readonly property real processCpu: process?.cpu ?? 0 + readonly property int processMemKB: process?.memoryKB ?? 0 + readonly property string processCmd: process?.command ?? "" + readonly property string processFullCmd: process?.fullCommand ?? processCmd + + height: isExpanded ? (44 + expandedRect.height + Theme.spacingXS) : 44 + radius: Theme.cornerRadius + color: { + if (isSelected) + return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15); + return processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent"; + } + border.color: { + if (isSelected) + return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3); + return processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"; + } + border.width: 1 + clip: true + + Behavior on height { + NumberAnimation { + duration: Theme.shortDuration + easing.type: Theme.standardEasing + } + } + + Behavior on color { + ColorAnimation { + duration: Theme.shortDuration + } + } + + MouseArea { + id: processMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: mouse => { + if (mouse.button === Qt.RightButton) { + processItemRoot.contextMenuRequested(mouse.x, mouse.y); + return; + } + processItemRoot.clicked(); + processItemRoot.toggleExpand(); + } + } + + Column { + anchors.fill: parent + spacing: 0 + + Item { + width: parent.width + height: 44 + + RowLayout { + anchors.fill: parent + anchors.leftMargin: Theme.spacingS + anchors.rightMargin: Theme.spacingS + spacing: 0 + + Item { + Layout.fillWidth: true + Layout.minimumWidth: 200 + height: parent.height + + Row { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankIcon { + name: DgopService.getProcessIcon(processItemRoot.processCmd) + size: Theme.iconSize - 4 + color: { + if (processItemRoot.processCpu > 80) + return Theme.error; + if (processItemRoot.processCpu > 50) + return Theme.warning; + return Theme.surfaceText; + } + opacity: 0.8 + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: processItemRoot.processCmd + font.pixelSize: Theme.fontSizeSmall + font.family: SettingsData.monoFontFamily + font.weight: Font.Medium + color: Theme.surfaceText + elide: Text.ElideRight + width: Math.min(implicitWidth, 280) + anchors.verticalCenter: parent.verticalCenter + } + } + } + + Item { + Layout.preferredWidth: 100 + height: parent.height + + Rectangle { + anchors.centerIn: parent + width: 70 + height: 24 + radius: Theme.cornerRadius + color: { + if (processItemRoot.processCpu > 80) + return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.15); + if (processItemRoot.processCpu > 50) + return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12); + return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.06); + } + + StyledText { + anchors.centerIn: parent + text: DgopService.formatCpuUsage(processItemRoot.processCpu) + font.pixelSize: Theme.fontSizeSmall + font.family: SettingsData.monoFontFamily + font.weight: Font.Bold + color: { + if (processItemRoot.processCpu > 80) + return Theme.error; + if (processItemRoot.processCpu > 50) + return Theme.warning; + return Theme.surfaceText; + } + } + } + } + + Item { + Layout.preferredWidth: 100 + height: parent.height + + Rectangle { + anchors.centerIn: parent + width: 70 + height: 24 + radius: Theme.cornerRadius + color: { + if (processItemRoot.processMemKB > 2 * 1024 * 1024) + return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.15); + if (processItemRoot.processMemKB > 1024 * 1024) + return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12); + return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.06); + } + + StyledText { + anchors.centerIn: parent + text: DgopService.formatMemoryUsage(processItemRoot.processMemKB) + font.pixelSize: Theme.fontSizeSmall + font.family: SettingsData.monoFontFamily + font.weight: Font.Bold + color: { + if (processItemRoot.processMemKB > 2 * 1024 * 1024) + return Theme.error; + if (processItemRoot.processMemKB > 1024 * 1024) + return Theme.warning; + return Theme.surfaceText; + } + } + } + } + + Item { + Layout.preferredWidth: 80 + height: parent.height + + StyledText { + anchors.centerIn: parent + text: processItemRoot.processPid > 0 ? processItemRoot.processPid.toString() : "" + font.pixelSize: Theme.fontSizeSmall + font.family: SettingsData.monoFontFamily + color: Theme.surfaceVariantText + } + } + + Item { + Layout.preferredWidth: 40 + height: parent.height + + DankIcon { + anchors.centerIn: parent + name: processItemRoot.isExpanded ? "expand_less" : "expand_more" + size: Theme.iconSize - 4 + color: Theme.surfaceVariantText + } + } + } + } + + Rectangle { + id: expandedRect + width: parent.width - Theme.spacingM * 2 + height: processItemRoot.isExpanded ? (expandedContent.implicitHeight + Theme.spacingS * 2) : 0 + anchors.horizontalCenter: parent.horizontalCenter + radius: Theme.cornerRadius - 2 + color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, 0.6) + clip: true + visible: processItemRoot.isExpanded + + Behavior on height { + NumberAnimation { + duration: Theme.shortDuration + easing.type: Theme.standardEasing + } + } + + Column { + id: expandedContent + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Theme.spacingS + spacing: Theme.spacingXS + + RowLayout { + width: parent.width + spacing: Theme.spacingS + + StyledText { + id: cmdLabel + text: I18n.tr("Full Command:", "process detail label") + font.pixelSize: Theme.fontSizeSmall - 2 + font.weight: Font.Bold + color: Theme.surfaceVariantText + Layout.alignment: Qt.AlignVCenter + } + + StyledText { + id: cmdText + text: processItemRoot.processFullCmd + font.pixelSize: Theme.fontSizeSmall - 2 + font.family: SettingsData.monoFontFamily + color: Theme.surfaceText + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + elide: Text.ElideMiddle + } + + Rectangle { + id: copyBtn + Layout.preferredWidth: 24 + Layout.preferredHeight: 24 + Layout.alignment: Qt.AlignVCenter + radius: Theme.cornerRadius - 2 + color: copyMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15) : "transparent" + + DankIcon { + anchors.centerIn: parent + name: "content_copy" + size: 14 + color: copyMouseArea.containsMouse ? Theme.primary : Theme.surfaceVariantText + } + + MouseArea { + id: copyMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + Quickshell.execDetached(["dms", "cl", "copy", processItemRoot.processFullCmd]); + } + } + } + } + + Row { + spacing: Theme.spacingL + + Row { + spacing: Theme.spacingXS + + StyledText { + text: "PPID:" + font.pixelSize: Theme.fontSizeSmall - 2 + font.weight: Font.Bold + color: Theme.surfaceVariantText + } + + StyledText { + text: (processItemRoot.process?.ppid ?? 0) > 0 ? processItemRoot.process.ppid.toString() : "--" + font.pixelSize: Theme.fontSizeSmall - 2 + font.family: SettingsData.monoFontFamily + color: Theme.surfaceText + } + } + + Row { + spacing: Theme.spacingXS + + StyledText { + text: "Mem:" + font.pixelSize: Theme.fontSizeSmall - 2 + font.weight: Font.Bold + color: Theme.surfaceVariantText + } + + StyledText { + text: (processItemRoot.process?.memoryPercent ?? 0).toFixed(1) + "%" + font.pixelSize: Theme.fontSizeSmall - 2 + font.family: SettingsData.monoFontFamily + color: Theme.surfaceText + } + } + } + } + } + } + } } diff --git a/quickshell/Modules/ProcessList/SortableHeader.qml b/quickshell/Modules/ProcessList/SortableHeader.qml deleted file mode 100644 index 94ca02b8..00000000 --- a/quickshell/Modules/ProcessList/SortableHeader.qml +++ /dev/null @@ -1,74 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Common -import qs.Widgets - -Item { - id: headerItem - - property string text: "" - property string sortKey: "" - property string currentSort: "" - property bool sortAscending: false - property int alignment: Text.AlignHCenter - - signal clicked - - readonly property bool isActive: sortKey === currentSort - - height: 36 - - Rectangle { - anchors.fill: parent - anchors.margins: 2 - radius: Theme.cornerRadius - color: headerItem.isActive ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (headerMouseArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.06) : "transparent") - - Behavior on color { - ColorAnimation { - duration: Theme.shortDuration - } - } - } - - RowLayout { - anchors.fill: parent - anchors.leftMargin: Theme.spacingS - anchors.rightMargin: Theme.spacingS - spacing: 4 - - Item { - Layout.fillWidth: headerItem.alignment === Text.AlignLeft - visible: headerItem.alignment !== Text.AlignLeft - } - - StyledText { - text: headerItem.text - font.pixelSize: Theme.fontSizeSmall - font.family: SettingsData.monoFontFamily - font.weight: headerItem.isActive ? Font.Bold : Font.Medium - color: headerItem.isActive ? Theme.primary : Theme.surfaceText - opacity: headerItem.isActive ? 1 : 0.8 - } - - DankIcon { - name: headerItem.sortAscending ? "arrow_upward" : "arrow_downward" - size: Theme.fontSizeSmall - color: Theme.primary - visible: headerItem.isActive - } - - Item { - Layout.fillWidth: headerItem.alignment !== Text.AlignLeft - visible: headerItem.alignment === Text.AlignLeft - } - } - - MouseArea { - id: headerMouseArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: headerItem.clicked() - } -} diff --git a/quickshell/Modules/ProcessList/SystemView.qml b/quickshell/Modules/ProcessList/SystemView.qml index ce3ab1fc..244129d8 100644 --- a/quickshell/Modules/ProcessList/SystemView.qml +++ b/quickshell/Modules/ProcessList/SystemView.qml @@ -358,4 +358,29 @@ Item { } } } + + component InfoRow: RowLayout { + property string label: "" + property string value: "" + + Layout.fillWidth: true + spacing: Theme.spacingS + + StyledText { + text: label + ":" + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Medium + color: Theme.surfaceVariantText + Layout.preferredWidth: 100 + } + + StyledText { + text: value + font.pixelSize: Theme.fontSizeSmall + font.family: SettingsData.monoFontFamily + color: Theme.surfaceText + Layout.fillWidth: true + elide: Text.ElideRight + } + } } diff --git a/quickshell/Services/NotifWrapper.qml b/quickshell/Services/NotifWrapper.qml deleted file mode 100644 index 61c4992e..00000000 --- a/quickshell/Services/NotifWrapper.qml +++ /dev/null @@ -1,147 +0,0 @@ -import QtQuick -import Quickshell.Services.Notifications -import qs.Common - -QtObject { - id: wrapper - - property bool popup: false - property bool removedByLimit: false - property bool isPersistent: true - property int seq: 0 - property string persistedImagePath: "" - - onPopupChanged: { - if (!popup) { - NotificationService.removeFromVisibleNotifications(wrapper); - } - } - - readonly property Timer timer: Timer { - interval: { - if (!wrapper.notification) - return 5000; - switch (wrapper.urgency) { - case NotificationUrgency.Low: - return SettingsData.notificationTimeoutLow; - case NotificationUrgency.Critical: - return SettingsData.notificationTimeoutCritical; - default: - return SettingsData.notificationTimeoutNormal; - } - } - repeat: false - running: false - onTriggered: { - if (interval > 0) { - wrapper.popup = false; - } - } - } - - readonly property date time: new Date() - readonly property string timeStr: { - NotificationService.timeUpdateTick; - NotificationService.clockFormatChanged; - - const now = new Date(); - const diff = now.getTime() - time.getTime(); - const minutes = Math.floor(diff / 60000); - const hours = Math.floor(minutes / 60); - - if (hours < 1) { - if (minutes < 1) { - return "now"; - } - return `${minutes}m ago`; - } - - const nowDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()); - const timeDate = new Date(time.getFullYear(), time.getMonth(), time.getDate()); - const daysDiff = Math.floor((nowDate - timeDate) / (1000 * 60 * 60 * 24)); - - if (daysDiff === 0) { - return formatTime(time); - } - - try { - const localeName = (typeof I18n !== "undefined" && I18n.locale) ? I18n.locale().name : "en-US"; - const weekday = time.toLocaleDateString(localeName, { - weekday: "long" - }); - return `${weekday}, ${formatTime(time)}`; - } catch (e) { - return formatTime(time); - } - } - - function formatTime(date) { - let use24Hour = true; - try { - if (typeof SettingsData !== "undefined" && SettingsData.use24HourClock !== undefined) { - use24Hour = SettingsData.use24HourClock; - } - } catch (e) { - use24Hour = true; - } - - if (use24Hour) { - return date.toLocaleTimeString(Qt.locale(), "HH:mm"); - } else { - return date.toLocaleTimeString(Qt.locale(), "h:mm AP"); - } - } - - required property Notification notification - readonly property string summary: (notification?.summary ?? "").replace(/]*>/gi, "") - readonly property string body: (notification?.body ?? "").replace(/]*>/gi, "") - readonly property string htmlBody: NotificationService._resolveHtmlBody(body) - readonly property string appIcon: notification?.appIcon ?? "" - readonly property string appName: { - if (!notification) - return "app"; - if (notification.appName == "") { - const entry = DesktopEntries.heuristicLookup(notification.desktopEntry); - if (entry && entry.name) - return entry.name.toLowerCase(); - } - return notification.appName || "app"; - } - readonly property string desktopEntry: notification?.desktopEntry ?? "" - readonly property string image: notification?.image ?? "" - readonly property string cleanImage: { - if (!image) - return ""; - return Paths.strip(image); - } - property int urgencyOverride: notification?.urgency ?? NotificationUrgency.Normal - readonly property int urgency: urgencyOverride - readonly property list actions: notification?.actions ?? [] - - readonly property Connections conn: Connections { - target: wrapper.notification?.Retainable ?? null - - function onDropped(): void { - NotificationService.allWrappers = NotificationService.allWrappers.filter(w => w !== wrapper); - NotificationService.notifications = NotificationService.notifications.filter(w => w !== wrapper); - - if (NotificationService.bulkDismissing) { - return; - } - - const groupKey = NotificationService.getGroupKey(wrapper); - const remainingInGroup = NotificationService.notifications.filter(n => NotificationService.getGroupKey(n) === groupKey); - - if (remainingInGroup.length <= 1) { - NotificationService.clearGroupExpansionState(groupKey); - } - - NotificationService.cleanupExpansionStates(); - NotificationService._recomputeGroupsLater(); - } - - function onAboutToDestroy(): void { - wrapper.destroy(); - } - } -} diff --git a/quickshell/Services/NotificationService.qml b/quickshell/Services/NotificationService.qml index 8a036008..0615b580 100644 --- a/quickshell/Services/NotificationService.qml +++ b/quickshell/Services/NotificationService.qml @@ -655,6 +655,150 @@ Singleton { } } + component NotifWrapper: QtObject { + id: wrapper + + property bool popup: false + property bool removedByLimit: false + property bool isPersistent: true + property int seq: 0 + property string persistedImagePath: "" + + onPopupChanged: { + if (!popup) { + removeFromVisibleNotifications(wrapper); + } + } + + readonly property Timer timer: Timer { + interval: { + if (!wrapper.notification) + return 5000; + switch (wrapper.urgency) { + case NotificationUrgency.Low: + return SettingsData.notificationTimeoutLow; + case NotificationUrgency.Critical: + return SettingsData.notificationTimeoutCritical; + default: + return SettingsData.notificationTimeoutNormal; + } + } + repeat: false + running: false + onTriggered: { + if (interval > 0) { + wrapper.popup = false; + } + } + } + + readonly property date time: new Date() + readonly property string timeStr: { + root.timeUpdateTick; + root.clockFormatChanged; + + const now = new Date(); + const diff = now.getTime() - time.getTime(); + const minutes = Math.floor(diff / 60000); + const hours = Math.floor(minutes / 60); + + if (hours < 1) { + if (minutes < 1) { + return "now"; + } + return `${minutes}m ago`; + } + + const nowDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const timeDate = new Date(time.getFullYear(), time.getMonth(), time.getDate()); + const daysDiff = Math.floor((nowDate - timeDate) / (1000 * 60 * 60 * 24)); + + if (daysDiff === 0) { + return formatTime(time); + } + + try { + const localeName = (typeof I18n !== "undefined" && I18n.locale) ? I18n.locale().name : "en-US"; + const weekday = time.toLocaleDateString(localeName, { + weekday: "long" + }); + return `${weekday}, ${formatTime(time)}`; + } catch (e) { + return formatTime(time); + } + } + + function formatTime(date) { + let use24Hour = true; + try { + if (typeof SettingsData !== "undefined" && SettingsData.use24HourClock !== undefined) { + use24Hour = SettingsData.use24HourClock; + } + } catch (e) { + use24Hour = true; + } + + if (use24Hour) { + return date.toLocaleTimeString(Qt.locale(), "HH:mm"); + } else { + return date.toLocaleTimeString(Qt.locale(), "h:mm AP"); + } + } + + required property Notification notification + readonly property string summary: (notification?.summary ?? "").replace(/]*>/gi, "") + readonly property string body: (notification?.body ?? "").replace(/]*>/gi, "") + readonly property string htmlBody: root._resolveHtmlBody(body) + readonly property string appIcon: notification?.appIcon ?? "" + readonly property string appName: { + if (!notification) + return "app"; + if (notification.appName == "") { + const entry = DesktopEntries.heuristicLookup(notification.desktopEntry); + if (entry && entry.name) + return entry.name.toLowerCase(); + } + return notification.appName || "app"; + } + readonly property string desktopEntry: notification?.desktopEntry ?? "" + readonly property string image: notification?.image ?? "" + readonly property string cleanImage: { + if (!image) + return ""; + return Paths.strip(image); + } + property int urgencyOverride: notification?.urgency ?? NotificationUrgency.Normal + readonly property int urgency: urgencyOverride + readonly property list actions: notification?.actions ?? [] + + readonly property Connections conn: Connections { + target: wrapper.notification?.Retainable ?? null + + function onDropped(): void { + root.allWrappers = root.allWrappers.filter(w => w !== wrapper); + root.notifications = root.notifications.filter(w => w !== wrapper); + + if (root.bulkDismissing) { + return; + } + + const groupKey = getGroupKey(wrapper); + const remainingInGroup = root.notifications.filter(n => getGroupKey(n) === groupKey); + + if (remainingInGroup.length <= 1) { + clearGroupExpansionState(groupKey); + } + + cleanupExpansionStates(); + root._recomputeGroupsLater(); + } + + function onAboutToDestroy(): void { + wrapper.destroy(); + } + } + } + Component { id: notifComponent NotifWrapper {}