240 lines
9.0 KiB
QML
240 lines
9.0 KiB
QML
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 }
|
|
}
|