Merge branch 'main' into module/wifi
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 16s
Python / lint-format (pull_request) Successful in 32s
Python / test (pull_request) Successful in 38s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m49s
C++ / build (pull_request) Successful in 3m5s
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 16s
Python / lint-format (pull_request) Successful in 32s
Python / test (pull_request) Successful in 38s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m49s
C++ / build (pull_request) Successful in 3m5s
This commit is contained in:
@@ -1,135 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Config
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
default property alias content: contentColumn.data
|
||||
property string description: ""
|
||||
property bool expanded: false
|
||||
property bool nested: false
|
||||
property bool showBackground: false
|
||||
required property string title
|
||||
|
||||
signal toggleRequested
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
Item {
|
||||
id: sectionHeaderItem
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.max(titleRow.implicitHeight + Appearance.padding.normal * 2, 48)
|
||||
|
||||
RowLayout {
|
||||
id: titleRow
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Appearance.padding.normal
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Appearance.padding.normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
CustomText {
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
font.weight: 500
|
||||
text: root.title
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
color: DynamicColors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
rotation: root.expanded ? 180 : 0
|
||||
text: "expand_more"
|
||||
|
||||
Behavior on rotation {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.small
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
anchors.fill: parent
|
||||
color: DynamicColors.palette.m3onSurface
|
||||
radius: Appearance.rounding.normal
|
||||
showHoverBackground: false
|
||||
|
||||
onClicked: {
|
||||
root.toggleRequested();
|
||||
root.expanded = !root.expanded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentWrapper
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: root.expanded ? (contentColumn.implicitHeight + Appearance.spacing.small * 2) : 0
|
||||
clip: true
|
||||
|
||||
Behavior on Layout.preferredHeight {
|
||||
Anim {
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: backgroundRect
|
||||
|
||||
anchors.fill: parent
|
||||
color: DynamicColors.transparency.enabled ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, root.nested ? 3 : 2) : (root.nested ? DynamicColors.palette.m3surfaceContainerHigh : DynamicColors.palette.m3surfaceContainer)
|
||||
opacity: root.showBackground && root.expanded ? 1.0 : 0.0
|
||||
radius: Appearance.rounding.normal
|
||||
visible: root.showBackground
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
|
||||
anchors.bottomMargin: Appearance.spacing.small
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Appearance.padding.normal
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Appearance.padding.normal
|
||||
opacity: root.expanded ? 1.0 : 0.0
|
||||
spacing: Appearance.spacing.small
|
||||
y: Appearance.spacing.small
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: descriptionText
|
||||
|
||||
Layout.bottomMargin: root.description !== "" ? Appearance.spacing.small : 0
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: root.description !== "" ? Appearance.spacing.smaller : 0
|
||||
color: DynamicColors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Appearance.font.size.small
|
||||
text: root.description
|
||||
visible: root.description !== ""
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Templates
|
||||
import qs.Config
|
||||
|
||||
Slider {
|
||||
id: root
|
||||
|
||||
property color nonPeakColor: DynamicColors.tPalette.m3primary
|
||||
required property real peak
|
||||
property color peakColor: DynamicColors.palette.m3primary
|
||||
|
||||
background: Item {
|
||||
CustomRect {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: root.implicitHeight / 3
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: root.implicitHeight / 3
|
||||
bottomRightRadius: root.implicitHeight / 15
|
||||
color: root.nonPeakColor
|
||||
implicitWidth: root.handle.x - root.implicitHeight
|
||||
radius: Appearance.rounding.full
|
||||
topRightRadius: root.implicitHeight / 15
|
||||
|
||||
CustomRect {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
bottomRightRadius: root.implicitHeight / 15
|
||||
color: root.peakColor
|
||||
implicitWidth: parent.width * root.peak
|
||||
radius: Appearance.rounding.full
|
||||
topRightRadius: root.implicitHeight / 15
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {
|
||||
duration: 50
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: root.implicitHeight / 3
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: root.implicitHeight / 3
|
||||
bottomLeftRadius: root.implicitHeight / 15
|
||||
color: DynamicColors.tPalette.m3surfaceContainer
|
||||
implicitWidth: root.implicitWidth - root.handle.x - root.handle.implicitWidth - root.implicitHeight
|
||||
radius: Appearance.rounding.full
|
||||
topLeftRadius: root.implicitHeight / 15
|
||||
}
|
||||
}
|
||||
handle: CustomRect {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: DynamicColors.palette.m3primary
|
||||
implicitHeight: 15
|
||||
implicitWidth: 5
|
||||
radius: Appearance.rounding.full
|
||||
x: root.visualPosition * root.availableWidth - implicitWidth / 2
|
||||
|
||||
MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
BusyIndicator {
|
||||
id: control
|
||||
|
||||
property int busySize: 64
|
||||
property color color: delegate.color
|
||||
|
||||
contentItem: Item {
|
||||
implicitHeight: control.busySize
|
||||
implicitWidth: control.busySize
|
||||
|
||||
Item {
|
||||
id: item
|
||||
|
||||
height: control.busySize
|
||||
opacity: control.running ? 1 : 0
|
||||
width: control.busySize
|
||||
x: parent.width / 2 - (control.busySize / 2)
|
||||
y: parent.height / 2 - (control.busySize / 2)
|
||||
|
||||
Behavior on opacity {
|
||||
OpacityAnimator {
|
||||
duration: 250
|
||||
}
|
||||
}
|
||||
|
||||
RotationAnimator {
|
||||
duration: 1250
|
||||
from: 0
|
||||
loops: Animation.Infinite
|
||||
running: control.visible && control.running
|
||||
target: item
|
||||
to: 360
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
model: 6
|
||||
|
||||
CustomRect {
|
||||
id: delegate
|
||||
|
||||
required property int index
|
||||
|
||||
color: control.color
|
||||
implicitHeight: 10
|
||||
implicitWidth: 10
|
||||
radius: 5
|
||||
x: item.width / 2 - width / 2
|
||||
y: item.height / 2 - height / 2
|
||||
|
||||
transform: [
|
||||
Translate {
|
||||
y: -Math.min(item.width, item.height) * 0.5 + 5
|
||||
},
|
||||
Rotation {
|
||||
angle: delegate.index / repeater.count * 360
|
||||
origin.x: 5
|
||||
origin.y: 5
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Config
|
||||
|
||||
Button {
|
||||
id: control
|
||||
|
||||
property color bgColor: DynamicColors.palette.m3primary
|
||||
property int radius: Appearance.rounding.smallest / 2
|
||||
property color textColor: DynamicColors.palette.m3onPrimary
|
||||
|
||||
background: CustomRect {
|
||||
color: control.bgColor
|
||||
opacity: control.enabled ? 1.0 : 0.5
|
||||
radius: control.radius
|
||||
}
|
||||
contentItem: CustomText {
|
||||
color: control.textColor
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
opacity: control.enabled ? 1.0 : 0.5
|
||||
text: control.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
radius: control.radius
|
||||
|
||||
onClicked: {
|
||||
control.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import qs.Config
|
||||
|
||||
ComboBox {
|
||||
id: root
|
||||
|
||||
property int cornerRadius: Appearance.rounding.normal
|
||||
property int fieldHeight: 42
|
||||
property bool filled: true
|
||||
property real focusRingOpacity: 0.70
|
||||
property int hPadding: 16
|
||||
property int menuCornerRadius: 16
|
||||
property int menuRowHeight: 46
|
||||
property int menuVisibleRows: 7
|
||||
property bool preferPopupWindow: false
|
||||
|
||||
hoverEnabled: true
|
||||
implicitHeight: fieldHeight
|
||||
implicitWidth: 240
|
||||
spacing: 8
|
||||
|
||||
// ---------- Field background (filled/outlined + state layers + focus ring) ----------
|
||||
background: Item {
|
||||
anchors.fill: parent
|
||||
|
||||
CustomRect {
|
||||
id: container
|
||||
|
||||
anchors.fill: parent
|
||||
color: DynamicColors.palette.m3surfaceVariant
|
||||
radius: root.cornerRadius
|
||||
|
||||
StateLayer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Content ----------
|
||||
contentItem: RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: root.hPadding
|
||||
anchors.rightMargin: root.hPadding
|
||||
spacing: 12
|
||||
|
||||
// Display text
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: root.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSurfaceVariant
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: 16
|
||||
font.weight: Font.Medium
|
||||
text: root.currentText
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
// Indicator chevron (simple, replace with your icon system)
|
||||
CustomText {
|
||||
color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurfaceVariant
|
||||
rotation: root.popup.visible ? 180 : 0
|
||||
text: "▾"
|
||||
transformOrigin: Item.Center
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
Behavior on rotation {
|
||||
NumberAnimation {
|
||||
duration: 140
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
popup: Popup {
|
||||
id: p
|
||||
|
||||
implicitHeight: list.contentItem.height + Appearance.padding.small * 2
|
||||
implicitWidth: root.width
|
||||
modal: true
|
||||
popupType: root.preferPopupWindow ? Popup.Window : Popup.Item
|
||||
y: -list.currentIndex * (root.menuRowHeight + Appearance.spacing.small) - Appearance.padding.small
|
||||
|
||||
background: CustomRect {
|
||||
color: DynamicColors.palette.m3surface
|
||||
radius: root.menuCornerRadius
|
||||
}
|
||||
contentItem: ListView {
|
||||
id: list
|
||||
|
||||
anchors.bottomMargin: Appearance.padding.small
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Appearance.padding.small
|
||||
clip: true
|
||||
currentIndex: root.currentIndex
|
||||
model: root.delegateModel
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
delegate: CustomRect {
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: (index === root.currentIndex) ? DynamicColors.palette.m3primary : "transparent"
|
||||
implicitHeight: root.menuRowHeight
|
||||
implicitWidth: p.implicitWidth - Appearance.padding.small * 2
|
||||
radius: Appearance.rounding.normal - Appearance.padding.small
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: DynamicColors.palette.m3onSurface
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: 15
|
||||
text: modelData
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
CustomText {
|
||||
color: DynamicColors.palette.m3onSurfaceVariant
|
||||
text: "✓"
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
visible: index === root.currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
onClicked: {
|
||||
root.currentIndex = index;
|
||||
p.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expressive-ish open/close motion: subtle scale+fade (tune to taste). :contentReference[oaicite:5]{index=5}
|
||||
enter: Transition {
|
||||
Anim {
|
||||
from: 0
|
||||
property: "opacity"
|
||||
to: 1
|
||||
}
|
||||
|
||||
Anim {
|
||||
from: 0.98
|
||||
property: "scale"
|
||||
to: 1.0
|
||||
}
|
||||
}
|
||||
exit: Transition {
|
||||
Anim {
|
||||
from: 1
|
||||
property: "opacity"
|
||||
to: 0
|
||||
}
|
||||
}
|
||||
|
||||
Elevation {
|
||||
anchors.fill: parent
|
||||
level: 2
|
||||
radius: root.menuCornerRadius
|
||||
z: -1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Components
|
||||
|
||||
ToolTip {
|
||||
id: root
|
||||
|
||||
property bool alternativeVisibleCondition: false
|
||||
property bool extraVisibleCondition: true
|
||||
readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
|
||||
|
||||
background: null
|
||||
horizontalPadding: 10
|
||||
verticalPadding: 5
|
||||
visible: internalVisibleCondition
|
||||
|
||||
contentItem: CustomTooltipContent {
|
||||
id: contentItem
|
||||
|
||||
horizontalPadding: root.horizontalPadding
|
||||
shown: root.internalVisibleCondition
|
||||
text: root.text
|
||||
verticalPadding: root.verticalPadding
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Components
|
||||
import qs.Config
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property real horizontalPadding: 10
|
||||
property bool isVisible: backgroundRectangle.implicitHeight > 0
|
||||
property bool shown: false
|
||||
required property string text
|
||||
property real verticalPadding: 5
|
||||
|
||||
implicitHeight: tooltipTextObject.implicitHeight + 2 * root.verticalPadding
|
||||
implicitWidth: tooltipTextObject.implicitWidth + 2 * root.horizontalPadding
|
||||
|
||||
Rectangle {
|
||||
id: backgroundRectangle
|
||||
|
||||
clip: true
|
||||
color: DynamicColors.tPalette.m3inverseSurface ?? "#3C4043"
|
||||
implicitHeight: shown ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0
|
||||
implicitWidth: shown ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0
|
||||
opacity: shown ? 1 : 0
|
||||
radius: Appearance.rounding.smallest
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on implicitWidth {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
anchors {
|
||||
bottom: root.bottom
|
||||
horizontalCenter: root.horizontalCenter
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: tooltipTextObject
|
||||
|
||||
anchors.centerIn: parent
|
||||
color: DynamicColors.palette.m3inverseOnSurface ?? "#FFFFFF"
|
||||
text: root.text
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import qs.Config
|
||||
import QtQuick
|
||||
|
||||
CustomRect {
|
||||
required property int extra
|
||||
|
||||
anchors.margins: 8
|
||||
anchors.right: parent.right
|
||||
color: DynamicColors.palette.m3tertiary
|
||||
implicitHeight: count.implicitHeight + 4 * 2
|
||||
implicitWidth: count.implicitWidth + 8 * 2
|
||||
opacity: extra > 0 ? 1 : 0
|
||||
radius: Appearance.rounding.smallest
|
||||
scale: extra > 0 ? 1 : 0.5
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
duration: MaterialEasing.expressiveEffectsTime
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
duration: MaterialEasing.expressiveEffectsTime
|
||||
easing.bezierCurve: MaterialEasing.expressiveEffects
|
||||
}
|
||||
}
|
||||
|
||||
Elevation {
|
||||
anchors.fill: parent
|
||||
level: 2
|
||||
opacity: parent.opacity
|
||||
radius: parent.radius
|
||||
z: -1
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: count
|
||||
|
||||
anchors.centerIn: parent
|
||||
animate: parent.opacity > 0
|
||||
color: DynamicColors.palette.m3onTertiary
|
||||
text: qsTr("+%1").arg(parent.extra)
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Config
|
||||
|
||||
IconButton {
|
||||
id: root
|
||||
|
||||
required property bool shouldBeVisible
|
||||
|
||||
opacity: 0
|
||||
scale: 0
|
||||
visible: root.scale > 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.small
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
onShouldBeVisibleChanged: {
|
||||
if (root.shouldBeVisible) {
|
||||
root.opacity = 1;
|
||||
root.scale = 1;
|
||||
} else {
|
||||
root.opacity = 0;
|
||||
root.scale = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,309 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Config
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
enum Variant {
|
||||
Outlined,
|
||||
Filled
|
||||
}
|
||||
|
||||
readonly property color accent: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3outline))
|
||||
readonly property real disabledOpacity: 0.38
|
||||
readonly property string effectiveTrailingIcon: root.trailingIcon.length > 0 ? root.trailingIcon : (root.password ? (field.echoMode === TextInput.Password ? "visibility" : "visibility_off") : (root.isError ? "error" : ""))
|
||||
property string errorText: ""
|
||||
readonly property alias field: field
|
||||
readonly property bool floating: focused || hasContent || prefixText.length > 0
|
||||
readonly property bool focused: field.activeFocus
|
||||
readonly property bool hasContent: field.text.length > 0
|
||||
readonly property color iconColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant))
|
||||
property int inputMethodHints: Qt.ImhNone
|
||||
property bool isError: false
|
||||
property string label: ""
|
||||
readonly property color labelColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant))
|
||||
property string leadingIcon: ""
|
||||
property int maxLength: 0
|
||||
property color notchColor: DynamicColors.palette.m3surface
|
||||
readonly property bool outlined: variant === M3TextField.Variant.Outlined
|
||||
property bool password: false
|
||||
property string placeholder: ""
|
||||
property string prefixText: ""
|
||||
property string suffixText: ""
|
||||
readonly property color supportColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurfaceVariant)
|
||||
property string supportingText: ""
|
||||
property alias text: field.text
|
||||
property string trailingIcon: ""
|
||||
readonly property bool trailingInteractive: root.password && root.trailingIcon.length === 0
|
||||
property var validator: null
|
||||
property int variant: M3TextField.Variant.Outlined
|
||||
|
||||
signal accepted
|
||||
|
||||
function forceFieldFocus(): void {
|
||||
field.forceActiveFocus();
|
||||
}
|
||||
|
||||
implicitHeight: 56 + (supportRow.visible ? supportRow.implicitHeight + Appearance.spacing.extraSmall : 0)
|
||||
implicitWidth: 240
|
||||
|
||||
CustomRect {
|
||||
id: filledBg
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
color: root.enabled ? (root.focused ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1) : DynamicColors.palette.m3surfaceContainerHigh) : DynamicColors.palette.m3onSurface
|
||||
implicitHeight: 56
|
||||
opacity: root.enabled ? 1 : 0.04 // M3 filled disabled container @ 4%
|
||||
|
||||
topLeftRadius: Appearance.rounding.small
|
||||
topRightRadius: Appearance.rounding.small
|
||||
visible: !root.outlined
|
||||
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
anchors.fill: parent
|
||||
color: root.isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface
|
||||
enabled: root.enabled
|
||||
topLeftRadius: Appearance.rounding.small
|
||||
topRightRadius: Appearance.rounding.small
|
||||
|
||||
onClicked: field.forceActiveFocus()
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: root.accent
|
||||
implicitHeight: root.focused || root.isError ? 2 : 1
|
||||
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
}
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
type: Anim.StandardSmall
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: outline
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
border.color: root.accent
|
||||
border.width: root.focused || root.isError ? 2 : 1
|
||||
color: "transparent"
|
||||
implicitHeight: 56
|
||||
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||
radius: Appearance.rounding.small
|
||||
visible: root.outlined
|
||||
|
||||
Behavior on border.color {
|
||||
CAnim {
|
||||
}
|
||||
}
|
||||
Behavior on border.width {
|
||||
Anim {
|
||||
type: Anim.StandardSmall
|
||||
}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
anchors.fill: parent
|
||||
anchors.margins: outline.border.width
|
||||
color: root.isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface
|
||||
enabled: root.enabled
|
||||
radius: Appearance.rounding.small
|
||||
|
||||
onClicked: field.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: root.notchColor
|
||||
height: outline.border.width
|
||||
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||
visible: root.outlined && root.floating && root.label.length > 0
|
||||
width: labelText.width + Appearance.spacing.extraSmall
|
||||
x: labelText.x - Appearance.spacing.extraSmall / 2
|
||||
y: 0
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: labelText
|
||||
|
||||
color: root.labelColor
|
||||
font.pointSize: root.floating ? Appearance.font.size.small : Appearance.font.size.medium
|
||||
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||
text: root.label
|
||||
visible: root.label.length > 0
|
||||
x: root.floating ? (Appearance.padding.larger + Appearance.spacing.extraSmall / 2) : (leading.visible ? leading.x + leading.width + Appearance.spacing.small : Appearance.padding.larger)
|
||||
y: root.floating ? (root.outlined ? -height / 2 : Appearance.padding.extraSmall) : (56 - height) / 2
|
||||
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
}
|
||||
Behavior on x {
|
||||
Anim {
|
||||
type: Anim.StandardSmall
|
||||
}
|
||||
}
|
||||
Behavior on y {
|
||||
Anim {
|
||||
type: Anim.StandardSmall
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: leading
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Appearance.padding.larger
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: (56 - height) / 2
|
||||
color: root.iconColor
|
||||
font.pointSize: Appearance.font.size.medium
|
||||
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||
text: root.leadingIcon
|
||||
visible: root.leadingIcon.length > 0
|
||||
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: trailing
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Appearance.padding.larger
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: (56 - height) / 2
|
||||
color: root.isError ? DynamicColors.palette.m3error : (root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface)
|
||||
font.pointSize: Appearance.font.size.medium
|
||||
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||
text: root.effectiveTrailingIcon
|
||||
visible: root.effectiveTrailingIcon.length > 0
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -Appearance.padding.small
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: root.trailingInteractive && root.enabled
|
||||
|
||||
onClicked: field.echoMode = field.echoMode === TextInput.Password ? TextInput.Normal : TextInput.Password
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: prefix
|
||||
|
||||
anchors.left: leading.visible ? leading.right : parent.left
|
||||
anchors.leftMargin: leading.visible ? Appearance.spacing.small : Appearance.padding.larger
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
|
||||
color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface
|
||||
font.pointSize: Appearance.font.size.medium
|
||||
height: 56
|
||||
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||
text: root.prefixText
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
visible: root.prefixText.length > 0 && root.floating
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: suffix
|
||||
|
||||
anchors.right: trailing.visible ? trailing.left : parent.right
|
||||
anchors.rightMargin: trailing.visible ? Appearance.spacing.small : Appearance.padding.larger
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
|
||||
color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface
|
||||
font.pointSize: Appearance.font.size.medium
|
||||
height: 56
|
||||
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||
text: root.suffixText
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
visible: root.suffixText.length > 0 && root.floating
|
||||
}
|
||||
|
||||
CustomTextField {
|
||||
id: field
|
||||
|
||||
anchors.left: prefix.visible ? prefix.right : (leading.visible ? leading.right : parent.left)
|
||||
anchors.leftMargin: prefix.visible ? Appearance.spacing.extraSmall : (leading.visible ? Appearance.spacing.small : Appearance.padding.larger)
|
||||
anchors.right: suffix.visible ? suffix.left : (trailing.visible ? trailing.left : parent.right)
|
||||
anchors.rightMargin: suffix.visible ? Appearance.spacing.extraSmall : (trailing.visible ? Appearance.spacing.small : Appearance.padding.larger)
|
||||
anchors.top: parent.top
|
||||
color: DynamicColors.palette.m3onSurface
|
||||
echoMode: root.password ? TextInput.Password : TextInput.Normal
|
||||
enabled: root.enabled
|
||||
font.pointSize: Appearance.font.size.medium
|
||||
height: 56
|
||||
inputMethodHints: root.inputMethodHints
|
||||
maximumLength: root.maxLength > 0 ? root.maxLength : 32767
|
||||
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||
placeholderText: root.focused ? root.placeholder : ""
|
||||
topPadding: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
|
||||
validator: root.validator
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
|
||||
onAccepted: root.accepted()
|
||||
onTextEdited: if (root.isError)
|
||||
root.isError = false
|
||||
}
|
||||
|
||||
Item {
|
||||
id: supportRow
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 56 + Appearance.spacing.extraSmall
|
||||
implicitHeight: Math.max(supportText.implicitHeight, counter.implicitHeight)
|
||||
visible: (root.isError && root.errorText.length > 0) || root.supportingText.length > 0 || root.maxLength > 0
|
||||
|
||||
CustomText {
|
||||
id: supportText
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Appearance.padding.larger
|
||||
anchors.right: counter.visible ? counter.left : parent.right
|
||||
anchors.rightMargin: Appearance.spacing.small
|
||||
color: root.supportColor
|
||||
font.pointSize: Appearance.font.size.small
|
||||
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||
text: root.isError && root.errorText.length > 0 ? root.errorText : root.supportingText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: counter
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Appearance.padding.larger
|
||||
color: root.supportColor
|
||||
font.pointSize: Appearance.font.size.small
|
||||
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||
text: `${root.text.length}/${root.maxLength}`
|
||||
visible: root.maxLength > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import QtQuick
|
||||
|
||||
Path {
|
||||
id: root
|
||||
|
||||
required property real viewHeight
|
||||
required property real viewWidth
|
||||
|
||||
startX: root.viewWidth / 2
|
||||
startY: 0
|
||||
|
||||
PathAttribute {
|
||||
name: "itemOpacity"
|
||||
value: 0.25
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: root.viewWidth / 2
|
||||
y: root.viewHeight * (1 / 6)
|
||||
}
|
||||
|
||||
PathAttribute {
|
||||
name: "itemOpacity"
|
||||
value: 0.45
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: root.viewWidth / 2
|
||||
y: root.viewHeight * (2 / 6)
|
||||
}
|
||||
|
||||
PathAttribute {
|
||||
name: "itemOpacity"
|
||||
value: 0.70
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: root.viewWidth / 2
|
||||
y: root.viewHeight * (3 / 6)
|
||||
}
|
||||
|
||||
PathAttribute {
|
||||
name: "itemOpacity"
|
||||
value: 1.00
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: root.viewWidth / 2
|
||||
y: root.viewHeight * (4 / 6)
|
||||
}
|
||||
|
||||
PathAttribute {
|
||||
name: "itemOpacity"
|
||||
value: 0.70
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: root.viewWidth / 2
|
||||
y: root.viewHeight * (5 / 6)
|
||||
}
|
||||
|
||||
PathAttribute {
|
||||
name: "itemOpacity"
|
||||
value: 0.45
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: root.viewWidth / 2
|
||||
y: root.viewHeight
|
||||
}
|
||||
|
||||
PathAttribute {
|
||||
name: "itemOpacity"
|
||||
value: 0.25
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import qs.Config
|
||||
|
||||
Elevation {
|
||||
id: root
|
||||
|
||||
required property int currentIndex
|
||||
property bool expanded
|
||||
required property int from
|
||||
property color insideTextColor: DynamicColors.palette.m3onPrimary
|
||||
property int itemHeight
|
||||
property int listHeight: 200
|
||||
property color outsideTextColor: DynamicColors.palette.m3onSurfaceVariant
|
||||
readonly property var spinnerModel: root.range(root.from, root.to)
|
||||
required property int to
|
||||
property Item triggerItem
|
||||
|
||||
signal itemSelected(item: int)
|
||||
|
||||
function range(first, last) {
|
||||
let out = [];
|
||||
for (let i = first; i <= last; ++i)
|
||||
out.push(i);
|
||||
return out;
|
||||
}
|
||||
|
||||
implicitHeight: root.expanded ? view.implicitHeight : 0
|
||||
level: root.expanded ? 2 : 0
|
||||
radius: itemHeight / 2
|
||||
visible: implicitHeight > 0
|
||||
z: root.expanded ? 100 : 0
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
onExpandedChanged: {
|
||||
if (!root.expanded)
|
||||
root.itemSelected(view.currentIndex + 1);
|
||||
}
|
||||
|
||||
Component {
|
||||
id: spinnerDelegate
|
||||
|
||||
Item {
|
||||
id: wrapper
|
||||
|
||||
readonly property color delegateTextColor: wrapper.PathView.view ? wrapper.PathView.view.delegateTextColor : "white"
|
||||
required property var modelData
|
||||
|
||||
height: root.itemHeight
|
||||
opacity: wrapper.PathView.itemOpacity
|
||||
visible: wrapper.PathView.onPath
|
||||
width: wrapper.PathView.view ? wrapper.PathView.view.width : 0
|
||||
z: wrapper.PathView.isCurrentItem ? 100 : Math.round(wrapper.PathView.itemScale * 100)
|
||||
|
||||
CustomText {
|
||||
anchors.centerIn: parent
|
||||
color: wrapper.delegateTextColor
|
||||
font.pointSize: Appearance.font.size.large
|
||||
text: wrapper.modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
color: DynamicColors.palette.m3surfaceContainer
|
||||
radius: parent.radius
|
||||
z: root.z
|
||||
|
||||
// Main visible spinner: normal/outside text color
|
||||
PathView {
|
||||
id: view
|
||||
|
||||
property color delegateTextColor: root.outsideTextColor
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
clip: true
|
||||
currentIndex: root.currentIndex - 1
|
||||
delegate: spinnerDelegate
|
||||
dragMargin: width
|
||||
highlightRangeMode: PathView.StrictlyEnforceRange
|
||||
implicitHeight: root.listHeight
|
||||
model: root.spinnerModel
|
||||
pathItemCount: 7
|
||||
preferredHighlightBegin: 0.5
|
||||
preferredHighlightEnd: 0.5
|
||||
snapMode: PathView.SnapToItem
|
||||
|
||||
path: PathMenu {
|
||||
viewHeight: view.height
|
||||
viewWidth: view.width
|
||||
}
|
||||
}
|
||||
|
||||
// The selection rectangle itself
|
||||
CustomRect {
|
||||
id: selectionRect
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: DynamicColors.palette.m3primary
|
||||
height: root.itemHeight
|
||||
radius: root.itemHeight / 2
|
||||
width: parent.width
|
||||
z: 2
|
||||
}
|
||||
|
||||
// Hidden source: same PathView, but with the "inside selection" text color
|
||||
Item {
|
||||
id: selectedTextSource
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
|
||||
PathView {
|
||||
id: selectedTextView
|
||||
|
||||
property color delegateTextColor: root.insideTextColor
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
clip: true
|
||||
currentIndex: view.currentIndex
|
||||
delegate: spinnerDelegate
|
||||
dragMargin: view.dragMargin
|
||||
highlightRangeMode: view.highlightRangeMode
|
||||
implicitHeight: root.listHeight
|
||||
interactive: false
|
||||
model: view.model
|
||||
|
||||
// Keep this PathView visually locked to the real one
|
||||
offset: view.offset
|
||||
pathItemCount: view.pathItemCount
|
||||
preferredHighlightBegin: view.preferredHighlightBegin
|
||||
preferredHighlightEnd: view.preferredHighlightEnd
|
||||
snapMode: view.snapMode
|
||||
|
||||
path: PathMenu {
|
||||
viewHeight: selectedTextView.height
|
||||
viewWidth: selectedTextView.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mask matching the selection rectangle
|
||||
Item {
|
||||
id: selectionMask
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
|
||||
CustomRect {
|
||||
color: "white"
|
||||
height: selectionRect.height
|
||||
radius: selectionRect.radius
|
||||
width: selectionRect.width
|
||||
x: selectionRect.x
|
||||
y: selectionRect.y
|
||||
}
|
||||
}
|
||||
|
||||
// Only show the "inside selection" text where the mask exists
|
||||
MultiEffect {
|
||||
anchors.fill: selectedTextSource
|
||||
maskEnabled: true
|
||||
maskInverted: false
|
||||
maskSource: selectionMask
|
||||
source: selectedTextSource
|
||||
z: 3
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Config
|
||||
|
||||
CustomRect {
|
||||
id: root
|
||||
|
||||
required property string label
|
||||
required property real max
|
||||
required property real min
|
||||
property var onValueModified: function (value) {}
|
||||
property real step: 1
|
||||
required property real value
|
||||
|
||||
Layout.fillWidth: true
|
||||
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
|
||||
implicitHeight: row.implicitHeight + Appearance.padding.large * 2
|
||||
radius: Appearance.rounding.normal
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Appearance.padding.large
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
text: root.label
|
||||
}
|
||||
|
||||
CustomSpinBox {
|
||||
max: root.max
|
||||
min: root.min
|
||||
step: root.step
|
||||
value: root.value
|
||||
|
||||
onValueModified: value => {
|
||||
root.onValueModified(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,10 +68,12 @@ JsonObject {
|
||||
property bool upower: true
|
||||
}
|
||||
component Tray: JsonObject {
|
||||
property bool recolorIcons: false
|
||||
property bool showAudio: true
|
||||
property bool showBluetooth: false
|
||||
property bool showMicrophone: true
|
||||
property bool showNetwork: false
|
||||
property bool showOnHover: false
|
||||
property bool showPower: true
|
||||
property bool showWifi: false
|
||||
property int trayIconSize: 24
|
||||
|
||||
@@ -170,7 +170,7 @@ Item {
|
||||
root.panels.osd.hovered = false;
|
||||
}
|
||||
|
||||
if (!root.popouts.currentName.startsWith("traymenu")) {
|
||||
if (!root.popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover) {
|
||||
root.popouts.hasCurrent = false;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@ CustomWindow {
|
||||
HyprlandFocusGrab {
|
||||
id: focusGrab
|
||||
|
||||
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.clipboard || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu"))
|
||||
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.clipboard || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu") && !Config.bar.tray.showOnHover)
|
||||
windows: [root]
|
||||
|
||||
onCleared: {
|
||||
|
||||
@@ -30,18 +30,6 @@ RowLayout {
|
||||
}
|
||||
}
|
||||
|
||||
// CustomRect {
|
||||
// Layout.fillWidth: true
|
||||
// color: DynamicColors.tPalette.m3surfaceContainer
|
||||
// implicitHeight: resources.implicitHeight
|
||||
// radius: Appearance.rounding.small
|
||||
//
|
||||
// Resources {
|
||||
// id: resources
|
||||
//
|
||||
// }
|
||||
// }
|
||||
|
||||
CustomClippingRect {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
function getTrayIcon(id: string, icon: string): string {
|
||||
if (icon.includes("?path=")) {
|
||||
const [name, path] = icon.split("?path=");
|
||||
icon = Qt.resolvedUrl(`${path}/${name.slice(name.lastIndexOf("/") + 1)}`);
|
||||
} else if (icon.includes("qspixmap") && id === "chrome_status_icon_1") {
|
||||
icon = icon.replace("qspixmap", "icon/discord-tray");
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property Item activeMenu: null
|
||||
property Item activeTrigger: null
|
||||
|
||||
function close(menu) {
|
||||
if (!menu)
|
||||
return;
|
||||
|
||||
if (activeMenu === menu) {
|
||||
activeMenu = null;
|
||||
activeTrigger = null;
|
||||
}
|
||||
|
||||
menu.expanded = false;
|
||||
}
|
||||
|
||||
function closeActive() {
|
||||
if (activeMenu)
|
||||
activeMenu.expanded = false;
|
||||
|
||||
activeMenu = null;
|
||||
activeTrigger = null;
|
||||
}
|
||||
|
||||
function forget(menu) {
|
||||
if (activeMenu === menu) {
|
||||
activeMenu = null;
|
||||
activeTrigger = null;
|
||||
}
|
||||
}
|
||||
|
||||
function hit(item, scenePos) {
|
||||
if (!item || !item.visible)
|
||||
return false;
|
||||
|
||||
const p = item.mapFromItem(null, scenePos.x, scenePos.y);
|
||||
return item.contains(p);
|
||||
}
|
||||
|
||||
function open(menu, trigger) {
|
||||
if (activeMenu && activeMenu !== menu)
|
||||
activeMenu.expanded = false;
|
||||
|
||||
activeMenu = menu;
|
||||
activeTrigger = trigger || null;
|
||||
menu.expanded = true;
|
||||
}
|
||||
|
||||
function toggle(menu, trigger) {
|
||||
if (activeMenu === menu && menu.expanded)
|
||||
close(menu);
|
||||
else
|
||||
open(menu, trigger);
|
||||
}
|
||||
}
|
||||
@@ -1,409 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
import qs.Config
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property string autoGpuType: "NONE"
|
||||
property string cpuName: ""
|
||||
property real cpuPerc
|
||||
property real cpuTemp
|
||||
|
||||
// Individual disks: Array of { mount, used, total, free, perc }
|
||||
property var disks: []
|
||||
property real gpuMemTotal: 0
|
||||
property real gpuMemUsed
|
||||
property real gpuPerc
|
||||
property real gpuTemp
|
||||
readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType
|
||||
property real lastCpuIdle
|
||||
property real lastCpuTotal
|
||||
readonly property real memPerc: memTotal > 0 ? memUsed / memTotal : 0
|
||||
property real memTotal
|
||||
property real memUsed
|
||||
property int refCount
|
||||
readonly property real storagePerc: {
|
||||
let totalUsed = 0;
|
||||
let totalSize = 0;
|
||||
for (const disk of disks) {
|
||||
totalUsed += disk.used;
|
||||
totalSize += disk.total;
|
||||
}
|
||||
return totalSize > 0 ? totalUsed / totalSize : 0;
|
||||
}
|
||||
|
||||
function cleanCpuName(name: string): string {
|
||||
return name.replace(/\(R\)/gi, "").replace(/\(TM\)/gi, "").replace(/CPU/gi, "").replace(/\d+th Gen /gi, "").replace(/\d+nd Gen /gi, "").replace(/\d+rd Gen /gi, "").replace(/\d+st Gen /gi, "").replace(/Core /gi, "").replace(/Processor/gi, "").replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
function cleanGpuName(name: string): string {
|
||||
return name.replace(/NVIDIA GeForce /gi, "").replace(/NVIDIA /gi, "").replace(/AMD Radeon /gi, "").replace(/AMD /gi, "").replace(/Intel /gi, "").replace(/\(R\)/gi, "").replace(/\(TM\)/gi, "").replace(/Graphics/gi, "").replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
function formatKib(kib: real): var {
|
||||
const mib = 1024;
|
||||
const gib = 1024 ** 2;
|
||||
const tib = 1024 ** 3;
|
||||
|
||||
if (kib >= tib)
|
||||
return {
|
||||
value: kib / tib,
|
||||
unit: "TiB"
|
||||
};
|
||||
if (kib >= gib)
|
||||
return {
|
||||
value: kib / gib,
|
||||
unit: "GiB"
|
||||
};
|
||||
if (kib >= mib)
|
||||
return {
|
||||
value: kib / mib,
|
||||
unit: "MiB"
|
||||
};
|
||||
return {
|
||||
value: kib,
|
||||
unit: "KiB"
|
||||
};
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: Config.dashboard.resourceUpdateInterval
|
||||
repeat: true
|
||||
running: root.refCount > 0
|
||||
triggeredOnStart: true
|
||||
|
||||
onTriggered: {
|
||||
stat.reload();
|
||||
meminfo.reload();
|
||||
if (root.gpuType === "GENERIC")
|
||||
gpuUsage.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 60000 * 120
|
||||
repeat: true
|
||||
running: true
|
||||
triggeredOnStart: true
|
||||
|
||||
onTriggered: {
|
||||
storage.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: Config.dashboard.resourceUpdateInterval * 5
|
||||
repeat: true
|
||||
running: root.refCount > 0
|
||||
triggeredOnStart: true
|
||||
|
||||
onTriggered: {
|
||||
sensors.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: cpuinfoInit
|
||||
|
||||
path: "/proc/cpuinfo"
|
||||
|
||||
onLoaded: {
|
||||
const nameMatch = text().match(/model name\s*:\s*(.+)/);
|
||||
if (nameMatch)
|
||||
root.cpuName = root.cleanCpuName(nameMatch[1]);
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: stat
|
||||
|
||||
path: "/proc/stat"
|
||||
|
||||
onLoaded: {
|
||||
const data = text().match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/);
|
||||
if (data) {
|
||||
const stats = data.slice(1).map(n => parseInt(n, 10));
|
||||
const total = stats.reduce((a, b) => a + b, 0);
|
||||
const idle = stats[3] + (stats[4] ?? 0);
|
||||
|
||||
const totalDiff = total - root.lastCpuTotal;
|
||||
const idleDiff = idle - root.lastCpuIdle;
|
||||
const newCpuPerc = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0;
|
||||
|
||||
root.lastCpuTotal = total;
|
||||
root.lastCpuIdle = idle;
|
||||
|
||||
if (Math.abs(newCpuPerc - root.cpuPerc) >= 0.01)
|
||||
root.cpuPerc = newCpuPerc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: meminfo
|
||||
|
||||
path: "/proc/meminfo"
|
||||
|
||||
onLoaded: {
|
||||
const data = text();
|
||||
const total = parseInt(data.match(/MemTotal: *(\d+)/)[1], 10) || 1;
|
||||
const used = (root.memTotal - parseInt(data.match(/MemAvailable: *(\d+)/)[1], 10)) || 0;
|
||||
|
||||
if (root.memTotal !== total)
|
||||
root.memTotal = total;
|
||||
|
||||
if (Math.abs(used - root.memUsed) >= 16384)
|
||||
root.memUsed = used;
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: storage
|
||||
|
||||
command: ["lsblk", "-b", "-o", "NAME,SIZE,TYPE,FSUSED,FSSIZE", "-P"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const diskMap = {}; // Map disk name -> { name, totalSize, used, fsTotal }
|
||||
const lines = text.trim().split("\n");
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.trim() === "")
|
||||
continue;
|
||||
const nameMatch = line.match(/NAME="([^"]+)"/);
|
||||
const sizeMatch = line.match(/SIZE="([^"]+)"/);
|
||||
const typeMatch = line.match(/TYPE="([^"]+)"/);
|
||||
const fsusedMatch = line.match(/FSUSED="([^"]*)"/);
|
||||
const fssizeMatch = line.match(/FSSIZE="([^"]*)"/);
|
||||
|
||||
if (!nameMatch || !typeMatch)
|
||||
continue;
|
||||
|
||||
const name = nameMatch[1];
|
||||
const type = typeMatch[1];
|
||||
const size = parseInt(sizeMatch?.[1] || "0", 10);
|
||||
const fsused = parseInt(fsusedMatch?.[1] || "0", 10);
|
||||
const fssize = parseInt(fssizeMatch?.[1] || "0", 10);
|
||||
|
||||
if (type === "disk") {
|
||||
// Skip zram (swap) devices
|
||||
if (name.startsWith("zram"))
|
||||
continue;
|
||||
|
||||
// Initialize disk entry
|
||||
if (!diskMap[name]) {
|
||||
diskMap[name] = {
|
||||
name: name,
|
||||
totalSize: size,
|
||||
used: 0,
|
||||
fsTotal: 0
|
||||
};
|
||||
}
|
||||
} else if (type === "part") {
|
||||
// Find parent disk (remove trailing numbers/p+numbers)
|
||||
let parentDisk = name.replace(/p?\d+$/, "");
|
||||
// For nvme devices like nvme0n1p1, parent is nvme0n1
|
||||
if (name.match(/nvme\d+n\d+p\d+/))
|
||||
parentDisk = name.replace(/p\d+$/, "");
|
||||
|
||||
// Aggregate partition usage to parent disk
|
||||
if (diskMap[parentDisk]) {
|
||||
diskMap[parentDisk].used += fsused;
|
||||
diskMap[parentDisk].fsTotal += fssize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const diskList = [];
|
||||
let totalUsed = 0;
|
||||
let totalSize = 0;
|
||||
|
||||
for (const diskName of Object.keys(diskMap).sort()) {
|
||||
const disk = diskMap[diskName];
|
||||
// Use filesystem total if available, otherwise use disk size
|
||||
const total = disk.fsTotal > 0 ? disk.fsTotal : disk.totalSize;
|
||||
const used = disk.used;
|
||||
const perc = total > 0 ? used / total : 0;
|
||||
|
||||
// Convert bytes to KiB for consistency with formatKib
|
||||
diskList.push({
|
||||
mount: disk.name // Using 'mount' property for compatibility
|
||||
,
|
||||
used: used / 1024,
|
||||
total: total / 1024,
|
||||
free: (total - used) / 1024,
|
||||
perc: perc
|
||||
});
|
||||
|
||||
totalUsed += used;
|
||||
totalSize += total;
|
||||
}
|
||||
|
||||
root.disks = diskList;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: gpuNameDetect
|
||||
|
||||
command: ["sh", "-c", "nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null || lspci 2>/dev/null | grep -i 'vga\\|3d\\|display' | head -1"]
|
||||
running: true
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const output = text.trim();
|
||||
if (!output)
|
||||
return;
|
||||
|
||||
// Check if it's from nvidia-smi (clean GPU name)
|
||||
if (output.toLowerCase().includes("nvidia") || output.toLowerCase().includes("geforce") || output.toLowerCase().includes("rtx") || output.toLowerCase().includes("gtx")) {
|
||||
root.gpuName = root.cleanGpuName(output);
|
||||
} else {
|
||||
// Parse lspci output: extract name from brackets or after colon
|
||||
const bracketMatch = output.match(/\[([^\]]+)\]/);
|
||||
if (bracketMatch) {
|
||||
root.gpuName = root.cleanGpuName(bracketMatch[1]);
|
||||
} else {
|
||||
const colonMatch = output.match(/:\s*(.+)/);
|
||||
if (colonMatch)
|
||||
root.gpuName = root.cleanGpuName(colonMatch[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: gpuTypeCheck
|
||||
|
||||
command: ["sh", "-c", "if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; then echo NVIDIA; elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC; else echo NONE; fi"]
|
||||
running: !Config.services.gpuType
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: root.autoGpuType = text.trim()
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: oneshotMem
|
||||
|
||||
command: ["nvidia-smi", "--query-gpu=memory.total", "--format=csv,noheader,nounits"]
|
||||
running: root.gpuType === "NVIDIA" && root.gpuMemTotal === 0
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
root.gpuMemTotal = Number(this.text.trim());
|
||||
oneshotMem.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: gpuUsageNvidia
|
||||
|
||||
command: ["/usr/bin/nvidia-smi", "--query-gpu=utilization.gpu,temperature.gpu,memory.used", "--format=csv,noheader,nounits", "-lms", "1000"]
|
||||
running: root.refCount > 0 && root.gpuType === "NVIDIA"
|
||||
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
const parts = String(data).trim().split(/\s*,\s*/);
|
||||
if (parts.length < 3)
|
||||
return;
|
||||
|
||||
const usageRaw = parseInt(parts[0], 10);
|
||||
const tempRaw = parseInt(parts[1], 10);
|
||||
const memRaw = parseInt(parts[2], 10);
|
||||
|
||||
if (!Number.isFinite(usageRaw) || !Number.isFinite(tempRaw) || !Number.isFinite(memRaw))
|
||||
return;
|
||||
|
||||
const newGpuPerc = Math.max(0, Math.min(1, usageRaw / 100));
|
||||
const newGpuTemp = tempRaw;
|
||||
const newGpuMemUsed = root.gpuMemTotal > 0 ? Math.max(0, Math.min(1, memRaw / root.gpuMemTotal)) : 0;
|
||||
|
||||
// Only publish meaningful changes to avoid needless binding churn / repaints
|
||||
if (Math.abs(root.gpuPerc - newGpuPerc) >= 0.01)
|
||||
root.gpuPerc = newGpuPerc;
|
||||
|
||||
if (Math.abs(root.gpuTemp - newGpuTemp) >= 1)
|
||||
root.gpuTemp = newGpuTemp;
|
||||
|
||||
if (Math.abs(root.gpuMemUsed - newGpuMemUsed) >= 0.01)
|
||||
root.gpuMemUsed = newGpuMemUsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: gpuUsage
|
||||
|
||||
command: root.gpuType === "GENERIC" ? ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"] : ["echo"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (root.gpuType === "GENERIC") {
|
||||
const percs = text.trim().split("\n");
|
||||
const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
|
||||
root.gpuPerc = sum / percs.length / 100;
|
||||
} else {
|
||||
root.gpuPerc = 0;
|
||||
root.gpuTemp = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: sensors
|
||||
|
||||
command: ["sensors"]
|
||||
environment: ({
|
||||
LANG: "C.UTF-8",
|
||||
LC_ALL: "C.UTF-8"
|
||||
})
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
let cpuTemp = text.match(/(?:Package id [0-9]+|Tdie):\s+((\+|-)[0-9.]+)(°| )C/);
|
||||
if (!cpuTemp)
|
||||
// If AMD Tdie pattern failed, try fallback on Tctl
|
||||
cpuTemp = text.match(/Tctl:\s+((\+|-)[0-9.]+)(°| )C/);
|
||||
|
||||
if (cpuTemp && Math.abs(parseFloat(cpuTemp[1]) - root.cpuTemp) >= 0.5)
|
||||
root.cpuTemp = parseFloat(cpuTemp[1]);
|
||||
|
||||
if (root.gpuType !== "GENERIC")
|
||||
return;
|
||||
|
||||
let eligible = false;
|
||||
let sum = 0;
|
||||
let count = 0;
|
||||
|
||||
for (const line of text.trim().split("\n")) {
|
||||
if (line === "Adapter: PCI adapter")
|
||||
eligible = true;
|
||||
else if (line === "")
|
||||
eligible = false;
|
||||
else if (eligible) {
|
||||
let match = line.match(/^(temp[0-9]+|GPU core|edge)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
|
||||
if (!match)
|
||||
// Fall back to junction/mem if GPU doesn't have edge temp (for AMD GPUs)
|
||||
match = line.match(/^(junction|mem)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
|
||||
|
||||
if (match) {
|
||||
sum += parseFloat(match[2]);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
root.gpuTemp = count > 0 ? sum / count : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
renderType: Text.NativeRendering
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
@@ -1,288 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property list<DesktopEntry> entryList: []
|
||||
property var preppedIcons: []
|
||||
property var preppedIds: []
|
||||
property var preppedNames: []
|
||||
|
||||
// Dynamic fixups
|
||||
property var regexSubstitutions: [
|
||||
{
|
||||
"regex": /^steam_app_(\d+)$/,
|
||||
"replace": "steam_icon_$1"
|
||||
},
|
||||
{
|
||||
"regex": /Minecraft.*/,
|
||||
"replace": "minecraft-launcher"
|
||||
},
|
||||
{
|
||||
"regex": /.*polkit.*/,
|
||||
"replace": "system-lock-screen"
|
||||
},
|
||||
{
|
||||
"regex": /gcr.prompter/,
|
||||
"replace": "system-lock-screen"
|
||||
}
|
||||
]
|
||||
property real scoreThreshold: 0.2
|
||||
|
||||
// Manual overrides for tricky apps
|
||||
property var substitutions: ({
|
||||
"code-url-handler": "visual-studio-code",
|
||||
"Code": "visual-studio-code",
|
||||
"gnome-tweaks": "org.gnome.tweaks",
|
||||
"pavucontrol-qt": "pavucontrol",
|
||||
"wps": "wps-office2019-kprometheus",
|
||||
"wpsoffice": "wps-office2019-kprometheus",
|
||||
"footclient": "foot"
|
||||
})
|
||||
|
||||
function checkCleanMatch(str) {
|
||||
if (!str || str.length <= 3)
|
||||
return null;
|
||||
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
|
||||
return null;
|
||||
|
||||
// Aggressive fallback: strip all separators
|
||||
const cleanStr = str.toLowerCase().replace(/[\.\-_]/g, '');
|
||||
const list = Array.from(entryList);
|
||||
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const entry = list[i];
|
||||
const cleanId = (entry.id || "").toLowerCase().replace(/[\.\-_]/g, '');
|
||||
if (cleanId.includes(cleanStr) || cleanStr.includes(cleanId)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function checkFuzzySearch(str) {
|
||||
if (typeof FuzzySort === 'undefined')
|
||||
return null;
|
||||
|
||||
// Check filenames (IDs) first
|
||||
if (preppedIds.length > 0) {
|
||||
let results = fuzzyQuery(str, preppedIds);
|
||||
if (results.length === 0) {
|
||||
const underscored = str.replace(/-/g, '_').toLowerCase();
|
||||
if (underscored !== str)
|
||||
results = fuzzyQuery(underscored, preppedIds);
|
||||
}
|
||||
if (results.length > 0)
|
||||
return results[0];
|
||||
}
|
||||
|
||||
// Then icons
|
||||
if (preppedIcons.length > 0) {
|
||||
const results = fuzzyQuery(str, preppedIcons);
|
||||
if (results.length > 0)
|
||||
return results[0];
|
||||
}
|
||||
|
||||
// Then names
|
||||
if (preppedNames.length > 0) {
|
||||
const results = fuzzyQuery(str, preppedNames);
|
||||
if (results.length > 0)
|
||||
return results[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- Lookup Helpers ---
|
||||
|
||||
function checkHeuristic(str) {
|
||||
if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) {
|
||||
const entry = DesktopEntries.heuristicLookup(str);
|
||||
if (entry)
|
||||
return entry;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function checkRegex(str) {
|
||||
for (let i = 0; i < regexSubstitutions.length; i++) {
|
||||
const sub = regexSubstitutions[i];
|
||||
const replaced = str.replace(sub.regex, sub.replace);
|
||||
if (replaced !== str) {
|
||||
return findAppEntry(replaced);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function checkSimpleTransforms(str) {
|
||||
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
|
||||
return null;
|
||||
|
||||
const lower = str.toLowerCase();
|
||||
|
||||
const variants = [str, lower, getFromReverseDomain(str), getFromReverseDomain(str)?.toLowerCase(), normalizeWithHyphens(str), str.replace(/_/g, '-').toLowerCase(), str.replace(/-/g, '_').toLowerCase()];
|
||||
|
||||
for (let i = 0; i < variants.length; i++) {
|
||||
const variant = variants[i];
|
||||
if (variant) {
|
||||
const entry = DesktopEntries.byId(variant);
|
||||
if (entry)
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function checkSubstitutions(str) {
|
||||
let effectiveStr = substitutions[str];
|
||||
if (!effectiveStr)
|
||||
effectiveStr = substitutions[str.toLowerCase()];
|
||||
|
||||
if (effectiveStr && effectiveStr !== str) {
|
||||
return findAppEntry(effectiveStr);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function distroLogoPath() {
|
||||
try {
|
||||
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : "";
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Robust lookup strategy
|
||||
function findAppEntry(str) {
|
||||
if (!str || str.length === 0)
|
||||
return null;
|
||||
|
||||
let result = null;
|
||||
|
||||
if (result = checkHeuristic(str))
|
||||
return result;
|
||||
if (result = checkSubstitutions(str))
|
||||
return result;
|
||||
if (result = checkRegex(str))
|
||||
return result;
|
||||
if (result = checkSimpleTransforms(str))
|
||||
return result;
|
||||
if (result = checkFuzzySearch(str))
|
||||
return result;
|
||||
if (result = checkCleanMatch(str))
|
||||
return result;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function fuzzyQuery(search, preppedData) {
|
||||
if (!search || !preppedData || preppedData.length === 0)
|
||||
return [];
|
||||
return FuzzySort.go(search, preppedData, {
|
||||
all: true,
|
||||
key: "name"
|
||||
}).map(r => r.obj.entry);
|
||||
}
|
||||
|
||||
function getFromReverseDomain(str) {
|
||||
if (!str)
|
||||
return "";
|
||||
return str.split('.').slice(-1)[0];
|
||||
}
|
||||
|
||||
// Deprecated shim
|
||||
function guessIcon(str) {
|
||||
const entry = findAppEntry(str);
|
||||
return entry ? entry.icon : "image-missing";
|
||||
}
|
||||
|
||||
function iconExists(iconName) {
|
||||
if (!iconName || iconName.length === 0)
|
||||
return false;
|
||||
if (iconName.startsWith("/"))
|
||||
return true;
|
||||
|
||||
const path = Quickshell.iconPath(iconName, true);
|
||||
return path && path.length > 0 && !path.includes("image-missing");
|
||||
}
|
||||
|
||||
function iconForAppId(appId, fallbackName) {
|
||||
const fallback = fallbackName || "application-x-executable";
|
||||
if (!appId)
|
||||
return iconFromName(fallback, fallback);
|
||||
|
||||
const entry = findAppEntry(appId);
|
||||
if (entry) {
|
||||
return iconFromName(entry.icon, fallback);
|
||||
}
|
||||
|
||||
return iconFromName(appId, fallback);
|
||||
}
|
||||
|
||||
function iconFromName(iconName, fallbackName) {
|
||||
const fallback = fallbackName || "application-x-executable";
|
||||
try {
|
||||
if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
|
||||
const p = Quickshell.iconPath(iconName, fallback);
|
||||
if (p && p !== "")
|
||||
return p;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : "";
|
||||
} catch (e2) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeWithHyphens(str) {
|
||||
if (!str)
|
||||
return "";
|
||||
return str.toLowerCase().replace(/\s+/g, "-");
|
||||
}
|
||||
|
||||
function refreshEntries() {
|
||||
if (typeof DesktopEntries === 'undefined')
|
||||
return;
|
||||
|
||||
const values = Array.from(DesktopEntries.applications.values);
|
||||
if (values) {
|
||||
entryList = values.sort((a, b) => a.name.localeCompare(b.name));
|
||||
updatePreppedData();
|
||||
}
|
||||
}
|
||||
|
||||
function updatePreppedData() {
|
||||
if (typeof FuzzySort === 'undefined')
|
||||
return;
|
||||
|
||||
const list = Array.from(entryList);
|
||||
preppedNames = list.map(a => ({
|
||||
name: FuzzySort.prepare(`${a.name} `),
|
||||
entry: a
|
||||
}));
|
||||
preppedIcons = list.map(a => ({
|
||||
name: FuzzySort.prepare(`${a.icon} `),
|
||||
entry: a
|
||||
}));
|
||||
preppedIds = list.map(a => ({
|
||||
name: FuzzySort.prepare(`${a.id} `),
|
||||
entry: a
|
||||
}));
|
||||
}
|
||||
|
||||
Component.onCompleted: refreshEntries()
|
||||
|
||||
Connections {
|
||||
function onValuesChanged() {
|
||||
refreshEntries();
|
||||
}
|
||||
|
||||
target: DesktopEntries.applications
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Config
|
||||
|
||||
GridLayout {
|
||||
id: root
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Appearance.padding.large
|
||||
anchors.right: parent.right
|
||||
columnSpacing: Appearance.spacing.large
|
||||
columns: 2
|
||||
rowSpacing: Appearance.spacing.large
|
||||
rows: 1
|
||||
|
||||
Ref {
|
||||
service: SystemUsage
|
||||
}
|
||||
|
||||
Resource {
|
||||
Layout.bottomMargin: Appearance.padding.large
|
||||
Layout.topMargin: Appearance.padding.large
|
||||
colour: DynamicColors.palette.m3primary
|
||||
icon: "memory"
|
||||
value: SystemUsage.cpuPerc
|
||||
}
|
||||
|
||||
Resource {
|
||||
Layout.bottomMargin: Appearance.padding.large
|
||||
Layout.topMargin: Appearance.padding.large
|
||||
colour: DynamicColors.palette.m3secondary
|
||||
icon: "memory_alt"
|
||||
value: SystemUsage.memPerc
|
||||
}
|
||||
|
||||
component Resource: CustomRect {
|
||||
id: res
|
||||
|
||||
required property color colour
|
||||
required property string icon
|
||||
required property real value
|
||||
|
||||
Layout.fillWidth: true
|
||||
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
|
||||
implicitHeight: width
|
||||
radius: Appearance.rounding.large
|
||||
|
||||
Behavior on value {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.large
|
||||
}
|
||||
}
|
||||
|
||||
CircularProgress {
|
||||
id: circ
|
||||
|
||||
anchors.fill: parent
|
||||
bgColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 3)
|
||||
fgColour: res.colour
|
||||
padding: Appearance.padding.large * 3
|
||||
strokeWidth: width < 200 ? Appearance.padding.smaller : Appearance.padding.normal
|
||||
value: res.value
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
anchors.centerIn: parent
|
||||
color: res.colour
|
||||
font.pointSize: (circ.arcRadius * 0.7) || 1
|
||||
font.weight: 600
|
||||
text: res.icon
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import qs.Helpers
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property int displayMonth: new Date().getMonth()
|
||||
property int displayYear: new Date().getFullYear()
|
||||
readonly property int weekStartDay: 1 // 0 = Sunday, 1 = Monday
|
||||
|
||||
function getISOWeekNumber(date: var): int {
|
||||
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
||||
const dayNum = d.getUTCDay() || 7;
|
||||
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
|
||||
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
||||
return Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
|
||||
}
|
||||
|
||||
function getWeekNumbers(month: int, year: int): var {
|
||||
const days = getWeeksForMonth(month, year);
|
||||
const weekNumbers = [];
|
||||
let lastWeekNumber = -1;
|
||||
|
||||
for (let i = 0; i < days.length; i++) {
|
||||
// Only add week numbers for days that belong to the current month
|
||||
if (days[i].isCurrentMonth) {
|
||||
const dayDate = new Date(days[i].year, days[i].month, days[i].day);
|
||||
const weekNumber = getISOWeekNumber(dayDate);
|
||||
|
||||
// Only push if this is a new week
|
||||
if (weekNumber !== lastWeekNumber) {
|
||||
weekNumbers.push(weekNumber);
|
||||
lastWeekNumber = weekNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return weekNumbers;
|
||||
}
|
||||
|
||||
function getWeekStartIndex(month: int, year: int): int {
|
||||
const today = new Date();
|
||||
if (today.getMonth() !== month || today.getFullYear() !== year) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const days = getWeeksForMonth(month, year);
|
||||
for (let i = 0; i < days.length; i++) {
|
||||
if (days[i].isToday) {
|
||||
// Return the start index of the week containing today
|
||||
return Math.floor(i / 7) * 7;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getWeeksForMonth(month: int, year: int): var {
|
||||
const firstDayOfMonth = new Date(year, month, 1);
|
||||
const lastDayOfMonth = new Date(year, month + 1, 0);
|
||||
|
||||
const days = [];
|
||||
let currentDate = new Date(year, month, 1);
|
||||
|
||||
// Back up to the start of the first week (Sunday or Monday based on locale)
|
||||
const dayOfWeek = firstDayOfMonth.getDay();
|
||||
const daysToBackup = (dayOfWeek - root.weekStartDay + 7) % 7;
|
||||
currentDate.setDate(currentDate.getDate() - daysToBackup);
|
||||
|
||||
// Collect all days, including padding from previous/next month to complete the weeks
|
||||
while (true) {
|
||||
days.push({
|
||||
day: currentDate.getDate(),
|
||||
month: currentDate.getMonth(),
|
||||
year: currentDate.getFullYear(),
|
||||
isCurrentMonth: currentDate.getMonth() === month,
|
||||
isToday: isDateToday(currentDate)
|
||||
});
|
||||
|
||||
currentDate.setDate(currentDate.getDate() + 1);
|
||||
|
||||
// Stop after we've completed a full week following the last day of the month
|
||||
if (currentDate > lastDayOfMonth && days.length % 7 === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
function isDateToday(date: var): bool {
|
||||
const today = new Date();
|
||||
return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
function getTrayIcon(id: string, icon: string): string {
|
||||
if (icon.includes("?path=")) {
|
||||
const [name, path] = icon.split("?path=");
|
||||
icon = Qt.resolvedUrl(`${path}/${name.slice(name.lastIndexOf("/") + 1)}`);
|
||||
} else if (icon.includes("qspixmap") && id === "chrome_status_icon_1") {
|
||||
icon = icon.replace("qspixmap", "icon/discord-tray");
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import qs.Helpers
|
||||
|
||||
Singleton {
|
||||
function getInitialTitle(callback) {
|
||||
let activeWindow = Hypr.activeToplevel.title;
|
||||
let activeClass = Hypr.activeToplevel.lastIpcObject.class.toString();
|
||||
let regex = new RegExp(activeClass, "i");
|
||||
|
||||
const evalTitle = activeWindow.match(regex);
|
||||
callback(evalTitle);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property alias centerX: notifCenterSpacing.centerX
|
||||
|
||||
JsonAdapter {
|
||||
id: notifCenterSpacing
|
||||
|
||||
property int centerX
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell.Io
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property alias notifPath: storage.notifPath
|
||||
|
||||
JsonAdapter {
|
||||
id: storage
|
||||
|
||||
property string notifPath: Quickshell.statePath("notifications.json")
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import ZShell.Models
|
||||
import qs.Config
|
||||
import qs.Modules
|
||||
import qs.Helpers
|
||||
import qs.Paths
|
||||
|
||||
Searcher {
|
||||
id: root
|
||||
|
||||
property string actualCurrent: WallpaperPath.currentWallpaperPath
|
||||
readonly property string current: showPreview ? previewPath : actualCurrent
|
||||
property string previewPath
|
||||
property bool showPreview: false
|
||||
|
||||
function preview(path: string): void {
|
||||
previewPath = path;
|
||||
showPreview = true;
|
||||
}
|
||||
|
||||
function setWallpaper(path: string): void {
|
||||
actualCurrent = path;
|
||||
WallpaperPath.currentWallpaperPath = path;
|
||||
Quickshell.execDetached(["sh", "-c", `python3 ${Quickshell.shellPath("scripts/LockScreenBg.py")} --input_image=${root.actualCurrent} --output_path=${Paths.state}/lockscreen_bg.png`]);
|
||||
}
|
||||
|
||||
function stopPreview(): void {
|
||||
showPreview = false;
|
||||
Quickshell.execDetached(["sh", "-c", `python3 ${Quickshell.shellPath("scripts/SchemeColorGen.py")} --path=${root.actualCurrent} --thumbnail=${Paths.cache}/imagecache/thumbnail.jpg --output=${Paths.state}/scheme.json --scheme=${Config.colors.schemeType}`]);
|
||||
}
|
||||
|
||||
extraOpts: useFuzzy ? ({}) : ({
|
||||
forward: false
|
||||
})
|
||||
key: "relativePath"
|
||||
list: wallpapers.entries
|
||||
useFuzzy: true
|
||||
|
||||
FileSystemModel {
|
||||
id: wallpapers
|
||||
|
||||
filter: FileSystemModel.Images
|
||||
path: Config.general.wallpaperPath
|
||||
recursive: true
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property Item activeMenu: null
|
||||
property Item activeTrigger: null
|
||||
|
||||
function close(menu) {
|
||||
if (!menu)
|
||||
return;
|
||||
|
||||
if (activeMenu === menu) {
|
||||
activeMenu = null;
|
||||
activeTrigger = null;
|
||||
}
|
||||
|
||||
menu.expanded = false;
|
||||
}
|
||||
|
||||
function closeActive() {
|
||||
if (activeMenu)
|
||||
activeMenu.expanded = false;
|
||||
|
||||
activeMenu = null;
|
||||
activeTrigger = null;
|
||||
}
|
||||
|
||||
function forget(menu) {
|
||||
if (activeMenu === menu) {
|
||||
activeMenu = null;
|
||||
activeTrigger = null;
|
||||
}
|
||||
}
|
||||
|
||||
function hit(item, scenePos) {
|
||||
if (!item || !item.visible)
|
||||
return false;
|
||||
|
||||
const p = item.mapFromItem(null, scenePos.x, scenePos.y);
|
||||
return item.contains(p);
|
||||
}
|
||||
|
||||
function open(menu, trigger) {
|
||||
if (activeMenu && activeMenu !== menu)
|
||||
activeMenu.expanded = false;
|
||||
|
||||
activeMenu = menu;
|
||||
activeTrigger = trigger || null;
|
||||
menu.expanded = true;
|
||||
}
|
||||
|
||||
function toggle(menu, trigger) {
|
||||
if (activeMenu === menu && menu.expanded)
|
||||
close(menu);
|
||||
else
|
||||
open(menu, trigger);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property string highlightedSetting: ""
|
||||
|
||||
function clear() {
|
||||
highlightedSetting = "";
|
||||
}
|
||||
|
||||
function highlight(settingName: string) {
|
||||
highlightedSetting = settingName;
|
||||
highlightTimer.restart();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: highlightTimer
|
||||
|
||||
interval: 2000
|
||||
|
||||
onTriggered: root.clear()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
renderType: Text.NativeRendering
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
@@ -1,288 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property list<DesktopEntry> entryList: []
|
||||
property var preppedIcons: []
|
||||
property var preppedIds: []
|
||||
property var preppedNames: []
|
||||
|
||||
// Dynamic fixups
|
||||
property var regexSubstitutions: [
|
||||
{
|
||||
"regex": /^steam_app_(\d+)$/,
|
||||
"replace": "steam_icon_$1"
|
||||
},
|
||||
{
|
||||
"regex": /Minecraft.*/,
|
||||
"replace": "minecraft-launcher"
|
||||
},
|
||||
{
|
||||
"regex": /.*polkit.*/,
|
||||
"replace": "system-lock-screen"
|
||||
},
|
||||
{
|
||||
"regex": /gcr.prompter/,
|
||||
"replace": "system-lock-screen"
|
||||
}
|
||||
]
|
||||
property real scoreThreshold: 0.2
|
||||
|
||||
// Manual overrides for tricky apps
|
||||
property var substitutions: ({
|
||||
"code-url-handler": "visual-studio-code",
|
||||
"Code": "visual-studio-code",
|
||||
"gnome-tweaks": "org.gnome.tweaks",
|
||||
"pavucontrol-qt": "pavucontrol",
|
||||
"wps": "wps-office2019-kprometheus",
|
||||
"wpsoffice": "wps-office2019-kprometheus",
|
||||
"footclient": "foot"
|
||||
})
|
||||
|
||||
function checkCleanMatch(str) {
|
||||
if (!str || str.length <= 3)
|
||||
return null;
|
||||
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
|
||||
return null;
|
||||
|
||||
// Aggressive fallback: strip all separators
|
||||
const cleanStr = str.toLowerCase().replace(/[\.\-_]/g, '');
|
||||
const list = Array.from(entryList);
|
||||
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const entry = list[i];
|
||||
const cleanId = (entry.id || "").toLowerCase().replace(/[\.\-_]/g, '');
|
||||
if (cleanId.includes(cleanStr) || cleanStr.includes(cleanId)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function checkFuzzySearch(str) {
|
||||
if (typeof FuzzySort === 'undefined')
|
||||
return null;
|
||||
|
||||
// Check filenames (IDs) first
|
||||
if (preppedIds.length > 0) {
|
||||
let results = fuzzyQuery(str, preppedIds);
|
||||
if (results.length === 0) {
|
||||
const underscored = str.replace(/-/g, '_').toLowerCase();
|
||||
if (underscored !== str)
|
||||
results = fuzzyQuery(underscored, preppedIds);
|
||||
}
|
||||
if (results.length > 0)
|
||||
return results[0];
|
||||
}
|
||||
|
||||
// Then icons
|
||||
if (preppedIcons.length > 0) {
|
||||
const results = fuzzyQuery(str, preppedIcons);
|
||||
if (results.length > 0)
|
||||
return results[0];
|
||||
}
|
||||
|
||||
// Then names
|
||||
if (preppedNames.length > 0) {
|
||||
const results = fuzzyQuery(str, preppedNames);
|
||||
if (results.length > 0)
|
||||
return results[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- Lookup Helpers ---
|
||||
|
||||
function checkHeuristic(str) {
|
||||
if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) {
|
||||
const entry = DesktopEntries.heuristicLookup(str);
|
||||
if (entry)
|
||||
return entry;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function checkRegex(str) {
|
||||
for (let i = 0; i < regexSubstitutions.length; i++) {
|
||||
const sub = regexSubstitutions[i];
|
||||
const replaced = str.replace(sub.regex, sub.replace);
|
||||
if (replaced !== str) {
|
||||
return findAppEntry(replaced);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function checkSimpleTransforms(str) {
|
||||
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
|
||||
return null;
|
||||
|
||||
const lower = str.toLowerCase();
|
||||
|
||||
const variants = [str, lower, getFromReverseDomain(str), getFromReverseDomain(str)?.toLowerCase(), normalizeWithHyphens(str), str.replace(/_/g, '-').toLowerCase(), str.replace(/-/g, '_').toLowerCase()];
|
||||
|
||||
for (let i = 0; i < variants.length; i++) {
|
||||
const variant = variants[i];
|
||||
if (variant) {
|
||||
const entry = DesktopEntries.byId(variant);
|
||||
if (entry)
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function checkSubstitutions(str) {
|
||||
let effectiveStr = substitutions[str];
|
||||
if (!effectiveStr)
|
||||
effectiveStr = substitutions[str.toLowerCase()];
|
||||
|
||||
if (effectiveStr && effectiveStr !== str) {
|
||||
return findAppEntry(effectiveStr);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function distroLogoPath() {
|
||||
try {
|
||||
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : "";
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Robust lookup strategy
|
||||
function findAppEntry(str) {
|
||||
if (!str || str.length === 0)
|
||||
return null;
|
||||
|
||||
let result = null;
|
||||
|
||||
if (result = checkHeuristic(str))
|
||||
return result;
|
||||
if (result = checkSubstitutions(str))
|
||||
return result;
|
||||
if (result = checkRegex(str))
|
||||
return result;
|
||||
if (result = checkSimpleTransforms(str))
|
||||
return result;
|
||||
if (result = checkFuzzySearch(str))
|
||||
return result;
|
||||
if (result = checkCleanMatch(str))
|
||||
return result;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function fuzzyQuery(search, preppedData) {
|
||||
if (!search || !preppedData || preppedData.length === 0)
|
||||
return [];
|
||||
return FuzzySort.go(search, preppedData, {
|
||||
all: true,
|
||||
key: "name"
|
||||
}).map(r => r.obj.entry);
|
||||
}
|
||||
|
||||
function getFromReverseDomain(str) {
|
||||
if (!str)
|
||||
return "";
|
||||
return str.split('.').slice(-1)[0];
|
||||
}
|
||||
|
||||
// Deprecated shim
|
||||
function guessIcon(str) {
|
||||
const entry = findAppEntry(str);
|
||||
return entry ? entry.icon : "image-missing";
|
||||
}
|
||||
|
||||
function iconExists(iconName) {
|
||||
if (!iconName || iconName.length === 0)
|
||||
return false;
|
||||
if (iconName.startsWith("/"))
|
||||
return true;
|
||||
|
||||
const path = Quickshell.iconPath(iconName, true);
|
||||
return path && path.length > 0 && !path.includes("image-missing");
|
||||
}
|
||||
|
||||
function iconForAppId(appId, fallbackName) {
|
||||
const fallback = fallbackName || "application-x-executable";
|
||||
if (!appId)
|
||||
return iconFromName(fallback, fallback);
|
||||
|
||||
const entry = findAppEntry(appId);
|
||||
if (entry) {
|
||||
return iconFromName(entry.icon, fallback);
|
||||
}
|
||||
|
||||
return iconFromName(appId, fallback);
|
||||
}
|
||||
|
||||
function iconFromName(iconName, fallbackName) {
|
||||
const fallback = fallbackName || "application-x-executable";
|
||||
try {
|
||||
if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
|
||||
const p = Quickshell.iconPath(iconName, fallback);
|
||||
if (p && p !== "")
|
||||
return p;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : "";
|
||||
} catch (e2) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeWithHyphens(str) {
|
||||
if (!str)
|
||||
return "";
|
||||
return str.toLowerCase().replace(/\s+/g, "-");
|
||||
}
|
||||
|
||||
function refreshEntries() {
|
||||
if (typeof DesktopEntries === 'undefined')
|
||||
return;
|
||||
|
||||
const values = Array.from(DesktopEntries.applications.values);
|
||||
if (values) {
|
||||
entryList = values.sort((a, b) => a.name.localeCompare(b.name));
|
||||
updatePreppedData();
|
||||
}
|
||||
}
|
||||
|
||||
function updatePreppedData() {
|
||||
if (typeof FuzzySort === 'undefined')
|
||||
return;
|
||||
|
||||
const list = Array.from(entryList);
|
||||
preppedNames = list.map(a => ({
|
||||
name: FuzzySort.prepare(`${a.name} `),
|
||||
entry: a
|
||||
}));
|
||||
preppedIcons = list.map(a => ({
|
||||
name: FuzzySort.prepare(`${a.icon} `),
|
||||
entry: a
|
||||
}));
|
||||
preppedIds = list.map(a => ({
|
||||
name: FuzzySort.prepare(`${a.id} `),
|
||||
entry: a
|
||||
}));
|
||||
}
|
||||
|
||||
Component.onCompleted: refreshEntries()
|
||||
|
||||
Connections {
|
||||
function onValuesChanged() {
|
||||
refreshEntries();
|
||||
}
|
||||
|
||||
target: DesktopEntries.applications
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import Quickshell.Io
|
||||
|
||||
JsonObject {
|
||||
property list<var> week_0: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_1: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_10: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_11: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_12: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_13: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_14: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_15: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_16: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_17: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_18: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_19: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_2: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_20: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_21: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_22: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_23: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_24: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_25: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_26: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_27: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_28: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_29: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_3: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_30: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_31: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_32: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_33: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_34: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_35: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_36: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_37: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_38: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_39: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_4: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_40: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_41: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_42: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_43: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_44: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_45: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_46: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_47: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_48: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_49: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_5: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_50: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_51: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_6: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_7: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_8: [0, 0, 0, 0, 0, 0,]
|
||||
property list<var> week_9: [0, 0, 0, 0, 0, 0,]
|
||||
}
|
||||
+2
-15
@@ -47,7 +47,7 @@ RowLayout {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!popouts.currentName.startsWith("traymenu"))
|
||||
if (!popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover)
|
||||
popouts.hasCurrent = false;
|
||||
}
|
||||
|
||||
@@ -56,19 +56,7 @@ RowLayout {
|
||||
const item = ch.item;
|
||||
const itemWidth = item.implicitWidth;
|
||||
|
||||
if (id === "audio" && Config.bar.popouts.audio) {
|
||||
popouts.currentName = "audio";
|
||||
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
|
||||
popouts.hasCurrent = true;
|
||||
} else if (id === "network" && Config.bar.popouts.network) {
|
||||
popouts.currentName = "network";
|
||||
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
|
||||
popouts.hasCurrent = true;
|
||||
} else if (id === "upower" && Config.bar.popouts.upower) {
|
||||
popouts.currentName = "upower";
|
||||
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
|
||||
popouts.hasCurrent = true;
|
||||
} else if (id === "updates") {
|
||||
if (id === "updates") {
|
||||
popouts.currentName = "updates";
|
||||
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
|
||||
popouts.hasCurrent = true;
|
||||
@@ -80,7 +68,6 @@ RowLayout {
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
// model: Config.bar.entries.filted(n => n.index > 50).sort(n => n.index)
|
||||
model: Config.bar.entries
|
||||
|
||||
DelegateChooser {
|
||||
|
||||
@@ -133,7 +133,7 @@ CustomRect {
|
||||
ParallelAnimation {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.small
|
||||
easing: Appearance.anim.curves.standardAccel
|
||||
easing.bezierCurve: Appearance.anim.curves.standardAccel
|
||||
property: "scale"
|
||||
target: listOrControls
|
||||
to: 0.7
|
||||
@@ -141,7 +141,7 @@ CustomRect {
|
||||
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.small
|
||||
easing: Appearance.anim.curves.standardAccel
|
||||
easing.bezierCurve: Appearance.anim.curves.standardAccel
|
||||
property: "opacity"
|
||||
target: listOrControls
|
||||
to: 0
|
||||
@@ -166,7 +166,7 @@ CustomRect {
|
||||
ParallelAnimation {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.small
|
||||
easing: Appearance.anim.curves.standardDecel
|
||||
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||
property: "scale"
|
||||
target: listOrControls
|
||||
to: 1
|
||||
@@ -174,7 +174,7 @@ CustomRect {
|
||||
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.small
|
||||
easing: Appearance.anim.curves.standardDecel
|
||||
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||
property: "opacity"
|
||||
target: listOrControls
|
||||
to: 1
|
||||
@@ -217,14 +217,14 @@ CustomRect {
|
||||
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.large
|
||||
easing: Appearance.anim.curves.emphasizedAccel
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasizedAccel
|
||||
from: 1
|
||||
to: 0
|
||||
}
|
||||
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.extraLarge
|
||||
easing: Appearance.anim.curves.emphasizedDecel
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
|
||||
from: 0
|
||||
to: 1
|
||||
}
|
||||
|
||||
+45
-37
@@ -1,8 +1,8 @@
|
||||
import Quickshell
|
||||
import Quickshell.Services.Polkit
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Widgets
|
||||
import ZShell.Components
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
@@ -243,17 +243,16 @@ Scope {
|
||||
Layout.preferredWidth: contentRow.implicitWidth
|
||||
spacing: 8
|
||||
|
||||
CustomButton {
|
||||
IconTextButton {
|
||||
id: detailsButton
|
||||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: 92
|
||||
bgColor: DynamicColors.palette.m3surfaceContainer
|
||||
enabled: true
|
||||
radius: Appearance.rounding.full
|
||||
icon: "info"
|
||||
inactiveColor: DynamicColors.palette.m3surfaceContainer
|
||||
inactiveOnColor: DynamicColors.palette.m3onSurface
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
text: "Details"
|
||||
textColor: DynamicColors.palette.m3onSurface
|
||||
|
||||
onClicked: {
|
||||
panelWindow.detailsOpen = !panelWindow.detailsOpen;
|
||||
@@ -266,41 +265,50 @@ Scope {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
CustomButton {
|
||||
id: okButton
|
||||
|
||||
ButtonRow {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: 76
|
||||
bgColor: DynamicColors.palette.m3primary
|
||||
enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired
|
||||
radius: Appearance.rounding.full
|
||||
text: "OK"
|
||||
textColor: DynamicColors.palette.m3onPrimary
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
onClicked: {
|
||||
polkitAgent.flow.submit(passInput.text);
|
||||
passInput.text = "";
|
||||
passInput.forceActiveFocus();
|
||||
IconTextButton {
|
||||
id: okButton
|
||||
|
||||
enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired
|
||||
horizontalPadding: Appearance.padding.large
|
||||
icon: "check"
|
||||
inactiveColor: DynamicColors.palette.m3primary
|
||||
inactiveOnColor: DynamicColors.palette.m3onPrimary
|
||||
isRound: true
|
||||
isToggle: false
|
||||
shapeMorph: true
|
||||
shapeMorphExpansion: pressed ? 12 : 0
|
||||
text: "OK"
|
||||
|
||||
onClicked: {
|
||||
polkitAgent.flow.submit(passInput.text);
|
||||
passInput.text = "";
|
||||
passInput.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomButton {
|
||||
id: cancelButton
|
||||
IconTextButton {
|
||||
id: cancelButton
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: 76
|
||||
bgColor: DynamicColors.palette.m3surfaceContainer
|
||||
enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired
|
||||
radius: Appearance.rounding.full
|
||||
text: "Cancel"
|
||||
textColor: DynamicColors.palette.m3onSurface
|
||||
enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired
|
||||
horizontalPadding: Appearance.padding.large
|
||||
icon: "close"
|
||||
inactiveColor: DynamicColors.palette.m3surfaceContainer
|
||||
inactiveOnColor: DynamicColors.palette.m3onSurface
|
||||
isRound: true
|
||||
isToggle: false
|
||||
shapeMorph: true
|
||||
shapeMorphExpansion: pressed ? 12 : 0
|
||||
text: "Cancel"
|
||||
|
||||
onClicked: {
|
||||
root.shouldShow = false;
|
||||
polkitAgent.flow.cancelAuthenticationRequest();
|
||||
passInput.text = "";
|
||||
onClicked: {
|
||||
root.shouldShow = false;
|
||||
polkitAgent.flow.cancelAuthenticationRequest();
|
||||
passInput.text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ PageBase {
|
||||
SpinRow {
|
||||
first: true
|
||||
from: 12
|
||||
last: true
|
||||
settingAnchor: "bar-tray-iconsize"
|
||||
stepSize: 1
|
||||
text: qsTr("Icon size")
|
||||
@@ -28,5 +27,24 @@ PageBase {
|
||||
|
||||
onMoved: value => Config.bar.tray.trayIconSize = value
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.bar.tray.showOnHover
|
||||
settingAnchor: "bar-tray-show-popout-on-hover"
|
||||
subtext: Config.bar.tray.showOnHover ? qsTr("Will show context menu on hover") : qsTr("Will show context menu on right-click")
|
||||
text: qsTr("Show popout on hover")
|
||||
|
||||
onToggled: Config.bar.tray.showOnHover = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.bar.tray.recolorIcons
|
||||
last: true
|
||||
settingAnchor: "bar-tray-recolor-icons"
|
||||
subtext: qsTr("Recolors icons to fit current scheme")
|
||||
text: qsTr("Recolor icons")
|
||||
|
||||
onToggled: Config.bar.tray.recolorIcons = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ Item {
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 3
|
||||
color: icon.layer.enabled && root.current ? DynamicColors.palette.m3primary : "transparent"
|
||||
color: icon.layer.enabled && enabled && root.current ? DynamicColors.palette.m3primary : "transparent"
|
||||
enabled: !Config.bar.tray.showOnHover
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
StateLayer {
|
||||
@@ -57,7 +58,7 @@ Item {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.item.activate();
|
||||
console.log(icon.source + "\n" + root.item.id);
|
||||
} else if (mouse.button === Qt.RightButton && Config.bar.popouts.tray) {
|
||||
} else if (mouse.button === Qt.RightButton && Config.bar.popouts.tray && !Config.bar.tray.showOnHover) {
|
||||
root.popouts.currentName = `traymenu${root.ind}`;
|
||||
root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x);
|
||||
root.popouts.hasCurrent = true;
|
||||
@@ -76,9 +77,9 @@ Item {
|
||||
|
||||
anchors.centerIn: parent
|
||||
antialiasing: true
|
||||
color: root.current ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
||||
color: root.current && !Config.bar.tray.showOnHover ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
||||
implicitSize: Config.bar.tray.trayIconSize * root.dpr
|
||||
layer.enabled: Config.general.color.smart || Config.general.color.scheduleDark
|
||||
layer.enabled: Config.bar.tray.recolorIcons
|
||||
scale: 1 / root.dpr
|
||||
source: root.resolveIcon(root.item.id, root.item.icon)
|
||||
}
|
||||
|
||||
@@ -66,12 +66,12 @@ RowLayout {
|
||||
}
|
||||
|
||||
let trayPos = mapToItem(sysTray, localX, localY);
|
||||
if (sysTray.contains(Qt.point(trayPos.x, trayPos.y))) {
|
||||
if (Config.bar.tray.showOnHover && sysTray.contains(Qt.point(trayPos.x, trayPos.y))) {
|
||||
let trayRowPos = sysTray.mapToItem(sysRow, trayPos.x, trayPos.y);
|
||||
let child = sysRow.childAt(trayRowPos.x, trayRowPos.y);
|
||||
if (child && child.hasOwnProperty("popoutId")) {
|
||||
if (child && child.hasOwnProperty("ind")) {
|
||||
return {
|
||||
id: child.popoutId,
|
||||
id: `traymenu${child.ind}`,
|
||||
item: child
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user