622 lines
26 KiB
QML
622 lines
26 KiB
QML
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|