diff --git a/modules/HoverPanel.qml b/modules/HoverPanel.qml index e930f67..efe889b 100644 --- a/modules/HoverPanel.qml +++ b/modules/HoverPanel.qml @@ -132,6 +132,7 @@ PanelWindow { } function dismiss() { + console.log("HoverPanel.dismiss ns=" + panelNamespace + " popup=" + popupMode + " grace=" + _grace + " showPanel=" + showPanel + " pinned=" + _pinned); _pinned = false; showAnim.stop(); hideAnim.start(); @@ -213,6 +214,7 @@ PanelWindow { onTapped: { const p = point.position; const pad = 8; + console.log("HoverPanel tap ns=" + root.panelNamespace + " at=(" + p.x.toFixed(0) + "," + p.y.toFixed(0) + ") container=(" + panelContainer.x + "," + panelContainer.y + " " + panelContainer.width + "x" + panelContainer.height + ")"); if (p.x < panelContainer.x - pad || p.x > panelContainer.x + panelContainer.width + pad || p.y < panelContainer.y - pad || p.y > panelContainer.y + panelContainer.height + pad) root.dismiss(); } diff --git a/modules/NotifCenter.qml b/modules/NotifCenter.qml index 7c3f2f8..f89a2e7 100644 --- a/modules/NotifCenter.qml +++ b/modules/NotifCenter.qml @@ -68,73 +68,25 @@ M.HoverPanel { property var _pendingDismissIds: [] - // Group notifications by appName, sorted by max urgency desc then most recent time desc - readonly property var _groups: { - const map = {}; - for (const n of M.NotifService.list) { - const key = n.appName || ""; - if (!map[key]) - map[key] = { - appName: key, - appIcon: n.appIcon, - notifs: [], - maxUrgency: 0, - maxTime: 0 - }; - map[key].notifs.push(n); - if (n.urgency > map[key].maxUrgency) - map[key].maxUrgency = n.urgency; - if (n.time > map[key].maxTime) - map[key].maxTime = n.time; - } - return Object.values(map).sort((a, b) => { - if (b.maxUrgency !== a.maxUrgency) - return b.maxUrgency - a.maxUrgency; - return b.maxTime - a.maxTime; - }); - } + function _cascadeDismiss() { + const list = M.NotifService.list; + if (list.length === 0) + return; - // Flat model: group header followed by its notifications - readonly property var _flatModel: { - const arr = []; - for (const g of _groups) { - arr.push({ - type: "header", - appName: g.appName, - appIcon: g.appIcon, - count: g.notifs.length - }); - for (const n of g.notifs) - arr.push({ - type: "notif", - data: n - }); - } - return arr; - } + _pendingDismissIds = list.map(n => n.id); - // Collect visible (non-dismissing) notif delegates, optionally filtered by appName - function _getVisibleNotifDelegates(appName) { - const result = []; - for (let i = 0; i < _flatModel.length; i++) { - const item = _flatModel[i]; - if (item.type !== "notif") - continue; - if (appName !== undefined && item.data.appName !== appName) - continue; + const visibles = []; + for (let i = 0; i < list.length; i++) { const d = notifList.itemAtIndex(i); - if (d && d._type === "notif" && d._notif?.state !== "dismissing") - result.push(d); + if (d && d.modelData?.state !== "dismissing") + visibles.push(d); } - return result; - } - function _startCascade(visibles, ids) { - _pendingDismissIds = ids; if (visibles.length === 0) { _finishCascade(); return; } + for (let i = 0; i < visibles.length; i++) { _cascadeTimer.createObject(menuWindow, { _target: visibles[i], @@ -144,20 +96,6 @@ M.HoverPanel { } } - function _cascadeDismiss() { - if (M.NotifService.list.length === 0) - return; - const ids = M.NotifService.list.map(n => n.id); - _startCascade(_getVisibleNotifDelegates(), ids); - } - - function _cascadeGroupDismiss(appName) { - const ids = M.NotifService.list.filter(n => n.appName === appName).map(n => n.id); - if (ids.length === 0) - return; - _startCascade(_getVisibleNotifDelegates(appName), ids); - } - function _finishCascade() { const ids = _pendingDismissIds; _pendingDismissIds = []; @@ -175,8 +113,10 @@ M.HoverPanel { onTriggered: { if (_target && _target.dismissVisualOnly) _target.dismissVisualOnly(); - if (_isLast) + if (_isLast) { + // Wait for the last animation to finish, then bulk dismiss _bulkTimer.createObject(menuWindow, {}); + } destroy(); } } @@ -208,36 +148,33 @@ M.HoverPanel { height: Math.min(contentHeight, 60 * (M.Modules.notifications.maxVisible || 10)) clip: true boundsBehavior: Flickable.StopAtBounds - model: menuWindow._flatModel + model: M.NotifService.list delegate: Item { - id: notifDelegate + id: notifItem required property var modelData required property int index - readonly property string _type: modelData.type - readonly property var _notif: _type === "notif" ? modelData.data : null - width: menuWindow.contentWidth height: _targetHeight * _heightScale - clip: true opacity: 0 + clip: true - readonly property real _targetHeight: _type === "header" ? 28 : (notifContent.implicitHeight + 12) + readonly property real _targetHeight: notifContent.height + 12 property real _heightScale: 1 property bool _skipDismiss: false function dismiss() { - if (_type !== "notif" || _notif.state === "dismissing") + if (notifItem.modelData.state === "dismissing") return; - _notif.beginDismiss(); + notifItem.modelData.beginDismiss(); _dismissAnim.start(); } function dismissVisualOnly() { - if (_type !== "notif" || _notif.state === "dismissing") + if (notifItem.modelData.state === "dismissing") return; - _notif.beginDismiss(); + notifItem.modelData.beginDismiss(); _skipDismiss = true; _dismissAnim.start(); } @@ -246,264 +183,194 @@ M.HoverPanel { NumberAnimation { id: fadeIn - target: notifDelegate + target: notifItem property: "opacity" to: 1 duration: 150 easing.type: Easing.OutCubic } - // ---- Group header ---- - Item { - visible: notifDelegate._type === "header" + Rectangle { anchors.fill: parent + anchors.leftMargin: 4 + anchors.rightMargin: 4 + color: notifArea.containsMouse ? M.Theme.base02 : "transparent" + radius: M.Theme.radius - Image { - id: _headerIcon + Rectangle { + visible: (notifItem.modelData.hints?.value ?? -1) >= 0 + anchors.top: parent.top + anchors.bottom: parent.bottom anchors.left: parent.left - anchors.leftMargin: 10 - anchors.verticalCenter: parent.verticalCenter - width: M.Theme.fontSize + 2 - height: M.Theme.fontSize + 2 - source: { - if (notifDelegate._type !== "header") - return ""; - const ic = notifDelegate.modelData.appIcon; - if (!ic) - return ""; - return ic.startsWith("/") ? ic : Quickshell.iconPath(ic, "dialog-information"); + width: parent.width * Math.min(1, Math.max(0, (notifItem.modelData.hints?.value ?? 0) / 100)) + color: M.Theme.base02 + radius: parent.radius + + Behavior on width { + NumberAnimation { + duration: 200 + } + } + } + } + + // Urgency accent + Rectangle { + anchors.left: parent.left + anchors.leftMargin: 4 + anchors.top: parent.top + anchors.topMargin: 4 + anchors.bottom: parent.bottom + anchors.bottomMargin: 4 + width: 2 + radius: 1 + color: { + const u = notifItem.modelData.urgency; + return u === NotificationUrgency.Critical ? M.Theme.base08 : u === NotificationUrgency.Low ? M.Theme.base04 : M.Theme.base0D; + } + } + + Image { + id: ncIcon + anchors.left: parent.left + anchors.leftMargin: 14 + anchors.top: parent.top + anchors.topMargin: 6 + width: 24 + height: 24 + source: { + const img = notifItem.modelData.image; + if (img) + return img; + const ic = notifItem.modelData.appIcon; + if (!ic) + return ""; + return ic.startsWith("/") ? ic : Quickshell.iconPath(ic, "dialog-information"); + } + visible: status === Image.Ready + fillMode: Image.PreserveAspectFit + sourceSize: Qt.size(24, 24) + asynchronous: true + } + + Column { + id: notifContent + anchors.left: ncIcon.visible ? ncIcon.right : parent.left + anchors.right: dismissBtn.left + anchors.top: parent.top + anchors.leftMargin: ncIcon.visible ? 6 : 14 + anchors.topMargin: 6 + spacing: 1 + + // App + time + Row { + width: parent.width + Text { + text: notifItem.modelData.appName || "Notification" + color: M.Theme.base04 + font.pixelSize: M.Theme.fontSize - 2 + font.family: M.Theme.fontFamily + elide: Text.ElideRight + width: parent.width - ageText.width - 4 + } + Text { + id: ageText + text: { + const diff = Math.floor((Date.now() - notifItem.modelData.time) / 60000); + if (diff < 1) + return "now"; + if (diff < 60) + return diff + "m"; + if (diff < 1440) + return Math.floor(diff / 60) + "h"; + return Math.floor(diff / 1440) + "d"; + } + color: M.Theme.base03 + font.pixelSize: M.Theme.fontSize - 2 + font.family: M.Theme.fontFamily } - visible: status === Image.Ready - fillMode: Image.PreserveAspectFit - sourceSize: Qt.size(M.Theme.fontSize + 2, M.Theme.fontSize + 2) - asynchronous: true } Text { - anchors.left: _headerIcon.visible ? _headerIcon.right : parent.left - anchors.leftMargin: _headerIcon.visible ? 6 : 10 - anchors.right: _groupDismissBtn.left - anchors.rightMargin: 6 - anchors.verticalCenter: parent.verticalCenter - text: notifDelegate._type === "header" ? (notifDelegate.modelData.appName || "Unknown") : "" + width: parent.width + text: notifItem.modelData.summary || "" color: M.Theme.base05 - font.pixelSize: M.Theme.fontSize - 1 + font.pixelSize: M.Theme.fontSize font.family: M.Theme.fontFamily font.bold: true elide: Text.ElideRight } Text { - id: _groupDismissBtn - anchors.right: parent.right - anchors.rightMargin: 10 - anchors.verticalCenter: parent.verticalCenter - text: "\uF1F8" - color: _groupDismissArea.containsMouse ? M.Theme.base08 : M.Theme.base04 + width: parent.width + text: notifItem.modelData.body || "" + color: M.Theme.base04 font.pixelSize: M.Theme.fontSize - 1 - font.family: M.Theme.iconFontFamily + font.family: M.Theme.fontFamily + wrapMode: Text.WordWrap + maximumLineCount: 2 + elide: Text.ElideRight + visible: text !== "" + } - MouseArea { - id: _groupDismissArea - anchors.fill: parent - anchors.margins: -4 - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - if (notifDelegate._type === "header") - menuWindow._cascadeGroupDismiss(notifDelegate.modelData.appName); + // Actions + Row { + spacing: 4 + visible: notifItem.modelData.actions && notifItem.modelData.actions.length > 0 + + Repeater { + model: notifItem.modelData.actions || [] + delegate: Rectangle { + required property var modelData + width: actText.implicitWidth + 10 + height: actText.implicitHeight + 4 + radius: M.Theme.radius + color: actArea.containsMouse ? M.Theme.base02 : "transparent" + border.color: M.Theme.base03 + border.width: 1 + + Text { + id: actText + anchors.centerIn: parent + text: parent.modelData.text + color: M.Theme.base0D + font.pixelSize: M.Theme.fontSize - 2 + font.family: M.Theme.fontFamily + } + MouseArea { + id: actArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + parent.modelData.invoke(); + M.NotifService.dismiss(notifItem.modelData.id); + } + } } } } } - // ---- Individual notification ---- - Item { - id: _notifRow - visible: notifDelegate._type === "notif" - anchors.fill: parent + // Dismiss button + Text { + id: dismissBtn + anchors.right: parent.right + anchors.rightMargin: 10 + anchors.top: parent.top + anchors.topMargin: 8 + text: "\uF00D" + color: dismissArea.containsMouse ? M.Theme.base08 : M.Theme.base03 + font.pixelSize: M.Theme.fontSize - 1 + font.family: M.Theme.iconFontFamily - // Hover + progress bar background - Rectangle { - anchors.fill: parent - anchors.leftMargin: 4 - anchors.rightMargin: 4 - color: notifArea.containsMouse ? M.Theme.base02 : "transparent" - radius: M.Theme.radius - - Rectangle { - visible: (notifDelegate._notif?.hints?.value ?? -1) >= 0 - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left - width: parent.width * Math.min(1, Math.max(0, (notifDelegate._notif?.hints?.value ?? 0) / 100)) - color: M.Theme.base02 - radius: parent.radius - - Behavior on width { - NumberAnimation { - duration: 200 - } - } - } - } - - // Urgency accent bar - Rectangle { - anchors.left: parent.left - anchors.leftMargin: 4 - anchors.top: parent.top - anchors.topMargin: 4 - anchors.bottom: parent.bottom - anchors.bottomMargin: 4 - width: 2 - radius: 1 - color: { - const u = notifDelegate._notif?.urgency ?? NotificationUrgency.Normal; - return u === NotificationUrgency.Critical ? M.Theme.base08 : u === NotificationUrgency.Low ? M.Theme.base04 : M.Theme.base0D; - } - } - - // App icon / image - Image { - id: ncIcon - anchors.left: parent.left - anchors.leftMargin: 14 - anchors.top: parent.top - anchors.topMargin: 6 - width: 24 - height: 24 - source: { - if (notifDelegate._type !== "notif") - return ""; - const img = notifDelegate._notif?.image; - if (img) - return img; - const ic = notifDelegate._notif?.appIcon; - if (!ic) - return ""; - return ic.startsWith("/") ? ic : Quickshell.iconPath(ic, "dialog-information"); - } - visible: status === Image.Ready - fillMode: Image.PreserveAspectFit - sourceSize: Qt.size(24, 24) - asynchronous: true - } - - Column { - id: notifContent - anchors.left: ncIcon.visible ? ncIcon.right : parent.left - anchors.right: dismissBtn.left - anchors.top: parent.top - anchors.leftMargin: ncIcon.visible ? 6 : 14 - anchors.topMargin: 6 - spacing: 1 - - // Summary + time on the same row - Row { - width: parent.width - - Text { - text: notifDelegate._notif?.summary ?? "" - color: M.Theme.base05 - font.pixelSize: M.Theme.fontSize - font.family: M.Theme.fontFamily - font.bold: true - elide: Text.ElideRight - width: parent.width - _notifTime.implicitWidth - 4 - } - - Text { - id: _notifTime - text: notifDelegate._notif?.timeStr ?? "" - color: M.Theme.base03 - font.pixelSize: M.Theme.fontSize - 2 - font.family: M.Theme.fontFamily - } - } - - Text { - width: parent.width - text: notifDelegate._notif?.body ?? "" - color: M.Theme.base04 - font.pixelSize: M.Theme.fontSize - 1 - font.family: M.Theme.fontFamily - wrapMode: Text.WordWrap - maximumLineCount: 2 - elide: Text.ElideRight - visible: text !== "" - } - - // Actions - Row { - spacing: 4 - visible: notifDelegate._notif?.actions && notifDelegate._notif.actions.length > 0 - - Repeater { - model: notifDelegate._notif?.actions ?? [] - delegate: Rectangle { - required property var modelData - width: actText.implicitWidth + 10 - height: actText.implicitHeight + 4 - radius: M.Theme.radius - color: actArea.containsMouse ? M.Theme.base02 : "transparent" - border.color: M.Theme.base03 - border.width: 1 - - Text { - id: actText - anchors.centerIn: parent - text: parent.modelData.text - color: M.Theme.base0D - font.pixelSize: M.Theme.fontSize - 2 - font.family: M.Theme.fontFamily - } - - MouseArea { - id: actArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - parent.modelData.invoke(); - M.NotifService.dismiss(notifDelegate._notif.id); - } - } - } - } - } - } - - // Per-notification dismiss button - Text { - id: dismissBtn - anchors.right: parent.right - anchors.rightMargin: 10 - anchors.top: parent.top - anchors.topMargin: 8 - text: "\uF00D" - color: dismissArea.containsMouse ? M.Theme.base08 : M.Theme.base03 - font.pixelSize: M.Theme.fontSize - 1 - font.family: M.Theme.iconFontFamily - - MouseArea { - id: dismissArea - anchors.fill: parent - anchors.margins: -4 - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: notifDelegate.dismiss() - } - } - - // Right-click to dismiss MouseArea { - id: notifArea + id: dismissArea anchors.fill: parent - z: -1 + anchors.margins: -4 hoverEnabled: true - acceptedButtons: Qt.RightButton - onClicked: notifDelegate.dismiss() + cursorShape: Qt.PointingHandCursor + onClicked: _dismissAnim.start() } } @@ -511,14 +378,14 @@ M.HoverPanel { id: _dismissAnim ParallelAnimation { NumberAnimation { - target: notifDelegate + target: notifItem property: "x" to: menuWindow.contentWidth duration: 200 easing.type: Easing.InCubic } NumberAnimation { - target: notifDelegate + target: notifItem property: "opacity" to: 0 duration: 200 @@ -526,7 +393,7 @@ M.HoverPanel { } } NumberAnimation { - target: notifDelegate + target: notifItem property: "_heightScale" to: 0 duration: 150 @@ -534,11 +401,20 @@ M.HoverPanel { } ScriptAction { script: { - if (notifDelegate._notif && !notifDelegate._skipDismiss) - M.NotifService.dismiss(notifDelegate._notif.id); + if (!notifItem._skipDismiss) + M.NotifService.dismiss(notifItem.modelData.id); } } } + + MouseArea { + id: notifArea + anchors.fill: parent + z: -1 + hoverEnabled: true + acceptedButtons: Qt.RightButton + onClicked: _dismissAnim.start() + } } } diff --git a/modules/SystemStats.qml b/modules/SystemStats.qml index 9ee7adf..3582a79 100644 --- a/modules/SystemStats.qml +++ b/modules/SystemStats.qml @@ -32,7 +32,6 @@ QtObject { // ── Temperature ────────────────────────────────────────────────────── property int tempCelsius: 0 - property var tempHistory: [] // 150 samples @ 4s each ≈ 10 min // ── Memory ─────────────────────────────────────────────────────────── property int memPercent: 0 @@ -84,8 +83,6 @@ QtObject { } } else if (ev.type === "temp") { root.tempCelsius = ev.celsius; - const th = root.tempHistory.concat([ev.celsius]); - root.tempHistory = th.length > 150 ? th.slice(th.length - 150) : th; } else if (ev.type === "mem") { root.memPercent = ev.percent; root.memUsedGb = ev.used_gb; diff --git a/modules/Temperature.qml b/modules/Temperature.qml index 0106632..d6f9ade 100644 --- a/modules/Temperature.qml +++ b/modules/Temperature.qml @@ -1,265 +1,27 @@ import QtQuick -import Quickshell import "." as M M.BarSection { id: root spacing: Math.max(1, M.Theme.moduleSpacing - 2) - tooltip: "" + tooltip: "Temperature: " + M.SystemStats.tempCelsius + "\u00B0C" - readonly property int _warm: M.Modules.temperature.warm || 80 - readonly property int _hot: M.Modules.temperature.hot || 90 - readonly property int _temp: M.SystemStats.tempCelsius - - property color _stateColor: _temp > _hot ? M.Theme.base08 : _temp > _warm ? M.Theme.base0A : root.accentColor + property color _stateColor: M.SystemStats.tempCelsius > (M.Modules.temperature.hot || 80) ? M.Theme.base09 : M.SystemStats.tempCelsius > (M.Modules.temperature.warm || 60) ? M.Theme.base0A : root.accentColor Behavior on _stateColor { ColorAnimation { duration: 300 } } - property bool _pinned: false - readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered - readonly property bool _showPanel: _anyHover || _pinned - - on_AnyHoverChanged: { - if (_anyHover) - _unpinTimer.stop(); - else if (_pinned) - _unpinTimer.start(); - } - - Timer { - id: _unpinTimer - interval: 500 - onTriggered: root._pinned = false - } - - // Returns a color interpolated green→yellow→red for a given celsius value - function _tempColor(celsius) { - const t = Math.max(0, Math.min(100, celsius)) / 100; - const a = t < 0.5 ? M.Theme.base0B : M.Theme.base0A; - const b = t < 0.5 ? M.Theme.base0A : M.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); - } - M.BarIcon { icon: "\uF2C9" color: root._stateColor anchors.verticalCenter: parent.verticalCenter - TapHandler { - cursorShape: Qt.PointingHandCursor - onTapped: root._pinned = !root._pinned - } } M.BarLabel { - label: root._temp + "\u00B0C" + label: M.SystemStats.tempCelsius + "\u00B0C" minText: "100\u00B0C" color: root._stateColor anchors.verticalCenter: parent.verticalCenter - TapHandler { - cursorShape: Qt.PointingHandCursor - onTapped: root._pinned = !root._pinned - } - } - - M.HoverPanel { - id: hoverPanel - showPanel: root._showPanel - screen: QsWindow.window?.screen ?? null - anchorItem: root - accentColor: root.accentColor - panelNamespace: "nova-temperature" - panelTitle: "Temperature" - contentWidth: 220 - - // Header — current temp - Item { - width: parent.width - height: 28 - - Text { - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.verticalCenter: parent.verticalCenter - text: root._temp + "\u00B0C" - color: root._stateColor - font.pixelSize: M.Theme.fontSize - font.family: M.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–100°C), with warm/hot threshold markers - Item { - width: parent.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: M.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._showPanel - 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: M.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: M.Theme.base08 - opacity: 0.6 - } - } - } - - // History sparkline (~10 min @ 4s per sample) - Canvas { - id: _sparkline - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.right: parent.right - anchors.rightMargin: 12 - height: 40 - - property var _hist: M.SystemStats.tempHistory - property color _col: root._stateColor - - on_HistChanged: if (root._showPanel) - requestPaint() - on_ColChanged: if (root._showPanel) - requestPaint() - - Connections { - target: root - function on_ShowPanelChanged() { - if (root._showPanel) - _sparkline.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 = M.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 = M.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 ? M.Theme.base08 : d[i] > root._warm ? M.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: parent.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: M.Theme.base03 - font.pixelSize: M.Theme.fontSize - 3 - font.family: M.Theme.fontFamily - font.letterSpacing: 0.5 - } - - Text { - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.verticalCenter: parent.verticalCenter - text: "10 min" - color: M.Theme.base03 - font.pixelSize: M.Theme.fontSize - 3 - font.family: M.Theme.fontFamily - } - } - - Item { - width: 1 - height: 4 - } } }