import QtQuick import QtQuick.Controls import QtQuick.Layouts import ISC // Right-column bento stack: DTW readout, overlap settings, frame/time // controls, and scrubber readout. Pure view — all comparison state lives // on DataTab; scrubber writes go up via frameIndexRequested. ColumnLayout { id: panel required property real warpingDistance required property int frameOffset required property real frameOffsetSeconds required property real maxSensorDeviation required property int seriesLen required property bool hasSeries required property var trendDataA required property var trendDataB required property var timeDataA required property var timeDataB required property var frameForTime // function(seconds) -> frame index required property real frameIndex // bound from ComparisonChartCard.frameIndex // Overlap settings outputs (consumed by OverlapMapCard via DataTab) property alias overlapEnabled: enableOverlapCheck.checked property alias blendAmount: blendSlider.value signal frameIndexRequested(real idx) signal scrubberFocusRequested() spacing: 12 // Bento 3: DTW Comparison Readout PanelBox { Layout.fillWidth: true Layout.preferredHeight: readoutCol.implicitHeight + 24 ColumnLayout { id: readoutCol anchors.fill: parent anchors.margins: 12 spacing: 8 SectionTitle { text: "DTW READOUT" } ReadoutStat { label: "DTW ALIGNMENT DISTANCE" value: panel.warpingDistance >= 0 ? panel.warpingDistance.toFixed(2) : "--" valueColor: panel.warpingDistance >= 0 ? (panel.warpingDistance < 50 ? Theme.metricGood : panel.warpingDistance < 100 ? Theme.metricWarn : Theme.metricBad) : Theme.sideMutedText } ReadoutStat { label: "TEMPORAL FRAME OFFSET" value: panel.warpingDistance >= 0 ? ((panel.frameOffset >= 0 ? "+" : "") + panel.frameOffset + " frames / " + (panel.frameOffsetSeconds >= 0 ? "+" : "") + panel.frameOffsetSeconds.toFixed(1) + "s") : "--" valueColor: panel.warpingDistance >= 0 ? Theme.headingColor : Theme.sideMutedText } ReadoutStat { label: "MAX SENSOR DEVIATION" value: panel.maxSensorDeviation >= 0 ? panel.maxSensorDeviation.toFixed(2) + "°C" : "--" valueColor: panel.maxSensorDeviation >= 0 ? (panel.maxSensorDeviation < 1.0 ? Theme.metricGood : panel.maxSensorDeviation < 3.0 ? Theme.metricWarn : Theme.metricBad) : Theme.sideMutedText } } } // Bento 2: Overlap Map Settings PanelBox { Layout.fillWidth: true Layout.preferredHeight: overlapCol.implicitHeight + 24 ColumnLayout { id: overlapCol anchors.fill: parent anchors.margins: 12 spacing: 8 SectionTitle { text: "OVERLAP SETTINGS" } PanelCheckBox { id: enableOverlapCheck Layout.fillWidth: true checked: true text: "Enable Overlap Map" font.pixelSize: Theme.fontSm } ColumnLayout { Layout.fillWidth: true spacing: 6 visible: enableOverlapCheck.checked Rectangle { Layout.fillWidth: true height: 1 color: Theme.cardBorder } RowLayout { Layout.fillWidth: true spacing: 6 Label { text: "Blend" font.pixelSize: Theme.fontSm color: Theme.bodyColor Layout.preferredWidth: 40 } PanelSlider { id: blendSlider Layout.fillWidth: true from: 0; to: 100; value: 50 stepSize: 1 } Label { text: Math.round(blendSlider.value) + "%" font.pixelSize: Theme.fontSm color: Theme.sideMutedText Layout.preferredWidth: 30 } } // Diff color legend Row { Layout.fillWidth: true spacing: 10 Row { spacing: 3 Rectangle { width: 8; height: 8; radius: 4; color: Theme.sensorLow; anchors.verticalCenter: parent.verticalCenter } Label { text: "B cooler"; font.pixelSize: Theme.fontSm; color: Theme.sideMutedText } } Row { spacing: 3 Rectangle { width: 8; height: 8; radius: 4; color: Theme.sensorInRange; anchors.verticalCenter: parent.verticalCenter } Label { text: "Match"; font.pixelSize: Theme.fontSm; color: Theme.sideMutedText } } Row { spacing: 3 Rectangle { width: 8; height: 8; radius: 4; color: Theme.sensorHigh; anchors.verticalCenter: parent.verticalCenter } Label { text: "B hotter"; font.pixelSize: Theme.fontSm; color: Theme.sideMutedText } } } } } } // Bento: Frame / Time Controls PanelBox { Layout.fillWidth: true Layout.preferredHeight: frameTimeCol.implicitHeight + 32 ColumnLayout { id: frameTimeCol anchors.fill: parent anchors.margins: 16 spacing: 20 SectionTitle { text: "FRAME / TIME" } EditableScrubStat { label: "Frame" value: scrubReadout.scrubIdx unit: "/ " + Math.max(0, panel.seriesLen - 1) + (Math.max(0, panel.seriesLen - 1) <= 1 ? " frame" : " frames") editable: panel.hasSeries onValueEdited: (newValue) => { var maxVal = Math.max(0, panel.seriesLen - 1) var val = Math.max(0, Math.min(newValue, maxVal)) panel.frameIndexRequested(val) } onEditingDone: panel.scrubberFocusRequested() } EditableScrubStat { label: "Time" value: scrubReadout.scrubTime unit: "/ " + scrubReadout.maxTime.toFixed(1) + "s" editable: panel.hasSeries onValueEdited: (newValue) => { var closestIdx = panel.frameForTime(newValue) panel.frameIndexRequested(closestIdx) } onEditingDone: panel.scrubberFocusRequested() } } } // Bento: Scrub Readout PanelBox { Layout.fillWidth: true Layout.preferredHeight: scrubReadout.implicitHeight + 24 ColumnLayout { id: scrubReadout anchors.fill: parent anchors.margins: 12 spacing: 8 readonly property int scrubIdx: Math.round(panel.frameIndex) readonly property bool hasA: scrubIdx < panel.trendDataA.length readonly property bool hasB: scrubIdx < panel.trendDataB.length readonly property real tempA: hasA ? panel.trendDataA[scrubIdx] : 0 readonly property real tempB: hasB ? panel.trendDataB[scrubIdx] : 0 readonly property real scrubTime: { var t = scrubIdx < panel.timeDataA.length ? panel.timeDataA[scrubIdx] : (scrubIdx < panel.timeDataB.length ? panel.timeDataB[scrubIdx] : 0) return t } readonly property real maxTime: { var ta = panel.timeDataA.length > 0 ? panel.timeDataA[panel.timeDataA.length - 1] : 0 var tb = panel.timeDataB.length > 0 ? panel.timeDataB[panel.timeDataB.length - 1] : 0 return Math.max(ta, tb) } SectionTitle { text: "SCRUBBER READOUT" } ReadoutStat { label: "Temp (Run A)" value: scrubReadout.hasA ? scrubReadout.tempA.toFixed(1) + "°C" : "none" valueColor: Theme.primaryAccent } ReadoutStat { label: "Temp (Run B)" value: scrubReadout.hasB ? scrubReadout.tempB.toFixed(1) + "°C" : "none" valueColor: Theme.themeSkill } ReadoutStat { label: "Difference" value: (scrubReadout.hasA && scrubReadout.hasB) ? Math.abs(scrubReadout.tempA - scrubReadout.tempB).toFixed(1) + "°C" : "none" } } } Item { Layout.fillHeight: true } }