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