From 8a8b039dcc5d0222c376d0fbaeb471659c5f0774 Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Sat, 31 Jan 2026 08:59:32 -0700 Subject: [PATCH] Add video node visualization --- gui/GraphEditorWidget.cpp | 2 ++ gui/WarpGraphModel.cpp | 41 +++++++++++++++++++++++++++----- gui/WarpGraphModel.h | 13 ++++++++++ tests/gui/warppipe_gui_tests.cpp | 7 +++++- 4 files changed, 56 insertions(+), 7 deletions(-) diff --git a/gui/GraphEditorWidget.cpp b/gui/GraphEditorWidget.cpp index b2f916d..bfde545 100644 --- a/gui/GraphEditorWidget.cpp +++ b/gui/GraphEditorWidget.cpp @@ -1252,6 +1252,8 @@ void GraphEditorWidget::rebuildMixerStrips() { const WarpNodeData *data = m_model->warpNodeData(nodeId); if (!data) continue; + if (!nodeHasVolume(WarpGraphModel::classifyNode(data->info))) + continue; auto *strip = new QWidget(); strip->setStyleSheet(QStringLiteral( diff --git a/gui/WarpGraphModel.cpp b/gui/WarpGraphModel.cpp index d1af6aa..b1bd3a5 100644 --- a/gui/WarpGraphModel.cpp +++ b/gui/WarpGraphModel.cpp @@ -230,6 +230,9 @@ QVariant WarpGraphModel::portData(QtNodes::NodeId nodeId, const auto &data = it->second; if (role == QtNodes::PortRole::DataType) { + WarpNodeType ntype = classifyNode(data.info); + if (ntype == WarpNodeType::kVideoSource || ntype == WarpNodeType::kVideoSink) + return QString("video"); return QString("audio"); } @@ -479,9 +482,11 @@ void WarpGraphModel::refreshFromClient() { } } - auto *volumeWidget = new NodeVolumeWidget(); - m_volumeWidgets[qtId] = volumeWidget; - m_volumeStates[qtId] = {}; + if (nodeHasVolume(nodeType)) { + auto *volumeWidget = new NodeVolumeWidget(); + m_volumeWidgets[qtId] = volumeWidget; + m_volumeStates[qtId] = {}; + } Q_EMIT nodeCreated(qtId); } @@ -801,6 +806,12 @@ WarpGraphModel::classifyNode(const warppipe::NodeInfo &info) { if (mc == "Stream/Output/Audio" || mc == "Stream/Input/Audio") { return WarpNodeType::kApplication; } + if (mc == "Video/Source") { + return WarpNodeType::kVideoSource; + } + if (mc == "Video/Sink") { + return WarpNodeType::kVideoSink; + } return WarpNodeType::kUnknown; } @@ -1071,9 +1082,11 @@ bool WarpGraphModel::loadLayout(const QString &path) { ? m_positions.at(qtId) : QPointF(0, 0); - auto *volumeWidget = new NodeVolumeWidget(); - m_volumeWidgets[qtId] = volumeWidget; - m_volumeStates[qtId] = {}; + if (nodeHasVolume(classifyNode(info))) { + auto *volumeWidget = new NodeVolumeWidget(); + m_volumeWidgets[qtId] = volumeWidget; + m_volumeStates[qtId] = {}; + } Q_EMIT nodeCreated(qtId); } @@ -1105,6 +1118,7 @@ void WarpGraphModel::autoArrange() { Column sources; Column apps; Column sinks; + Column video; for (const auto &[qtId, data] : m_nodes) { WarpNodeType type = classifyNode(data.info); @@ -1121,6 +1135,11 @@ void WarpGraphModel::autoArrange() { apps.ids.push_back(qtId); apps.maxWidth = std::max(apps.maxWidth, w); break; + case WarpNodeType::kVideoSource: + case WarpNodeType::kVideoSink: + video.ids.push_back(qtId); + video.maxWidth = std::max(video.maxWidth, w); + break; default: sinks.ids.push_back(qtId); sinks.maxWidth = std::max(sinks.maxWidth, w); @@ -1146,6 +1165,10 @@ void WarpGraphModel::autoArrange() { layoutColumn(apps, x); x += apps.maxWidth + kHorizontalGap * 3; layoutColumn(sinks, x); + if (!video.ids.empty()) { + x += sinks.maxWidth + kHorizontalGap * 3; + layoutColumn(video, x); + } } QVariant WarpGraphModel::styleForNode(WarpNodeType type, bool ghost) { @@ -1168,6 +1191,12 @@ QVariant WarpGraphModel::styleForNode(WarpNodeType type, bool ghost) { case WarpNodeType::kApplication: base = QColor(138, 104, 72); break; + case WarpNodeType::kVideoSource: + base = QColor(120, 80, 130); + break; + case WarpNodeType::kVideoSink: + base = QColor(100, 70, 140); + break; default: base = QColor(86, 94, 108); break; diff --git a/gui/WarpGraphModel.h b/gui/WarpGraphModel.h index b339057..807383d 100644 --- a/gui/WarpGraphModel.h +++ b/gui/WarpGraphModel.h @@ -20,8 +20,21 @@ enum class WarpNodeType : uint8_t { kVirtualSink, kVirtualSource, kApplication, + kVideoSource, + kVideoSink, }; +inline bool nodeHasVolume(WarpNodeType type) { + switch (type) { + case WarpNodeType::kVideoSource: + case WarpNodeType::kVideoSink: + case WarpNodeType::kUnknown: + return false; + default: + return true; + } +} + struct WarpNodeData { warppipe::NodeInfo info; std::vector inputPorts; diff --git a/tests/gui/warppipe_gui_tests.cpp b/tests/gui/warppipe_gui_tests.cpp index d5e2c6d..8fd9971 100644 --- a/tests/gui/warppipe_gui_tests.cpp +++ b/tests/gui/warppipe_gui_tests.cpp @@ -18,6 +18,8 @@ #include #include +#include + namespace { warppipe::ConnectionOptions TestOptions() { @@ -1093,7 +1095,8 @@ TEST_CASE("setNodeVolumeState syncs inline widget") { ns.mute = true; model.setNodeVolumeState(qtId, ns); - REQUIRE(vol->volume() == 70); + // Cubic scaling: slider = cbrt(0.7) * 100 ≈ 89 + REQUIRE(vol->volume() == static_cast(std::round(std::cbrt(0.7f) * 100.0f))); REQUIRE(vol->isMuted()); } @@ -1139,6 +1142,8 @@ TEST_CASE("preset saves and loads volume state") { } REQUIRE(found); + tc.client->Test_SetNodeVolume(warppipe::NodeId{100650}, 1.0f, false); + WarpGraphModel model2(tc.client.get()); model2.refreshFromClient(); auto qtId2 = model2.qtNodeIdForPw(100650);