523 lines
17 KiB
QML
523 lines
17 KiB
QML
import QtQuick
|
||
import QtQuick.Controls
|
||
import QtQuick.Controls.impl
|
||
import QtQuick.Layouts
|
||
import ISC
|
||
|
||
// ===== Readout Panel =====
|
||
// Right-rail panel with three bordered sections: READOUT, DISPLAY, THRESHOLDS.
|
||
// Cards match the left-rail SOURCE / CONNECTION FLOW style.
|
||
ColumnLayout {
|
||
spacing: 6
|
||
property var s: streamController.stats
|
||
property alias showLabels: labelsToggle.checked
|
||
property alias heatmapBlend: heatmapSlider.value
|
||
property alias showThickness: thicknessToggle.checked
|
||
|
||
signal exportRequested(string filePath)
|
||
|
||
component PanelCheckBox: CheckBox {
|
||
id: toggle
|
||
indicator: Rectangle {
|
||
implicitWidth: 18
|
||
implicitHeight: 18
|
||
x: toggle.leftPadding
|
||
y: parent.height / 2 - height / 2
|
||
radius: Theme.radiusXs
|
||
color: toggle.checked ? Theme.primaryAccent : "transparent"
|
||
border.width: Theme.borderThin
|
||
border.color: toggle.checked ? Theme.primaryAccent : Theme.fieldBorder
|
||
|
||
Behavior on color {
|
||
ColorAnimation { duration: Theme.durationFast }
|
||
}
|
||
Behavior on border.color {
|
||
ColorAnimation { duration: Theme.durationFast }
|
||
}
|
||
|
||
IconImage {
|
||
anchors.centerIn: parent
|
||
source: "../icons/check.svg"
|
||
width: 12; height: 12
|
||
sourceSize.width: 12
|
||
sourceSize.height: 12
|
||
color: Theme.panelBackground
|
||
opacity: toggle.checked ? 1.0 : 0.0
|
||
Behavior on opacity {
|
||
NumberAnimation { duration: Theme.durationFast }
|
||
}
|
||
}
|
||
}
|
||
|
||
contentItem: Text {
|
||
text: toggle.text
|
||
color: Theme.headingColor
|
||
font.family: Theme.uiFontFamily
|
||
verticalAlignment: Text.AlignVCenter
|
||
leftPadding: toggle.indicator.width + toggle.spacing
|
||
font.pixelSize: toggle.font.pixelSize || Theme.fontXs
|
||
}
|
||
}
|
||
|
||
component BadgePill: Rectangle {
|
||
implicitWidth: badgeLabel.implicitWidth + 10
|
||
implicitHeight: 10
|
||
radius: 9
|
||
color: Theme.fieldBackground
|
||
border.color: Theme.cardBorder
|
||
border.width: 1
|
||
|
||
property alias text: badgeLabel.text
|
||
Label {
|
||
id: badgeLabel
|
||
anchors.centerIn: parent
|
||
color: Theme.bodyColor
|
||
font.pixelSize: Theme.font2xs
|
||
font.weight: Font.Medium
|
||
}
|
||
}
|
||
|
||
StreamControlPanel {
|
||
Layout.fillWidth: true
|
||
onExportRequested: function(filePath) {
|
||
root.exportRequested(filePath);
|
||
}
|
||
}
|
||
|
||
Rectangle {
|
||
Layout.fillWidth: true
|
||
implicitHeight: readoutCard.implicitHeight + 24
|
||
color: Theme.sidePanelBackground
|
||
radius: Theme.sidePanelRadius
|
||
border.color: Theme.sideBorder
|
||
border.width: 1
|
||
|
||
ColumnLayout {
|
||
id: readoutCard
|
||
anchors {
|
||
left: parent.left
|
||
right: parent.right
|
||
top: parent.top
|
||
topMargin: 12
|
||
}
|
||
spacing: 0
|
||
|
||
// Header Inside the Box
|
||
Label {
|
||
text: "READOUT"
|
||
color: Theme.sideMutedText
|
||
font.family: Theme.uiFontFamily
|
||
font.pixelSize: Theme.fontSm
|
||
font.letterSpacing: 1.5
|
||
font.bold: true
|
||
Layout.leftMargin: 14
|
||
Layout.rightMargin: 14
|
||
Layout.topMargin: 2
|
||
Layout.bottomMargin: 10
|
||
}
|
||
|
||
// Min Temp Row
|
||
RowLayout {
|
||
Layout.fillWidth: true
|
||
Layout.leftMargin: 14
|
||
Layout.rightMargin: 14
|
||
Layout.preferredHeight: 32
|
||
Label {
|
||
text: "MIN TEMP"
|
||
color: Theme.sideMutedText
|
||
font.family: Theme.uiFontFamily
|
||
font.pixelSize: Theme.fontSm
|
||
font.bold: true
|
||
}
|
||
Item { Layout.fillWidth: true }
|
||
RowLayout {
|
||
spacing: 4
|
||
Label {
|
||
text: s.min !== undefined ? s.min : "—"
|
||
color: Theme.sensorLow
|
||
font.family: Theme.codeFontFamily
|
||
font.pixelSize: Theme.fontLg
|
||
font.bold: true
|
||
}
|
||
Label {
|
||
visible: (s.minIndex !== undefined) && s.minIndex >= 0
|
||
text: "#" + (s.minIndex !== undefined ? s.minIndex : "")
|
||
color: Theme.sideMutedText
|
||
font.family: Theme.uiFontFamily
|
||
font.pixelSize: Theme.font2xs
|
||
Layout.alignment: Qt.AlignBottom
|
||
bottomPadding: 3
|
||
}
|
||
}
|
||
}
|
||
|
||
Rectangle {
|
||
Layout.fillWidth: true
|
||
height: 1
|
||
color: Theme.cardBorder
|
||
}
|
||
|
||
// Max Temp Row
|
||
RowLayout {
|
||
Layout.fillWidth: true
|
||
Layout.leftMargin: 14
|
||
Layout.rightMargin: 14
|
||
Layout.preferredHeight: 32
|
||
Label {
|
||
text: "MAX TEMP"
|
||
color: Theme.sideMutedText
|
||
font.family: Theme.uiFontFamily
|
||
font.pixelSize: Theme.fontSm
|
||
font.bold: true
|
||
}
|
||
Item { Layout.fillWidth: true }
|
||
RowLayout {
|
||
spacing: 4
|
||
Label {
|
||
text: s.max !== undefined ? s.max : "—"
|
||
color: Theme.sensorHigh
|
||
font.family: Theme.codeFontFamily
|
||
font.pixelSize: Theme.fontLg
|
||
font.bold: true
|
||
}
|
||
Label {
|
||
visible: (s.maxIndex !== undefined) && s.maxIndex >= 0
|
||
text: "#" + (s.maxIndex !== undefined ? s.maxIndex : "")
|
||
color: Theme.sideMutedText
|
||
font.family: Theme.uiFontFamily
|
||
font.pixelSize: Theme.font2xs
|
||
Layout.alignment: Qt.AlignBottom
|
||
bottomPadding: 3
|
||
}
|
||
}
|
||
}
|
||
|
||
Rectangle {
|
||
Layout.fillWidth: true
|
||
height: 1
|
||
color: Theme.cardBorder
|
||
}
|
||
|
||
// Differential Row
|
||
RowLayout {
|
||
Layout.fillWidth: true
|
||
Layout.leftMargin: 14
|
||
Layout.rightMargin: 14
|
||
Layout.preferredHeight: 32
|
||
Label {
|
||
text: "DIFF"
|
||
color: Theme.sideMutedText
|
||
font.family: Theme.uiFontFamily
|
||
font.pixelSize: Theme.fontSm
|
||
font.bold: true
|
||
}
|
||
Item { Layout.fillWidth: true }
|
||
Label {
|
||
text: s.diff !== undefined ? s.diff : "—"
|
||
color: Theme.diffAccent
|
||
font.family: Theme.codeFontFamily
|
||
font.pixelSize: Theme.fontLg
|
||
font.bold: true
|
||
}
|
||
}
|
||
|
||
Rectangle {
|
||
Layout.fillWidth: true
|
||
height: 1
|
||
color: Theme.cardBorder
|
||
}
|
||
|
||
// Average Row
|
||
RowLayout {
|
||
Layout.fillWidth: true
|
||
Layout.leftMargin: 14
|
||
Layout.rightMargin: 14
|
||
Layout.preferredHeight: 32
|
||
Label {
|
||
text: "AVERAGE"
|
||
color: Theme.sideMutedText
|
||
font.family: Theme.uiFontFamily
|
||
font.pixelSize: Theme.fontSm
|
||
font.bold: true
|
||
}
|
||
Item { Layout.fillWidth: true }
|
||
Label {
|
||
text: s.avg !== undefined ? s.avg : "—"
|
||
color: Theme.headingColor
|
||
font.family: Theme.codeFontFamily
|
||
font.pixelSize: Theme.fontLg
|
||
font.bold: true
|
||
}
|
||
}
|
||
|
||
Rectangle {
|
||
Layout.fillWidth: true
|
||
height: 1
|
||
color: Theme.cardBorder
|
||
}
|
||
|
||
// Sigma Row
|
||
RowLayout {
|
||
Layout.fillWidth: true
|
||
Layout.leftMargin: 14
|
||
Layout.rightMargin: 14
|
||
Layout.preferredHeight: 32
|
||
Label {
|
||
text: "SIGMA (Σ)"
|
||
color: Theme.sideMutedText
|
||
font.family: Theme.uiFontFamily
|
||
font.pixelSize: Theme.fontSm
|
||
font.bold: true
|
||
}
|
||
Item { Layout.fillWidth: true }
|
||
Label {
|
||
text: s.sigma !== undefined ? s.sigma : "—"
|
||
color: Theme.headingColor
|
||
font.family: Theme.codeFontFamily
|
||
font.pixelSize: Theme.fontLg
|
||
font.bold: true
|
||
}
|
||
}
|
||
|
||
Rectangle {
|
||
Layout.fillWidth: true
|
||
height: 1
|
||
color: Theme.cardBorder
|
||
}
|
||
|
||
// 3-Sigma Row
|
||
RowLayout {
|
||
Layout.fillWidth: true
|
||
Layout.leftMargin: 14
|
||
Layout.rightMargin: 14
|
||
Layout.preferredHeight: 32
|
||
Label {
|
||
text: "3Σ VALUE"
|
||
color: Theme.sideMutedText
|
||
font.family: Theme.uiFontFamily
|
||
font.pixelSize: Theme.fontSm
|
||
font.bold: true
|
||
}
|
||
Item { Layout.fillWidth: true }
|
||
Label {
|
||
text: s.threeSigma !== undefined ? s.threeSigma : "—"
|
||
color: Theme.headingColor
|
||
font.family: Theme.codeFontFamily
|
||
font.pixelSize: Theme.fontLg
|
||
font.bold: true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Rectangle {
|
||
Layout.fillWidth: true
|
||
implicitHeight: displayCard.implicitHeight + 24
|
||
color: Theme.sidePanelBackground
|
||
radius: Theme.sidePanelRadius
|
||
border.color: Theme.sideBorder
|
||
border.width: 1
|
||
|
||
ColumnLayout {
|
||
id: displayCard
|
||
anchors {
|
||
left: parent.left
|
||
right: parent.right
|
||
top: parent.top
|
||
margins: 12
|
||
}
|
||
spacing: 8
|
||
|
||
Label {
|
||
text: "DISPLAY"
|
||
color: Theme.sideMutedText
|
||
font.family: Theme.uiFontFamily
|
||
font.pixelSize: Theme.fontSm
|
||
font.letterSpacing: 1.5
|
||
font.bold: true
|
||
Layout.bottomMargin: 4
|
||
}
|
||
|
||
PanelCheckBox {
|
||
id: thicknessToggle
|
||
text: "Show Thickness"
|
||
checked: false
|
||
font.pixelSize: Theme.fontSm
|
||
}
|
||
|
||
PanelCheckBox {
|
||
id: labelsToggle
|
||
text: "Labels"
|
||
checked: true
|
||
font.pixelSize: Theme.fontSm
|
||
}
|
||
|
||
PanelCheckBox {
|
||
id: clusterAverageToggle
|
||
text: "Average Clusters"
|
||
checked: streamController.clusterAveragingEnabled
|
||
font.pixelSize: Theme.fontSm
|
||
onCheckedChanged: streamController.clusterAveragingEnabled = checked
|
||
}
|
||
|
||
Rectangle {
|
||
Layout.fillWidth: true
|
||
height: 1
|
||
color: Theme.cardBorder
|
||
}
|
||
|
||
RowLayout {
|
||
Layout.fillWidth: true
|
||
spacing: 6
|
||
Label {
|
||
text: "Heatmap"
|
||
color: Theme.bodyColor
|
||
font.pixelSize: Theme.fontSm
|
||
Layout.preferredWidth: 52
|
||
}
|
||
Slider {
|
||
id: heatmapSlider
|
||
from: 0
|
||
to: 1
|
||
value: 0
|
||
Layout.fillWidth: true
|
||
|
||
AppToolTip {
|
||
visible: heatmapSlider.hovered
|
||
text: Math.round(heatmapSlider.value * 100) + "%"
|
||
}
|
||
|
||
handle: Rectangle {
|
||
x: heatmapSlider.leftPadding + heatmapSlider.visualPosition * (heatmapSlider.availableWidth - width)
|
||
y: heatmapSlider.topPadding + heatmapSlider.availableHeight / 2 - height / 2
|
||
implicitWidth: 18
|
||
implicitHeight: 18
|
||
radius: 9
|
||
color: Theme.primaryAccent
|
||
scale: heatmapSlider.pressed ? 1.15 : 1.0
|
||
Behavior on scale {
|
||
NumberAnimation { duration: Theme.durationFast; easing.type: Theme.easeStandard }
|
||
}
|
||
}
|
||
}
|
||
Label {
|
||
text: Math.round(heatmapSlider.value * 100) + "%"
|
||
color: Theme.bodyColor
|
||
font.pixelSize: Theme.fontSm
|
||
font.weight: Font.Medium
|
||
Layout.preferredWidth: 32
|
||
horizontalAlignment: Text.AlignRight
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Rectangle {
|
||
Layout.fillWidth: true
|
||
implicitHeight: thresholdCard.implicitHeight + 24
|
||
color: Theme.sidePanelBackground
|
||
radius: Theme.sidePanelRadius
|
||
border.color: Theme.sideBorder
|
||
border.width: 1
|
||
|
||
ColumnLayout {
|
||
id: thresholdCard
|
||
anchors {
|
||
left: parent.left
|
||
right: parent.right
|
||
top: parent.top
|
||
margins: 12
|
||
}
|
||
spacing: 6
|
||
|
||
Label {
|
||
text: "THRESHOLDS"
|
||
color: Theme.sideMutedText
|
||
font.family: Theme.uiFontFamily
|
||
font.pixelSize: Theme.fontSm
|
||
font.letterSpacing: 1.5
|
||
font.bold: true
|
||
Layout.bottomMargin: 4
|
||
}
|
||
|
||
Label {
|
||
text: "Set Point (°C)"
|
||
color: Theme.bodyColor
|
||
font.pixelSize: Theme.fontSm
|
||
}
|
||
TextField {
|
||
id: spField
|
||
Layout.fillWidth: true
|
||
text: "149.0"
|
||
font.pixelSize: Theme.fontSm
|
||
inputMethodHints: Qt.ImhFormattedNumbersOnly
|
||
color: Theme.fieldText
|
||
placeholderTextColor: Theme.fieldPlaceholder
|
||
selectedTextColor: Theme.fieldBackground
|
||
selectionColor: Theme.fieldText
|
||
background: Rectangle {
|
||
radius: Theme.radiusXs
|
||
color: Theme.fieldBackground
|
||
border.width: spField.activeFocus ? Theme.borderStrong : Theme.borderThin
|
||
border.color: spField.activeFocus ? Theme.fieldBorderFocus : Theme.fieldBorder
|
||
Behavior on color {
|
||
ColorAnimation { duration: Theme.durationFast }
|
||
}
|
||
Behavior on border.color {
|
||
ColorAnimation { duration: Theme.durationFast }
|
||
}
|
||
}
|
||
onEditingFinished: pushThresholds()
|
||
}
|
||
|
||
Label {
|
||
text: "Margin (±°C)"
|
||
color: Theme.bodyColor
|
||
font.pixelSize: Theme.fontSm
|
||
}
|
||
TextField {
|
||
id: mgField
|
||
Layout.fillWidth: true
|
||
text: "1.0"
|
||
font.pixelSize: Theme.fontSm
|
||
inputMethodHints: Qt.ImhFormattedNumbersOnly
|
||
color: Theme.fieldText
|
||
placeholderTextColor: Theme.fieldPlaceholder
|
||
selectedTextColor: Theme.fieldBackground
|
||
selectionColor: Theme.fieldText
|
||
background: Rectangle {
|
||
radius: Theme.radiusXs
|
||
color: Theme.fieldBackground
|
||
border.width: mgField.activeFocus ? Theme.borderStrong : Theme.borderThin
|
||
border.color: mgField.activeFocus ? Theme.fieldBorderFocus : Theme.fieldBorder
|
||
Behavior on color {
|
||
ColorAnimation { duration: Theme.durationFast }
|
||
}
|
||
Behavior on border.color {
|
||
ColorAnimation { duration: Theme.durationFast }
|
||
}
|
||
}
|
||
onEditingFinished: pushThresholds()
|
||
}
|
||
|
||
PanelCheckBox {
|
||
id: autoCheck
|
||
text: "Auto range (mean ± 1σ)"
|
||
checked: true
|
||
font.pixelSize: Theme.fontSm
|
||
onCheckedChanged: pushThresholds()
|
||
}
|
||
}
|
||
}
|
||
|
||
Item {
|
||
Layout.fillHeight: true
|
||
}
|
||
|
||
function pushThresholds() {
|
||
var sp = parseFloat(spField.text) || 149.0;
|
||
var mg = parseFloat(mgField.text) || 1.0;
|
||
streamController.setThresholds(sp, mg, autoCheck.checked);
|
||
}
|
||
}
|