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:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user