nova-shell/shell/applets/TemperatureApplet.qml

265 lines
7.8 KiB
QML

import QtQuick
import "../services" as S
Column {
id: root
required property int temp
required property int warm
required property int hot
required property var history
required property var devices
required property color accentColor
property bool active: true
property string deviceFilter: ""
property color stateColor: temp > hot ? S.Theme.base08 : temp > warm ? S.Theme.base0A : root.accentColor
Behavior on stateColor {
ColorAnimation {
duration: 300
}
}
function _tempColor(celsius) {
const t = Math.max(0, Math.min(100, celsius)) / 100;
const a = t < 0.5 ? S.Theme.base0B : S.Theme.base0A;
const b = t < 0.5 ? S.Theme.base0A : S.Theme.base08;
const u = t < 0.5 ? t * 2 : (t - 0.5) * 2;
return Qt.rgba(a.r + (b.r - a.r) * u, a.g + (b.g - a.g) * u, a.b + (b.b - a.b) * u, 1);
}
// Header - current temp
Item {
width: root.width
height: 28
Text {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: root.temp + "\u00B0C"
color: root.stateColor
font.pixelSize: S.Theme.fontSize
font.family: S.Theme.fontFamily
font.bold: true
width: _tempSizer.implicitWidth
horizontalAlignment: Text.AlignRight
Text {
id: _tempSizer
visible: false
text: "100\u00B0C"
font: parent.font
}
}
}
// Gauge bar (0-100C), with warm/hot threshold markers
Item {
width: root.width
height: 16
Item {
id: _gaugeBar
anchors.left: parent.left
anchors.leftMargin: 12
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
height: 6
Rectangle {
anchors.fill: parent
color: S.Theme.base02
radius: 3
}
Rectangle {
width: parent.width * Math.min(1, root.temp / 100)
height: parent.height
color: root.stateColor
radius: 3
Behavior on width {
enabled: root.active
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
}
// Warm threshold marker
Rectangle {
x: parent.width * (root.warm / 100) - 1
width: 1
height: parent.height + 4
anchors.verticalCenter: parent.verticalCenter
color: S.Theme.base0A
opacity: 0.6
}
// Hot threshold marker
Rectangle {
x: parent.width * (root.hot / 100) - 1
width: 1
height: parent.height + 4
anchors.verticalCenter: parent.verticalCenter
color: S.Theme.base08
opacity: 0.6
}
}
}
// History sparkline
Canvas {
id: _sparkline
anchors.left: parent.left
anchors.leftMargin: 12
anchors.right: parent.right
anchors.rightMargin: 12
height: 40
property var _hist: root.history
property color _col: root.stateColor
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 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);
}
}
}
// Threshold labels
Item {
width: root.width
height: 16
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "warm " + root.warm + "\u00B0 hot " + root.hot + "\u00B0"
color: S.Theme.base03
font.pixelSize: S.Theme.fontSize - 3
font.family: S.Theme.fontFamily
font.letterSpacing: 0.5
}
Text {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "10 min"
color: S.Theme.base03
font.pixelSize: S.Theme.fontSize - 3
font.family: S.Theme.fontFamily
}
}
// Per-device breakdown
Rectangle {
width: root.width - 16
height: 1
anchors.horizontalCenter: parent.horizontalCenter
color: S.Theme.base03
visible: root.devices.length > 0
}
Repeater {
model: root.devices
delegate: Item {
required property var modelData
width: root.width
height: 22
readonly property bool _isActive: root.deviceFilter === modelData.name
Rectangle {
anchors.fill: parent
anchors.leftMargin: 8
anchors.rightMargin: 8
color: _isActive ? Qt.rgba(root.accentColor.r, root.accentColor.g, root.accentColor.b, 0.12) : "transparent"
radius: 3
}
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: modelData.name
color: _isActive ? root.accentColor : S.Theme.base04
font.pixelSize: S.Theme.fontSize - 2
font.family: S.Theme.fontFamily
elide: Text.ElideRight
width: parent.width - 80
}
Text {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: modelData.celsius + "\u00B0C"
color: root._tempColor(modelData.celsius)
font.pixelSize: S.Theme.fontSize - 2
font.family: S.Theme.fontFamily
font.bold: _isActive
}
}
}
Item {
width: 1
height: 4
}
}