extract SparklineCanvas component from 5 applets
This commit is contained in:
parent
732a14e5cb
commit
8d76df6ef5
8 changed files with 178 additions and 233 deletions
|
|
@ -87,91 +87,28 @@ Column {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 24h history sparkline (area chart)
|
// 24h history sparkline (area chart)
|
||||||
Canvas {
|
SparklineCanvas {
|
||||||
id: _sparkline
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 12
|
anchors.leftMargin: 12
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 12
|
anchors.rightMargin: 12
|
||||||
height: 44
|
height: 44
|
||||||
|
history: S.BatteryService.history
|
||||||
property var _hist: S.BatteryService.history
|
color: root._stateColor
|
||||||
property color _col: root._stateColor
|
active: root.active
|
||||||
|
maxSamples: 1440
|
||||||
on_HistChanged: if (root.active)
|
backgroundTint: 0.07
|
||||||
requestPaint()
|
areaMode: true
|
||||||
on_ColChanged: if (root.active)
|
thresholds: [
|
||||||
requestPaint()
|
{
|
||||||
|
value: S.BatteryService.warnThresh,
|
||||||
onVisibleChanged: if (visible)
|
color: S.Theme.base0A
|
||||||
requestPaint()
|
},
|
||||||
|
{
|
||||||
onPaint: {
|
value: S.BatteryService.critThresh,
|
||||||
const ctx = getContext("2d");
|
color: S.Theme.base08
|
||||||
if (!ctx)
|
|
||||||
return;
|
|
||||||
ctx.clearRect(0, 0, width, height);
|
|
||||||
const d = _hist;
|
|
||||||
if (d.length < 2)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const maxSamples = 1440;
|
|
||||||
const xScale = width / maxSamples;
|
|
||||||
const xOffset = (maxSamples - d.length) * xScale;
|
|
||||||
|
|
||||||
// Background tint
|
|
||||||
ctx.fillStyle = Qt.rgba(_col.r, _col.g, _col.b, 0.07).toString();
|
|
||||||
ctx.fillRect(0, 0, width, height);
|
|
||||||
|
|
||||||
// Warning threshold line
|
|
||||||
const warnY = height - height * (S.BatteryService.warnThresh / 100);
|
|
||||||
ctx.strokeStyle = S.Theme.base0A.toString();
|
|
||||||
ctx.globalAlpha = 0.3;
|
|
||||||
ctx.lineWidth = 1;
|
|
||||||
ctx.setLineDash([3, 3]);
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(0, warnY);
|
|
||||||
ctx.lineTo(width, warnY);
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
// Critical threshold line
|
|
||||||
const critY = height - height * (S.BatteryService.critThresh / 100);
|
|
||||||
ctx.strokeStyle = S.Theme.base08.toString();
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(0, critY);
|
|
||||||
ctx.lineTo(width, critY);
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
ctx.setLineDash([]);
|
|
||||||
ctx.globalAlpha = 1.0;
|
|
||||||
|
|
||||||
// Filled area under the curve
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(xOffset, height);
|
|
||||||
for (let i = 0; i < d.length; i++) {
|
|
||||||
const x = xOffset + i * xScale;
|
|
||||||
const y = height - height * (d[i] / 100);
|
|
||||||
ctx.lineTo(x, y);
|
|
||||||
}
|
}
|
||||||
ctx.lineTo(xOffset + (d.length - 1) * xScale, height);
|
]
|
||||||
ctx.closePath();
|
|
||||||
ctx.fillStyle = Qt.rgba(_col.r, _col.g, _col.b, 0.18).toString();
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
// Top line
|
|
||||||
ctx.beginPath();
|
|
||||||
for (let i = 0; i < d.length; i++) {
|
|
||||||
const x = xOffset + i * xScale;
|
|
||||||
const y = height - height * (d[i] / 100);
|
|
||||||
if (i === 0)
|
|
||||||
ctx.moveTo(x, y);
|
|
||||||
else
|
|
||||||
ctx.lineTo(x, y);
|
|
||||||
}
|
|
||||||
ctx.strokeStyle = _col.toString();
|
|
||||||
ctx.lineWidth = 1.5;
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Footer: thresholds + time label
|
// Footer: thresholds + time label
|
||||||
|
|
|
||||||
|
|
@ -96,41 +96,16 @@ Column {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sparkline
|
SparklineCanvas {
|
||||||
Canvas {
|
|
||||||
id: sparkline
|
id: sparkline
|
||||||
anchors.right: freqLabel.left
|
anchors.right: freqLabel.left
|
||||||
anchors.rightMargin: 6
|
anchors.rightMargin: 6
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: 32
|
width: 32
|
||||||
height: 10
|
height: 10
|
||||||
|
history: root.cores[parent.parent.index]?.history ?? []
|
||||||
property var _hist: root.cores[parent.parent.index]?.history ?? []
|
color: parent.parent._barColor
|
||||||
property color _col: parent.parent._barColor
|
active: root.active
|
||||||
|
|
||||||
on_HistChanged: if (root.active)
|
|
||||||
requestPaint()
|
|
||||||
on_ColChanged: if (root.active)
|
|
||||||
requestPaint()
|
|
||||||
|
|
||||||
onVisibleChanged: if (visible)
|
|
||||||
requestPaint()
|
|
||||||
|
|
||||||
onPaint: {
|
|
||||||
const ctx = getContext("2d");
|
|
||||||
if (!ctx)
|
|
||||||
return;
|
|
||||||
ctx.clearRect(0, 0, width, height);
|
|
||||||
const d = _hist;
|
|
||||||
if (!d.length)
|
|
||||||
return;
|
|
||||||
const bw = width / d.length;
|
|
||||||
ctx.fillStyle = _col.toString();
|
|
||||||
for (let i = 0; i < d.length; i++) {
|
|
||||||
const h = Math.max(1, height * d[i] / 100);
|
|
||||||
ctx.fillRect(i * bw, height - h, Math.max(1, bw - 0.5), h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
|
|
||||||
|
|
@ -76,40 +76,16 @@ Column {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage history sparkline
|
// Usage history sparkline
|
||||||
Canvas {
|
SparklineCanvas {
|
||||||
id: _sparkline
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 12
|
anchors.leftMargin: 12
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 12
|
anchors.rightMargin: 12
|
||||||
height: 36
|
height: 36
|
||||||
|
history: S.SystemStats.gpuHistory
|
||||||
property var _hist: S.SystemStats.gpuHistory
|
active: root.active
|
||||||
|
maxSamples: 60
|
||||||
on_HistChanged: if (root.active)
|
colorFunction: v => S.Theme.loadColor(v)
|
||||||
requestPaint()
|
|
||||||
|
|
||||||
onVisibleChanged: if (visible)
|
|
||||||
requestPaint()
|
|
||||||
|
|
||||||
onPaint: {
|
|
||||||
const ctx = getContext("2d");
|
|
||||||
if (!ctx)
|
|
||||||
return;
|
|
||||||
ctx.clearRect(0, 0, width, height);
|
|
||||||
const d = _hist;
|
|
||||||
if (!d.length)
|
|
||||||
return;
|
|
||||||
const maxSamples = 60;
|
|
||||||
const bw = width / maxSamples;
|
|
||||||
const offset = maxSamples - d.length;
|
|
||||||
for (let i = 0; i < d.length; i++) {
|
|
||||||
const barH = Math.max(1, height * d[i] / 100);
|
|
||||||
const col = S.Theme.loadColor(d[i]);
|
|
||||||
ctx.fillStyle = col.toString();
|
|
||||||
ctx.fillRect((offset + i) * bw, height - barH, Math.max(1, bw - 0.5), barH);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// VRAM section
|
// VRAM section
|
||||||
|
|
|
||||||
|
|
@ -71,49 +71,36 @@ Column {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Memory history sparkline
|
// Memory history sparkline
|
||||||
Canvas {
|
SparklineCanvas {
|
||||||
id: memSparkline
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 12
|
anchors.leftMargin: 12
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 12
|
anchors.rightMargin: 12
|
||||||
height: 18
|
height: 18
|
||||||
|
history: S.SystemStats.memHistory
|
||||||
property var _hist: S.SystemStats.memHistory
|
color: root.accentColor
|
||||||
property color _col: root.accentColor
|
active: root.active
|
||||||
|
maxSamples: 30
|
||||||
on_HistChanged: if (root.active)
|
backgroundTint: 0.15
|
||||||
requestPaint()
|
|
||||||
on_ColChanged: if (root.active)
|
|
||||||
requestPaint()
|
|
||||||
|
|
||||||
onVisibleChanged: if (visible)
|
|
||||||
requestPaint()
|
|
||||||
|
|
||||||
onPaint: {
|
|
||||||
const ctx = getContext("2d");
|
|
||||||
if (!ctx)
|
|
||||||
return;
|
|
||||||
ctx.clearRect(0, 0, width, height);
|
|
||||||
const d = _hist;
|
|
||||||
if (!d.length)
|
|
||||||
return;
|
|
||||||
const bw = width / 30;
|
|
||||||
ctx.fillStyle = Qt.rgba(_col.r, _col.g, _col.b, 0.15).toString();
|
|
||||||
ctx.fillRect(0, 0, width, height);
|
|
||||||
ctx.fillStyle = _col.toString();
|
|
||||||
for (let i = 0; i < d.length; i++) {
|
|
||||||
const h = Math.max(1, height * d[i] / 100);
|
|
||||||
ctx.fillRect((30 - d.length + i) * bw, height - h, Math.max(1, bw - 0.5), h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Breakdown rows
|
// Breakdown rows
|
||||||
InfoRow { label: "Used"; value: root._fmt(root.usedGb) }
|
InfoRow {
|
||||||
InfoRow { label: "Cached"; value: root._fmt(root.cachedGb) }
|
label: "Used"
|
||||||
InfoRow { label: "Available"; value: root._fmt(root.availGb) }
|
value: root._fmt(root.usedGb)
|
||||||
InfoRow { label: "Total"; value: root._fmt(root.totalGb) }
|
}
|
||||||
|
InfoRow {
|
||||||
|
label: "Cached"
|
||||||
|
value: root._fmt(root.cachedGb)
|
||||||
|
}
|
||||||
|
InfoRow {
|
||||||
|
label: "Available"
|
||||||
|
value: root._fmt(root.availGb)
|
||||||
|
}
|
||||||
|
InfoRow {
|
||||||
|
label: "Total"
|
||||||
|
value: root._fmt(root.totalGb)
|
||||||
|
}
|
||||||
|
|
||||||
Separator {}
|
Separator {}
|
||||||
|
|
||||||
|
|
|
||||||
112
shell/applets/SparklineCanvas.qml
Normal file
112
shell/applets/SparklineCanvas.qml
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Canvas {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// Data - array of 0-100 percentage values
|
||||||
|
property var history: []
|
||||||
|
property color color: "white"
|
||||||
|
property bool active: true
|
||||||
|
|
||||||
|
// Max x-axis slots (0 = use history.length, right-aligns shorter data)
|
||||||
|
property int maxSamples: 0
|
||||||
|
|
||||||
|
// Optional background tint using `color` at low opacity
|
||||||
|
property real backgroundTint: 0 // 0 = off, e.g. 0.15 for memory, 0.08 for temperature
|
||||||
|
|
||||||
|
// Area chart mode (battery-style filled curve + stroke line)
|
||||||
|
property bool areaMode: false
|
||||||
|
property real areaOpacity: 0.18
|
||||||
|
property real lineWidth: 1.5
|
||||||
|
|
||||||
|
// Per-bar color override: function(value) => color. Null = uniform `color`.
|
||||||
|
property var colorFunction: null
|
||||||
|
|
||||||
|
// Horizontal threshold lines: [{value: 70, color: "#..."}]
|
||||||
|
property var thresholds: []
|
||||||
|
|
||||||
|
onHistoryChanged: if (root.active)
|
||||||
|
requestPaint()
|
||||||
|
onColorChanged: if (root.active)
|
||||||
|
requestPaint()
|
||||||
|
|
||||||
|
onVisibleChanged: if (visible)
|
||||||
|
requestPaint()
|
||||||
|
|
||||||
|
onPaint: {
|
||||||
|
const ctx = getContext("2d");
|
||||||
|
if (!ctx)
|
||||||
|
return;
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
const d = history;
|
||||||
|
if (!d.length || (areaMode && d.length < 2))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const samples = maxSamples > 0 ? maxSamples : d.length;
|
||||||
|
const step = width / samples;
|
||||||
|
const offset = samples - d.length;
|
||||||
|
|
||||||
|
// Background tint
|
||||||
|
if (backgroundTint > 0) {
|
||||||
|
ctx.fillStyle = Qt.rgba(color.r, color.g, color.b, backgroundTint).toString();
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Threshold lines
|
||||||
|
if (thresholds.length) {
|
||||||
|
ctx.globalAlpha = 0.3;
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.setLineDash([3, 3]);
|
||||||
|
for (let t = 0; t < thresholds.length; t++) {
|
||||||
|
const th = thresholds[t];
|
||||||
|
const ty = height - height * (th.value / 100);
|
||||||
|
ctx.strokeStyle = th.color.toString();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, ty);
|
||||||
|
ctx.lineTo(width, ty);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
ctx.globalAlpha = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (areaMode) {
|
||||||
|
// Filled area under curve
|
||||||
|
const xOff = offset * step;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(xOff, height);
|
||||||
|
for (let i = 0; i < d.length; i++) {
|
||||||
|
ctx.lineTo(xOff + i * step, height - height * (d[i] / 100));
|
||||||
|
}
|
||||||
|
ctx.lineTo(xOff + (d.length - 1) * step, height);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fillStyle = Qt.rgba(color.r, color.g, color.b, areaOpacity).toString();
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Top stroke
|
||||||
|
ctx.beginPath();
|
||||||
|
for (let i = 0; i < d.length; i++) {
|
||||||
|
const x = xOff + i * step;
|
||||||
|
const y = height - height * (d[i] / 100);
|
||||||
|
if (i === 0)
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
else
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
ctx.strokeStyle = color.toString();
|
||||||
|
ctx.lineWidth = root.lineWidth;
|
||||||
|
ctx.stroke();
|
||||||
|
} else {
|
||||||
|
// Bar chart
|
||||||
|
const hasCF = typeof colorFunction === "function";
|
||||||
|
if (!hasCF)
|
||||||
|
ctx.fillStyle = color.toString();
|
||||||
|
for (let i = 0; i < d.length; i++) {
|
||||||
|
const barH = Math.max(1, height * d[i] / 100);
|
||||||
|
if (hasCF)
|
||||||
|
ctx.fillStyle = colorFunction(d[i]).toString();
|
||||||
|
ctx.fillRect((offset + i) * step, height - barH, Math.max(1, step - 0.5), barH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -104,72 +104,28 @@ Column {
|
||||||
}
|
}
|
||||||
|
|
||||||
// History sparkline
|
// History sparkline
|
||||||
Canvas {
|
SparklineCanvas {
|
||||||
id: _sparkline
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 12
|
anchors.leftMargin: 12
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 12
|
anchors.rightMargin: 12
|
||||||
height: 40
|
height: 40
|
||||||
|
history: root.history
|
||||||
property var _hist: root.history
|
color: root.stateColor
|
||||||
property color _col: root.stateColor
|
active: root.active
|
||||||
|
maxSamples: 150
|
||||||
on_HistChanged: if (root.active)
|
backgroundTint: 0.08
|
||||||
requestPaint()
|
thresholds: [
|
||||||
on_ColChanged: if (root.active)
|
{
|
||||||
requestPaint()
|
value: root.warm,
|
||||||
|
color: S.Theme.base0A
|
||||||
onVisibleChanged: if (visible)
|
},
|
||||||
requestPaint()
|
{
|
||||||
|
value: root.hot,
|
||||||
onPaint: {
|
color: S.Theme.base08
|
||||||
const ctx = getContext("2d");
|
|
||||||
if (!ctx)
|
|
||||||
return;
|
|
||||||
ctx.clearRect(0, 0, width, height);
|
|
||||||
const d = _hist;
|
|
||||||
if (!d.length)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const maxSamples = 150;
|
|
||||||
const bw = width / maxSamples;
|
|
||||||
|
|
||||||
// Background tint
|
|
||||||
ctx.fillStyle = Qt.rgba(_col.r, _col.g, _col.b, 0.08).toString();
|
|
||||||
ctx.fillRect(0, 0, width, height);
|
|
||||||
|
|
||||||
// Warm threshold line
|
|
||||||
const warmY = height - height * (root.warm / 100);
|
|
||||||
ctx.strokeStyle = S.Theme.base0A.toString();
|
|
||||||
ctx.globalAlpha = 0.3;
|
|
||||||
ctx.lineWidth = 1;
|
|
||||||
ctx.setLineDash([3, 3]);
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(0, warmY);
|
|
||||||
ctx.lineTo(width, warmY);
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
// Hot threshold line
|
|
||||||
const hotY = height - height * (root.hot / 100);
|
|
||||||
ctx.strokeStyle = S.Theme.base08.toString();
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(0, hotY);
|
|
||||||
ctx.lineTo(width, hotY);
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
ctx.setLineDash([]);
|
|
||||||
ctx.globalAlpha = 1.0;
|
|
||||||
|
|
||||||
// Bars
|
|
||||||
const offset = maxSamples - d.length;
|
|
||||||
for (let i = 0; i < d.length; i++) {
|
|
||||||
const barH = Math.max(1, height * d[i] / 100);
|
|
||||||
const barColor = d[i] > root.hot ? S.Theme.base08 : d[i] > root.warm ? S.Theme.base0A : _col;
|
|
||||||
ctx.fillStyle = barColor.toString();
|
|
||||||
ctx.fillRect((offset + i) * bw, height - barH, Math.max(1, bw - 0.5), barH);
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
|
colorFunction: v => v > root.hot ? S.Theme.base08 : v > root.warm ? S.Theme.base0A : root.stateColor
|
||||||
}
|
}
|
||||||
|
|
||||||
// Threshold labels
|
// Threshold labels
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,9 @@ InfoRow 1.0 InfoRow.qml
|
||||||
MemoryApplet 1.0 MemoryApplet.qml
|
MemoryApplet 1.0 MemoryApplet.qml
|
||||||
MprisApplet 1.0 MprisApplet.qml
|
MprisApplet 1.0 MprisApplet.qml
|
||||||
NetworkApplet 1.0 NetworkApplet.qml
|
NetworkApplet 1.0 NetworkApplet.qml
|
||||||
Separator 1.0 Separator.qml
|
|
||||||
NotifApplet 1.0 NotifApplet.qml
|
NotifApplet 1.0 NotifApplet.qml
|
||||||
|
Separator 1.0 Separator.qml
|
||||||
|
SparklineCanvas 1.0 SparklineCanvas.qml
|
||||||
TemperatureApplet 1.0 TemperatureApplet.qml
|
TemperatureApplet 1.0 TemperatureApplet.qml
|
||||||
VolumeApplet 1.0 VolumeApplet.qml
|
VolumeApplet 1.0 VolumeApplet.qml
|
||||||
WeatherApplet 1.0 WeatherApplet.qml
|
WeatherApplet 1.0 WeatherApplet.qml
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ shell/applets/NetworkApplet.qml: Unqualified access [unqualified]
|
||||||
shell/applets/NotifApplet.qml: Member "_notif" not found on type "QQuickItem" [missing-property]
|
shell/applets/NotifApplet.qml: Member "_notif" not found on type "QQuickItem" [missing-property]
|
||||||
shell/applets/NotifApplet.qml: Member "_type" not found on type "QQuickItem" [missing-property]
|
shell/applets/NotifApplet.qml: Member "_type" not found on type "QQuickItem" [missing-property]
|
||||||
shell/applets/NotifApplet.qml: Unqualified access [unqualified]
|
shell/applets/NotifApplet.qml: Unqualified access [unqualified]
|
||||||
|
shell/applets/SparklineCanvas.qml: Property "colorFunction" is a var property. It may or may not be a method. Use a regular function instead. [use-proper-function]
|
||||||
shell/applets/TemperatureApplet.qml: Unqualified access [unqualified]
|
shell/applets/TemperatureApplet.qml: Unqualified access [unqualified]
|
||||||
shell/applets/VolumeApplet.qml: Unqualified access [unqualified]
|
shell/applets/VolumeApplet.qml: Unqualified access [unqualified]
|
||||||
shell/lock/Lock.qml: Unqualified access [unqualified]
|
shell/lock/Lock.qml: Unqualified access [unqualified]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue