add noctalia and fuzzel
This commit is contained in:
+119
@@ -0,0 +1,119 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.DesktopWidgets
|
||||
import qs.Services.Media
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import qs.Widgets.AudioSpectrum
|
||||
|
||||
DraggableDesktopWidget {
|
||||
id: root
|
||||
|
||||
defaultY: 280
|
||||
|
||||
readonly property var widgetMetadata: DesktopWidgetRegistry.widgetMetadata["AudioVisualizer"]
|
||||
|
||||
readonly property int visualizerWidth: (widgetData && widgetData.width !== undefined) ? widgetData.width : (widgetMetadata?.width ?? 320)
|
||||
readonly property int visualizerHeight: (widgetData && widgetData.height !== undefined) ? widgetData.height : (widgetMetadata?.height ?? 72)
|
||||
readonly property string visualizerType: (widgetData && widgetData.visualizerType !== undefined) ? widgetData.visualizerType : (widgetMetadata?.visualizerType ?? "linear")
|
||||
readonly property bool hideWhenIdle: (widgetData && widgetData.hideWhenIdle !== undefined) ? widgetData.hideWhenIdle : (widgetMetadata?.hideWhenIdle ?? false)
|
||||
readonly property string colorName: (widgetData && widgetData.colorName !== undefined) ? widgetData.colorName : (widgetMetadata?.colorName ?? "primary")
|
||||
|
||||
readonly property color fillColor: Color.resolveColorKey(colorName)
|
||||
|
||||
readonly property bool shouldShow: visualizerType !== "" && visualizerType !== "none" && (!hideWhenIdle || MediaService.isPlaying)
|
||||
readonly property bool isHidden: !shouldShow
|
||||
readonly property bool shouldRegisterSpectrum: shouldShow
|
||||
|
||||
// Keep widget visible in edit mode so users can move/configure it
|
||||
visible: !root.isHidden || DesktopWidgetRegistry.editMode
|
||||
|
||||
readonly property string spectrumComponentId: "desktop:audiovisualizer:" + (root.screen ? root.screen.name : "unknown") + ":" + root.widgetIndex
|
||||
|
||||
onShouldRegisterSpectrumChanged: {
|
||||
if (root.shouldRegisterSpectrum) {
|
||||
SpectrumService.registerComponent(root.spectrumComponentId);
|
||||
} else {
|
||||
SpectrumService.unregisterComponent(root.spectrumComponentId);
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.shouldRegisterSpectrum) {
|
||||
SpectrumService.registerComponent(root.spectrumComponentId);
|
||||
}
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
SpectrumService.unregisterComponent(root.spectrumComponentId);
|
||||
}
|
||||
|
||||
implicitWidth: Math.round(visualizerWidth * widgetScale)
|
||||
implicitHeight: Math.round(visualizerHeight * widgetScale)
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
Rectangle {
|
||||
id: visualizerMask
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
radius: root.roundedCorners ? Math.min(Math.round(Style.radiusL * root.widgetScale), Style.radiusL, width / 2, height / 2) : 0
|
||||
clip: true
|
||||
|
||||
Loader {
|
||||
id: visualizerLoader
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.showBackground ? Math.round(Style.marginXS * root.widgetScale) : 0
|
||||
active: root.shouldShow
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: {
|
||||
switch (root.visualizerType) {
|
||||
case "linear":
|
||||
return linearComponent;
|
||||
case "mirrored":
|
||||
return mirroredComponent;
|
||||
case "wave":
|
||||
return waveComponent;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: linearComponent
|
||||
NLinearSpectrum {
|
||||
anchors.fill: parent
|
||||
values: SpectrumService.values
|
||||
fillColor: root.fillColor
|
||||
showMinimumSignal: true
|
||||
mirrored: Settings.data.audio.spectrumMirrored
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: mirroredComponent
|
||||
NMirroredSpectrum {
|
||||
anchors.fill: parent
|
||||
values: SpectrumService.values
|
||||
fillColor: root.fillColor
|
||||
showMinimumSignal: true
|
||||
mirrored: Settings.data.audio.spectrumMirrored
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: waveComponent
|
||||
NWaveSpectrum {
|
||||
anchors.fill: parent
|
||||
values: SpectrumService.values
|
||||
fillColor: root.fillColor
|
||||
showMinimumSignal: true
|
||||
mirrored: Settings.data.audio.spectrumMirrored
|
||||
}
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.DesktopWidgets
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
DraggableDesktopWidget {
|
||||
id: root
|
||||
|
||||
readonly property var now: Time.now
|
||||
readonly property var widgetMetadata: DesktopWidgetRegistry.widgetMetadata["Clock"]
|
||||
|
||||
readonly property color clockTextColor: Color.resolveColorKey(clockColor)
|
||||
readonly property real fontSize: Math.round(Style.fontSizeXXXL * 2.5 * widgetScale)
|
||||
readonly property real widgetOpacity: widgetData.opacity !== undefined ? widgetData.opacity : 1.0
|
||||
readonly property string clockStyle: widgetData.clockStyle !== undefined ? widgetData.clockStyle : widgetMetadata.clockStyle
|
||||
readonly property string clockColor: widgetData.clockColor !== undefined ? widgetData.clockColor : widgetMetadata.clockColor
|
||||
readonly property bool useCustomFont: widgetData.useCustomFont !== undefined ? widgetData.useCustomFont : widgetMetadata.useCustomFont
|
||||
readonly property string customFont: widgetData.customFont !== undefined ? widgetData.customFont : widgetMetadata.customFont
|
||||
readonly property string format: widgetData.format !== undefined ? widgetData.format : widgetMetadata.format
|
||||
|
||||
readonly property real contentPadding: Math.round((clockStyle === "minimal" ? Style.marginL : Style.marginXL) * widgetScale)
|
||||
implicitWidth: contentLoader.item ? Math.round((contentLoader.item.implicitWidth || contentLoader.item.width || 0) + contentPadding * 2) : 0
|
||||
implicitHeight: contentLoader.item ? Math.round((contentLoader.item.implicitHeight || contentLoader.item.height || 0) + contentPadding * 2) : 0
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
Component {
|
||||
id: nclockComponent
|
||||
NClock {
|
||||
now: root.now
|
||||
clockStyle: root.clockStyle
|
||||
backgroundColor: "transparent"
|
||||
clockColor: clockTextColor
|
||||
progressColor: Color.mPrimary
|
||||
opacity: root.widgetOpacity
|
||||
height: Math.round(fontSize * 1.9)
|
||||
width: height
|
||||
hoursFontSize: fontSize * 0.6
|
||||
minutesFontSize: fontSize * 0.4
|
||||
scaleRatio: root.widgetScale
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: minimalClockComponent
|
||||
ColumnLayout {
|
||||
spacing: -2
|
||||
opacity: root.widgetOpacity
|
||||
|
||||
Repeater {
|
||||
model: I18n.locale.toString(root.now, root.format.trim()).split("\\n")
|
||||
delegate: NText {
|
||||
visible: text !== ""
|
||||
text: modelData
|
||||
family: root.useCustomFont && root.customFont ? root.customFont : Settings.data.ui.fontDefault
|
||||
pointSize: {
|
||||
if (model.length == 1) {
|
||||
return Math.round(Style.fontSizeXXL * root.widgetScale);
|
||||
} else {
|
||||
return Math.round((index == 0) ? Style.fontSizeXXL * root.widgetScale : Style.fontSizeM * root.widgetScale);
|
||||
}
|
||||
}
|
||||
font.weight: Style.fontWeightBold
|
||||
color: root.clockTextColor
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors.centerIn: parent
|
||||
z: 2
|
||||
sourceComponent: clockStyle === "minimal" ? minimalClockComponent : nclockComponent
|
||||
}
|
||||
}
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.DesktopWidgets
|
||||
import qs.Services.Media
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import qs.Widgets.AudioSpectrum
|
||||
|
||||
DraggableDesktopWidget {
|
||||
id: root
|
||||
|
||||
defaultY: 200
|
||||
|
||||
// Widget settings - check widgetData exists before accessing properties
|
||||
readonly property string hideMode: (widgetData && widgetData.hideMode !== undefined) ? widgetData.hideMode : "visible"
|
||||
readonly property bool showButtons: (widgetData && widgetData.showButtons !== undefined) ? widgetData.showButtons : true
|
||||
readonly property bool showAlbumArt: (widgetData && widgetData.showAlbumArt !== undefined) ? widgetData.showAlbumArt : true
|
||||
readonly property bool showVisualizer: (widgetData && widgetData.showVisualizer !== undefined) ? widgetData.showVisualizer : true
|
||||
readonly property string visualizerType: (widgetData && widgetData.visualizerType && widgetData.visualizerType !== "") ? widgetData.visualizerType : "linear"
|
||||
readonly property bool roundedCorners: (widgetData && widgetData.roundedCorners !== undefined) ? widgetData.roundedCorners : true
|
||||
readonly property bool hasPlayer: MediaService.currentPlayer !== null
|
||||
readonly property bool isPlaying: MediaService.isPlaying
|
||||
readonly property bool hasActiveTrack: hasPlayer && (MediaService.trackTitle || MediaService.trackArtist)
|
||||
|
||||
// State
|
||||
// Hide when idle when playback is not active
|
||||
readonly property bool shouldHideIdle: (hideMode === "idle") && !isPlaying
|
||||
readonly property bool shouldHideEmpty: !hasPlayer && hideMode === "hidden"
|
||||
readonly property bool isHidden: (shouldHideIdle || shouldHideEmpty) && !DesktopWidgetRegistry.editMode
|
||||
visible: !isHidden
|
||||
|
||||
// SpectrumService registration for visualizer
|
||||
readonly property string spectrumComponentId: "desktopmediaplayer:" + (root.screen ? root.screen.name : "unknown")
|
||||
readonly property bool needsSpectrum: root.shouldShowVisualizer && !root.isHidden
|
||||
|
||||
onNeedsSpectrumChanged: {
|
||||
if (root.needsSpectrum) {
|
||||
SpectrumService.registerComponent(root.spectrumComponentId);
|
||||
} else {
|
||||
SpectrumService.unregisterComponent(root.spectrumComponentId);
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.needsSpectrum) {
|
||||
SpectrumService.registerComponent(root.spectrumComponentId);
|
||||
}
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
SpectrumService.unregisterComponent(root.spectrumComponentId);
|
||||
}
|
||||
|
||||
readonly property bool showPrev: hasPlayer && MediaService.canGoPrevious
|
||||
readonly property bool showNext: hasPlayer && MediaService.canGoNext
|
||||
readonly property int visibleButtonCount: root.showButtons ? (1 + (showPrev ? 1 : 0) + (showNext ? 1 : 0)) : 0
|
||||
|
||||
implicitWidth: Math.round(400 * widgetScale)
|
||||
implicitHeight: Math.round(64 * widgetScale + Style.margin2M * widgetScale)
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
// Visualizer visibility mode
|
||||
readonly property bool shouldShowVisualizer: {
|
||||
if (!root.showVisualizer)
|
||||
return false;
|
||||
if (root.visualizerType === "" || root.visualizerType === "none")
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Visualizer overlay (visibility controlled by visualizerVisibility setting)
|
||||
// Completely disabled during scaling to avoid expensive canvas redraws
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Math.round(Style.marginXS * widgetScale)
|
||||
anchors.rightMargin: Math.round(Style.marginXS * widgetScale)
|
||||
anchors.topMargin: Math.round(Style.marginXS * widgetScale)
|
||||
anchors.bottomMargin: 0
|
||||
z: 0
|
||||
clip: true
|
||||
active: needsSpectrum
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: root.roundedCorners
|
||||
maskSource: ShaderEffectSource {
|
||||
sourceItem: Rectangle {
|
||||
width: root.width - Math.round(Style.marginXS * widgetScale) * 2
|
||||
height: root.height - Math.round(Style.marginXS * widgetScale)
|
||||
radius: root.roundedCorners ? Math.round(Math.max(0, (Style.radiusL - Style.marginXS) * widgetScale)) : 0
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceComponent: {
|
||||
switch (root.visualizerType) {
|
||||
case "linear":
|
||||
return linearComponent;
|
||||
case "mirrored":
|
||||
return mirroredComponent;
|
||||
case "wave":
|
||||
return waveComponent;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: linearComponent
|
||||
NLinearSpectrum {
|
||||
anchors.fill: parent
|
||||
values: SpectrumService.values
|
||||
fillColor: Color.mPrimary
|
||||
opacity: 0.5
|
||||
mirrored: Settings.data.audio.spectrumMirrored
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: mirroredComponent
|
||||
NMirroredSpectrum {
|
||||
anchors.fill: parent
|
||||
values: SpectrumService.values
|
||||
fillColor: Color.mPrimary
|
||||
opacity: 0.5
|
||||
mirrored: Settings.data.audio.spectrumMirrored
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: waveComponent
|
||||
NWaveSpectrum {
|
||||
anchors.fill: parent
|
||||
values: SpectrumService.values
|
||||
fillColor: Color.mPrimary
|
||||
opacity: 0.5
|
||||
mirrored: Settings.data.audio.spectrumMirrored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drop shadow for text and controls readability over visualizer
|
||||
// Disabled during scaling to avoid expensive recomputations
|
||||
NDropShadow {
|
||||
visible: !root.isScaling
|
||||
anchors.fill: contentLayout
|
||||
source: contentLayout
|
||||
z: 1
|
||||
autoPaddingEnabled: true
|
||||
shadowBlur: 1.0
|
||||
shadowOpacity: 0.9
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 0
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: contentLayout
|
||||
states: [
|
||||
State {
|
||||
when: root.showButtons
|
||||
AnchorChanges {
|
||||
target: contentLayout
|
||||
anchors.horizontalCenter: undefined
|
||||
anchors.verticalCenter: undefined
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
},
|
||||
State {
|
||||
when: !root.showButtons
|
||||
AnchorChanges {
|
||||
target: contentLayout
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.top: undefined
|
||||
anchors.bottom: undefined
|
||||
anchors.left: undefined
|
||||
anchors.right: undefined
|
||||
}
|
||||
}
|
||||
]
|
||||
anchors.margins: Math.round(Style.marginM * widgetScale)
|
||||
spacing: Math.round(Style.marginS * widgetScale)
|
||||
z: 2
|
||||
|
||||
Item {
|
||||
visible: root.showAlbumArt
|
||||
Layout.preferredWidth: Math.round(48 * widgetScale)
|
||||
Layout.preferredHeight: Math.round(48 * widgetScale)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
NImageRounded {
|
||||
visible: hasPlayer
|
||||
anchors.fill: parent
|
||||
radius: Math.round(Style.radiusM * widgetScale)
|
||||
imagePath: MediaService.trackArtUrl
|
||||
imageFillMode: Image.PreserveAspectCrop
|
||||
fallbackIcon: isPlaying ? "media-pause" : "media-play"
|
||||
fallbackIconSize: Math.round(20 * widgetScale)
|
||||
borderWidth: 0
|
||||
}
|
||||
|
||||
NIcon {
|
||||
visible: !hasPlayer
|
||||
anchors.centerIn: parent
|
||||
icon: "disc"
|
||||
pointSize: Math.round(24 * widgetScale)
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: root.showAlbumArt
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: root.showButtons ? Qt.AlignVCenter : Qt.AlignCenter
|
||||
spacing: 0
|
||||
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
text: hasPlayer ? (MediaService.trackTitle || "Unknown Track") : "No media playing"
|
||||
pointSize: Math.round(Style.fontSizeS * widgetScale)
|
||||
font.weight: Style.fontWeightSemiBold
|
||||
color: Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: hasPlayer && MediaService.trackArtist
|
||||
Layout.fillWidth: true
|
||||
text: MediaService.trackArtist || ""
|
||||
pointSize: Math.round(Style.fontSizeXS * widgetScale)
|
||||
font.weight: Style.fontWeightRegular
|
||||
color: Color.mSecondary
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: controlsRow
|
||||
spacing: Math.round(Style.marginXS * widgetScale)
|
||||
z: 10
|
||||
visible: root.showButtons
|
||||
Layout.alignment: root.showAlbumArt ? Qt.AlignVCenter : Qt.AlignCenter
|
||||
|
||||
NIconButton {
|
||||
opacity: showPrev ? 1 : 0
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
baseSize: Math.round(32 * widgetScale)
|
||||
icon: "media-prev"
|
||||
enabled: hasPlayer && MediaService.canGoPrevious
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorFg: enabled ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
customRadius: Math.round(Style.radiusS * widgetScale)
|
||||
onClicked: {
|
||||
if (enabled)
|
||||
MediaService.previous();
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
baseSize: Math.round(36 * widgetScale)
|
||||
icon: isPlaying ? "media-pause" : "media-play"
|
||||
enabled: hasPlayer && (MediaService.canPlay || MediaService.canPause)
|
||||
colorBg: Color.mPrimary
|
||||
colorFg: Color.mOnPrimary
|
||||
colorBgHover: Qt.lighter(Color.mPrimary, 1.1)
|
||||
colorFgHover: Color.mOnPrimary
|
||||
customRadius: Math.round(Style.radiusS * widgetScale)
|
||||
onClicked: {
|
||||
if (enabled) {
|
||||
MediaService.playPause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
opacity: showNext ? 1 : 0
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
baseSize: Math.round(32 * widgetScale)
|
||||
icon: "media-next"
|
||||
enabled: hasPlayer && MediaService.canGoNext
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorFg: enabled ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
customRadius: Math.round(Style.radiusS * widgetScale)
|
||||
onClicked: {
|
||||
if (enabled)
|
||||
MediaService.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.DesktopWidgets
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
DraggableDesktopWidget {
|
||||
id: root
|
||||
|
||||
// Widget settings
|
||||
readonly property var widgetMetadata: DesktopWidgetRegistry.widgetMetadata["SystemStat"]
|
||||
readonly property string statType: (widgetData && widgetData.statType !== undefined) ? widgetData.statType : (widgetMetadata.statType !== undefined ? widgetMetadata.statType : "CPU")
|
||||
readonly property string diskPath: (widgetData && widgetData.diskPath !== undefined) ? widgetData.diskPath : "/"
|
||||
readonly property string layout: (widgetData && widgetData.layout !== undefined) ? widgetData.layout : (widgetMetadata.layout !== undefined ? widgetMetadata.layout : "side")
|
||||
|
||||
// Fixed colors
|
||||
readonly property color color: Color.mPrimary
|
||||
readonly property color color2: Color.mSecondary
|
||||
|
||||
// Legend items model - each item has: text, color, icon (optional), bold (optional), opacity (optional), elide (optional)
|
||||
readonly property var legendItems: {
|
||||
switch (root.statType) {
|
||||
case "CPU":
|
||||
return [
|
||||
{
|
||||
icon: "cpu-usage",
|
||||
text: Math.round(SystemStatService.cpuUsage) + "%",
|
||||
color: root.color
|
||||
},
|
||||
{
|
||||
text: "cpu-usage",
|
||||
text: SystemStatService.cpuFreq,
|
||||
color: root.color,
|
||||
opacity: 0.8
|
||||
},
|
||||
{
|
||||
icon: "cpu-temperature",
|
||||
text: SystemStatService.cpuTemp + "°C",
|
||||
color: root.color2
|
||||
}
|
||||
];
|
||||
case "GPU":
|
||||
return [
|
||||
{
|
||||
icon: "gpu-temperature",
|
||||
text: Math.round(SystemStatService.gpuTemp) + "°C",
|
||||
color: root.color
|
||||
}
|
||||
];
|
||||
case "Memory":
|
||||
return [
|
||||
{
|
||||
icon: "memory",
|
||||
text: Math.round(SystemStatService.memPercent) + "%",
|
||||
color: root.color
|
||||
}
|
||||
];
|
||||
case "Disk":
|
||||
var items = [
|
||||
{
|
||||
icon: "storage",
|
||||
text: Math.round(SystemStatService.diskPercents[root.diskPath] || 0) + "%",
|
||||
color: root.color
|
||||
}
|
||||
];
|
||||
if (root.diskPath !== "/") {
|
||||
items.push({
|
||||
text: root.diskPath,
|
||||
color: root.color,
|
||||
opacity: 0.8,
|
||||
elide: true
|
||||
});
|
||||
}
|
||||
return items;
|
||||
case "Network":
|
||||
return [
|
||||
{
|
||||
icon: "download-speed",
|
||||
text: SystemStatService.formatSpeed(SystemStatService.rxSpeed),
|
||||
color: root.color
|
||||
},
|
||||
{
|
||||
icon: "upload-speed",
|
||||
text: SystemStatService.formatSpeed(SystemStatService.txSpeed),
|
||||
color: root.color2
|
||||
}
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// History from service
|
||||
readonly property var history: {
|
||||
switch (root.statType) {
|
||||
case "CPU":
|
||||
return SystemStatService.cpuHistory;
|
||||
case "GPU":
|
||||
return SystemStatService.gpuTempHistory;
|
||||
case "Memory":
|
||||
return SystemStatService.memHistory;
|
||||
case "Disk":
|
||||
return SystemStatService.diskHistories[root.diskPath] || [];
|
||||
case "Network":
|
||||
return SystemStatService.rxSpeedHistory;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Secondary history (CPU temp for CPU, Tx for Network)
|
||||
readonly property var history2: {
|
||||
switch (root.statType) {
|
||||
case "CPU":
|
||||
return SystemStatService.cpuTempHistory;
|
||||
case "Network":
|
||||
return SystemStatService.txSpeedHistory;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Graph min/max values
|
||||
readonly property real graphMinValue: root.statType === "GPU" ? Math.max(SystemStatService.gpuTempHistoryMin - 5, 0) : 0
|
||||
readonly property real graphMaxValue: {
|
||||
switch (root.statType) {
|
||||
case "CPU":
|
||||
case "Memory":
|
||||
case "Disk":
|
||||
return 100; // Percentage-based stats use fixed 0-100 range
|
||||
case "GPU":
|
||||
return Math.max(SystemStatService.gpuTempHistoryMax + 5, 1);
|
||||
case "Network":
|
||||
return SystemStatService.rxMaxSpeed;
|
||||
default:
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
readonly property real graphMinValue2: {
|
||||
switch (root.statType) {
|
||||
case "CPU":
|
||||
return Math.max(SystemStatService.cpuTempHistoryMin - 5, 0);
|
||||
default:
|
||||
return graphMinValue;
|
||||
}
|
||||
}
|
||||
readonly property real graphMaxValue2: {
|
||||
switch (root.statType) {
|
||||
case "CPU":
|
||||
return Math.max(SystemStatService.cpuTempHistoryMax + 5, 1);
|
||||
case "Network":
|
||||
return SystemStatService.txMaxSpeed;
|
||||
default:
|
||||
return graphMaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: SystemStatService.registerComponent("desktop-sysstat:" + root.statType)
|
||||
Component.onDestruction: SystemStatService.unregisterComponent("desktop-sysstat:" + root.statType)
|
||||
|
||||
implicitWidth: Math.round(240 * widgetScale)
|
||||
implicitHeight: Math.round(120 * widgetScale)
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
// Update interval per stat type
|
||||
readonly property int graphUpdateInterval: {
|
||||
switch (root.statType) {
|
||||
case "CPU":
|
||||
return SystemStatService.cpuUsageIntervalMs;
|
||||
case "GPU":
|
||||
return SystemStatService.gpuIntervalMs;
|
||||
case "Memory":
|
||||
return SystemStatService.memIntervalMs;
|
||||
case "Disk":
|
||||
return SystemStatService.diskIntervalMs;
|
||||
case "Network":
|
||||
return SystemStatService.networkIntervalMs;
|
||||
default:
|
||||
return 1000;
|
||||
}
|
||||
}
|
||||
|
||||
// Graph component (shared between layouts)
|
||||
Component {
|
||||
id: graphComponent
|
||||
NGraph {
|
||||
values: root.history
|
||||
values2: root.history2
|
||||
minValue: root.graphMinValue
|
||||
maxValue: root.graphMaxValue
|
||||
minValue2: root.graphMinValue2
|
||||
maxValue2: root.graphMaxValue2
|
||||
color: root.color
|
||||
color2: root.color2
|
||||
fill: true
|
||||
updateInterval: root.graphUpdateInterval
|
||||
strokeWidth: Math.max(1, root.widgetScale)
|
||||
animateScale: root.statType === "Network"
|
||||
}
|
||||
}
|
||||
|
||||
// Side layout: icon + legend on left, graph on right
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Math.round(Style.marginM * widgetScale)
|
||||
spacing: Math.round(Style.marginL * widgetScale)
|
||||
visible: root.layout === "side"
|
||||
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: Math.round(64 * widgetScale)
|
||||
spacing: Style.marginXS * root.widgetScale
|
||||
|
||||
Repeater {
|
||||
model: root.legendItems
|
||||
delegate: RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Math.round(Style.marginXXS * root.widgetScale)
|
||||
|
||||
NIcon {
|
||||
visible: !!modelData.icon
|
||||
icon: modelData.icon || ""
|
||||
color: modelData.color
|
||||
pointSize: Style.fontSizeS * root.widgetScale
|
||||
opacity: modelData.opacity !== undefined ? modelData.opacity : 1.0
|
||||
}
|
||||
|
||||
NText {
|
||||
text: modelData.text
|
||||
color: modelData.color
|
||||
pointSize: Style.fontSizeS * root.widgetScale
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.weight: modelData.bold ? Style.fontWeightBold : Style.fontWeightRegular
|
||||
opacity: modelData.opacity !== undefined ? modelData.opacity : 1.0
|
||||
elide: modelData.elide ? Text.ElideMiddle : Text.ElideNone
|
||||
Layout.maximumWidth: modelData.elide ? Math.round(56 * root.widgetScale) : -1
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.layout === "side"
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
sourceComponent: graphComponent
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom layout: full-width graph, horizontal legend at bottom
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Math.round(Style.marginM * widgetScale)
|
||||
spacing: Math.round(Style.marginS * widgetScale)
|
||||
visible: root.layout === "bottom"
|
||||
|
||||
Loader {
|
||||
active: root.layout === "bottom"
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
sourceComponent: graphComponent
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Math.round(Style.marginM * widgetScale)
|
||||
|
||||
Repeater {
|
||||
model: root.legendItems
|
||||
delegate: RowLayout {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Math.round(Style.marginXXS * root.widgetScale)
|
||||
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: !!modelData.icon
|
||||
icon: modelData.icon || ""
|
||||
color: modelData.color
|
||||
pointSize: Style.fontSizeS * root.widgetScale
|
||||
opacity: modelData.opacity !== undefined ? modelData.opacity : 1.0
|
||||
}
|
||||
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: modelData.text
|
||||
color: modelData.color
|
||||
pointSize: Style.fontSizeS * root.widgetScale
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.weight: modelData.bold ? Style.fontWeightBold : Style.fontWeightRegular
|
||||
opacity: modelData.opacity !== undefined ? modelData.opacity : 1.0
|
||||
elide: modelData.elide ? Text.ElideMiddle : Text.ElideNone
|
||||
Layout.maximumWidth: modelData.elide ? Math.round(56 * root.widgetScale) : -1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.DesktopWidgets
|
||||
import qs.Services.Location
|
||||
import qs.Widgets
|
||||
|
||||
DraggableDesktopWidget {
|
||||
id: root
|
||||
|
||||
readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null)
|
||||
readonly property int currentWeatherCode: weatherReady ? LocationService.data.weather.current_weather.weathercode : 0
|
||||
readonly property real currentTemp: {
|
||||
if (!weatherReady)
|
||||
return 0;
|
||||
var temp = LocationService.data.weather.current_weather.temperature;
|
||||
if (Settings.data.location.useFahrenheit) {
|
||||
temp = LocationService.celsiusToFahrenheit(temp);
|
||||
}
|
||||
return Math.round(temp);
|
||||
}
|
||||
readonly property real todayMax: {
|
||||
if (!weatherReady || !LocationService.data.weather.daily || LocationService.data.weather.daily.temperature_2m_max.length === 0)
|
||||
return 0;
|
||||
var temp = LocationService.data.weather.daily.temperature_2m_max[0];
|
||||
if (Settings.data.location.useFahrenheit) {
|
||||
temp = LocationService.celsiusToFahrenheit(temp);
|
||||
}
|
||||
return Math.round(temp);
|
||||
}
|
||||
readonly property real todayMin: {
|
||||
if (!weatherReady || !LocationService.data.weather.daily || LocationService.data.weather.daily.temperature_2m_min.length === 0)
|
||||
return 0;
|
||||
var temp = LocationService.data.weather.daily.temperature_2m_min[0];
|
||||
if (Settings.data.location.useFahrenheit) {
|
||||
temp = LocationService.celsiusToFahrenheit(temp);
|
||||
}
|
||||
return Math.round(temp);
|
||||
}
|
||||
readonly property string tempUnit: Settings.data.location.useFahrenheit ? "F" : "C"
|
||||
readonly property string locationName: {
|
||||
const chunks = Settings.data.location.name.split(",");
|
||||
return chunks[0];
|
||||
}
|
||||
|
||||
implicitWidth: Math.round(Math.max(240 * widgetScale, contentLayout.implicitWidth + Style.margin2M * widgetScale))
|
||||
implicitHeight: Math.round(64 * widgetScale + Style.margin2M * widgetScale)
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
RowLayout {
|
||||
id: contentLayout
|
||||
anchors.fill: parent
|
||||
anchors.margins: Math.round(Style.marginM * widgetScale)
|
||||
spacing: Math.round(Style.marginM * widgetScale)
|
||||
z: 2
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: Math.round(64 * widgetScale)
|
||||
Layout.preferredHeight: Math.round(64 * widgetScale)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
NIcon {
|
||||
visible: !LocationService.taliaWeatherMascotActive || !weatherReady
|
||||
anchors.centerIn: parent
|
||||
icon: weatherReady ? LocationService.weatherSymbolFromCode(currentWeatherCode) : (LocationService.locationConfigured ? "weather-cloud-off" : "map-pin-off")
|
||||
pointSize: Math.round(Style.fontSizeXXXL * 2 * widgetScale)
|
||||
color: weatherReady ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
}
|
||||
Loader {
|
||||
active: LocationService.taliaWeatherMascotActive && weatherReady
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
sourceComponent: Component {
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
mipmap: true
|
||||
asynchronous: true
|
||||
source: Qt.resolvedUrl(LocationService.taliaWeatherImageFromCode(currentWeatherCode))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: weatherReady ? `${currentTemp}°${tempUnit}` : "--"
|
||||
pointSize: Math.round(Style.fontSizeXXXL * widgetScale)
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Math.round(Style.marginXXS * widgetScale)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
text: locationName || I18n.tr("common.weather-no-location")
|
||||
pointSize: Math.round(Style.fontSizeS * widgetScale)
|
||||
font.weight: Style.fontWeightRegular
|
||||
color: Color.mOnSurfaceVariant
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
visible: !Settings.data.location.hideWeatherCityName
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Math.round(Style.marginXS * widgetScale)
|
||||
visible: weatherReady && todayMax > 0 && todayMin > 0
|
||||
|
||||
NText {
|
||||
text: "H:"
|
||||
pointSize: Math.round(Style.fontSizeXS * widgetScale)
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
NText {
|
||||
text: `${todayMax}°`
|
||||
pointSize: Math.round(Style.fontSizeXS * widgetScale)
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "•"
|
||||
pointSize: Math.round(Style.fontSizeXXS * widgetScale)
|
||||
color: Color.mOnSurfaceVariant
|
||||
opacity: 0.5
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "L:"
|
||||
pointSize: Math.round(Style.fontSizeXS * widgetScale)
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
NText {
|
||||
text: `${todayMin}°`
|
||||
pointSize: Math.round(Style.fontSizeXS * widgetScale)
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user