diff --git a/src/pygui/ISC/Tabs/components/TransportBar.qml b/src/pygui/ISC/Tabs/components/TransportBar.qml index 590bb08..51b7516 100644 --- a/src/pygui/ISC/Tabs/components/TransportBar.qml +++ b/src/pygui/ISC/Tabs/components/TransportBar.qml @@ -3,101 +3,337 @@ import QtQuick.Controls import QtQuick.Layouts import ISC -RowLayout { - spacing: 2 - - component TBtn: Button { - implicitWidth: 44 - implicitHeight: 32 - hoverEnabled: true - font.pixelSize: 13 - background: Rectangle { - color: parent.pressed ? Theme.transportButtonHover - : parent.hovered ? Theme.transportButtonBg - : Theme.transportBackground - radius: Theme.radiusSm - } - contentItem: Text { - text: parent.text - color: Theme.headingColor - font: parent.font - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } +// ===== Control Bar ===== +// Mode-aware footer: cross-fades between Live and Review layouts. +// Both variants render as a single centered pill — no full-width bar. +Item { + id: bar + Layout.fillWidth: true + implicitHeight: hasContent ? 80 : 0 + Behavior on implicitHeight { + NumberAnimation { duration: Theme.durationBase; easing.type: Theme.easeStandard } } + clip: true - // Frame counter badge on the left - Rectangle { - id: frameCounter - implicitWidth: frameCounterText.implicitWidth + 16 - implicitHeight: 32 - color: Theme.fieldBackground - border.color: Theme.cardBorder - border.width: Theme.borderThin - radius: Theme.radiusSm + // Hide entirely when in review mode with no file loaded + readonly property bool hasContent: { + if (streamController.mode === "live") return true; + return streamController.loadedFile !== ""; + } + readonly property bool isLive: streamController.mode === "live" - Text { - id: frameCounterText + // ── LIVE MODE ───────────────────────────────────────────────── + Item { + id: liveContent + anchors.fill: parent + opacity: bar.isLive ? 1.0 : 0.0 + visible: opacity > 0 + Behavior on opacity { + NumberAnimation { duration: Theme.durationBase; easing.type: Theme.easeStandard } + } + + // Single centered pill for Received + Errors + Stop + Rectangle { anchors.centerIn: parent - text: "Frame " + (streamController.frameIndex + 1) + " / " + streamController.frameTotal - color: Theme.headingColor - font.pixelSize: 11 - font.weight: Font.Medium + implicitWidth: liveRow.implicitWidth + 48 + implicitHeight: 68 + radius: Theme.radiusMd + color: Theme.sidePanelBackground + border.color: Theme.sideBorder + border.width: 1 + + Row { + id: liveRow + anchors.centerIn: parent + spacing: 24 + + // Received counter + Text { + anchors.verticalCenter: parent.verticalCenter + text: "Received: " + streamController.receivedCount + color: Theme.sideMutedText + font.family: Theme.uiFontFamily + font.pixelSize: Theme.fontMd + font.weight: Font.Medium + } + + // Separator + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 1 + height: 36 + color: Theme.sideBorder + } + + // Errors counter + Text { + anchors.verticalCenter: parent.verticalCenter + text: "Errors: " + streamController.errorCount + color: streamController.errorCount > 0 + ? Theme.statusWarningColor : Theme.sideMutedText + font.family: Theme.uiFontFamily + font.pixelSize: Theme.fontMd + font.weight: Font.Medium + } + + // Separator + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 1 + height: 36 + color: Theme.sideBorder + } + + // STOP button + Button { + id: stopBtn + anchors.verticalCenter: parent.verticalCenter + implicitWidth: 112 + implicitHeight: 48 + hoverEnabled: true + text: "STOP" + font.family: Theme.uiFontFamily + font.pixelSize: Theme.fontSm + font.weight: Font.Medium + font.letterSpacing: 1.0 + + background: Rectangle { + radius: 8 + color: stopBtn.pressed ? Theme.transportButtonHover + : Theme.transportButtonBg + border.color: Theme.sideBorder + border.width: 1 + Behavior on color { + ColorAnimation { duration: Theme.durationFast } + } + } + contentItem: Text { + text: parent.text + color: Theme.headingColor + font: parent.font + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: streamController.stopStream() + } + } } } - // Left fill — centers the button cluster - Item { Layout.fillWidth: true } - - TBtn { text: "⏮"; onClicked: streamController.step(-1) } - TBtn { text: "⏹"; onClicked: streamController.stop() } - TBtn { text: "⏸"; onClicked: streamController.pause() } - // Play: slightly wider; leftPadding nudge compensates for ▶ optical offset - TBtn { - text: "▶" - implicitWidth: 52 - contentItem: Text { - text: parent.text - color: Theme.headingColor - font: parent.font - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - leftPadding: 2 + // ── REVIEW MODE ────────────────────────────────────────────── + Item { + id: reviewContent + anchors.fill: parent + opacity: !bar.isLive ? 1.0 : 0.0 + visible: opacity > 0 + Behavior on opacity { + NumberAnimation { duration: Theme.durationBase; easing.type: Theme.easeStandard } } - onClicked: streamController.play() - } - TBtn { text: "⏭"; onClicked: streamController.step(1) } - // Right fill — balances centering - Item { Layout.fillWidth: true } + // Single centered pill for frame counter + transport + speed + Rectangle { + anchors.centerIn: parent + implicitWidth: reviewRow.implicitWidth + 48 + implicitHeight: 68 + radius: Theme.radiusMd + color: Theme.sidePanelBackground + border.color: Theme.sideBorder + border.width: 1 - // Speed cycle - Button { - id: speedBtn - property var speeds: [1, 2, 5] - property int idx: 0 - implicitWidth: 44 - implicitHeight: 32 - hoverEnabled: true - text: speeds[idx] + "×" - font.pixelSize: 11 - font.weight: Font.Medium - background: Rectangle { - color: parent.pressed ? Theme.transportButtonHover - : parent.hovered ? Theme.transportButtonBg - : Theme.transportBackground - radius: Theme.radiusSm - } - contentItem: Text { - text: parent.text - color: Theme.bodyColor - font: parent.font - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - onClicked: { - idx = (idx + 1) % speeds.length - streamController.setSpeed(speeds[idx]) + Row { + id: reviewRow + anchors.centerIn: parent + spacing: 24 + + // Frame counter + Text { + anchors.verticalCenter: parent.verticalCenter + text: "Frame " + (streamController.frameIndex + 1) + + " / " + streamController.frameTotal + color: Theme.sideMutedText + font.family: Theme.uiFontFamily + font.pixelSize: Theme.fontMd + font.weight: Font.Medium + leftPadding: 8 + } + + // Separator 1 + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 1 + height: 36 + color: Theme.sideBorder + } + + // Media Buttons Row + Row { + anchors.verticalCenter: parent.verticalCenter + spacing: 12 + + // Skip-start + Button { + id: skipStartBtn + anchors.verticalCenter: parent.verticalCenter + implicitWidth: 56 + implicitHeight: 56 + hoverEnabled: true + text: "⏮" + font.pixelSize: 24 + background: Rectangle { + radius: 8 + color: skipStartBtn.pressed ? Theme.transportButtonHover + : skipStartBtn.hovered ? Theme.transportButtonBg + : "transparent" + Behavior on color { + ColorAnimation { duration: Theme.durationFast } + } + } + contentItem: Text { + text: parent.text + color: Theme.headingColor + font: parent.font + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: streamController.step(-1) + } + + // Stop + Button { + id: reviewStopBtn + anchors.verticalCenter: parent.verticalCenter + implicitWidth: 56 + implicitHeight: 56 + hoverEnabled: true + text: "■" + font.pixelSize: 22 + background: Rectangle { + radius: 8 + color: reviewStopBtn.pressed ? Theme.transportButtonHover + : reviewStopBtn.hovered ? Theme.transportButtonBg + : "transparent" + Behavior on color { + ColorAnimation { duration: Theme.durationFast } + } + } + contentItem: Text { + text: parent.text + color: Theme.headingColor + font: parent.font + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: streamController.stop() + } + + // Play / Pause toggle + Button { + id: playPauseBtn + anchors.verticalCenter: parent.verticalCenter + implicitWidth: 64 + implicitHeight: 56 + hoverEnabled: true + text: streamController.playing ? "⏸" : "▶" + font.pixelSize: 24 + background: Rectangle { + radius: 8 + color: playPauseBtn.pressed ? Theme.transportButtonHover + : playPauseBtn.hovered ? Theme.transportButtonBg + : "transparent" + Behavior on color { + ColorAnimation { duration: Theme.durationFast } + } + } + contentItem: Text { + text: parent.text + color: Theme.headingColor + font: parent.font + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + leftPadding: parent.text === "▶" ? 4 : 0 + } + onClicked: { + if (streamController.playing) { + streamController.pause(); + } else { + streamController.play(); + } + } + } + + // Skip-end + Button { + id: skipEndBtn + anchors.verticalCenter: parent.verticalCenter + implicitWidth: 56 + implicitHeight: 56 + hoverEnabled: true + text: "⏭" + font.pixelSize: 24 + background: Rectangle { + radius: 8 + color: skipEndBtn.pressed ? Theme.transportButtonHover + : skipEndBtn.hovered ? Theme.transportButtonBg + : "transparent" + Behavior on color { + ColorAnimation { duration: Theme.durationFast } + } + } + contentItem: Text { + text: parent.text + color: Theme.headingColor + font: parent.font + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: streamController.step(1) + } + } + + // Separator 2 + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 1 + height: 36 + color: Theme.sideBorder + } + + // Speed capsule button + Button { + id: speedBtn + anchors.verticalCenter: parent.verticalCenter + property var speeds: [1, 2, 4] + property int idx: 0 + implicitWidth: 72 + implicitHeight: 48 + hoverEnabled: true + text: speeds[idx] + "x" + font.family: Theme.uiFontFamily + font.pixelSize: Theme.fontSm + font.weight: Font.Medium + background: Rectangle { + radius: 8 + color: speedBtn.pressed ? Theme.transportButtonHover + : Theme.transportButtonBg + border.color: Theme.sideBorder + border.width: 1 + Behavior on color { + ColorAnimation { duration: Theme.durationFast } + } + } + contentItem: Text { + text: parent.text + color: Theme.headingColor + font: parent.font + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: { + idx = (idx + 1) % speeds.length; + streamController.setSpeed(speeds[idx]); + } + } + } } } }