Add video node visualization

This commit is contained in:
Joey Yakimowich-Payne 2026-01-31 08:59:32 -07:00
commit 8a8b039dcc
4 changed files with 56 additions and 7 deletions

View file

@ -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(

View file

@ -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;

View file

@ -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<warppipe::PortInfo> inputPorts;

View file

@ -18,6 +18,8 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <cmath>
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<int>(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);