refactor(qml): extract sidebar panels and data tab comparison cards to modular components
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
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 }
|
||||
}
|
||||
Reference in New Issue
Block a user