mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-02 02:22:06 -04:00
Compare commits
3 Commits
3194fc3fbe
...
37f92677cf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37f92677cf | ||
|
|
13e8130858 | ||
|
|
f6e590a518 |
@@ -7,60 +7,8 @@ import Quickshell
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
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 {}
|
||||
}
|
||||
readonly property AppearanceRounding rounding: AppearanceRounding {}
|
||||
readonly property AppearanceSpacing spacing: AppearanceSpacing {}
|
||||
readonly property AppearanceFontSize fontSize: AppearanceFontSize {}
|
||||
readonly property AppearanceAnim anim: AppearanceAnim {}
|
||||
}
|
||||
|
||||
6
quickshell/Common/AppearanceAnim.qml
Normal file
6
quickshell/Common/AppearanceAnim.qml
Normal file
@@ -0,0 +1,6 @@
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
readonly property AppearanceAnimCurves curves: AppearanceAnimCurves {}
|
||||
readonly property AppearanceAnimDurations durations: AppearanceAnimDurations {}
|
||||
}
|
||||
13
quickshell/Common/AppearanceAnimCurves.qml
Normal file
13
quickshell/Common/AppearanceAnimCurves.qml
Normal file
@@ -0,0 +1,13 @@
|
||||
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]
|
||||
}
|
||||
11
quickshell/Common/AppearanceAnimDurations.qml
Normal file
11
quickshell/Common/AppearanceAnimDurations.qml
Normal file
@@ -0,0 +1,11 @@
|
||||
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
|
||||
}
|
||||
9
quickshell/Common/AppearanceFontSize.qml
Normal file
9
quickshell/Common/AppearanceFontSize.qml
Normal file
@@ -0,0 +1,9 @@
|
||||
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
|
||||
}
|
||||
9
quickshell/Common/AppearanceRounding.qml
Normal file
9
quickshell/Common/AppearanceRounding.qml
Normal file
@@ -0,0 +1,9 @@
|
||||
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
|
||||
}
|
||||
9
quickshell/Common/AppearanceSpacing.qml
Normal file
9
quickshell/Common/AppearanceSpacing.qml
Normal file
@@ -0,0 +1,9 @@
|
||||
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
|
||||
}
|
||||
@@ -301,6 +301,7 @@ Singleton {
|
||||
property var workspaceNameIcons: ({})
|
||||
property bool waveProgressEnabled: true
|
||||
property bool scrollTitleEnabled: true
|
||||
property bool mediaAdaptiveWidthEnabled: true
|
||||
property bool audioVisualizerEnabled: true
|
||||
property string audioScrollMode: "volume"
|
||||
property int audioWheelScrollAmount: 5
|
||||
|
||||
@@ -140,6 +140,7 @@ var SPEC = {
|
||||
workspaceNameIcons: { def: {} },
|
||||
waveProgressEnabled: { def: true },
|
||||
scrollTitleEnabled: { def: true },
|
||||
mediaAdaptiveWidthEnabled: { def: true },
|
||||
audioVisualizerEnabled: { def: true },
|
||||
audioScrollMode: { def: "volume" },
|
||||
audioWheelScrollAmount: { def: 5 },
|
||||
|
||||
54
quickshell/Modals/WindowRuleCheckboxRow.qml
Normal file
54
quickshell/Modals/WindowRuleCheckboxRow.qml
Normal file
@@ -0,0 +1,54 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
17
quickshell/Modals/WindowRuleInputField.qml
Normal file
17
quickshell/Modals/WindowRuleInputField.qml
Normal file
@@ -0,0 +1,17 @@
|
||||
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
|
||||
}
|
||||
@@ -297,78 +297,6 @@ 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
|
||||
@@ -447,7 +375,7 @@ FloatingWindow {
|
||||
width: flickable.width - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
InputField {
|
||||
WindowRuleInputField {
|
||||
hasFocus: nameInput.activeFocus
|
||||
DankTextField {
|
||||
id: nameInput
|
||||
@@ -460,11 +388,11 @@ FloatingWindow {
|
||||
}
|
||||
}
|
||||
|
||||
SectionHeader {
|
||||
WindowRuleSectionHeader {
|
||||
title: I18n.tr("Match Criteria")
|
||||
}
|
||||
|
||||
InputField {
|
||||
WindowRuleInputField {
|
||||
hasFocus: appIdInput.activeFocus
|
||||
DankTextField {
|
||||
id: appIdInput
|
||||
@@ -481,7 +409,7 @@ FloatingWindow {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
InputField {
|
||||
WindowRuleInputField {
|
||||
width: addTitleBtn.visible ? parent.width - addTitleBtn.width - Theme.spacingS : parent.width
|
||||
hasFocus: titleInput.activeFocus
|
||||
DankTextField {
|
||||
@@ -514,7 +442,7 @@ FloatingWindow {
|
||||
}
|
||||
}
|
||||
|
||||
SectionHeader {
|
||||
WindowRuleSectionHeader {
|
||||
title: I18n.tr("Window Opening")
|
||||
}
|
||||
|
||||
@@ -522,24 +450,24 @@ FloatingWindow {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: floatingToggle
|
||||
label: I18n.tr("Float")
|
||||
}
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: maximizedToggle
|
||||
label: I18n.tr("Maximize")
|
||||
}
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: fullscreenToggle
|
||||
label: I18n.tr("Fullscreen")
|
||||
}
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: maximizedToEdgesToggle
|
||||
label: I18n.tr("Max to Edges")
|
||||
visible: isNiri
|
||||
}
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: openFocusedToggle
|
||||
label: I18n.tr("Focus")
|
||||
visible: isNiri
|
||||
@@ -563,7 +491,7 @@ FloatingWindow {
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
InputField {
|
||||
WindowRuleInputField {
|
||||
width: parent.width
|
||||
hasFocus: outputInput.activeFocus
|
||||
DankTextField {
|
||||
@@ -590,7 +518,7 @@ FloatingWindow {
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
InputField {
|
||||
WindowRuleInputField {
|
||||
width: parent.width
|
||||
hasFocus: workspaceInput.activeFocus
|
||||
DankTextField {
|
||||
@@ -623,7 +551,7 @@ FloatingWindow {
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
InputField {
|
||||
WindowRuleInputField {
|
||||
width: parent.width
|
||||
hasFocus: columnWidthInput.activeFocus
|
||||
DankTextField {
|
||||
@@ -650,7 +578,7 @@ FloatingWindow {
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
InputField {
|
||||
WindowRuleInputField {
|
||||
width: parent.width
|
||||
hasFocus: windowHeightInput.activeFocus
|
||||
DankTextField {
|
||||
@@ -666,7 +594,7 @@ FloatingWindow {
|
||||
}
|
||||
}
|
||||
|
||||
SectionHeader {
|
||||
WindowRuleSectionHeader {
|
||||
title: I18n.tr("Dynamic Properties")
|
||||
}
|
||||
|
||||
@@ -674,7 +602,7 @@ FloatingWindow {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: opacityEnabled
|
||||
label: I18n.tr("Opacity")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -696,19 +624,19 @@ FloatingWindow {
|
||||
spacing: Theme.spacingL
|
||||
visible: isNiri
|
||||
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: vrrToggle
|
||||
label: I18n.tr("VRR On-Demand")
|
||||
}
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: clipToGeometryToggle
|
||||
label: I18n.tr("Clip to Geometry")
|
||||
}
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: tiledStateToggle
|
||||
label: I18n.tr("Tiled State")
|
||||
}
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: drawBorderBgToggle
|
||||
label: I18n.tr("Border with BG")
|
||||
}
|
||||
@@ -769,7 +697,7 @@ FloatingWindow {
|
||||
spacing: Theme.spacingM
|
||||
visible: isNiri
|
||||
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: scrollFactorEnabled
|
||||
label: I18n.tr("Scroll Factor")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -790,7 +718,7 @@ FloatingWindow {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: cornerRadiusEnabled
|
||||
label: I18n.tr("Corner Radius")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -807,7 +735,7 @@ FloatingWindow {
|
||||
}
|
||||
}
|
||||
|
||||
SectionHeader {
|
||||
WindowRuleSectionHeader {
|
||||
title: I18n.tr("Size Constraints")
|
||||
}
|
||||
|
||||
@@ -827,7 +755,7 @@ FloatingWindow {
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
InputField {
|
||||
WindowRuleInputField {
|
||||
width: parent.width
|
||||
hasFocus: minWidthInput.activeFocus
|
||||
DankTextField {
|
||||
@@ -854,7 +782,7 @@ FloatingWindow {
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
InputField {
|
||||
WindowRuleInputField {
|
||||
width: parent.width
|
||||
hasFocus: maxWidthInput.activeFocus
|
||||
DankTextField {
|
||||
@@ -881,7 +809,7 @@ FloatingWindow {
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
InputField {
|
||||
WindowRuleInputField {
|
||||
width: parent.width
|
||||
hasFocus: minHeightInput.activeFocus
|
||||
DankTextField {
|
||||
@@ -908,7 +836,7 @@ FloatingWindow {
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
InputField {
|
||||
WindowRuleInputField {
|
||||
width: parent.width
|
||||
hasFocus: maxHeightInput.activeFocus
|
||||
DankTextField {
|
||||
@@ -924,7 +852,7 @@ FloatingWindow {
|
||||
}
|
||||
}
|
||||
|
||||
SectionHeader {
|
||||
WindowRuleSectionHeader {
|
||||
title: I18n.tr("Hyprland Options")
|
||||
visible: isHyprland
|
||||
}
|
||||
@@ -934,43 +862,43 @@ FloatingWindow {
|
||||
spacing: Theme.spacingL
|
||||
visible: isHyprland
|
||||
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: tileToggle
|
||||
label: I18n.tr("Tile")
|
||||
}
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: noFocusToggle
|
||||
label: I18n.tr("No Focus")
|
||||
}
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: noBorderToggle
|
||||
label: I18n.tr("No Border")
|
||||
}
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: noShadowToggle
|
||||
label: I18n.tr("No Shadow")
|
||||
}
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: noDimToggle
|
||||
label: I18n.tr("No Dim")
|
||||
}
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: noBlurToggle
|
||||
label: I18n.tr("No Blur")
|
||||
}
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: noAnimToggle
|
||||
label: I18n.tr("No Anim")
|
||||
}
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: noRoundingToggle
|
||||
label: I18n.tr("No Rounding")
|
||||
}
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: pinToggle
|
||||
label: I18n.tr("Pin")
|
||||
}
|
||||
CheckboxRow {
|
||||
WindowRuleCheckboxRow {
|
||||
id: opaqueToggle
|
||||
label: I18n.tr("Opaque")
|
||||
}
|
||||
@@ -993,7 +921,7 @@ FloatingWindow {
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
InputField {
|
||||
WindowRuleInputField {
|
||||
width: parent.width
|
||||
hasFocus: sizeInput.activeFocus
|
||||
DankTextField {
|
||||
@@ -1020,7 +948,7 @@ FloatingWindow {
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
InputField {
|
||||
WindowRuleInputField {
|
||||
width: parent.width
|
||||
hasFocus: moveInput.activeFocus
|
||||
DankTextField {
|
||||
@@ -1053,7 +981,7 @@ FloatingWindow {
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
InputField {
|
||||
WindowRuleInputField {
|
||||
width: parent.width
|
||||
hasFocus: monitorInput.activeFocus
|
||||
DankTextField {
|
||||
@@ -1080,7 +1008,7 @@ FloatingWindow {
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
InputField {
|
||||
WindowRuleInputField {
|
||||
width: parent.width
|
||||
hasFocus: hyprWorkspaceInput.activeFocus
|
||||
DankTextField {
|
||||
|
||||
15
quickshell/Modals/WindowRuleSectionHeader.qml
Normal file
15
quickshell/Modals/WindowRuleSectionHeader.qml
Normal file
@@ -0,0 +1,15 @@
|
||||
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
|
||||
}
|
||||
@@ -969,6 +969,7 @@ Item {
|
||||
axis: barWindow.axis
|
||||
barSpacing: barConfig?.spacing ?? 4
|
||||
barConfig: topBarContent.barConfig
|
||||
widgetData: parent.widgetData
|
||||
isAutoHideBar: topBarContent.barConfig?.autoHide ?? false
|
||||
isAtBottom: barWindow.axis?.edge === "bottom"
|
||||
visible: SettingsData.getFilteredScreens("systemTray").includes(barWindow.screen) && SystemTray.items.values.length > 0
|
||||
|
||||
@@ -19,7 +19,8 @@ BasePill {
|
||||
readonly property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser
|
||||
property bool compactMode: false
|
||||
property var widgetData: null
|
||||
readonly property int textWidth: {
|
||||
readonly property bool adaptiveWidthEnabled: SettingsData.mediaAdaptiveWidthEnabled
|
||||
readonly property int maxTextWidth: {
|
||||
const size = widgetData?.mediaSize !== undefined ? widgetData.mediaSize : SettingsData.mediaSize;
|
||||
switch (size) {
|
||||
case 0:
|
||||
@@ -36,10 +37,7 @@ BasePill {
|
||||
if (isVerticalOrientation) {
|
||||
return widgetThickness - horizontalPadding * 2;
|
||||
}
|
||||
const controlsWidth = 20 + Theme.spacingXS + 24 + Theme.spacingXS + 20;
|
||||
const audioVizWidth = 20;
|
||||
const contentWidth = audioVizWidth + Theme.spacingXS + controlsWidth;
|
||||
return contentWidth + (textWidth > 0 ? textWidth + Theme.spacingXS : 0);
|
||||
return 0;
|
||||
}
|
||||
readonly property int currentContentHeight: {
|
||||
if (!isVerticalOrientation) {
|
||||
@@ -119,7 +117,28 @@ BasePill {
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.playerAvailable ? root.currentContentWidth : 0
|
||||
id: contentRoot
|
||||
readonly property real measuredTextWidth: {
|
||||
if (!root.playerAvailable || root.maxTextWidth <= 0 || !textContainer.visible)
|
||||
return 0;
|
||||
// Preserve the fixed-width text slot even if metadata is briefly empty.
|
||||
if (!root.adaptiveWidthEnabled)
|
||||
return root.maxTextWidth;
|
||||
if (textContainer.displayText.length === 0)
|
||||
return 0;
|
||||
const rawWidth = mediaText.contentWidth;
|
||||
if (!isFinite(rawWidth) || rawWidth <= 0)
|
||||
return 0;
|
||||
return Math.min(root.maxTextWidth, Math.ceil(rawWidth));
|
||||
}
|
||||
readonly property int horizontalContentWidth: {
|
||||
const controlsWidth = 20 + Theme.spacingXS + 24 + Theme.spacingXS + 20;
|
||||
const audioVizWidth = 20;
|
||||
const baseWidth = audioVizWidth + Theme.spacingXS + controlsWidth;
|
||||
return baseWidth + (measuredTextWidth > 0 ? measuredTextWidth + Theme.spacingXS : 0);
|
||||
}
|
||||
|
||||
implicitWidth: root.playerAvailable ? (root.isVerticalOrientation ? root.currentContentWidth : horizontalContentWidth) : 0
|
||||
implicitHeight: root.playerAvailable ? root.currentContentHeight : 0
|
||||
opacity: root.playerAvailable ? 1 : 0
|
||||
|
||||
@@ -132,8 +151,9 @@ BasePill {
|
||||
|
||||
Behavior on implicitWidth {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +289,7 @@ BasePill {
|
||||
}
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: textWidth
|
||||
width: contentRoot.measuredTextWidth
|
||||
height: root.widgetThickness
|
||||
visible: {
|
||||
const size = widgetData?.mediaSize !== undefined ? widgetData.mediaSize : SettingsData.mediaSize;
|
||||
@@ -278,50 +298,95 @@ BasePill {
|
||||
clip: true
|
||||
color: "transparent"
|
||||
|
||||
StyledText {
|
||||
id: mediaText
|
||||
property bool needsScrolling: implicitWidth > textContainer.width && SettingsData.scrollTitleEnabled
|
||||
property real scrollOffset: 0
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: textContainer.displayText
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
wrapMode: Text.NoWrap
|
||||
x: needsScrolling ? -scrollOffset : 0
|
||||
onTextChanged: {
|
||||
scrollOffset = 0;
|
||||
scrollAnimation.restart();
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: scrollAnimation
|
||||
running: mediaText.needsScrolling && textContainer.visible
|
||||
loops: Animation.Infinite
|
||||
Item {
|
||||
id: textClip
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
PauseAnimation {
|
||||
duration: 2000
|
||||
StyledText {
|
||||
id: mediaText
|
||||
property bool needsScrolling: implicitWidth > textContainer.width && SettingsData.scrollTitleEnabled
|
||||
property real scrollOffset: 0
|
||||
property real textShift: 0
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: textContainer.displayText
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
wrapMode: Text.NoWrap
|
||||
x: (needsScrolling ? -scrollOffset : 0) + textShift
|
||||
opacity: 1
|
||||
|
||||
onTextChanged: {
|
||||
scrollOffset = 0;
|
||||
textShift = 0;
|
||||
scrollAnimation.restart();
|
||||
textChangeAnimation.restart();
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: mediaText
|
||||
property: "scrollOffset"
|
||||
from: 0
|
||||
to: mediaText.implicitWidth - textContainer.width + 5
|
||||
duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
|
||||
easing.type: Easing.Linear
|
||||
SequentialAnimation {
|
||||
id: scrollAnimation
|
||||
running: mediaText.needsScrolling && textContainer.visible
|
||||
loops: Animation.Infinite
|
||||
|
||||
PauseAnimation {
|
||||
duration: 2000
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: mediaText
|
||||
property: "scrollOffset"
|
||||
from: 0
|
||||
to: mediaText.implicitWidth - textContainer.width + 5
|
||||
duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
|
||||
easing.type: Easing.Linear
|
||||
}
|
||||
|
||||
PauseAnimation {
|
||||
duration: 2000
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: mediaText
|
||||
property: "scrollOffset"
|
||||
to: 0
|
||||
duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
|
||||
easing.type: Easing.Linear
|
||||
}
|
||||
}
|
||||
|
||||
PauseAnimation {
|
||||
duration: 2000
|
||||
}
|
||||
SequentialAnimation {
|
||||
id: textChangeAnimation
|
||||
|
||||
NumberAnimation {
|
||||
target: mediaText
|
||||
property: "scrollOffset"
|
||||
to: 0
|
||||
duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
|
||||
easing.type: Easing.Linear
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
target: mediaText
|
||||
property: "opacity"
|
||||
from: 0.7
|
||||
to: 1
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: mediaText
|
||||
property: "textShift"
|
||||
from: 4
|
||||
to: 0
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,11 @@ BasePill {
|
||||
enableCursor: false
|
||||
|
||||
property var parentWindow: null
|
||||
property var widgetData: null
|
||||
property string section: "right"
|
||||
property bool isAtBottom: false
|
||||
property bool isAutoHideBar: false
|
||||
property bool useOverflowPopup: !widgetData?.trayUseInlineExpansion
|
||||
readonly property var hiddenTrayIds: {
|
||||
const envValue = Quickshell.env("DMS_HIDE_TRAYIDS") || "";
|
||||
return envValue ? envValue.split(",").map(id => id.trim().toLowerCase()) : [];
|
||||
@@ -40,6 +43,76 @@ BasePill {
|
||||
return `${id}::${tooltipTitle}`;
|
||||
}
|
||||
|
||||
function trayIconSourceFor(trayItem) {
|
||||
let icon = trayItem && trayItem.icon;
|
||||
if (typeof icon === 'string' || icon instanceof String) {
|
||||
if (icon === "")
|
||||
return "";
|
||||
if (icon.includes("?path=")) {
|
||||
const split = icon.split("?path=");
|
||||
if (split.length !== 2)
|
||||
return icon;
|
||||
const name = split[0];
|
||||
const path = split[1];
|
||||
let fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
if (fileName.startsWith("dropboxstatus")) {
|
||||
fileName = `hicolor/16x16/status/${fileName}`;
|
||||
}
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
if (icon.startsWith("/") && !icon.startsWith("file://"))
|
||||
return `file://${icon}`;
|
||||
return icon;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function activateInlineTrayItem(trayItem, anchorItem) {
|
||||
if (!trayItem)
|
||||
return;
|
||||
if (!trayItem.onlyMenu) {
|
||||
trayItem.activate();
|
||||
return;
|
||||
}
|
||||
if (!trayItem.hasMenu)
|
||||
return;
|
||||
root.showForTrayItem(trayItem, anchorItem, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
|
||||
}
|
||||
|
||||
function openInlineTrayContextMenu(trayItem, areaItem, mouse, anchorItem) {
|
||||
if (!trayItem) {
|
||||
return;
|
||||
}
|
||||
if (!trayItem.hasMenu) {
|
||||
const gp = areaItem.mapToGlobal(mouse.x, mouse.y);
|
||||
root.callContextMenuFallback(trayItem.id, Math.round(gp.x), Math.round(gp.y));
|
||||
return;
|
||||
}
|
||||
root.showForTrayItem(trayItem, anchorItem, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
|
||||
}
|
||||
|
||||
function toggleIconName() {
|
||||
const edge = root.axis?.edge;
|
||||
if (root.useOverflowPopup) {
|
||||
switch (edge) {
|
||||
case "left":
|
||||
return root.menuOpen ? "keyboard_arrow_left" : "keyboard_arrow_right";
|
||||
case "right":
|
||||
return root.menuOpen ? "keyboard_arrow_right" : "keyboard_arrow_left";
|
||||
case "bottom":
|
||||
return root.menuOpen ? "keyboard_arrow_down" : "keyboard_arrow_up";
|
||||
case "top":
|
||||
return root.menuOpen ? "keyboard_arrow_up" : "keyboard_arrow_down";
|
||||
}
|
||||
}
|
||||
|
||||
if (edge === "left" || edge === "right") {
|
||||
return root.menuOpen == (root.section !== "right") ? "keyboard_arrow_up" : "keyboard_arrow_down";
|
||||
}
|
||||
|
||||
return root.menuOpen != (root.section === "right") ? "keyboard_arrow_left" : "keyboard_arrow_right";
|
||||
}
|
||||
|
||||
// ! TODO - replace with either native dbus client (like plugins use) or just a DMS cli or something
|
||||
function callContextMenuFallback(trayItemId, globalX, globalY) {
|
||||
const script = ['ITEMS=$(dbus-send --session --print-reply --dest=org.kde.StatusNotifierWatcher /StatusNotifierWatcher org.freedesktop.DBus.Properties.Get string:org.kde.StatusNotifierWatcher string:RegisteredStatusNotifierItems 2>/dev/null)', 'while IFS= read -r line; do', ' line="${line#*\\\"}"', ' line="${line%\\\"*}"', ' [ -z "$line" ] && continue', ' BUS="${line%%/*}"', ' OBJ="/${line#*/}"', ' ID=$(dbus-send --session --print-reply --dest="$BUS" "$OBJ" org.freedesktop.DBus.Properties.Get string:org.kde.StatusNotifierItem string:Id 2>/dev/null | grep -oP "(?<=\\\")(.*?)(?=\\\")" | tail -1)', ' if [ "$ID" = "$1" ]; then', ' dbus-send --session --type=method_call --dest="$BUS" "$OBJ" org.kde.StatusNotifierItem.ContextMenu int32:"$2" int32:"$3"', ' exit 0', ' fi', 'done <<< "$ITEMS"',].join("\n");
|
||||
@@ -78,6 +151,13 @@ BasePill {
|
||||
item: item
|
||||
}))
|
||||
readonly property var hiddenBarItems: allSortedTrayItems.filter(item => SessionData.isHiddenTrayId(root.getTrayItemKey(item)))
|
||||
readonly property bool reverseInlineHorizontal: !useOverflowPopup && !isVerticalOrientation && section === "right"
|
||||
readonly property bool reverseInlineVertical: !useOverflowPopup && isVerticalOrientation && section === "right"
|
||||
readonly property var displayedMainBarItems: reverseInlineHorizontal ? [...mainBarItems].reverse() : mainBarItems
|
||||
readonly property var displayedInlineExpandedItems: (reverseInlineHorizontal ? [...hiddenBarItems].reverse() : hiddenBarItems).map(item => ({
|
||||
key: getTrayItemKey(item),
|
||||
item: item
|
||||
}))
|
||||
|
||||
function moveTrayItemInFullOrder(visibleFromIndex, visibleToIndex) {
|
||||
if (visibleFromIndex === visibleToIndex || visibleFromIndex < 0 || visibleToIndex < 0)
|
||||
@@ -103,6 +183,7 @@ BasePill {
|
||||
property int dropTargetIndex: -1
|
||||
property bool suppressShiftAnimation: false
|
||||
readonly property bool hasHiddenItems: allTrayItems.length > mainBarItems.length
|
||||
readonly property bool inlineExpanded: hasHiddenItems && !useOverflowPopup && menuOpen
|
||||
visible: allTrayItems.length > 0
|
||||
opacity: allTrayItems.length > 0 ? 1 : 0
|
||||
|
||||
@@ -198,10 +279,11 @@ BasePill {
|
||||
id: rowComp
|
||||
Row {
|
||||
spacing: 0
|
||||
layoutDirection: root.reverseInlineHorizontal ? Qt.RightToLeft : Qt.LeftToRight
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.mainBarItems
|
||||
values: root.displayedMainBarItems
|
||||
objectProp: "key"
|
||||
}
|
||||
|
||||
@@ -209,29 +291,7 @@ BasePill {
|
||||
id: delegateRoot
|
||||
property var trayItem: modelData.item
|
||||
property string itemKey: modelData.key
|
||||
property string iconSource: {
|
||||
let icon = trayItem && trayItem.icon;
|
||||
if (typeof icon === 'string' || icon instanceof String) {
|
||||
if (icon === "")
|
||||
return "";
|
||||
if (icon.includes("?path=")) {
|
||||
const split = icon.split("?path=");
|
||||
if (split.length !== 2)
|
||||
return icon;
|
||||
const name = split[0];
|
||||
const path = split[1];
|
||||
let fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
if (fileName.startsWith("dropboxstatus")) {
|
||||
fileName = `hicolor/16x16/status/${fileName}`;
|
||||
}
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
if (icon.startsWith("/") && !icon.startsWith("file://"))
|
||||
return `file://${icon}`;
|
||||
return icon;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
property string iconSource: root.trayIconSourceFor(trayItem)
|
||||
|
||||
width: root.trayItemSize
|
||||
height: root.barThickness
|
||||
@@ -371,7 +431,8 @@ BasePill {
|
||||
}
|
||||
if (!delegateRoot.trayItem.hasMenu)
|
||||
return;
|
||||
root.menuOpen = false;
|
||||
if (root.useOverflowPopup)
|
||||
root.menuOpen = false;
|
||||
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
|
||||
}
|
||||
|
||||
@@ -380,8 +441,8 @@ BasePill {
|
||||
const distance = Math.abs(mouse.x - dragHandler.dragStartPos.x);
|
||||
if (distance > 5) {
|
||||
dragHandler.dragging = true;
|
||||
root.draggedIndex = index;
|
||||
root.dropTargetIndex = index;
|
||||
root.draggedIndex = root.reverseInlineHorizontal ? (root.mainBarItems.length - 1 - index) : index;
|
||||
root.dropTargetIndex = root.draggedIndex;
|
||||
}
|
||||
}
|
||||
if (!dragHandler.dragging)
|
||||
@@ -391,7 +452,8 @@ BasePill {
|
||||
dragHandler.dragAxisOffset = axisOffset;
|
||||
const itemSize = root.trayItemSize;
|
||||
const slotOffset = Math.round(axisOffset / itemSize);
|
||||
const newTargetIndex = Math.max(0, Math.min(root.mainBarItems.length - 1, index + slotOffset));
|
||||
const visualTargetIndex = Math.max(0, Math.min(root.mainBarItems.length - 1, index + slotOffset));
|
||||
const newTargetIndex = root.reverseInlineHorizontal ? (root.mainBarItems.length - 1 - visualTargetIndex) : visualTargetIndex;
|
||||
if (newTargetIndex !== root.dropTargetIndex) {
|
||||
root.dropTargetIndex = newTargetIndex;
|
||||
}
|
||||
@@ -407,7 +469,8 @@ BasePill {
|
||||
root.callContextMenuFallback(delegateRoot.trayItem.id, Math.round(gp.x), Math.round(gp.y));
|
||||
return;
|
||||
}
|
||||
root.menuOpen = false;
|
||||
if (root.useOverflowPopup)
|
||||
root.menuOpen = false;
|
||||
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
|
||||
}
|
||||
}
|
||||
@@ -429,7 +492,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: root.menuOpen ? "expand_less" : "expand_more"
|
||||
name: root.toggleIconName()
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
color: Theme.widgetTextColor
|
||||
}
|
||||
@@ -451,6 +514,301 @@ BasePill {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.displayedInlineExpandedItems
|
||||
objectProp: "key"
|
||||
}
|
||||
|
||||
delegate: inlineExpandedTrayItemDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: inlineExpandedTrayItemDelegate
|
||||
|
||||
Item {
|
||||
property var trayItem: modelData.item
|
||||
property string itemKey: modelData.key
|
||||
property string iconSource: root.trayIconSourceFor(trayItem)
|
||||
|
||||
width: root.isVerticalOrientation ? root.barThickness : (root.inlineExpanded ? root.trayItemSize : 0)
|
||||
height: root.isVerticalOrientation ? (root.inlineExpanded ? root.trayItemSize : 0) : root.barThickness
|
||||
visible: width > 0 || height > 0
|
||||
|
||||
Behavior on width {
|
||||
enabled: !root.isVerticalOrientation
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
enabled: root.isVerticalOrientation
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: inlineVisualContent
|
||||
width: root.trayItemSize
|
||||
height: root.trayItemSize
|
||||
x: root.isVerticalOrientation ? Math.round((parent.width - width) / 2) : (root.reverseInlineHorizontal ? parent.width - width : 0)
|
||||
y: root.isVerticalOrientation ? (root.reverseInlineVertical ? parent.height - height : 0) : Math.round((parent.height - height) / 2)
|
||||
radius: Theme.cornerRadius
|
||||
color: inlineTrayItemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
|
||||
opacity: root.inlineExpanded ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: inlineIconImg
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
source: iconSource
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
mipmap: true
|
||||
visible: status === Image.Ready
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: !inlineIconImg.visible
|
||||
text: {
|
||||
const itemId = trayItem?.id || "";
|
||||
if (!itemId)
|
||||
return "?";
|
||||
return itemId.charAt(0).toUpperCase();
|
||||
}
|
||||
font.pixelSize: 10
|
||||
color: Theme.widgetTextColor
|
||||
}
|
||||
|
||||
DankRipple {
|
||||
id: inlineItemRipple
|
||||
cornerRadius: Theme.cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: inlineTrayItemArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: root.inlineExpanded
|
||||
|
||||
onPressed: mouse => {
|
||||
const pos = mapToItem(inlineVisualContent, mouse.x, mouse.y);
|
||||
inlineItemRipple.trigger(pos.x, pos.y);
|
||||
}
|
||||
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.activateInlineTrayItem(trayItem, inlineVisualContent);
|
||||
return;
|
||||
}
|
||||
if (mouse.button !== Qt.RightButton)
|
||||
return;
|
||||
root.openInlineTrayContextMenu(trayItem, inlineTrayItemArea, mouse, inlineVisualContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: verticalMainTrayItemDelegate
|
||||
|
||||
Item {
|
||||
property var trayItem: modelData.item
|
||||
property string itemKey: modelData.key
|
||||
property string iconSource: root.trayIconSourceFor(trayItem)
|
||||
|
||||
width: root.barThickness
|
||||
height: root.trayItemSize
|
||||
z: dragHandler.dragging ? 100 : 0
|
||||
|
||||
property real shiftOffset: {
|
||||
if (root.draggedIndex < 0)
|
||||
return 0;
|
||||
if (index === root.draggedIndex)
|
||||
return 0;
|
||||
const dragIdx = root.draggedIndex;
|
||||
const dropIdx = root.dropTargetIndex;
|
||||
const shiftAmount = root.trayItemSize;
|
||||
if (dropIdx < 0)
|
||||
return 0;
|
||||
if (dragIdx < dropIdx && index > dragIdx && index <= dropIdx)
|
||||
return -shiftAmount;
|
||||
if (dragIdx > dropIdx && index >= dropIdx && index < dragIdx)
|
||||
return shiftAmount;
|
||||
return 0;
|
||||
}
|
||||
|
||||
transform: Translate {
|
||||
y: shiftOffset
|
||||
Behavior on y {
|
||||
enabled: !root.suppressShiftAnimation
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dragHandler
|
||||
anchors.fill: parent
|
||||
property bool dragging: false
|
||||
property point dragStartPos: Qt.point(0, 0)
|
||||
property real dragAxisOffset: 0
|
||||
property bool longPressing: false
|
||||
|
||||
Timer {
|
||||
id: longPressTimer
|
||||
interval: 400
|
||||
repeat: false
|
||||
onTriggered: dragHandler.longPressing = true
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: visualContent
|
||||
width: root.trayItemSize
|
||||
height: root.trayItemSize
|
||||
anchors.centerIn: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: trayItemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
|
||||
border.width: dragHandler.dragging ? 2 : 0
|
||||
border.color: Theme.primary
|
||||
opacity: dragHandler.dragging ? 0.8 : 1.0
|
||||
|
||||
transform: Translate {
|
||||
y: dragHandler.dragging ? dragHandler.dragAxisOffset : 0
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: iconImg
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
source: iconSource
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
mipmap: true
|
||||
visible: status === Image.Ready
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: !iconImg.visible
|
||||
text: {
|
||||
const itemId = trayItem?.id || "";
|
||||
if (!itemId)
|
||||
return "?";
|
||||
return itemId.charAt(0).toUpperCase();
|
||||
}
|
||||
font.pixelSize: 10
|
||||
color: Theme.widgetTextColor
|
||||
}
|
||||
|
||||
DankRipple {
|
||||
id: itemRipple
|
||||
cornerRadius: Theme.cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: trayItemArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
cursorShape: dragHandler.longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
|
||||
|
||||
onPressed: mouse => {
|
||||
const pos = mapToItem(visualContent, mouse.x, mouse.y);
|
||||
itemRipple.trigger(pos.x, pos.y);
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y);
|
||||
longPressTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: mouse => {
|
||||
longPressTimer.stop();
|
||||
const wasDragging = dragHandler.dragging;
|
||||
const didReorder = wasDragging && root.dropTargetIndex >= 0 && root.dropTargetIndex !== root.draggedIndex;
|
||||
|
||||
if (didReorder) {
|
||||
root.suppressShiftAnimation = true;
|
||||
root.moveTrayItemInFullOrder(root.draggedIndex, root.dropTargetIndex);
|
||||
Qt.callLater(() => root.suppressShiftAnimation = false);
|
||||
}
|
||||
|
||||
dragHandler.longPressing = false;
|
||||
dragHandler.dragging = false;
|
||||
dragHandler.dragAxisOffset = 0;
|
||||
root.draggedIndex = -1;
|
||||
root.dropTargetIndex = -1;
|
||||
|
||||
if (wasDragging || mouse.button !== Qt.LeftButton)
|
||||
return;
|
||||
|
||||
if (!trayItem)
|
||||
return;
|
||||
if (!trayItem.onlyMenu) {
|
||||
trayItem.activate();
|
||||
return;
|
||||
}
|
||||
if (!trayItem.hasMenu)
|
||||
return;
|
||||
if (root.useOverflowPopup)
|
||||
root.menuOpen = false;
|
||||
root.showForTrayItem(trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (dragHandler.longPressing && !dragHandler.dragging) {
|
||||
const distance = Math.abs(mouse.y - dragHandler.dragStartPos.y);
|
||||
if (distance > 5) {
|
||||
dragHandler.dragging = true;
|
||||
root.draggedIndex = index;
|
||||
root.dropTargetIndex = root.draggedIndex;
|
||||
}
|
||||
}
|
||||
if (!dragHandler.dragging)
|
||||
return;
|
||||
|
||||
const axisOffset = mouse.y - dragHandler.dragStartPos.y;
|
||||
dragHandler.dragAxisOffset = axisOffset;
|
||||
const itemSize = root.trayItemSize;
|
||||
const slotOffset = Math.round(axisOffset / itemSize);
|
||||
const newTargetIndex = Math.max(0, Math.min(root.mainBarItems.length - 1, index + slotOffset));
|
||||
if (newTargetIndex !== root.dropTargetIndex) {
|
||||
root.dropTargetIndex = newTargetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: mouse => {
|
||||
if (dragHandler.dragging)
|
||||
return;
|
||||
if (mouse.button !== Qt.RightButton)
|
||||
return;
|
||||
root.openInlineTrayContextMenu(trayItem, trayItemArea, mouse, visualContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,219 +817,23 @@ BasePill {
|
||||
Column {
|
||||
spacing: 0
|
||||
|
||||
// Column lacks layoutDirection, so we use four repeaters with mutually exclusive models to control whether main items or expanded items appear above/ below the toggle button.
|
||||
// When reverseInlineVertical is true the first and third repeaters are empty and the second and fourth are active, and vice-versa.
|
||||
// Because items are swapped between repeaters rather than reversed within a single list, vertical drag-and-drop indices don't need remapping (unlike the horizontal RightToLeft case).
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.mainBarItems
|
||||
values: root.reverseInlineVertical ? [] : root.displayedMainBarItems
|
||||
objectProp: "key"
|
||||
}
|
||||
delegate: verticalMainTrayItemDelegate
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: delegateRoot
|
||||
property var trayItem: modelData.item
|
||||
property string itemKey: modelData.key
|
||||
property string iconSource: {
|
||||
let icon = trayItem && trayItem.icon;
|
||||
if (typeof icon === 'string' || icon instanceof String) {
|
||||
if (icon === "")
|
||||
return "";
|
||||
if (icon.includes("?path=")) {
|
||||
const split = icon.split("?path=");
|
||||
if (split.length !== 2)
|
||||
return icon;
|
||||
const name = split[0];
|
||||
const path = split[1];
|
||||
let fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
if (fileName.startsWith("dropboxstatus")) {
|
||||
fileName = `hicolor/16x16/status/${fileName}`;
|
||||
}
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
if (icon.startsWith("/") && !icon.startsWith("file://"))
|
||||
return `file://${icon}`;
|
||||
return icon;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
width: root.barThickness
|
||||
height: root.trayItemSize
|
||||
z: dragHandler.dragging ? 100 : 0
|
||||
|
||||
property real shiftOffset: {
|
||||
if (root.draggedIndex < 0)
|
||||
return 0;
|
||||
if (index === root.draggedIndex)
|
||||
return 0;
|
||||
const dragIdx = root.draggedIndex;
|
||||
const dropIdx = root.dropTargetIndex;
|
||||
const shiftAmount = root.trayItemSize;
|
||||
if (dropIdx < 0)
|
||||
return 0;
|
||||
if (dragIdx < dropIdx && index > dragIdx && index <= dropIdx)
|
||||
return -shiftAmount;
|
||||
if (dragIdx > dropIdx && index >= dropIdx && index < dragIdx)
|
||||
return shiftAmount;
|
||||
return 0;
|
||||
}
|
||||
|
||||
transform: Translate {
|
||||
y: delegateRoot.shiftOffset
|
||||
Behavior on y {
|
||||
enabled: !root.suppressShiftAnimation
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dragHandler
|
||||
anchors.fill: parent
|
||||
property bool dragging: false
|
||||
property point dragStartPos: Qt.point(0, 0)
|
||||
property real dragAxisOffset: 0
|
||||
property bool longPressing: false
|
||||
|
||||
Timer {
|
||||
id: longPressTimer
|
||||
interval: 400
|
||||
repeat: false
|
||||
onTriggered: dragHandler.longPressing = true
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: visualContent
|
||||
width: root.trayItemSize
|
||||
height: root.trayItemSize
|
||||
anchors.centerIn: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: trayItemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
|
||||
border.width: dragHandler.dragging ? 2 : 0
|
||||
border.color: Theme.primary
|
||||
opacity: dragHandler.dragging ? 0.8 : 1.0
|
||||
|
||||
transform: Translate {
|
||||
y: dragHandler.dragging ? dragHandler.dragAxisOffset : 0
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: iconImg
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
source: delegateRoot.iconSource
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
mipmap: true
|
||||
visible: status === Image.Ready
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: !iconImg.visible
|
||||
text: {
|
||||
const itemId = trayItem?.id || "";
|
||||
if (!itemId)
|
||||
return "?";
|
||||
return itemId.charAt(0).toUpperCase();
|
||||
}
|
||||
font.pixelSize: 10
|
||||
color: Theme.widgetTextColor
|
||||
}
|
||||
|
||||
DankRipple {
|
||||
id: itemRipple
|
||||
cornerRadius: Theme.cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: trayItemArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
cursorShape: dragHandler.longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
|
||||
|
||||
onPressed: mouse => {
|
||||
const pos = mapToItem(visualContent, mouse.x, mouse.y);
|
||||
itemRipple.trigger(pos.x, pos.y);
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y);
|
||||
longPressTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: mouse => {
|
||||
longPressTimer.stop();
|
||||
const wasDragging = dragHandler.dragging;
|
||||
const didReorder = wasDragging && root.dropTargetIndex >= 0 && root.dropTargetIndex !== root.draggedIndex;
|
||||
|
||||
if (didReorder) {
|
||||
root.suppressShiftAnimation = true;
|
||||
root.moveTrayItemInFullOrder(root.draggedIndex, root.dropTargetIndex);
|
||||
Qt.callLater(() => root.suppressShiftAnimation = false);
|
||||
}
|
||||
|
||||
dragHandler.longPressing = false;
|
||||
dragHandler.dragging = false;
|
||||
dragHandler.dragAxisOffset = 0;
|
||||
root.draggedIndex = -1;
|
||||
root.dropTargetIndex = -1;
|
||||
|
||||
if (wasDragging || mouse.button !== Qt.LeftButton)
|
||||
return;
|
||||
|
||||
if (!delegateRoot.trayItem)
|
||||
return;
|
||||
if (!delegateRoot.trayItem.onlyMenu) {
|
||||
delegateRoot.trayItem.activate();
|
||||
return;
|
||||
}
|
||||
if (!delegateRoot.trayItem.hasMenu)
|
||||
return;
|
||||
root.menuOpen = false;
|
||||
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (dragHandler.longPressing && !dragHandler.dragging) {
|
||||
const distance = Math.abs(mouse.y - dragHandler.dragStartPos.y);
|
||||
if (distance > 5) {
|
||||
dragHandler.dragging = true;
|
||||
root.draggedIndex = index;
|
||||
root.dropTargetIndex = index;
|
||||
}
|
||||
}
|
||||
if (!dragHandler.dragging)
|
||||
return;
|
||||
|
||||
const axisOffset = mouse.y - dragHandler.dragStartPos.y;
|
||||
dragHandler.dragAxisOffset = axisOffset;
|
||||
const itemSize = root.trayItemSize;
|
||||
const slotOffset = Math.round(axisOffset / itemSize);
|
||||
const newTargetIndex = Math.max(0, Math.min(root.mainBarItems.length - 1, index + slotOffset));
|
||||
if (newTargetIndex !== root.dropTargetIndex) {
|
||||
root.dropTargetIndex = newTargetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: mouse => {
|
||||
if (dragHandler.dragging)
|
||||
return;
|
||||
if (mouse.button !== Qt.RightButton)
|
||||
return;
|
||||
if (!delegateRoot.trayItem?.hasMenu) {
|
||||
const gp = trayItemArea.mapToGlobal(mouse.x, mouse.y);
|
||||
root.callContextMenuFallback(delegateRoot.trayItem.id, Math.round(gp.x), Math.round(gp.y));
|
||||
return;
|
||||
}
|
||||
root.menuOpen = false;
|
||||
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.reverseInlineVertical ? root.displayedInlineExpandedItems : []
|
||||
objectProp: "key"
|
||||
}
|
||||
delegate: inlineExpandedTrayItemDelegate
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -689,14 +851,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
const edge = root.axis?.edge;
|
||||
if (edge === "left") {
|
||||
return root.menuOpen ? "chevron_left" : "chevron_right";
|
||||
} else {
|
||||
return root.menuOpen ? "chevron_right" : "chevron_left";
|
||||
}
|
||||
}
|
||||
name: root.toggleIconName()
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
color: Theme.widgetTextColor
|
||||
}
|
||||
@@ -718,6 +873,22 @@ BasePill {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.reverseInlineVertical ? [] : root.displayedInlineExpandedItems
|
||||
objectProp: "key"
|
||||
}
|
||||
delegate: inlineExpandedTrayItemDelegate
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.reverseInlineVertical ? root.displayedMainBarItems : []
|
||||
objectProp: "key"
|
||||
}
|
||||
delegate: verticalMainTrayItemDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,7 +904,7 @@ BasePill {
|
||||
blurRadius: Theme.cornerRadius
|
||||
}
|
||||
|
||||
visible: root.menuOpen
|
||||
visible: root.useOverflowPopup && root.menuOpen
|
||||
screen: root.parentScreen
|
||||
WlrLayershell.layer: WlrLayershell.Top
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
@@ -749,13 +920,14 @@ BasePill {
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [overflowMenu]
|
||||
active: CompositorService.useHyprlandFocusGrab && root.menuOpen
|
||||
active: CompositorService.useHyprlandFocusGrab && root.useOverflowPopup && root.menuOpen
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PopoutManager
|
||||
function onPopoutOpening() {
|
||||
root.menuOpen = false;
|
||||
if (root.useOverflowPopup)
|
||||
root.menuOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1021,30 +1193,7 @@ BasePill {
|
||||
|
||||
delegate: Rectangle {
|
||||
property var trayItem: modelData
|
||||
property string iconSource: {
|
||||
let icon = trayItem?.icon;
|
||||
if (typeof icon === 'string' || icon instanceof String) {
|
||||
if (icon === "")
|
||||
return "";
|
||||
if (icon.includes("?path=")) {
|
||||
const split = icon.split("?path=");
|
||||
if (split.length !== 2)
|
||||
return icon;
|
||||
const name = split[0];
|
||||
const path = split[1];
|
||||
let fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
if (fileName.startsWith("dropboxstatus")) {
|
||||
fileName = `hicolor/16x16/status/${fileName}`;
|
||||
}
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
if (icon.startsWith("/") && !icon.startsWith("file://")) {
|
||||
return `file://${icon}`;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
property string iconSource: root.trayIconSourceFor(trayItem)
|
||||
|
||||
width: root.trayItemSize + 4
|
||||
height: root.trayItemSize + 4
|
||||
@@ -1313,7 +1462,8 @@ BasePill {
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
updatePosition();
|
||||
root.menuOpen = false;
|
||||
if (root.useOverflowPopup)
|
||||
root.menuOpen = false;
|
||||
PopoutManager.closeAllPopouts();
|
||||
ModalManager.closeAllModalsExcept(null);
|
||||
}
|
||||
|
||||
161
quickshell/Modules/ProcessList/CircleGauge.qml
Normal file
161
quickshell/Modules/ProcessList/CircleGauge.qml
Normal file
@@ -0,0 +1,161 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
29
quickshell/Modules/ProcessList/InfoRow.qml
Normal file
29
quickshell/Modules/ProcessList/InfoRow.qml
Normal file
@@ -0,0 +1,29 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
160
quickshell/Modules/ProcessList/PerformanceCard.qml
Normal file
160
quickshell/Modules/ProcessList/PerformanceCard.qml
Normal file
@@ -0,0 +1,160 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -73,6 +72,7 @@ Item {
|
||||
PerformanceCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
historySize: root.historySize
|
||||
title: "CPU"
|
||||
icon: "memory"
|
||||
value: DgopService.cpuUsage.toFixed(1) + "%"
|
||||
@@ -88,6 +88,7 @@ Item {
|
||||
PerformanceCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
historySize: root.historySize
|
||||
title: I18n.tr("Memory")
|
||||
icon: "sd_card"
|
||||
value: DgopService.memoryUsage.toFixed(1) + "%"
|
||||
@@ -109,6 +110,7 @@ Item {
|
||||
PerformanceCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
historySize: root.historySize
|
||||
title: I18n.tr("Network")
|
||||
icon: "swap_horiz"
|
||||
value: "↓ " + root.formatBytes(DgopService.networkRxRate)
|
||||
@@ -125,6 +127,7 @@ Item {
|
||||
PerformanceCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
historySize: root.historySize
|
||||
title: I18n.tr("Disk")
|
||||
icon: "storage"
|
||||
value: "R: " + root.formatBytes(DgopService.diskReadRate)
|
||||
@@ -146,159 +149,4 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
334
quickshell/Modules/ProcessList/ProcessItem.qml
Normal file
334
quickshell/Modules/ProcessList/ProcessItem.qml
Normal file
@@ -0,0 +1,334 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -374,162 +374,4 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,402 +368,4 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
74
quickshell/Modules/ProcessList/SortableHeader.qml
Normal file
74
quickshell/Modules/ProcessList/SortableHeader.qml
Normal file
@@ -0,0 +1,74 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -358,29 +358,4 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,13 @@ Item {
|
||||
onToggled: checked => SettingsData.set("audioVisualizerEnabled", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
text: I18n.tr("Adaptive Media Width")
|
||||
description: I18n.tr("Shrink the media widget to fit shorter song titles while still respecting the configured maximum size")
|
||||
checked: SettingsData.mediaAdaptiveWidthEnabled
|
||||
onToggled: checked => SettingsData.set("mediaAdaptiveWidthEnabled", checked)
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
property var scrollOptsInternal: ["volume", "song", "nothing"]
|
||||
property var scrollOptsDisplay: [I18n.tr("Change Volume", "media scroll wheel option"), I18n.tr("Change Song", "media scroll wheel option"), I18n.tr("Nothing", "media scroll wheel option")]
|
||||
|
||||
@@ -430,7 +430,7 @@ Item {
|
||||
"id": widget.id,
|
||||
"enabled": widget.enabled
|
||||
};
|
||||
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"];
|
||||
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge", "trayUseInlineExpansion"];
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (widget[keys[i]] !== undefined)
|
||||
result[keys[i]] = widget[keys[i]];
|
||||
@@ -712,6 +712,8 @@ Item {
|
||||
item.barMaxVisibleRunningApps = widget.barMaxVisibleRunningApps;
|
||||
if (widget.barShowOverflowBadge !== undefined)
|
||||
item.barShowOverflowBadge = widget.barShowOverflowBadge;
|
||||
if (widget.trayUseInlineExpansion !== undefined)
|
||||
item.trayUseInlineExpansion = widget.trayUseInlineExpansion;
|
||||
}
|
||||
widgets.push(item);
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@ Column {
|
||||
"id": widget.id,
|
||||
"enabled": widget.enabled
|
||||
};
|
||||
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"];
|
||||
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge", "trayUseInlineExpansion"];
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (widget[keys[i]] !== undefined)
|
||||
result[keys[i]] = widget[keys[i]];
|
||||
@@ -437,7 +437,7 @@ Column {
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
visible: modelData.id === "clock" || modelData.id === "focusedWindow" || modelData.id === "keyboard_layout_name" || modelData.id === "appsDock"
|
||||
visible: modelData.id === "clock" || modelData.id === "focusedWindow" || modelData.id === "keyboard_layout_name" || modelData.id === "appsDock" || modelData.id === "systemTray"
|
||||
|
||||
DankActionButton {
|
||||
id: compactModeButton
|
||||
@@ -543,6 +543,39 @@ Column {
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: trayMenuButton
|
||||
buttonSize: 32
|
||||
visible: modelData.id === "systemTray"
|
||||
iconName: "more_vert"
|
||||
iconSize: 18
|
||||
iconColor: Theme.outline
|
||||
onClicked: {
|
||||
trayContextMenu.widgetData = modelData;
|
||||
trayContextMenu.sectionId = root.sectionId;
|
||||
trayContextMenu.widgetIndex = index;
|
||||
|
||||
var buttonPos = trayMenuButton.mapToItem(root, 0, 0);
|
||||
var popupWidth = trayContextMenu.width;
|
||||
var popupHeight = trayContextMenu.height;
|
||||
|
||||
var xPos = buttonPos.x - popupWidth - Theme.spacingS;
|
||||
if (xPos < 0)
|
||||
xPos = buttonPos.x + trayMenuButton.width + Theme.spacingS;
|
||||
|
||||
var yPos = buttonPos.y - popupHeight / 2 + trayMenuButton.height / 2;
|
||||
if (yPos < 0) {
|
||||
yPos = Theme.spacingS;
|
||||
} else if (yPos + popupHeight > root.height) {
|
||||
yPos = root.height - popupHeight - Theme.spacingS;
|
||||
}
|
||||
|
||||
trayContextMenu.x = xPos;
|
||||
trayContextMenu.y = yPos;
|
||||
trayContextMenu.open();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: compactModeTooltip
|
||||
width: tooltipText.contentWidth + Theme.spacingM * 2
|
||||
@@ -931,6 +964,88 @@ Column {
|
||||
}
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: trayContextMenu
|
||||
|
||||
property var widgetData: null
|
||||
property string sectionId: ""
|
||||
property int widgetIndex: -1
|
||||
readonly property var currentWidgetData: (widgetIndex >= 0 && widgetIndex < root.items.length) ? root.items[widgetIndex] : widgetData
|
||||
|
||||
width: 220
|
||||
height: contentColumn.implicitHeight + Theme.spacingS * 2
|
||||
padding: 0
|
||||
modal: true
|
||||
focus: true
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
Column {
|
||||
id: contentColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 2
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: trayOverflowArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "arrow_selector_tool"
|
||||
size: 16
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Use Inline Expansion")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: trayOverflowToggle
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 40
|
||||
height: 20
|
||||
checked: trayContextMenu.currentWidgetData?.trayUseInlineExpansion ?? false
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: trayOverflowArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
const newValue = !(trayContextMenu.currentWidgetData?.trayUseInlineExpansion ?? false);
|
||||
root.overflowSettingChanged(trayContextMenu.sectionId, trayContextMenu.widgetIndex, "trayUseInlineExpansion", newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: diskUsageContextMenu
|
||||
|
||||
|
||||
147
quickshell/Services/NotifWrapper.qml
Normal file
147
quickshell/Services/NotifWrapper.qml
Normal file
@@ -0,0 +1,147 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -655,150 +655,6 @@ 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 {}
|
||||
|
||||
Reference in New Issue
Block a user