35cee98579
- 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
302 lines
11 KiB
QML
302 lines
11 KiB
QML
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);
|
||
}
|
||
}
|