feat: implement master file configuration, UI indicators, and a Run Comparison tool for the Data tab
This commit is contained in:
+287
-7
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user