Compare commits
2 commits
916965488c
...
621d67ebab
| Author | SHA1 | Date | |
|---|---|---|---|
| 621d67ebab | |||
| 07e2fb4c5a |
4 changed files with 785 additions and 145 deletions
|
|
@ -802,13 +802,13 @@ void GraphEditorWidget::onContextMenuRequested(const QPoint &pos) {
|
||||||
QPointF scenePos = m_view->mapToScene(pos);
|
QPointF scenePos = m_view->mapToScene(pos);
|
||||||
m_lastContextMenuScenePos = scenePos;
|
m_lastContextMenuScenePos = scenePos;
|
||||||
|
|
||||||
|
bool hitNode = false;
|
||||||
uint32_t hitPwNodeId = 0;
|
uint32_t hitPwNodeId = 0;
|
||||||
QtNodes::NodeId hitQtNodeId = 0;
|
QtNodes::NodeId hitQtNodeId = 0;
|
||||||
for (auto nodeId : m_model->allNodeIds()) {
|
for (auto nodeId : m_model->allNodeIds()) {
|
||||||
const WarpNodeData *data = m_model->warpNodeData(nodeId);
|
const WarpNodeData *data = m_model->warpNodeData(nodeId);
|
||||||
if (!data) {
|
if (!data)
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
QPointF nodePos =
|
QPointF nodePos =
|
||||||
m_model->nodeData(nodeId, QtNodes::NodeRole::Position).toPointF();
|
m_model->nodeData(nodeId, QtNodes::NodeRole::Position).toPointF();
|
||||||
QSize nodeSize =
|
QSize nodeSize =
|
||||||
|
|
@ -817,12 +817,13 @@ void GraphEditorWidget::onContextMenuRequested(const QPoint &pos) {
|
||||||
if (nodeRect.contains(scenePos)) {
|
if (nodeRect.contains(scenePos)) {
|
||||||
hitPwNodeId = data->info.id.value;
|
hitPwNodeId = data->info.id.value;
|
||||||
hitQtNodeId = nodeId;
|
hitQtNodeId = nodeId;
|
||||||
|
hitNode = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QPoint screenPos = m_view->mapToGlobal(pos);
|
QPoint screenPos = m_view->mapToGlobal(pos);
|
||||||
if (hitPwNodeId != 0) {
|
if (hitNode) {
|
||||||
showNodeContextMenu(screenPos, hitPwNodeId, hitQtNodeId);
|
showNodeContextMenu(screenPos, hitPwNodeId, hitQtNodeId);
|
||||||
} else {
|
} else {
|
||||||
showCanvasContextMenu(screenPos, scenePos);
|
showCanvasContextMenu(screenPos, scenePos);
|
||||||
|
|
@ -1673,13 +1674,29 @@ void GraphEditorWidget::updateMeters() {
|
||||||
const WarpNodeData *data = m_model->warpNodeData(nodeId);
|
const WarpNodeData *data = m_model->warpNodeData(nodeId);
|
||||||
if (!data || !row.meter)
|
if (!data || !row.meter)
|
||||||
continue;
|
continue;
|
||||||
auto peak = m_client->NodeMeterPeak(data->info.id);
|
|
||||||
if (peak.ok()) {
|
const AppGroupData *group = m_model->appGroupData(nodeId);
|
||||||
float level = std::max(peak.value.peak_left, peak.value.peak_right);
|
if (group) {
|
||||||
row.meter->setLevel(level);
|
float maxLevel = 0.0f;
|
||||||
m_model->setNodePeakLevel(nodeId, level);
|
for (uint32_t memberPwId : group->memberPwIds) {
|
||||||
if (level > 0.001f)
|
auto peak = m_client->NodeMeterPeak(warppipe::NodeId{memberPwId});
|
||||||
|
if (peak.ok())
|
||||||
|
maxLevel = std::max(maxLevel,
|
||||||
|
std::max(peak.value.peak_left, peak.value.peak_right));
|
||||||
|
}
|
||||||
|
row.meter->setLevel(maxLevel);
|
||||||
|
m_model->setNodePeakLevel(nodeId, maxLevel);
|
||||||
|
if (maxLevel > 0.001f)
|
||||||
anyActive = true;
|
anyActive = true;
|
||||||
|
} else {
|
||||||
|
auto peak = m_client->NodeMeterPeak(data->info.id);
|
||||||
|
if (peak.ok()) {
|
||||||
|
float level = std::max(peak.value.peak_left, peak.value.peak_right);
|
||||||
|
row.meter->setLevel(level);
|
||||||
|
m_model->setNodePeakLevel(nodeId, level);
|
||||||
|
if (level > 0.001f)
|
||||||
|
anyActive = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1720,8 +1737,16 @@ void GraphEditorWidget::rebuildNodeMeters() {
|
||||||
if (!data)
|
if (!data)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
new_pw_ids[data->info.id.value] = true;
|
const AppGroupData *group = m_model->appGroupData(nodeId);
|
||||||
m_client->EnsureNodeMeter(data->info.id);
|
if (group) {
|
||||||
|
for (uint32_t memberPwId : group->memberPwIds) {
|
||||||
|
new_pw_ids[memberPwId] = true;
|
||||||
|
m_client->EnsureNodeMeter(warppipe::NodeId{memberPwId});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
new_pw_ids[data->info.id.value] = true;
|
||||||
|
m_client->EnsureNodeMeter(data->info.id);
|
||||||
|
}
|
||||||
|
|
||||||
auto *row = new QWidget();
|
auto *row = new QWidget();
|
||||||
auto *rowLayout = new QHBoxLayout(row);
|
auto *rowLayout = new QHBoxLayout(row);
|
||||||
|
|
@ -2299,8 +2324,21 @@ void GraphEditorWidget::updateNodeDetailsPanel(QtNodes::NodeId nodeId) {
|
||||||
QString::fromStdString(info.media_role));
|
QString::fromStdString(info.media_role));
|
||||||
}
|
}
|
||||||
|
|
||||||
addField(QStringLiteral("NODE ID"),
|
const AppGroupData *groupData = m_model->appGroupData(nodeId);
|
||||||
QString::number(info.id.value));
|
if (groupData) {
|
||||||
|
addField(QStringLiteral("STREAMS"),
|
||||||
|
QString::number(groupData->memberPwIds.size()));
|
||||||
|
QString memberIds;
|
||||||
|
for (uint32_t pwId : groupData->memberPwIds) {
|
||||||
|
if (!memberIds.isEmpty())
|
||||||
|
memberIds += QStringLiteral(", ");
|
||||||
|
memberIds += QString::number(pwId);
|
||||||
|
}
|
||||||
|
addField(QStringLiteral("MEMBER NODE IDS"), memberIds);
|
||||||
|
} else {
|
||||||
|
addField(QStringLiteral("NODE ID"),
|
||||||
|
QString::number(info.id.value));
|
||||||
|
}
|
||||||
|
|
||||||
if (!data->inputPorts.empty()) {
|
if (!data->inputPorts.empty()) {
|
||||||
layout->addSpacing(8);
|
layout->addSpacing(8);
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,17 @@ QPainterPath SquareConnectionPainter::orthogonalPath(
|
||||||
constexpr double kNodePad = 15.0;
|
constexpr double kNodePad = 15.0;
|
||||||
|
|
||||||
auto const cId = cgo.connectionId();
|
auto const cId = cgo.connectionId();
|
||||||
double const spread = static_cast<double>(cId.outPortIndex) * kSpacing;
|
|
||||||
|
double spread = static_cast<double>(cId.outPortIndex) * kSpacing;
|
||||||
|
auto *sceneForChannel = cgo.nodeScene();
|
||||||
|
if (sceneForChannel) {
|
||||||
|
auto *mdl = dynamic_cast<WarpGraphModel *>(&sceneForChannel->graphModel());
|
||||||
|
if (mdl) {
|
||||||
|
auto ch = mdl->connectionChannel(cId);
|
||||||
|
spread = (static_cast<double>(ch.index) - (ch.count - 1) / 2.0)
|
||||||
|
* kSpacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
double const dy = in.y() - out.y();
|
double const dy = in.y() - out.y();
|
||||||
|
|
||||||
|
|
@ -114,51 +124,52 @@ QPainterPath SquareConnectionPainter::orthogonalPath(
|
||||||
// out -------+ seg 1 + corner 1
|
// out -------+ seg 1 + corner 1
|
||||||
//
|
//
|
||||||
|
|
||||||
double rightX = out.x() + kMinStub + spread;
|
double railOffset = 0.0;
|
||||||
double leftX = in.x() - kMinStub - spread;
|
if (sceneForChannel) {
|
||||||
double midY = (out.y() + in.y()) / 2.0;
|
auto *mdl2 = dynamic_cast<WarpGraphModel *>(&sceneForChannel->graphModel());
|
||||||
|
if (mdl2) {
|
||||||
|
auto ch = mdl2->connectionChannel(cId);
|
||||||
|
railOffset = static_cast<double>(ch.index) * kSpacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double rightX = out.x() + kMinStub + railOffset;
|
||||||
|
double leftX = in.x() - kMinStub - railOffset;
|
||||||
|
double midY = (out.y() + in.y()) / 2.0 + spread;
|
||||||
|
|
||||||
// Use actual node geometry when available so the path routes cleanly
|
|
||||||
// around both nodes instead of cutting through them.
|
|
||||||
auto *scene = cgo.nodeScene();
|
auto *scene = cgo.nodeScene();
|
||||||
if (scene) {
|
if (scene) {
|
||||||
auto *outNGO = scene->nodeGraphicsObject(cId.outNodeId);
|
auto *outNGO = scene->nodeGraphicsObject(cId.outNodeId);
|
||||||
auto *inNGO = scene->nodeGraphicsObject(cId.inNodeId);
|
auto *inNGO = scene->nodeGraphicsObject(cId.inNodeId);
|
||||||
if (outNGO && inNGO) {
|
if (outNGO && inNGO) {
|
||||||
// Map node scene bounds into the CGO's local coordinate space
|
|
||||||
// (endPoint() values live there too).
|
|
||||||
QRectF const outRect =
|
QRectF const outRect =
|
||||||
cgo.mapRectFromScene(outNGO->sceneBoundingRect());
|
cgo.mapRectFromScene(outNGO->sceneBoundingRect());
|
||||||
QRectF const inRect =
|
QRectF const inRect =
|
||||||
cgo.mapRectFromScene(inNGO->sceneBoundingRect());
|
cgo.mapRectFromScene(inNGO->sceneBoundingRect());
|
||||||
|
|
||||||
// Push vertical rails outside both nodes.
|
|
||||||
double const rightEdge = std::max(outRect.right(), inRect.right());
|
double const rightEdge = std::max(outRect.right(), inRect.right());
|
||||||
rightX = std::max(rightX, rightEdge + kNodePad + spread);
|
rightX = std::max(rightX, rightEdge + kNodePad + railOffset);
|
||||||
|
|
||||||
double const leftEdge = std::min(outRect.left(), inRect.left());
|
double const leftEdge = std::min(outRect.left(), inRect.left());
|
||||||
leftX = std::min(leftX, leftEdge - kNodePad - spread);
|
leftX = std::min(leftX, leftEdge - kNodePad - railOffset);
|
||||||
|
|
||||||
// Place the horizontal crossover in the gap between nodes when
|
|
||||||
// they don't overlap vertically; otherwise route above or below.
|
|
||||||
double const topInner =
|
double const topInner =
|
||||||
std::min(outRect.bottom(), inRect.bottom());
|
std::min(outRect.bottom(), inRect.bottom());
|
||||||
double const botInner =
|
double const botInner =
|
||||||
std::max(outRect.top(), inRect.top());
|
std::max(outRect.top(), inRect.top());
|
||||||
|
|
||||||
if (botInner > topInner + 2.0 * kNodePad) {
|
if (botInner > topInner + 2.0 * kNodePad) {
|
||||||
// Vertical gap exists — clamp midY into the gap.
|
|
||||||
midY = std::clamp(midY, topInner + kNodePad,
|
midY = std::clamp(midY, topInner + kNodePad,
|
||||||
botInner - kNodePad);
|
botInner - kNodePad);
|
||||||
} else {
|
} else {
|
||||||
// Nodes overlap vertically — pick the shorter detour.
|
|
||||||
double const above =
|
double const above =
|
||||||
std::min(outRect.top(), inRect.top()) - kNodePad;
|
std::min(outRect.top(), inRect.top()) - kNodePad;
|
||||||
double const below =
|
double const below =
|
||||||
std::max(outRect.bottom(), inRect.bottom()) + kNodePad;
|
std::max(outRect.bottom(), inRect.bottom()) + kNodePad;
|
||||||
midY = (std::abs(midY - above) < std::abs(midY - below))
|
double baseMidY = (out.y() + in.y()) / 2.0;
|
||||||
? above
|
midY = (std::abs(baseMidY - above) < std::abs(baseMidY - below))
|
||||||
: below;
|
? above + spread
|
||||||
|
: below + spread;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -43,6 +43,15 @@ struct WarpNodeData {
|
||||||
std::vector<warppipe::PortInfo> outputPorts;
|
std::vector<warppipe::PortInfo> outputPorts;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Data for an app-group node (multiple PipeWire streams collapsed into one visual node).
|
||||||
|
struct AppGroupData {
|
||||||
|
std::string groupKey;
|
||||||
|
std::vector<uint32_t> memberPwIds; ///< PipeWire node IDs in this group.
|
||||||
|
/// canonical port index → list of actual member PortIds for fan-out.
|
||||||
|
std::unordered_map<unsigned int, std::vector<warppipe::PortId>> outputPortMap;
|
||||||
|
std::unordered_map<unsigned int, std::vector<warppipe::PortId>> inputPortMap;
|
||||||
|
};
|
||||||
|
|
||||||
class WarpGraphModel : public QtNodes::AbstractGraphModel {
|
class WarpGraphModel : public QtNodes::AbstractGraphModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
|
@ -87,6 +96,9 @@ public:
|
||||||
|
|
||||||
uint32_t findPwNodeIdByName(const std::string &name) const;
|
uint32_t findPwNodeIdByName(const std::string &name) const;
|
||||||
|
|
||||||
|
bool isGroupNode(QtNodes::NodeId nodeId) const;
|
||||||
|
const AppGroupData *appGroupData(QtNodes::NodeId nodeId) const;
|
||||||
|
|
||||||
struct NodeVolumeState {
|
struct NodeVolumeState {
|
||||||
float volume = 1.0f;
|
float volume = 1.0f;
|
||||||
bool mute = false;
|
bool mute = false;
|
||||||
|
|
@ -98,6 +110,12 @@ public:
|
||||||
void setNodePeakLevel(QtNodes::NodeId nodeId, float level);
|
void setNodePeakLevel(QtNodes::NodeId nodeId, float level);
|
||||||
float nodePeakLevel(QtNodes::NodeId nodeId) const;
|
float nodePeakLevel(QtNodes::NodeId nodeId) const;
|
||||||
|
|
||||||
|
struct ConnectionChannel {
|
||||||
|
int index = 0;
|
||||||
|
int count = 1;
|
||||||
|
};
|
||||||
|
ConnectionChannel connectionChannel(QtNodes::ConnectionId cId) const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void beginBatchUpdate();
|
void beginBatchUpdate();
|
||||||
void endBatchUpdate();
|
void endBatchUpdate();
|
||||||
|
|
@ -173,4 +191,20 @@ private:
|
||||||
std::unordered_map<QtNodes::NodeId, QPointer<QWidget>> m_volumeWidgets;
|
std::unordered_map<QtNodes::NodeId, QPointer<QWidget>> m_volumeWidgets;
|
||||||
mutable std::unordered_map<QtNodes::NodeId, QVariant> m_styleCache;
|
mutable std::unordered_map<QtNodes::NodeId, QVariant> m_styleCache;
|
||||||
std::unordered_map<QtNodes::NodeId, float> m_peakLevels;
|
std::unordered_map<QtNodes::NodeId, float> m_peakLevels;
|
||||||
|
std::unordered_map<QtNodes::ConnectionId, ConnectionChannel> m_connectionChannels;
|
||||||
|
|
||||||
|
std::unordered_map<QtNodes::NodeId, AppGroupData> m_appGroups;
|
||||||
|
std::unordered_map<std::string, QtNodes::NodeId> m_groupKeyToQt;
|
||||||
|
std::unordered_map<uint32_t, QtNodes::NodeId> m_pwToGroupQt;
|
||||||
|
|
||||||
|
struct GroupPortRef {
|
||||||
|
QtNodes::NodeId groupQtId;
|
||||||
|
QtNodes::PortIndex portIndex;
|
||||||
|
bool isInput;
|
||||||
|
};
|
||||||
|
std::unordered_map<uint32_t, GroupPortRef> m_portToGroupPort;
|
||||||
|
|
||||||
|
void rebuildGroupPortMap(QtNodes::NodeId groupQtId);
|
||||||
|
|
||||||
|
void recomputeConnectionChannels();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue