feat(ui): decouple QML page modules and update tab layouts

Extract ReadoutPanel, WaferMapView, and TransportBar into reusable components and bind tabs to the new session controllers.
This commit is contained in:
jack
2026-06-25 13:48:20 -07:00
parent f4621f1faf
commit cfadbdf1c3
6 changed files with 205 additions and 79 deletions
+111 -59
View File
@@ -1,8 +1,10 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
import QtQuick.Layouts
import ISC
import ISC.Tabs.components
import ISC.Wafer
Item {
id: root
@@ -23,6 +25,16 @@ Item {
var ss = s % 60
return (m < 10 ? "0" : "") + m + ":" + (ss < 10 ? "0" : "") + ss
}
// Wire streamController.trendData (Signal(str) carrying JSON list of floats)
// into the trend chart's data property. The slot parseJsonToData() is defined
// on the Python TrendChartItem; we use it so malformed payloads are logged
// there instead of crashing the QML binding.
Connections {
target: streamController
function onTrendData(avgsJson) {
trendChart.setDataFromJson(avgsJson)
}
}
ColumnLayout {
anchors.fill: parent
@@ -37,7 +49,7 @@ Item {
// Mode toggle
TabBar {
id: modeBar
currentIndex: 1 // Default to "Review"
currentIndex: 0 // Default to "Review"
spacing: 2
padding: 2
background: Rectangle {
@@ -46,6 +58,22 @@ Item {
border.color: Theme.cardBorder
border.width: Theme.borderThin
}
TabButton {
text: "Review"
implicitWidth: 64; implicitHeight: 28
background: Rectangle {
color: parent.checked ? Theme.tabActiveBackground : "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: "Live"
enabled: deviceController.connectionStatus === "Connected"
@@ -64,33 +92,20 @@ Item {
verticalAlignment: Text.AlignVCenter
}
}
TabButton {
text: "Review"
implicitWidth: 64; implicitHeight: 28
background: Rectangle {
color: parent.checked ? Theme.tabActiveBackground : "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:
if (currentIndex === 0){
// Entering Live mode, only work when wafer detected/connected
streamController.setMode("live")
if (deviceController.connectionStatus === "Connected") {
var fc = ""
if (deviceController.lastWaferInfo && deviceController.lastWaferInfo.length > 0) {
fc = deviceController.lastWaferInfo[0]
}
streamController.startStream(deviceController.selectedPort, fc)
if (currentIndex === 1){
// Guard: revert to Review if not connected
if (deviceController.connectionStatus !== "Connected") {
currentIndex = 0
return
}
// Entering Live mode
streamController.setMode("live")
var fc = ""
if (deviceController.lastWaferInfo && deviceController.lastWaferInfo.length > 0) {
fc = deviceController.lastWaferInfo[0]
}
streamController.startStream(deviceController.selectedPort, fc)
} else {
// Entering Review Mode (automatically calls stopStream in backend)
streamController.setMode("review")
@@ -172,34 +187,62 @@ Item {
}
// 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
}
}
// 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
// Label {
// visible: streamController.mode === "review" || streamController.state === "idle"
// text: streamController.state.toUpperCas`e`()
// color: Theme.bodyColor
// font.pixelSize: 11
// font.weight: Font.Medium
// font.letterSpacing: 1.2
// }
Button{
text: "Export PNG"
onClicked: exportDialog.open()
}
FileDialog {
id: exportDialog
title: "Export Wafer Map"
fileMode: FileDialog.SaveFile
nameFilters: ["PNG files (*.png)"]
onAccepted: waferView.exportImage(String(selectedFile).replace(/^file:\/\//, ""))
}
}
// ── Body: 3 zones ─────────────────────────────────────────────────
// ── Body: 3 zones (TODO P2.1: becomes 2 zones) ──────────────────────
// -------------------------------------------------------------------
// TODO P2.1: Delete the SourcePanel Rectangle (lines ~219-233) —
// the file list moved to the rail (P1.3 Map context panel).
//
// THINKING:
// SourcePanel shows a file list driven by file_browser — now that
// the Map tab's context panel in the rail (P1.3) provides the same
// file list with search/filter, SourcePanel in the wafer body is
// redundant. The wafer map gets more horizontal space.
//
// After deletion: RowLayout has 2 children instead of 3.
// Add Layout.fillWidth: true to the center ColumnLayout so it
// fills the vacated width.
//
// Cross-ref: P1.3 (Map context panel), P2.1 (this task)
// -------------------------------------------------------------------
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
@@ -233,20 +276,29 @@ Item {
blend: readoutPanel.heatmapBlend
showLabels: readoutPanel.showLabels
}
// Horizontal separation line with vertical padding
Item {
Rectangle {
id: trendPane
Layout.fillWidth: true
implicitHeight: 32
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Theme.cardBorder
implicitHeight: 160
visible: trendChart.hasData
color: Theme.cardBackground
border.color: Theme.cardBorder
border.width: 1
radius: Theme.radiusMd
TrendChartItem {
id: trendChart
anchors.fill: parent
anchors.margins: 8
}
}
TransportBar { Layout.fillWidth: true }
Item { Layout.fillWidth: true; implicitHeight: 16 }
TransportBar {
Layout.fillWidth: true
visible: streamController.mode !== "live" || trendChart.hasData
height: visible ? implicitHeight : 0
}
}
// Readout panel — bordered card