1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-08 14:52:08 -04:00

Revert "qml: cut down on inline components for performance"

This reverts commit f6e590a518.
This commit is contained in:
bbedward
2026-04-07 15:22:46 -04:00
parent 37f92677cf
commit 32c063aab8
22 changed files with 1051 additions and 1098 deletions

View File

@@ -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<real> standard: [0.2, 0, 0, 1, 1, 1]
readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1
/ 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1]
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1]
readonly property list<real> 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 {}
}
}

View File

@@ -1,6 +0,0 @@
import QtQuick
QtObject {
readonly property AppearanceAnimCurves curves: AppearanceAnimCurves {}
readonly property AppearanceAnimDurations durations: AppearanceAnimDurations {}
}

View File

@@ -1,13 +0,0 @@
import QtQuick
QtObject {
readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1]
readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1]
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1]
readonly property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1]
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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
}
}
}
}
}
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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
}
}
}
}
}
}
}
}

View File

@@ -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()
}
}

View File

@@ -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
}
}
}

View File

@@ -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(/<img\b[^>]*>/gi, "")
readonly property string body: (notification?.body ?? "").replace(/<img\b[^>]*>/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<NotificationAction> 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();
}
}
}

View File

@@ -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(/<img\b[^>]*>/gi, "")
readonly property string body: (notification?.body ?? "").replace(/<img\b[^>]*>/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<NotificationAction> 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 {}