Files
pyGUI/src/pygui/ISC/Tabs/components/ReadoutPanel.qml
T
jack 35cee98579 feat(wafer-map): add threshold legend overlay and scrollbar polish
- Add color legend (Low/In-range/High + Auto/Manual mode)
  as a floating overlay in WaferMapView
- Expose thresholdAuto on SessionController/SessionModel
  to drive the legend's Auto · / Manual · label
- Style ReadoutPanel scrollbar (always-visible, 8px thumb)
  and reserve 14px gap so cards don't overlap it
2026-07-11 21:57:57 -07:00

302 lines
11 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
import QtQuick.Layouts
import ISC
// ===== Readout Panel =====
// Right-rail panel with three bento-tile sections: THRESHOLDS, DISPLAY,
// READOUT. Thresholds comes first so Set Point/Margin/Auto are visible
// without scrolling on first use. The whole panel scrolls as one column
// (see WaferMapTab.qml) when content outgrows the available height.
// Uses the same PanelBox / SectionTitle / ReadoutStat / PanelCheckBox /
// PanelSlider components as the Data tab's ComparisonSidePanel, so both
// right rails share one visual language.
ColumnLayout {
id: root
spacing: 6
property var s: streamController.stats
property alias showLabels: labelsToggle.checked
property alias showExtremes: extremesToggle.checked
property alias heatmapBlend: heatmapSlider.value
property alias showThickness: thicknessToggle.checked
property bool hasThicknessData: false
signal exportRequested(string filePath, string extra)
signal thicknessFileChosen(string filePath)
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, extra) {
root.exportRequested(filePath, extra);
}
}
// ── THRESHOLDS ──────────────────────────────────────────────────
PanelBox {
Layout.fillWidth: true
Layout.preferredHeight: thresholdCard.implicitHeight + 24
ColumnLayout {
id: thresholdCard
anchors.fill: parent
anchors.margins: 12
spacing: 6
SectionTitle { text: "THRESHOLDS" }
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()
}
}
}
// ── DISPLAY ─────────────────────────────────────────────────────
PanelBox {
Layout.fillWidth: true
Layout.preferredHeight: displayCard.implicitHeight + 24
ColumnLayout {
id: displayCard
anchors.fill: parent
anchors.margins: 12
spacing: 8
SectionTitle { text: "DISPLAY" }
PanelCheckBox {
id: thicknessToggle
text: "Show Thickness"
checked: false
font.pixelSize: Theme.fontSm
// First check with no data prompts for the customer CSV;
// unchecking only hides the overlay, data stays loaded.
onToggled: {
if (checked && !root.hasThicknessData)
thicknessDialog.open();
}
}
FileDialog {
id: thicknessDialog
title: "Select Thickness Data File"
nameFilters: ["CSV files (*.csv)"]
onAccepted: root.thicknessFileChosen(String(selectedFile).replace(/^file:\/\//, ""))
onRejected: thicknessToggle.checked = false
}
PanelCheckBox {
id: labelsToggle
text: "Labels"
checked: true
font.pixelSize: Theme.fontSm
}
PanelCheckBox {
id: extremesToggle
text: "Highlight Min/Max"
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
}
PanelSlider {
id: heatmapSlider
from: 0
to: 1
value: 0
Layout.fillWidth: true
AppToolTip {
visible: heatmapSlider.hovered
text: Math.round(heatmapSlider.value * 100) + "%"
}
}
Label {
text: Math.round(heatmapSlider.value * 100) + "%"
color: Theme.bodyColor
font.pixelSize: Theme.fontSm
font.weight: Font.Medium
Layout.preferredWidth: 32
horizontalAlignment: Text.AlignRight
}
}
}
}
// ── READOUT ─────────────────────────────────────────────────────
PanelBox {
Layout.fillWidth: true
Layout.preferredHeight: readoutCol.implicitHeight + 24
ColumnLayout {
id: readoutCol
anchors.fill: parent
anchors.margins: 12
spacing: 8
SectionTitle { text: "READOUT" }
ReadoutStat {
label: "Min Temp"
value: s.min !== undefined
? s.min + ((s.minIndex !== undefined && s.minIndex >= 0) ? " #" + s.minIndex : "")
: "—"
valueColor: Theme.sensorLow
}
ReadoutStat {
label: "Max Temp"
value: s.max !== undefined
? s.max + ((s.maxIndex !== undefined && s.maxIndex >= 0) ? " #" + s.maxIndex : "")
: "—"
valueColor: Theme.sensorHigh
}
ReadoutStat {
label: "Diff"
value: s.diff !== undefined ? s.diff : "—"
valueColor: Theme.diffAccent
}
ReadoutStat {
label: "Average"
value: s.avg !== undefined ? s.avg : "—"
}
ReadoutStat {
label: "Sigma (Σ)"
value: s.sigma !== undefined ? s.sigma : "—"
}
ReadoutStat {
label: "3Σ Value"
value: s.threeSigma !== undefined ? s.threeSigma : "—"
}
ReadoutStat {
visible: s.ecMinDelta !== undefined
label: "E-C Δ Min"
value: s.ecMinDelta !== undefined
? s.ecMinDelta.toFixed(2) + " (#" + s.ecMinEdgeIndex + "→#" + s.ecMinCenterIndex + ")"
: "—"
}
ReadoutStat {
visible: s.ecMaxDelta !== undefined
label: "E-C Δ Max"
value: s.ecMaxDelta !== undefined
? s.ecMaxDelta.toFixed(2) + " (#" + s.ecMaxEdgeIndex + "→#" + s.ecMaxCenterIndex + ")"
: "—"
}
}
}
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);
}
}