import QtQuick import QtQuick.Controls import QtQuick.Layouts import ISC import QtQuick.Dialogs // ===== Home Workspace Shell ===== Rectangle { id: root anchors.fill: parent color: Theme.pageBackground clip: true border.color: Theme.outerFrameBorder border.width: Theme.borderStrong // ===== Navigation Model ===== // --------------------------------------------------------------------------- // TODO P1.1: Delete these 3 property blocks — superseded by mockup rail // // THINKING: // The new rail uses a pill-tab-bar for navigation (Status/Data/Map) instead // of sideActions[] + bottomTabs[]. The Repeaters below that consume these // arrays are also deleted with the old layout (P1.1). // selectedSideActionIndex is dead — the new rail has no concept of a // globally-selected side action. selectedTabIndex stays, redefined below. // // Cross-ref: P1.2 (pill tabs), P1.3 (context panels), P1.4 (workspace) // --------------------------------------------------------------------------- property var sideActions: ["DETECT WAFER", "READ MEMORY", "ERASE MEMORY"] // ===== Footer Tab Model ===== property var bottomTabs: ["Status", "Data", "Wafer Map", "Settings"] // ===== View State ===== property int selectedTabIndex: 0 property int selectedSideActionIndex: -1 property bool memoryRead: false property bool waferDetected: false Connections { target: deviceController function onDetectResult(result){ //result is a waferInfo dict on success, None on failure root.waferDetected = (result != null && result!= undefined) } function onParsedDataReady(result) { if (result.success && result.csv_path) { // Load the freshly read CSV into the player streamController.loadFile(result.csv_path) // Automatically switch to the "Wafer Map Tab" (index 3) root.selectedTabIndex = 3 } } } Connections { target: deviceController function onReadResult(result) { // result has "success" key on success, "error" key on failure root.memoryRead = (result !== null && result !== undefined && result.success === true) if (root.memoryRead) { console.log(`[P1.1] Memory read complete: ${result.bytes} bytes`) } else { console.log(`[P1.1] Read failed: ${result.error || "unknown"}`) } } } function cleanFolderUrl(url) { return decodeURIComponent(String(url).replace(/^file:\/\//, "")) } function _doDetect() { root.memoryRead = false streamController.setMode("review") streamController.stopStream() deviceController.detectWafer() } FolderDialog { id: saveDirDialog title: "Choose a folder to save Data" onAccepted: { deviceController.setSaveDataDir(root.cleanFolderUrl(selectedFolder)) root._doDetect() } } // --------------------------------------------------------------------------- // TODO P1.1: Delete importFileDialog + storedDataDialog (and the // `selectedTabIndex = 3` lines inside them) // // THINKING: // These dialogs were only opened from the old side-rail buttons // "IMPORT DATA" (index 4) and "STORED DATA" (index 5). With the // mockup rail, the Map tab's file list (P1.3) replaces both — it // drives `streamController.loadFile()` directly from a file_browser // Repeater. Keeping these dead dialogs would bloat the QML and // confuse future contributors. // // `selectedTabIndex = 3` is also stale (Map tab is index 2 now). // The replacement flow: file_row.onClicked → loadFile() → // selectedTabIndex = 2. // --------------------------------------------------------------------------- FileDialog { id: importFileDialog title: "Import CSV / ZWafer file" nameFilters: ["CSV files (*.csv)", "ZWafer files (*.zwafer)", "All files (*)"] onAccepted: { var path = root.cleanFolderUrl(selectedFile) streamController.setMode("review") streamController.stopStream() streamController.loadFile(path) root.selectedTabIndex = 3 // Switch to Wafer Map tab root.selectedSideActionIndex = -1 } } FileDialog { id: storedDataDialog title: "Open Stored CSV File" nameFilters: ["CSV files (*.csv)", "All files (*)"] onAccepted: { var path = root.cleanFolderUrl(selectedFile) streamController.setMode("review") streamController.stopStream() streamController.loadFile(path) root.selectedTabIndex = 3 // Switch to Wafer Map tab root.selectedSideActionIndex = -1 } } // ===== Main Two-Column Layout ====== // --------------------------------------------------------------------------- // TODO P1.1: Delete the ENTIRE RowLayout below (lines ~112-341) — old // side-rail + workspace + footer-tab-strip. // // THINKING: // This ~230-line RowLayout is being replaced by: // P1.2 — Pill tab bar (BOX 1, top of rail) // P1.3 — Context-sensitive panel (BOX 2, mid rail) // P1.5 — Hardware status footer (BOX 3, below BOX 2) // P1.6 — Settings/About utility buttons (BOX 4, rail bottom) // P1.4 — Workspace StackLayout (fills right side) // // None of the old structure (Repeater for sideActions[], Repeater for // bottomTabs[], footer tab strip) survives. Keeping it would cause // double-rendering or visual conflicts. // // Keep: saveDirDialog (FolderDialog), cleanFolderUrl(), _doDetect(), // and all Connections blocks (lines 31-61). // // Cross-ref: P1.2, P1.3, P1.4, P1.5, P1.6 // --------------------------------------------------------------------------- RowLayout { anchors.fill: parent spacing: 0 // ===== Left Action Rail ===== // Left control rail. Rectangle { id: sideRail Layout.preferredWidth: Theme.sideRailWidth Layout.fillHeight: true color: Theme.sideRailBackground border.color: Theme.workspaceBorder border.width: Theme.borderThin readonly property int actionCount: root.sideActions.length readonly property real computedButtonHeight: Math.min(Theme.sideButtonHeight, (height - (Theme.panelPadding * 2) - (Theme.sideRailSpacing * Math.max(0, actionCount - 1))) / Math.max(1, actionCount)) ColumnLayout { anchors.fill: parent anchors.margins: Theme.panelPadding spacing: Theme.sideRailSpacing Repeater { model: root.sideActions Button { id: control text: modelData enabled: { switch (index) { case 0: return true // DETECT WAFER case 1: return root.waferDetected // READ MEMORY case 2: return root.waferDetected // ERASE MEMORY default: return true } } property bool isActive: index === root.selectedSideActionIndex Layout.fillWidth: true Layout.preferredHeight: Math.max(Theme.sideButtonMinHeight, sideRail.computedButtonHeight) hoverEnabled: true onClicked: { root.selectedSideActionIndex = index root.selectedTabIndex = 0 // always jump to Status tab if (index === 0) { if (!deviceController.saveDataDir) { saveDirDialog.open() return } root._doDetect() } else if (index === 1) { streamController.setMode("review") streamController.stopStream() deviceController.readMemoryAsync() } else if (index === 2) { // ERASE MEMORY streamController.setMode("review") streamController.stopStream() deviceController.eraseMemory(deviceController.selectedPort || "") } } background: Rectangle { color: { if (control.down) { return Theme.buttonPressed; } if (control.isActive) { return Theme.sideActiveBackground; } return "transparent"; } border.color: control.isActive ? Theme.cardBorder : "transparent" border.width: 1 radius: Theme.radiusSm Rectangle { anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom width: 3 visible: control.isActive color: Theme.primaryAccent radius: Theme.radiusXs } } contentItem: Text { text: control.text color: control.isActive ? Theme.headingColor : Theme.bodyColor font.bold: control.isActive font.pixelSize: 18 wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } } } } // Main workspace and footer navigation. Rectangle { Layout.fillWidth: true Layout.fillHeight: true color: Theme.workspaceBackground border.color: Theme.workspaceBorder border.width: Theme.borderThin ColumnLayout { anchors.fill: parent anchors.margins: Theme.mainAreaPadding spacing: Theme.rightPaneGap // ===== Active Tab Content Area ===== Rectangle { Layout.fillWidth: true Layout.fillHeight: true Layout.minimumHeight: 220 color: Theme.responseBackground border.color: Theme.responseBorder border.width: 1 radius: Theme.radiusMd StackLayout { anchors.fill: parent currentIndex: root.selectedTabIndex // ===== Tab Content Routing ===== Repeater { model: root.bottomTabs Item { property string tabName: modelData Loader { anchors.fill: parent active: parent.tabName === "Settings" source: parent.tabName === "Settings" ? "Tabs/SettingsTab.qml" : "" } Loader { anchors.fill: parent active: parent.tabName === "Status" source: parent.tabName === "Status" ? "Tabs/StatusTab.qml" : "" } Loader { anchors.fill: parent active: parent.tabName === "Data" source: parent.tabName === "Data" ? "Tabs/DataTab.qml" : "" } Label { anchors.centerIn: parent visible: parent.tabName !== "Settings" && parent.tabName !== "Status" && parent.tabName !== "Data" && parent.tabName !== "Wafer Map" && parent.tabName !== "Graph" text: parent.tabName + " content" color: Theme.bodyColor font.pixelSize: 20 } Loader { anchors.fill: parent active: parent.tabName === "Wafer Map" source: parent.tabName === "Wafer Map" ? "Tabs/WaferMapTab.qml" : "" } Loader { anchors.fill: parent active: parent.tabName === "Graph" source: parent.tabName === "Graph" ? "Tabs/GraphTab.qml" : "" } } } } } // Keep the tab strip evenly distributed across the footer. // ===== Footer Tab Strip ===== Rectangle { Layout.fillWidth: true Layout.preferredHeight: Theme.tabBarHeight + (Theme.tabBarPadding * 2) color: Theme.tabBarBackground border.color: Theme.workspaceBorder border.width: Theme.borderThin radius: Theme.radiusMd RowLayout { anchors.fill: parent anchors.margins: Theme.tabBarPadding spacing: Theme.tabSpacing Repeater { model: root.bottomTabs Button { id: botTabBtn property bool isActive: index === root.selectedTabIndex text: modelData Layout.fillWidth: true Layout.preferredHeight: Theme.tabBarHeight Layout.minimumWidth: Theme.tabButtonMinWidth onClicked: root.selectedTabIndex = index hoverEnabled: true background: Rectangle { color: botTabBtn.isActive ? Theme.tabActiveBackground : (botTabBtn.hovered ? Theme.tabHoverBackground : Theme.tabBackground) border.color: Theme.tabBorder border.width: 1 radius: Theme.tabRadius } contentItem: Text { text: botTabBtn.text color: botTabBtn.isActive ? Theme.tabActiveText : Theme.tabText font.pixelSize: Theme.tabFontSize font.bold: botTabBtn.isActive horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } } } } } } } } }