feat: implement master file configuration, UI indicators, and a Run Comparison tool for the Data tab

This commit is contained in:
jack
2026-07-07 15:05:22 -07:00
parent 1e03227788
commit 92f130b3bd
8 changed files with 798 additions and 428 deletions
+287 -7
View File
@@ -181,8 +181,6 @@ Rectangle {
Layout.preferredWidth: Theme.sideRailWidth
Layout.fillHeight: true
color: Theme.sideRailBackground
border.color: Theme.sideBorder
border.width: 1
ColumnLayout {
anchors.fill: parent
@@ -290,6 +288,286 @@ Rectangle {
}
}
// ── BOX 1.5: Run Comparison (Data tab only) ────────────────
Rectangle {
id: runComparisonBox
Layout.fillWidth: true
Layout.preferredHeight: runCompCol.implicitHeight + 28
visible: root.selectedTabIndex === 1
radius: Theme.sidePanelRadius
border.color: Theme.sideBorder
border.width: 1
color: Theme.sidePanelBackground
readonly property var dataTab: dataTabLoader.item
component RunSlot: Rectangle {
id: slotBox
property string title
property string filePath: ""
property color accent: Theme.primaryAccent
property bool active: false
property bool locked: false
property string placeholderText: "Click to select"
signal cleared()
signal armed()
Layout.fillWidth: true
implicitHeight: 58
radius: Theme.radiusSm
color: Theme.fieldBackground
opacity: slotBox.locked ? 0.65 : 1.0
border.width: slotBox.active ? 2 : 1
border.color: slotBox.active ? accent : Theme.sideBorder
Behavior on border.color { ColorAnimation { duration: Theme.durationFast } }
MouseArea {
anchors.fill: parent
enabled: !slotBox.locked
cursorShape: slotBox.locked ? Qt.ArrowCursor : Qt.PointingHandCursor
onClicked: slotBox.armed()
}
RowLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 8
Rectangle {
width: 8; height: 8; radius: 4
color: slotBox.accent
Layout.alignment: Qt.AlignVCenter
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
Label {
text: slotBox.title
font.pixelSize: Theme.fontXs
font.bold: true
font.letterSpacing: 1.0
color: slotBox.active ? slotBox.accent : Theme.sideMutedText
}
Label {
text: slotBox.filePath !== ""
? slotBox.filePath.split("/").pop()
: (slotBox.locked ? slotBox.placeholderText : (slotBox.active ? "<- Select file below..." : "Click to select"))
font.pixelSize: Theme.fontSm
color: slotBox.filePath !== "" ? Theme.headingColor : Theme.bodyColor
opacity: slotBox.filePath !== "" ? 1.0 : 0.6
elide: Text.ElideMiddle
Layout.fillWidth: true
}
}
Button {
visible: slotBox.filePath !== "" && !slotBox.locked
flat: true
implicitWidth: 24; implicitHeight: 24
onClicked: slotBox.cleared()
background: Rectangle {
radius: Theme.radiusSm
color: parent.hovered ? Theme.flatButtonHover : "transparent"
}
contentItem: IconImage {
source: "Tabs/icons/x.svg"
width: 14; height: 14
sourceSize.width: 14
sourceSize.height: 14
color: Theme.bodyColor
anchors.centerIn: parent
}
}
}
}
component PanelCheckBox: CheckBox {
id: toggle
indicator: Rectangle {
implicitWidth: 18
implicitHeight: 18
x: toggle.leftPadding
y: parent.height / 2 - height / 2
radius: Theme.radiusXs
color: toggle.checked ? Theme.primaryAccent : "transparent"
border.width: Theme.borderThin
border.color: toggle.checked ? Theme.primaryAccent : Theme.fieldBorder
Behavior on color { ColorAnimation { duration: Theme.durationFast } }
Behavior on border.color { ColorAnimation { duration: Theme.durationFast } }
IconImage {
anchors.centerIn: parent
source: "Tabs/icons/check.svg"
width: 12; height: 12
sourceSize.width: 12
sourceSize.height: 12
color: Theme.panelBackground
opacity: toggle.checked ? 1.0 : 0.0
Behavior on opacity { NumberAnimation { duration: Theme.durationFast } }
}
}
contentItem: Text {
text: toggle.text
color: Theme.headingColor
font.family: Theme.uiFontFamily
verticalAlignment: Text.AlignVCenter
leftPadding: toggle.indicator.width + toggle.spacing
font.pixelSize: toggle.font.pixelSize || Theme.fontXs
}
}
ColumnLayout {
id: runCompCol
anchors.fill: parent
anchors.margins: 14
spacing: 10
RowLayout {
Layout.fillWidth: true
spacing: 8
Label {
text: "RUN COMPARISON (DTW)"
color: Theme.sideMutedText
font.family: Theme.uiFontFamily
font.pixelSize: Theme.fontSm
font.letterSpacing: 1.5
font.bold: true
Layout.fillWidth: true
elide: Text.ElideRight
}
Button {
id: resetRunBtn
flat: true
implicitWidth: 32
implicitHeight: 32
visible: true
onClicked: if (runComparisonBox.dataTab) runComparisonBox.dataTab.resetComparison()
background: Rectangle {
radius: Theme.radiusSm
color: resetRunBtn.hovered ? Theme.flatButtonHover : "transparent"
}
contentItem: IconImage {
source: "Tabs/icons/rotate-ccw.svg"
width: 18; height: 18
sourceSize.width: 18
sourceSize.height: 18
color: Theme.bodyColor
anchors.centerIn: parent
}
ToolTip.visible: resetRunBtn.hovered
ToolTip.text: "Reset comparison"
}
}
PanelCheckBox {
id: useMasterCheck
Layout.fillWidth: true
text: "Compare vs Master File"
checked: runComparisonBox.dataTab ? runComparisonBox.dataTab.useMasterFile : false
onToggled: {
if (!runComparisonBox.dataTab) return
runComparisonBox.dataTab.useMasterFile = checked
if (checked) runComparisonBox.dataTab.activeBox = 2
}
}
RunSlot {
readonly property bool useMaster: runComparisonBox.dataTab ? runComparisonBox.dataTab.useMasterFile : false
title: useMaster ? "MASTER FILE" : "REFERENCE (RUN A)"
accent: Theme.primaryAccent
locked: useMaster
placeholderText: useMaster
? (runComparisonBox.dataTab.runBWaferType !== ""
? "No master file for type " + runComparisonBox.dataTab.runBWaferType
: "Select Run B to resolve master")
: "Click to select"
active: runComparisonBox.dataTab ? (!useMaster && runComparisonBox.dataTab.activeBox === 1) : false
filePath: runComparisonBox.dataTab
? (useMaster ? runComparisonBox.dataTab.masterFileForRunB : runComparisonBox.dataTab.compareFileA)
: ""
onArmed: if (runComparisonBox.dataTab && !useMaster) runComparisonBox.dataTab.activeBox = 1
onCleared: {
if (!runComparisonBox.dataTab) return
runComparisonBox.dataTab.compareFileA = ""
runComparisonBox.dataTab.activeBox = 1
runComparisonBox.dataTab.clearResults()
}
}
RunSlot {
title: "SESSION (RUN B)"
accent: Theme.themeSkill
active: runComparisonBox.dataTab ? runComparisonBox.dataTab.activeBox === 2 : false
filePath: runComparisonBox.dataTab ? runComparisonBox.dataTab.compareFileB : ""
onArmed: if (runComparisonBox.dataTab) runComparisonBox.dataTab.activeBox = 2
onCleared: {
if (!runComparisonBox.dataTab) return
runComparisonBox.dataTab.compareFileB = ""
runComparisonBox.dataTab.activeBox = 2
runComparisonBox.dataTab.clearResults()
}
}
Button {
id: compareRunBtn
Layout.fillWidth: true
Layout.preferredHeight: 32
readonly property var dt: runComparisonBox.dataTab
readonly property string fileA: !!dt ? (dt.useMasterFile ? dt.masterFileForRunB : dt.compareFileA) : ""
readonly property bool masterMissing: !!dt && dt.useMasterFile && dt.masterFileForRunB === ""
readonly property bool sameFile: !!dt && fileA !== "" && fileA === dt.compareFileB
enabled: !!dt && fileA !== "" && dt.compareFileB !== "" && !sameFile && !dt.comparing && !masterMissing
ToolTip.visible: (sameFile || masterMissing) && hovered
ToolTip.text: masterMissing
? "No master file registered for this wafer type (Settings → Master Files)"
: "Choose two different runs to compare"
onClicked: {
dt.comparing = true
dt.clearResults()
streamController.compareFiles(fileA, dt.compareFileB)
}
background: Rectangle {
radius: Theme.radiusSm
color: compareRunBtn.enabled
? (compareRunBtn.hovered ? Qt.lighter(Theme.primaryAccent, 1.1) : Theme.primaryAccent)
: Theme.buttonNeutralBackground
Behavior on color { ColorAnimation { duration: Theme.durationFast } }
}
contentItem: Row {
spacing: 8
anchors.centerIn: parent
Label {
anchors.verticalCenter: parent.verticalCenter
text: !compareRunBtn.dt ? "Select both runs"
: compareRunBtn.dt.comparing
? "Comparing…"
: compareRunBtn.enabled
? "Run DTW Comparison"
: (compareRunBtn.masterMissing ? "No master for this type"
: compareRunBtn.sameFile ? "Choose different runs" : "Select both runs")
color: compareRunBtn.enabled ? Theme.tone100 : Theme.sideMutedText
font.pixelSize: Theme.fontSm
font.bold: true
}
BusyIndicator {
running: !!compareRunBtn.dt && compareRunBtn.dt.comparing
visible: !!compareRunBtn.dt && compareRunBtn.dt.comparing
width: 14; height: 14
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
// ── BOX 2: Context-Sensitive Rail Panel ────────────────────
Rectangle {
Layout.fillWidth: true
@@ -363,20 +641,23 @@ Rectangle {
}
// ── BOX 3: Hardware Status Footer ──────────────────────────
// Not relevant on the Data tab — Run Comparison takes its place above.
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: Theme.sideFooterConnection
visible: root.selectedTabIndex !== 1
radius: Theme.sidePanelRadius
border.color: Theme.sideBorder
border.width: 1
color: Theme.sidePanelBackground
clip: true
ColumnLayout {
anchors.fill: parent
anchors.margins: 14
spacing: 6
// Row 1: Title + status badge (unchanged)
// Row 1: Title + status badge
RowLayout {
spacing: 8
Layout.fillWidth: true
@@ -388,12 +669,15 @@ Rectangle {
font.pixelSize: Theme.fontSm
font.letterSpacing: 1.5
font.bold: true
Layout.fillWidth: true
elide: Text.ElideRight
}
Rectangle {
height: 18
width: badgeLabel.implicitWidth + 14
radius: 9
Layout.alignment: Qt.AlignVCenter
color: {
var s = deviceController.connectionStatus
if (s === "Connected") return Theme.statusSuccessColor
@@ -410,8 +694,6 @@ Rectangle {
font.bold: true
}
}
Item { Layout.fillWidth: true }
}
// Row 2: Status dot + descriptive text (replaces dotted trail)
@@ -595,8 +877,6 @@ Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Theme.workspaceBackground
border.color: Theme.workspaceBorder
border.width: 1
StackLayout {
anchors.fill: parent