From c9cb7ab8d86029acbddc0e6b074b955ec8099241 Mon Sep 17 00:00:00 2001 From: jack Date: Mon, 29 Jun 2026 14:57:13 -0700 Subject: [PATCH] feat(panels): redesign ReadoutPanel and SourcePanel to match mockup ReadoutPanel: - Move section headers inside card containers - Redesign readout table with uppercase labels, code fonts, color mapping - Add full-width dividers, fix vertical spacing with fillHeight spacer - Unify typography with Theme tokens SourcePanel: - Bump font sizes to new standard (fontMd primary, fontSm secondary) - Replace unicode button text with edit.svg/refresh.svg IconImages - Add styled hover tooltips on edit and refresh buttons --- .../ISC/Tabs/components/ReadoutPanel.qml | 547 ++++++++++++------ src/pygui/ISC/Tabs/components/SourcePanel.qml | 198 ++++--- 2 files changed, 498 insertions(+), 247 deletions(-) diff --git a/src/pygui/ISC/Tabs/components/ReadoutPanel.qml b/src/pygui/ISC/Tabs/components/ReadoutPanel.qml index 8fef514..1f313ea 100644 --- a/src/pygui/ISC/Tabs/components/ReadoutPanel.qml +++ b/src/pygui/ISC/Tabs/components/ReadoutPanel.qml @@ -3,8 +3,11 @@ import QtQuick.Controls import QtQuick.Layouts import ISC +// ===== Readout Panel ===== +// Right-rail panel with three bordered sections: READOUT, DISPLAY, THRESHOLDS. +// Cards match the left-rail SOURCE / CONNECTION FLOW style. ColumnLayout { - spacing: 0 + spacing: 6 property var s: streamController.stats property alias showLabels: labelsToggle.checked property alias heatmapBlend: heatmapSlider.value @@ -21,13 +24,23 @@ ColumnLayout { 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 } + } + Text { anchors.centerIn: parent text: "✓" - font.pixelSize: 13 + font.pixelSize: Theme.fontSm font.bold: true color: Theme.panelBackground - visible: toggle.checked + opacity: toggle.checked ? 1.0 : 0.0 + Behavior on opacity { + NumberAnimation { duration: Theme.durationFast } + } verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } @@ -36,268 +49,462 @@ ColumnLayout { 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 || 11 + font.pixelSize: toggle.font.pixelSize || Theme.fontXs } } - // ── READOUT ─────────────────────────────────────────────────────────── - Label { - text: "READOUT" - color: Theme.bodyColor - font.pixelSize: 10 - font.letterSpacing: 1.8 - font.weight: Font.Medium - topPadding: 6 - bottomPadding: 8 + component BadgePill: Rectangle { + implicitWidth: badgeLabel.implicitWidth + 10 + implicitHeight: 10 + radius: 9 + color: Theme.fieldBackground + border.color: Theme.cardBorder + border.width: 1 + + property alias text: badgeLabel.text + Label { + id: badgeLabel + anchors.centerIn: parent + color: Theme.bodyColor + font.pixelSize: Theme.font2xs + font.weight: Font.Medium + } } Rectangle { Layout.fillWidth: true - implicitHeight: statsLayout.implicitHeight + 20 - color: Theme.subtleSectionBackground - radius: Theme.radiusSm - border.color: Theme.cardBorder + implicitHeight: readoutCard.implicitHeight + 24 + color: Theme.sidePanelBackground + radius: Theme.sidePanelRadius + border.color: Theme.sideBorder border.width: 1 ColumnLayout { - id: statsLayout - anchors { fill: parent; margins: 10 } - spacing: 8 + id: readoutCard + anchors { + left: parent.left + right: parent.right + top: parent.top + topMargin: 12 + } + spacing: 0 + + // Header Inside the Box + Label { + text: "READOUT" + color: Theme.sideMutedText + font.family: Theme.uiFontFamily + font.pixelSize: Theme.fontXs + font.letterSpacing: 1.5 + font.bold: true + Layout.leftMargin: 14 + Layout.rightMargin: 14 + Layout.topMargin: 2 + Layout.bottomMargin: 10 + } // Min Temp Row RowLayout { Layout.fillWidth: true - Label { text: "Min Temp"; color: Theme.bodyColor; font.pixelSize: 11 } + Layout.leftMargin: 14 + Layout.rightMargin: 14 + Layout.preferredHeight: 32 + Label { + text: "MIN TEMP" + color: Theme.sideMutedText + font.family: Theme.uiFontFamily + font.pixelSize: Theme.fontXs + font.bold: true + } Item { Layout.fillWidth: true } RowLayout { spacing: 4 Label { text: s.min !== undefined ? s.min : "—" color: Theme.sensorLow - font.pixelSize: 13; font.weight: Font.Bold + font.family: Theme.codeFontFamily + font.pixelSize: Theme.fontMd + font.bold: true } Label { - text: s.minIndex !== undefined ? "#" + s.minIndex : "" - color: Theme.bodyColor - font.pixelSize: 10 + visible: (s.minIndex !== undefined) && s.minIndex >= 0 + text: "#" + (s.minIndex !== undefined ? s.minIndex : "") + color: Theme.sideMutedText + font.family: Theme.uiFontFamily + font.pixelSize: Theme.font2xs + Layout.alignment: Qt.AlignBottom + bottomPadding: 3 } } } + Rectangle { + Layout.fillWidth: true + height: 1 + color: Theme.cardBorder + } + // Max Temp Row RowLayout { Layout.fillWidth: true - Label { text: "Max Temp"; color: Theme.bodyColor; font.pixelSize: 11 } + Layout.leftMargin: 14 + Layout.rightMargin: 14 + Layout.preferredHeight: 32 + Label { + text: "MAX TEMP" + color: Theme.sideMutedText + font.family: Theme.uiFontFamily + font.pixelSize: Theme.fontXs + font.bold: true + } Item { Layout.fillWidth: true } RowLayout { spacing: 4 Label { text: s.max !== undefined ? s.max : "—" color: Theme.sensorHigh - font.pixelSize: 13; font.weight: Font.Bold + font.family: Theme.codeFontFamily + font.pixelSize: Theme.fontMd + font.bold: true } Label { - text: s.maxIndex !== undefined ? "#" + s.maxIndex : "" - color: Theme.bodyColor - font.pixelSize: 10 + visible: (s.maxIndex !== undefined) && s.maxIndex >= 0 + text: "#" + (s.maxIndex !== undefined ? s.maxIndex : "") + color: Theme.sideMutedText + font.family: Theme.uiFontFamily + font.pixelSize: Theme.font2xs + Layout.alignment: Qt.AlignBottom + bottomPadding: 3 } } } - Rectangle { Layout.fillWidth: true; height: 1; color: Theme.cardBorder } + Rectangle { + Layout.fillWidth: true + height: 1 + color: Theme.cardBorder + } // Differential Row RowLayout { Layout.fillWidth: true - Label { text: "Differential"; color: Theme.bodyColor; font.pixelSize: 11 } + Layout.leftMargin: 14 + Layout.rightMargin: 14 + Layout.preferredHeight: 32 + Label { + text: "DIFF" + color: Theme.sideMutedText + font.family: Theme.uiFontFamily + font.pixelSize: Theme.fontXs + font.bold: true + } Item { Layout.fillWidth: true } Label { text: s.diff !== undefined ? s.diff : "—" - color: Theme.headingColor - font.pixelSize: 13; font.weight: Font.Bold + color: Theme.isDarkMode ? "#A78BFA" : "#8B5CF6" + font.family: Theme.codeFontFamily + font.pixelSize: Theme.fontMd + font.bold: true } } + Rectangle { + Layout.fillWidth: true + height: 1 + color: Theme.cardBorder + } + // Average Row RowLayout { Layout.fillWidth: true - Label { text: "Average"; color: Theme.bodyColor; font.pixelSize: 11 } + Layout.leftMargin: 14 + Layout.rightMargin: 14 + Layout.preferredHeight: 32 + Label { + text: "AVERAGE" + color: Theme.sideMutedText + font.family: Theme.uiFontFamily + font.pixelSize: Theme.fontXs + font.bold: true + } Item { Layout.fillWidth: true } Label { text: s.avg !== undefined ? s.avg : "—" color: Theme.headingColor - font.pixelSize: 13; font.weight: Font.Bold + font.family: Theme.codeFontFamily + font.pixelSize: Theme.fontMd + font.bold: true } } - Rectangle { Layout.fillWidth: true; height: 1; color: Theme.cardBorder } + Rectangle { + Layout.fillWidth: true + height: 1 + color: Theme.cardBorder + } // Sigma Row RowLayout { Layout.fillWidth: true - Label { text: "Sigma (σ)"; color: Theme.bodyColor; font.pixelSize: 11 } + Layout.leftMargin: 14 + Layout.rightMargin: 14 + Layout.preferredHeight: 32 + Label { + text: "SIGMA (Σ)" + color: Theme.sideMutedText + font.family: Theme.uiFontFamily + font.pixelSize: Theme.fontXs + font.bold: true + } Item { Layout.fillWidth: true } Label { text: s.sigma !== undefined ? s.sigma : "—" color: Theme.headingColor - font.pixelSize: 13; font.weight: Font.Bold + font.family: Theme.codeFontFamily + font.pixelSize: Theme.fontMd + font.bold: true } } + Rectangle { + Layout.fillWidth: true + height: 1 + color: Theme.cardBorder + } + // 3-Sigma Row RowLayout { Layout.fillWidth: true - Label { text: "3σ Value"; color: Theme.bodyColor; font.pixelSize: 11 } + Layout.leftMargin: 14 + Layout.rightMargin: 14 + Layout.preferredHeight: 32 + Label { + text: "3Σ VALUE" + color: Theme.sideMutedText + font.family: Theme.uiFontFamily + font.pixelSize: Theme.fontXs + font.bold: true + } Item { Layout.fillWidth: true } Label { text: s.threeSigma !== undefined ? s.threeSigma : "—" - color: Theme.bodyColor - font.pixelSize: 12; font.weight: Font.Medium + color: Theme.headingColor + font.family: Theme.codeFontFamily + font.pixelSize: Theme.fontMd + font.bold: true } } } } - Item { height: 12 } - Rectangle { Layout.fillWidth: true; height: 1; color: Theme.cardBorder } - Item { height: 12 } - - // ── DISPLAY ─────────────────────────────────────────────────────────── - Label { - text: "DISPLAY" - color: Theme.bodyColor - font.pixelSize: 10 - font.letterSpacing: 1.8 - font.weight: Font.Medium - topPadding: 6 - bottomPadding: 8 - } - - PanelCheckBox { - id: thicknessToggle - text: "Show Thickness" - checked: false - font.pixelSize: 11 - onCheckedChanged: waferMapItem.showThickness = checked - } - - PanelCheckBox { - id: labelsToggle - text: "Labels" - checked: true - font.pixelSize: 11 - } - - PanelCheckBox { - id: clusterAverageToggle - text: "Average Clusters" - checked: streamController.clusterAveragingEnabled - font.pixelSize: 11 - onCheckedChanged: streamController.clusterAveragingEnabled = checked - } - - RowLayout { + Rectangle { Layout.fillWidth: true - spacing: 6 - Label { - text: "Heatmap" - color: Theme.bodyColor - font.pixelSize: 11 - Layout.preferredWidth: 52 - } - Slider { - id: heatmapSlider - from: 0; to: 1; value: 0 - Layout.fillWidth: true - ToolTip.visible: hovered - ToolTip.text: Math.round(value * 100) + "%" + implicitHeight: displayCard.implicitHeight + 24 + color: Theme.sidePanelBackground + radius: Theme.sidePanelRadius + border.color: Theme.sideBorder + border.width: 1 + + ColumnLayout { + id: displayCard + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: 12 + } + spacing: 8 + + Label { + text: "DISPLAY" + color: Theme.sideMutedText + font.family: Theme.uiFontFamily + font.pixelSize: Theme.fontXs + font.letterSpacing: 1.5 + font.bold: true + Layout.bottomMargin: 4 + } + + PanelCheckBox { + id: thicknessToggle + text: "Show Thickness" + checked: false + font.pixelSize: Theme.fontXs + onCheckedChanged: waferMapItem.showThickness = checked + } + + PanelCheckBox { + id: labelsToggle + text: "Labels" + checked: true + font.pixelSize: Theme.fontXs + } + + PanelCheckBox { + id: clusterAverageToggle + text: "Average Clusters" + checked: streamController.clusterAveragingEnabled + font.pixelSize: Theme.fontXs + onCheckedChanged: streamController.clusterAveragingEnabled = checked + } + + Rectangle { + Layout.fillWidth: true + height: 1 + color: Theme.cardBorder + } + + RowLayout { + Layout.fillWidth: true + spacing: 6 + Label { + text: "Heatmap" + color: Theme.bodyColor + font.pixelSize: Theme.fontXs + Layout.preferredWidth: 52 + } + Slider { + id: heatmapSlider + from: 0 + to: 1 + value: 0 + Layout.fillWidth: true + ToolTip.visible: hovered + ToolTip.text: Math.round(value * 100) + "%" + + handle: Rectangle { + x: heatmapSlider.leftPadding + heatmapSlider.visualPosition * (heatmapSlider.availableWidth - width) + y: heatmapSlider.topPadding + heatmapSlider.availableHeight / 2 - height / 2 + implicitWidth: 18 + implicitHeight: 18 + radius: 9 + color: Theme.primaryAccent + scale: heatmapSlider.pressed ? 1.15 : 1.0 + Behavior on scale { + NumberAnimation { duration: Theme.durationFast; easing.type: Theme.easeStandard } + } + } + } + Label { + text: Math.round(heatmapSlider.value * 100) + "%" + color: Theme.bodyColor + font.pixelSize: Theme.fontXs + font.weight: Font.Medium + Layout.preferredWidth: 32 + horizontalAlignment: Text.AlignRight + } + } } } - Item { height: 12 } - Rectangle { Layout.fillWidth: true; height: 1; color: Theme.cardBorder } - Item { height: 12 } - - // ── THRESHOLDS ──────────────────────────────────────────────────────── - Label { - text: "THRESHOLDS" - color: Theme.bodyColor - font.pixelSize: 10 - font.letterSpacing: 1.8 - font.weight: Font.Medium - topPadding: 6 - bottomPadding: 8 - } - - Label { - text: "Set Point (°C)" - color: Theme.bodyColor - font.pixelSize: 11 - bottomPadding: 3 - } - TextField { - id: spField + Rectangle { Layout.fillWidth: true - text: "149.0" - font.pixelSize: 12 - inputMethodHints: Qt.ImhFormattedNumbersOnly - color: Theme.fieldText - placeholderTextColor: Theme.fieldPlaceholder - selectedTextColor: Theme.fieldBackground - selectionColor: Theme.fieldText - background: Rectangle { - radius: Theme.radiusXs - color: Theme.fieldBackground - border.width: spField.activeFocus ? Theme.borderStrong : Theme.borderThin - border.color: spField.activeFocus ? Theme.fieldBorderFocus : Theme.fieldBorder + implicitHeight: thresholdCard.implicitHeight + 24 + color: Theme.sidePanelBackground + radius: Theme.sidePanelRadius + border.color: Theme.sideBorder + border.width: 1 + + ColumnLayout { + id: thresholdCard + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: 12 + } + spacing: 6 + + Label { + text: "THRESHOLDS" + color: Theme.sideMutedText + font.family: Theme.uiFontFamily + font.pixelSize: Theme.fontXs + font.letterSpacing: 1.5 + font.bold: true + Layout.bottomMargin: 4 + } + + Label { + text: "Set Point (°C)" + color: Theme.bodyColor + font.pixelSize: Theme.fontXs + } + TextField { + id: spField + Layout.fillWidth: true + text: "149.0" + font.pixelSize: Theme.fontSm + inputMethodHints: Qt.ImhFormattedNumbersOnly + color: Theme.fieldText + placeholderTextColor: Theme.fieldPlaceholder + selectedTextColor: Theme.fieldBackground + selectionColor: Theme.fieldText + background: Rectangle { + radius: Theme.radiusXs + color: Theme.fieldBackground + border.width: spField.activeFocus ? Theme.borderStrong : Theme.borderThin + border.color: spField.activeFocus ? Theme.fieldBorderFocus : Theme.fieldBorder + Behavior on color { + ColorAnimation { duration: Theme.durationFast } + } + Behavior on border.color { + ColorAnimation { duration: Theme.durationFast } + } + } + onEditingFinished: pushThresholds() + } + + Label { + text: "Margin (±°C)" + color: Theme.bodyColor + font.pixelSize: Theme.fontXs + } + TextField { + id: mgField + Layout.fillWidth: true + text: "1.0" + font.pixelSize: Theme.fontSm + inputMethodHints: Qt.ImhFormattedNumbersOnly + color: Theme.fieldText + placeholderTextColor: Theme.fieldPlaceholder + selectedTextColor: Theme.fieldBackground + selectionColor: Theme.fieldText + background: Rectangle { + radius: Theme.radiusXs + color: Theme.fieldBackground + border.width: mgField.activeFocus ? Theme.borderStrong : Theme.borderThin + border.color: mgField.activeFocus ? Theme.fieldBorderFocus : Theme.fieldBorder + Behavior on color { + ColorAnimation { duration: Theme.durationFast } + } + Behavior on border.color { + ColorAnimation { duration: Theme.durationFast } + } + } + onEditingFinished: pushThresholds() + } + + PanelCheckBox { + id: autoCheck + text: "Auto range (mean ± 1σ)" + checked: true + font.pixelSize: Theme.fontXs + onCheckedChanged: pushThresholds() + } } - onEditingFinished: pushThresholds() } - Item { height: 10 } - - Label { - text: "Margin (±°C)" - color: Theme.bodyColor - font.pixelSize: 11 - bottomPadding: 3 + Item { + Layout.fillHeight: true } - TextField { - id: mgField - Layout.fillWidth: true - text: "1.0" - font.pixelSize: 12 - inputMethodHints: Qt.ImhFormattedNumbersOnly - color: Theme.fieldText - placeholderTextColor: Theme.fieldPlaceholder - selectedTextColor: Theme.fieldBackground - selectionColor: Theme.fieldText - background: Rectangle { - radius: Theme.radiusXs - color: Theme.fieldBackground - border.width: mgField.activeFocus ? Theme.borderStrong : Theme.borderThin - border.color: mgField.activeFocus ? Theme.fieldBorderFocus : Theme.fieldBorder - } - onEditingFinished: pushThresholds() - } - - Item { height: 10 } - - PanelCheckBox { - id: autoCheck - text: "Auto range (mean ± 1σ)" - checked: true - font.pixelSize: 11 - onCheckedChanged: pushThresholds() - } - - Item { Layout.fillHeight: true } function pushThresholds() { - var sp = parseFloat(spField.text) || 149.0 - var mg = parseFloat(mgField.text) || 1.0 - streamController.setThresholds(sp, mg, autoCheck.checked) + var sp = parseFloat(spField.text) || 149.0; + var mg = parseFloat(mgField.text) || 1.0; + streamController.setThresholds(sp, mg, autoCheck.checked); } } diff --git a/src/pygui/ISC/Tabs/components/SourcePanel.qml b/src/pygui/ISC/Tabs/components/SourcePanel.qml index d44ed39..c235376 100644 --- a/src/pygui/ISC/Tabs/components/SourcePanel.qml +++ b/src/pygui/ISC/Tabs/components/SourcePanel.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Controls.impl import ISC import ".." @@ -14,10 +15,11 @@ ColumnLayout { Label { text: "SOURCE" - color: Theme.bodyColor - font.pixelSize: 10 - font.letterSpacing: 1.8 - font.weight: Font.Medium + color: Theme.sideMutedText + font.family: Theme.uiFontFamily + font.pixelSize: Theme.fontSm + font.letterSpacing: 1.5 + font.bold: true topPadding: 6 bottomPadding: 8 Layout.fillWidth: true @@ -26,51 +28,79 @@ ColumnLayout { Button { id: editBtn - text: "✎" - implicitWidth: 26 - implicitHeight: 26 + implicitWidth: 28 + implicitHeight: 28 hoverEnabled: true - font.pixelSize: 14 background: Rectangle { - color: editBtn.pressed ? Theme.buttonPressed - : editBtn.hovered ? Theme.buttonNeutralHover - : "transparent" + color: editBtn.pressed ? Theme.buttonPressed : editBtn.hovered ? Theme.buttonNeutralHover : "transparent" radius: Theme.radiusSm } - contentItem: Text { - text: parent.text + contentItem: IconImage { + source: "../icons/edit.svg" + sourceSize.width: 16 + sourceSize.height: 16 color: Theme.bodyColor - font: parent.font - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter } onClicked: { - file_browser.refreshFiles() - csvEditorDialog.open() + file_browser.refreshFiles(); + csvEditorDialog.open(); + } + + ToolTip { + visible: editBtn.hovered + text: "Edit" + delay: 400 + font.pixelSize: Theme.fontXs + font.family: Theme.uiFontFamily + contentItem: Text { + text: parent.text + color: Theme.headingColor + font: parent.font + } + background: Rectangle { + color: Theme.cardBackground + border.color: Theme.sideBorder + border.width: 1 + radius: Theme.radiusXs + } } } Button { id: refreshBtn - text: "⟳" - implicitWidth: 26 - implicitHeight: 26 + implicitWidth: 28 + implicitHeight: 28 hoverEnabled: true - font.pixelSize: 14 background: Rectangle { - color: refreshBtn.pressed ? Theme.buttonPressed - : refreshBtn.hovered ? Theme.buttonNeutralHover - : "transparent" + color: refreshBtn.pressed ? Theme.buttonPressed : refreshBtn.hovered ? Theme.buttonNeutralHover : "transparent" radius: Theme.radiusSm } - contentItem: Text { - text: parent.text + contentItem: IconImage { + source: "../icons/refresh.svg" + sourceSize.width: 16 + sourceSize.height: 16 color: Theme.bodyColor - font: parent.font - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter } onClicked: file_browser.refreshFiles() + + ToolTip { + visible: refreshBtn.hovered + text: "Refresh" + delay: 400 + font.pixelSize: Theme.fontXs + font.family: Theme.uiFontFamily + contentItem: Text { + text: parent.text + color: Theme.headingColor + font: parent.font + } + background: Rectangle { + color: Theme.cardBackground + border.color: Theme.sideBorder + border.width: 1 + radius: Theme.radiusXs + } + } } } @@ -79,14 +109,12 @@ ColumnLayout { id: dirBtn Layout.fillWidth: true hoverEnabled: true - implicitHeight: 32 + implicitHeight: 36 leftPadding: 8 rightPadding: 8 background: Rectangle { radius: Theme.radiusSm - color: dirBtn.pressed ? Theme.buttonNeutralPressed - : dirBtn.hovered ? Theme.buttonNeutralHover - : Theme.buttonNeutralBackground + color: dirBtn.pressed ? Theme.buttonNeutralPressed : dirBtn.hovered ? Theme.buttonNeutralHover : Theme.buttonNeutralBackground border.width: Theme.borderThin border.color: Theme.fieldBorder } @@ -95,12 +123,12 @@ ColumnLayout { Label { text: "▤" color: Theme.bodyColor - font.pixelSize: 12 + font.pixelSize: Theme.fontSm } Label { text: file_browser.currentDirectory.split("/").pop() || file_browser.currentDirectory color: Theme.headingColor - font.pixelSize: 11 + font.pixelSize: Theme.fontSm elide: Text.ElideMiddle Layout.fillWidth: true } @@ -113,7 +141,7 @@ ColumnLayout { id: filter Layout.fillWidth: true placeholderText: "Filter…" - font.pixelSize: 11 + font.pixelSize: Theme.fontSm color: Theme.fieldText placeholderTextColor: Theme.fieldPlaceholder selectedTextColor: Theme.fieldBackground @@ -123,6 +151,12 @@ ColumnLayout { color: Theme.fieldBackground border.width: filter.activeFocus ? Theme.borderStrong : Theme.borderThin border.color: filter.activeFocus ? Theme.fieldBorderFocus : Theme.fieldBorder + Behavior on color { + ColorAnimation { duration: Theme.durationFast } + } + Behavior on border.color { + ColorAnimation { duration: Theme.durationFast } + } } } @@ -146,17 +180,14 @@ ColumnLayout { focusPolicy: Qt.NoFocus readonly property bool matchesFilter: { - if (filter.text === "") return true - var q = filter.text.toLowerCase() - return (modelData.baseName + modelData.waferType + modelData.date) - .toLowerCase().indexOf(q) >= 0 + if (filter.text === "") + return true; + var q = filter.text.toLowerCase(); + return (modelData.baseName + modelData.waferType + modelData.date).toLowerCase().indexOf(q) >= 0; } // Active only in review mode; no highlight during live streaming - readonly property bool isActive: - streamController.mode === "review" && - streamController.loadedFile !== "" && - modelData.fileName === streamController.loadedFile + readonly property bool isActive: streamController.mode === "review" && streamController.loadedFile !== "" && modelData.fileName === streamController.loadedFile visible: matchesFilter height: matchesFilter ? implicitHeight : 0 @@ -164,10 +195,11 @@ ColumnLayout { onClicked: streamController.loadFile(modelData.fileName) background: Rectangle { - color: fileItem.isActive - ? Qt.rgba(1, 1, 1, 0.06) - : fileItem.hovered ? Qt.rgba(1, 1, 1, 0.04) : "transparent" + color: fileItem.isActive ? Qt.rgba(1, 1, 1, 0.06) : fileItem.hovered ? Qt.rgba(1, 1, 1, 0.04) : "transparent" radius: Theme.radiusSm + Behavior on color { + ColorAnimation { duration: Theme.durationFast } + } // Left accent bar — shown only when active Rectangle { @@ -175,15 +207,21 @@ ColumnLayout { width: 3 radius: 1.5 anchors { - top: parent.top; bottom: parent.bottom; left: parent.left - topMargin: 5; bottomMargin: 5 + top: parent.top + bottom: parent.bottom + left: parent.left + topMargin: 5 + bottomMargin: 5 } color: { - var t = modelData.waferType - if (t === "A" || t === "E" || t === "P") return "#3B82F6" - if (t === "B" || t === "C" || t === "D") return "#10B981" - if (t === "Z") return "#8B5CF6" - return Theme.headingColor + var t = modelData.waferType; + if (t === "A" || t === "E" || t === "P") + return "#3B82F6"; + if (t === "B" || t === "C" || t === "D") + return "#10B981"; + if (t === "Z") + return "#8B5CF6"; + return Theme.headingColor; } } } @@ -193,22 +231,26 @@ ColumnLayout { // Wafer-type avatar (circle) Rectangle { - implicitWidth: 28; implicitHeight: 28 + implicitWidth: 28 + implicitHeight: 28 Layout.preferredWidth: 28 Layout.preferredHeight: 28 Layout.alignment: Qt.AlignVCenter radius: 14 color: { - var t = modelData.waferType - if (t === "A" || t === "E" || t === "P") return "#1D4ED8" - if (t === "B" || t === "C" || t === "D") return "#065F46" - if (t === "Z") return "#7C3AED" - return "#374151" + var t = modelData.waferType; + if (t === "A" || t === "E" || t === "P") + return "#1D4ED8"; + if (t === "B" || t === "C" || t === "D") + return "#065F46"; + if (t === "Z") + return "#7C3AED"; + return "#374151"; } Label { anchors.centerIn: parent text: modelData.waferType || "?" - font.pixelSize: 12 + font.pixelSize: Theme.fontMd font.weight: Font.Bold color: "#FFFFFF" } @@ -223,7 +265,7 @@ ColumnLayout { Label { text: modelData.serialNumber ? (modelData.waferType + modelData.serialNumber) : modelData.baseName color: Theme.headingColor - font.pixelSize: 13 + font.pixelSize: Theme.fontMd font.weight: Font.Bold elide: Text.ElideRight Layout.fillWidth: true @@ -235,24 +277,24 @@ ColumnLayout { Layout.fillWidth: true Label { text: { - var d = modelData.date || "" - return d.length >= 10 ? d.substring(0, 10) : (d || "—") + var d = modelData.date || ""; + return d.length >= 10 ? d.substring(0, 10) : (d || "—"); } color: Theme.bodyColor - font.pixelSize: 10 + font.pixelSize: Theme.fontSm } Label { visible: (modelData.timeStr || "") !== "" text: "·" color: Theme.bodyColor - font.pixelSize: 10 + font.pixelSize: Theme.fontSm opacity: 0.45 } Label { visible: (modelData.timeStr || "") !== "" text: modelData.timeStr || "" color: Theme.bodyColor - font.pixelSize: 10 + font.pixelSize: Theme.fontSm opacity: 0.65 } } @@ -260,18 +302,20 @@ ColumnLayout { // Tertiary: edited metadata (chamber · notes) if any Label { visible: { - var c = modelData.chamber || "" - var n = modelData.notes || "" - return c !== "" || n !== "" + var c = modelData.chamber || ""; + var n = modelData.notes || ""; + return c !== "" || n !== ""; } text: { - var parts = [] - if (modelData.chamber) parts.push(modelData.chamber) - if (modelData.notes) parts.push(modelData.notes) - return parts.join(" · ") + var parts = []; + if (modelData.chamber) + parts.push(modelData.chamber); + if (modelData.notes) + parts.push(modelData.notes); + return parts.join(" · "); } color: Theme.bodyColor - font.pixelSize: 9 + font.pixelSize: Theme.fontXs opacity: 0.7 elide: Text.ElideRight Layout.fillWidth: true @@ -285,7 +329,7 @@ ColumnLayout { Label { text: file_browser.files.length + " files · CSV only" color: Theme.bodyColor - font.pixelSize: 10 + font.pixelSize: Theme.fontSm } SelectFileDialog {