feat: Add Wafer Map tab and associated components

- Introduced a new Wafer Map tab in the HomePage.qml, which loads WaferMapTab.qml when selected.
- Created WaferMapTab.qml to display wafer map with live data and controls.
- Added ReadoutPanel.qml for displaying sensor statistics and thresholds.
- Implemented SourcePanel.qml for file selection and filtering.
- Developed TransportBar.qml for playback controls.
- Added WaferMapView.qml to visualize wafer data with interactive features.
- Created ReplaceSensorDialog.qml for sensor value overrides.
- Updated Theme.qml for new color schemes and UI adjustments.
- Modified qmldir files to include new components in the ISC.Tabs and ISC.Tabs.components modules.
This commit is contained in:
jack
2026-06-11 11:57:24 -07:00
parent b52b983bb2
commit 97ca58bfc2
10 changed files with 1080 additions and 13 deletions
+231
View File
@@ -0,0 +1,231 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import ISC
import ISC.Tabs.components
Item {
id: root
anchors.fill: parent
// Live elapsed-time counter
property int _liveSecs: 0
Timer {
id: liveTimer
interval: 1000
repeat: true
running: streamController.mode === "live" && streamController.state !== "idle"
onTriggered: root._liveSecs++
onRunningChanged: if (!running) root._liveSecs = 0
}
function fmtTime(s) {
var m = Math.floor(s / 60)
var ss = s % 60
return (m < 10 ? "0" : "") + m + ":" + (ss < 10 ? "0" : "") + ss
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Theme.panelPadding
spacing: Theme.rightPaneGap
// ── Toolbar ───────────────────────────────────────────────────────
RowLayout {
Layout.fillWidth: true
spacing: 10
// Mode toggle
TabBar {
id: modeBar
spacing: 2
padding: 2
background: Rectangle {
color: Theme.subtleSectionBackground
radius: Theme.radiusSm
border.color: Theme.cardBorder
border.width: Theme.borderThin
}
TabButton {
text: "Live"
implicitWidth: 58; implicitHeight: 28
background: Rectangle {
color: parent.checked ? Theme.buttonNeutralBackground : "transparent"
radius: Theme.radiusSm - 1
}
contentItem: Text {
text: parent.text
color: parent.checked ? Theme.headingColor : Theme.bodyColor
font.pixelSize: 11
font.weight: parent.checked ? Font.Medium : Font.Normal
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
TabButton {
text: "Review"
implicitWidth: 64; implicitHeight: 28
background: Rectangle {
color: parent.checked ? Theme.buttonNeutralBackground : "transparent"
radius: Theme.radiusSm - 1
}
contentItem: Text {
text: parent.text
color: parent.checked ? Theme.headingColor : Theme.bodyColor
font.pixelSize: 11
font.weight: parent.checked ? Font.Medium : Font.Normal
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
onCurrentIndexChanged:
streamController.setMode(currentIndex === 0 ? "live" : "review")
}
// Live source info: WiFi icon + port · serial
RowLayout {
visible: streamController.mode === "live"
spacing: 5
// WiFi icon (Unicode approximation; swap for SVG if available)
Label {
text: "◉"
color: streamController.state !== "idle"
? Theme.liveColor : Theme.bodyColor
font.pixelSize: 13
}
Label {
text: "Live stream"
color: Theme.bodyColor
font.pixelSize: 11
}
}
Item { Layout.fillWidth: true }
// SET badge + avg temp
Rectangle {
visible: streamController.state === "set"
height: 22; radius: Theme.radiusSm
color: "transparent"
border.color: Theme.liveColor
border.width: 1
implicitWidth: setLabel.implicitWidth + 16
Label {
id: setLabel
anchors.centerIn: parent
text: "SET " + (streamController.stats.avg !== undefined
? streamController.stats.avg + " avg" : "")
color: Theme.liveColor
font.pixelSize: 10
font.weight: Font.Medium
font.letterSpacing: 0.8
}
}
// Record indicator
RowLayout {
spacing: 5
visible: streamController.recording
Rectangle {
width: 7; height: 7; radius: 4
color: Theme.recordColor
SequentialAnimation on opacity {
running: streamController.recording
loops: Animation.Infinite
NumberAnimation { to: 0.2; duration: 600 }
NumberAnimation { to: 1.0; duration: 600 }
}
}
Label {
text: "REC"
color: Theme.recordColor
font.pixelSize: 10
font.weight: Font.Medium
font.letterSpacing: 1.2
}
}
// LIVE timer
RowLayout {
spacing: 5
visible: streamController.mode === "live" && streamController.state !== "idle"
Rectangle {
width: 7; height: 7; radius: 4
color: Theme.liveColor
}
Label {
text: "LIVE " + root.fmtTime(root._liveSecs)
color: Theme.liveColor
font.pixelSize: 10
font.weight: Font.Medium
font.letterSpacing: 0.8
}
}
// IDLE label (review / not connected)
Label {
visible: streamController.mode === "review" || streamController.state === "idle"
text: streamController.state.toUpperCase()
color: Theme.bodyColor
font.pixelSize: 11
font.weight: Font.Medium
font.letterSpacing: 1.2
}
}
// ── Body: 3 zones ─────────────────────────────────────────────────
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: Theme.rightPaneGap
// Source panel — bordered card
Rectangle {
Layout.preferredWidth: 220
Layout.fillHeight: true
color: Theme.cardBackground
border.color: Theme.cardBorder
border.width: 1
radius: Theme.radiusMd
clip: true
SourcePanel {
anchors.fill: parent
anchors.margins: 10
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 8
WaferMapView {
id: waferView
Layout.fillWidth: true
Layout.fillHeight: true
blend: readoutPanel.heatmapBlend
showLabels: readoutPanel.showLabels
}
TransportBar { Layout.fillWidth: true }
}
// Readout panel — bordered card
Rectangle {
Layout.preferredWidth: 220
Layout.fillHeight: true
color: Theme.cardBackground
border.color: Theme.cardBorder
border.width: 1
radius: Theme.radiusMd
clip: true
ReadoutPanel {
id: readoutPanel
anchors.fill: parent
anchors.margins: 10
}
}
}
}
}