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); } }