import QtQuick import QtQuick.Controls import QtQuick.Controls.impl import QtQuick.Layouts import ISC import ISC.Tabs.components import QtQuick.Dialogs // ===== Home Workspace Shell ===== // Unified left rail: pill tab bar → context panel → status footer → utility buttons. // Workspace fills the remaining area with the active tab's content. Rectangle { id: root anchors.fill: parent color: Theme.pageBackground clip: true border.color: Theme.outerFrameBorder border.width: Theme.borderStrong // ===== View State ===== property int selectedTabIndex: 0 onSelectedTabIndexChanged: { if (selectedTabIndex === 2) { file_browser.refreshFiles(); streamController.unloadFile() } } property bool memoryRead: false property bool waferDetected: false Connections { target: deviceController function onDetectResult(result){ root.waferDetected = (result != null && result != undefined) } function onParsedDataReady(result) { if (result.success && result.csv_path) { streamController.loadFile(result.csv_path) if (root.selectedTabIndex === 0) { root.selectedTabIndex = 2 } } } } Connections { target: deviceController function onReadResult(result) { 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() } } // ===== Split Dialog (Threshold Segmentation) ===== SplitDialog { id: splitDialog } // ===== Settings Popup ===== Popup { id: settingsPopup modal: true dim: true closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside width: Math.min(root.width - 80, 800) height: Math.min(root.height - 80, 600) anchors.centerIn: Overlay.overlay background: Rectangle { radius: Theme.radiusMd color: Theme.cardBackground border.color: Theme.cardBorder border.width: 1 } contentItem: Rectangle { color: Theme.pageBackground radius: Theme.radiusMd clip: true ColumnLayout { anchors.fill: parent spacing: 0 Rectangle { Layout.fillWidth: true Layout.preferredHeight: 40 color: Theme.panelBackground border.color: Theme.cardBorder border.width: 1 radius: Theme.radiusMd RowLayout { anchors.fill: parent anchors.leftMargin: 14 anchors.rightMargin: 4 Label { text: "Settings" font.pixelSize: Theme.fontLg font.bold: true color: Theme.headingColor Layout.fillWidth: true } Button { text: "\u2715" flat: true Layout.preferredWidth: 32 Layout.preferredHeight: 32 onClicked: settingsPopup.close() background: Rectangle { radius: Theme.radiusXs color: parent.hovered ? Theme.buttonNeutralHover : "transparent" } contentItem: Label { text: parent.text color: Theme.bodyColor horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } } } Item { Layout.fillWidth: true Layout.fillHeight: true Layout.rightMargin: 6 clip: true Loader { anchors.fill: parent source: "Tabs/SettingsTab.qml" } } } } } // ===== About Dialog ===== AboutDialog { id: aboutDialog } // ===== Main Layout: Rail + Workspace ===== RowLayout { anchors.fill: parent spacing: 0 // ── LEFT RAIL ────────────────────────────────────────────────────── Rectangle { id: rail Layout.preferredWidth: Theme.sideRailWidth Layout.fillHeight: true color: Theme.sideRailBackground border.color: Theme.sideBorder border.width: 1 ColumnLayout { anchors.fill: parent anchors.margins: Theme.sideRailMargin spacing: Theme.sideRailSpacing // ── BOX 1: Pill Tab Bar ───────────────────────────────────── Rectangle { id: pillBar Layout.fillWidth: true Layout.preferredHeight: Theme.sidePillHeight radius: 999 border.color: Theme.sideBorder border.width: 1 color: "transparent" ListModel { id: pillModel ListElement { label: "STATUS"; icon: "Tabs/icons/status.svg"; expandW: 86 } ListElement { label: "DATA"; icon: "Tabs/icons/data.svg"; expandW: 66 } ListElement { label: "MAP"; icon: "Tabs/icons/map.svg"; expandW: 58 } } RowLayout { anchors.fill: parent anchors.margins: 3 spacing: 0 Repeater { model: pillModel delegate: Button { id: pillBtn checked: root.selectedTabIndex === index flat: true Layout.fillWidth: true Layout.fillHeight: true Layout.preferredWidth: pillBtn.checked ? model.expandW : 44 Behavior on Layout.preferredWidth { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } } background: Rectangle { radius: 50 color: pillBtn.checked ? Theme.sidePanelBackground : "transparent" Behavior on color { ColorAnimation { duration: Theme.durationFast } } } contentItem: Row { id: pillContent spacing: pillBtn.checked ? 6 : 0 width: pillIcon.width + (pillBtn.checked ? pillContent.spacing + pillLabel.width : 0) anchors.verticalCenter: parent.verticalCenter x: (parent.width - width) / 2 Behavior on x { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } } IconImage { id: pillIcon anchors.verticalCenter: parent.verticalCenter width: 16; height: 16 source: model.icon sourceSize.width: 16; sourceSize.height: 16 color: pillBtn.checked ? Theme.headingColor : pillBtn.hovered ? Qt.lighter(Theme.sideMutedText, 1.7) : Theme.sideMutedText opacity: pillBtn.checked ? 0.9 : pillBtn.hovered ? 0.7 : 0.4 Behavior on color { ColorAnimation { duration: Theme.durationFast } } Behavior on opacity { NumberAnimation { duration: Theme.durationFast } } } Label { id: pillLabel anchors.verticalCenter: parent.verticalCenter width: pillBtn.checked ? implicitWidth : 0 text: model.label color: pillBtn.checked ? Theme.headingColor : Theme.sideMutedText font.family: Theme.uiFontFamily font.pixelSize: Theme.fontSm font.bold: true font.letterSpacing: 1.0 opacity: pillBtn.checked ? 1.0 : 0.0 clip: true Behavior on width { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } } Behavior on color { ColorAnimation { duration: Theme.durationBase; easing.type: Theme.easeStandard } } Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } } } } onClicked: root.selectedTabIndex = index } } } } // ── BOX 2: Context-Sensitive Rail Panel ──────────────────── Rectangle { Layout.fillWidth: true Layout.fillHeight: true Layout.minimumHeight: 120 radius: Theme.sidePanelRadius border.color: Theme.sideBorder border.width: 1 color: Theme.sidePanelBackground ColumnLayout { anchors.fill: parent anchors.margins: 14 spacing: 8 StackLayout { Layout.fillWidth: true Layout.fillHeight: true currentIndex: root.selectedTabIndex === 0 ? 0 : 1 // ── Status tab: Hardware Actions ──────────────── ColumnLayout { spacing: 6 Label { text: "HARDWARE ACTIONS" color: Theme.sideMutedText font.family: Theme.uiFontFamily font.pixelSize: Theme.fontSm topPadding: 6 font.letterSpacing: 1.5 font.bold: true } RailActionButton { label: "DETECT WAFER" iconSource: "../icons/detect.svg" Layout.fillWidth: true enabled: !deviceController.operationInProgress onClicked: root._doDetect() } RailActionButton { label: "READ MEMORY" iconSource: "../icons/read.svg" Layout.fillWidth: true enabled: root.waferDetected onClicked: deviceController.readMemoryAsync() } RailActionButton { label: "ERASE MEMORY" iconSource: "../icons/erase.svg" Layout.fillWidth: true destructive: true enabled: root.waferDetected onClicked: deviceController.eraseMemory( deviceController.selectedPort) } Item { Layout.fillHeight: true } } // ── Map tab: Source file browser ────────────── SourcePanel { Layout.fillWidth: true Layout.fillHeight: true } } } } // ── BOX 3: Hardware Status Footer ────────────────────────── Rectangle { Layout.fillWidth: true Layout.preferredHeight: Theme.sideFooterConnection radius: Theme.sidePanelRadius border.color: Theme.sideBorder border.width: 1 color: Theme.sidePanelBackground ColumnLayout { anchors.fill: parent anchors.margins: 14 spacing: 6 // Row 1: Title + status badge (unchanged) RowLayout { spacing: 8 Layout.fillWidth: true Label { text: "CONNECTION FLOW" color: Theme.sideMutedText font.pixelSize: Theme.fontXs font.letterSpacing: 1.2 font.bold: true } Rectangle { height: 18 width: badgeLabel.implicitWidth + 14 radius: 9 color: { var s = deviceController.connectionStatus if (s === "Connected") return Theme.statusSuccessColor if (s === "Disconnected") return Theme.statusErrorColor return Theme.statusWarningColor } Label { id: badgeLabel anchors.centerIn: parent text: deviceController.connectionStatus || "Disconnected" color: Theme.statusBadgeText font.pixelSize: Theme.font2xs font.bold: true } } Item { Layout.fillWidth: true } } // Row 2: Status dot + descriptive text (replaces dotted trail) Row { spacing: 8 Layout.fillWidth: true Rectangle { id: statusDot anchors.verticalCenter: parent.verticalCenter width: 8 height: 8 radius: 4 color: deviceController.connectionStatus === "Connected" ? Theme.statusSuccessColor : Theme.statusErrorColor SequentialAnimation on opacity { running: deviceController.connectionStatus === "Connected" loops: Animation.Infinite NumberAnimation { to: 0.4; duration: 1200 } NumberAnimation { to: 1.0; duration: 1200 } } } Label { anchors.verticalCenter: parent.verticalCenter text: { if (deviceController.connectionStatus === "Connected") return deviceController.selectedPort || "Connected" if (deviceController.selectedPort) return deviceController.selectedPort return "No port selected" } color: Theme.bodyColor font.family: Theme.uiFontFamily font.pixelSize: Theme.fontXs elide: Text.ElideRight } } // Row 3: Mode chip — neutral gray tag Row { spacing: 6 Layout.fillWidth: true Rectangle { anchors.verticalCenter: parent.verticalCenter width: modeChipLabel.implicitWidth + 12 height: 18 radius: 4 color: Theme.fieldBackground border.color: Theme.sideBorder border.width: 1 Label { id: modeChipLabel anchors.centerIn: parent text: streamController.mode ? streamController.mode.toUpperCase() : "REVIEW" color: Theme.sideMutedText font.family: Theme.uiFontFamily font.pixelSize: Theme.font2xs font.bold: true font.letterSpacing: 0.8 } } } } } // ── BOX 4: Utility Buttons ───────────────────────────────── Rectangle { Layout.fillWidth: true Layout.preferredHeight: Theme.sideFooterUtility radius: Theme.sidePanelRadius border.color: Theme.sideBorder border.width: 1 color: Theme.sidePanelBackground RowLayout { anchors.fill: parent anchors.margins: 4 spacing: 4 Button { id: settingsBtn flat: true Layout.fillWidth: true Layout.fillHeight: true icon.source: "Tabs/icons/settings.svg" onClicked: settingsPopup.open() background: Rectangle { radius: 8 color: settingsBtn.hovered ? Theme.sideActiveBackground : "transparent" } contentItem: Row { spacing: 6 anchors.centerIn: parent IconImage { anchors.verticalCenter: parent.verticalCenter width: 16 height: 16 source: "Tabs/icons/settings.svg" sourceSize.width: 16 sourceSize.height: 16 color: Theme.headingColor opacity: 0.75 } Label { anchors.verticalCenter: parent.verticalCenter text: "SETTINGS" color: Theme.headingColor font.family: Theme.uiFontFamily font.pixelSize: Theme.fontXs font.bold: true font.letterSpacing: 1.2 opacity: 0.8 } } } Rectangle { width: 1 height: 24 color: Theme.sideBorder } Button { id: aboutBtn flat: true Layout.fillWidth: true Layout.fillHeight: true icon.source: "Tabs/icons/about.svg" onClicked: aboutDialog.open() background: Rectangle { radius: 8 color: aboutBtn.hovered ? Theme.sideActiveBackground : "transparent" } contentItem: Row { spacing: 6 anchors.centerIn: parent IconImage { anchors.verticalCenter: parent.verticalCenter width: 16 height: 16 source: "Tabs/icons/about.svg" sourceSize.width: 16 sourceSize.height: 16 color: Theme.headingColor opacity: 0.75 } Label { anchors.verticalCenter: parent.verticalCenter text: "ABOUT" color: Theme.headingColor font.family: Theme.uiFontFamily font.pixelSize: Theme.fontXs font.bold: true font.letterSpacing: 1.2 opacity: 0.8 } } } } } } } // ── WORKSPACE ───────────────────────────────────────────────────── Rectangle { Layout.fillWidth: true Layout.fillHeight: true color: Theme.workspaceBackground border.color: Theme.workspaceBorder border.width: 1 StackLayout { anchors.fill: parent anchors.margins: Theme.mainAreaPadding currentIndex: root.selectedTabIndex Loader { source: "Tabs/StatusTab.qml" active: StackLayout.index === root.selectedTabIndex } Loader { id: dataTabLoader source: "Tabs/DataTab.qml" active: StackLayout.index === root.selectedTabIndex } Loader { source: "Tabs/WaferMapTab.qml" active: StackLayout.index === root.selectedTabIndex } } } } }