feat(transport): redesign transport bar

This commit is contained in:
jack
2026-06-29 14:57:52 -07:00
parent c9cb7ab8d8
commit db21f201f6
+296 -60
View File
@@ -3,101 +3,337 @@ import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import ISC import ISC
RowLayout { // ===== Control Bar =====
spacing: 2 // 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
component TBtn: Button { // Hide entirely when in review mode with no file loaded
implicitWidth: 44 readonly property bool hasContent: {
implicitHeight: 32 if (streamController.mode === "live") return true;
hoverEnabled: true return streamController.loadedFile !== "";
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
} }
readonly property bool isLive: streamController.mode === "live"
// ── 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 }
} }
// Frame counter badge on the left // Single centered pill for Received + Errors + Stop
Rectangle { Rectangle {
id: frameCounter
implicitWidth: frameCounterText.implicitWidth + 16
implicitHeight: 32
color: Theme.fieldBackground
border.color: Theme.cardBorder
border.width: Theme.borderThin
radius: Theme.radiusSm
Text {
id: frameCounterText
anchors.centerIn: parent anchors.centerIn: parent
text: "Frame " + (streamController.frameIndex + 1) + " / " + streamController.frameTotal implicitWidth: liveRow.implicitWidth + 48
color: Theme.headingColor implicitHeight: 68
font.pixelSize: 11 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 font.weight: Font.Medium
} }
// Separator
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: 1
height: 36
color: Theme.sideBorder
} }
// Left fill — centers the button cluster // Errors counter
Item { Layout.fillWidth: true } 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
}
TBtn { text: "⏮"; onClicked: streamController.step(-1) } // Separator
TBtn { text: "⏹"; onClicked: streamController.stop() } Rectangle {
TBtn { text: "⏸"; onClicked: streamController.pause() } anchors.verticalCenter: parent.verticalCenter
// Play: slightly wider; leftPadding nudge compensates for ▶ optical offset width: 1
TBtn { height: 36
text: "▶" color: Theme.sideBorder
implicitWidth: 52 }
// 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 { contentItem: Text {
text: parent.text text: parent.text
color: Theme.headingColor color: Theme.headingColor
font: parent.font font: parent.font
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
leftPadding: 2
} }
onClicked: streamController.play() onClicked: streamController.stopStream()
}
}
}
} }
TBtn { text: "⏭"; onClicked: streamController.step(1) }
// Right fill — balances centering // ── REVIEW MODE ──────────────────────────────────────────────
Item { Layout.fillWidth: true } 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 }
}
// Speed cycle // 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
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 { Button {
id: speedBtn id: speedBtn
property var speeds: [1, 2, 5] anchors.verticalCenter: parent.verticalCenter
property var speeds: [1, 2, 4]
property int idx: 0 property int idx: 0
implicitWidth: 44 implicitWidth: 72
implicitHeight: 32 implicitHeight: 48
hoverEnabled: true hoverEnabled: true
text: speeds[idx] + "×" text: speeds[idx] + "x"
font.pixelSize: 11 font.family: Theme.uiFontFamily
font.pixelSize: Theme.fontSm
font.weight: Font.Medium font.weight: Font.Medium
background: Rectangle { background: Rectangle {
color: parent.pressed ? Theme.transportButtonHover radius: 8
: parent.hovered ? Theme.transportButtonBg color: speedBtn.pressed ? Theme.transportButtonHover
: Theme.transportBackground : Theme.transportButtonBg
radius: Theme.radiusSm border.color: Theme.sideBorder
border.width: 1
Behavior on color {
ColorAnimation { duration: Theme.durationFast }
}
} }
contentItem: Text { contentItem: Text {
text: parent.text text: parent.text
color: Theme.bodyColor color: Theme.headingColor
font: parent.font font: parent.font
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
onClicked: { onClicked: {
idx = (idx + 1) % speeds.length idx = (idx + 1) % speeds.length;
streamController.setSpeed(speeds[idx]) streamController.setSpeed(speeds[idx]);
}
}
}
} }
} }
} }