#pragma once #include #include #include #include #include #include #include #include #include #include #include enum class WarpNodeType : uint8_t { kUnknown = 0, kHardwareSink, kHardwareSource, 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; std::vector outputPorts; }; /// Data for an app-group node (multiple PipeWire streams collapsed into one visual node). struct AppGroupData { std::string groupKey; std::vector memberPwIds; ///< PipeWire node IDs in this group. /// canonical port index → list of actual member PortIds for fan-out. std::unordered_map> outputPortMap; std::unordered_map> inputPortMap; }; class WarpGraphModel : public QtNodes::AbstractGraphModel { Q_OBJECT public: explicit WarpGraphModel(warppipe::Client *client, QObject *parent = nullptr); QtNodes::NodeId newNodeId() override; std::unordered_set allNodeIds() const override; std::unordered_set allConnectionIds( QtNodes::NodeId nodeId) const override; std::unordered_set connections( QtNodes::NodeId nodeId, QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override; bool connectionExists(QtNodes::ConnectionId const connectionId) const override; QtNodes::NodeId addNode(QString const nodeType = QString()) override; bool connectionPossible(QtNodes::ConnectionId const connectionId) const override; void addConnection(QtNodes::ConnectionId const connectionId) override; bool nodeExists(QtNodes::NodeId const nodeId) const override; QVariant nodeData(QtNodes::NodeId nodeId, QtNodes::NodeRole role) const override; bool setNodeData(QtNodes::NodeId nodeId, QtNodes::NodeRole role, QVariant value) override; QVariant portData(QtNodes::NodeId nodeId, QtNodes::PortType portType, QtNodes::PortIndex portIndex, QtNodes::PortRole role) const override; bool setPortData(QtNodes::NodeId nodeId, QtNodes::PortType portType, QtNodes::PortIndex portIndex, QVariant const &value, QtNodes::PortRole role = QtNodes::PortRole::Data) override; bool deleteConnection(QtNodes::ConnectionId const connectionId) override; bool deleteNode(QtNodes::NodeId const nodeId) override; QJsonObject saveNode(QtNodes::NodeId const) const override; void loadNode(QJsonObject const &) override; void refreshFromClient(); const WarpNodeData *warpNodeData(QtNodes::NodeId nodeId) const; QtNodes::NodeId qtNodeIdForPw(uint32_t pwNodeId) const; bool isGhost(QtNodes::NodeId nodeId) const; bool ghostConnectionExists(QtNodes::ConnectionId connectionId) const; std::unordered_set allGhostConnectionIds( QtNodes::NodeId nodeId) const; void setPendingPosition(const std::string &nodeName, QPointF pos); static WarpNodeType classifyNode(const warppipe::NodeInfo &info); uint32_t findPwNodeIdByName(const std::string &name) const; bool isGroupNode(QtNodes::NodeId nodeId) const; const AppGroupData *appGroupData(QtNodes::NodeId nodeId) const; struct NodeVolumeState { float volume = 1.0f; bool mute = false; }; void setNodeVolumeState(QtNodes::NodeId nodeId, const NodeVolumeState &state); NodeVolumeState nodeVolumeState(QtNodes::NodeId nodeId) const; void setNodePeakLevel(QtNodes::NodeId nodeId, float level); float nodePeakLevel(QtNodes::NodeId nodeId) const; struct ConnectionChannel { int index = 0; int count = 1; }; ConnectionChannel connectionChannel(QtNodes::ConnectionId cId) const; Q_SIGNALS: void beginBatchUpdate(); void endBatchUpdate(); void nodeVolumeChanged(QtNodes::NodeId nodeId, NodeVolumeState previous, NodeVolumeState current); public: struct ViewState { double scale; double centerX; double centerY; double zoomSensitivity; double zoomMin; double zoomMax; int splitterGraph; int splitterSidebar; int connectionStyle; bool valid; }; void saveLayout(const QString &path) const; void saveLayout(const QString &path, const ViewState &viewState) const; bool loadLayout(const QString &path); ViewState savedViewState() const; void clearSavedPositions(); void autoArrange(); private: static QString captionForNode(const warppipe::NodeInfo &info); static QVariant styleForNode(WarpNodeType type, bool ghost); QPointF nextPosition(const WarpNodeData &data); QPointF findNonOverlappingPosition(QPointF candidate, const WarpNodeData &data) const; std::optional findAppGroupPosition(const WarpNodeData &data) const; static std::string appGroupKey(const warppipe::NodeInfo &info); static QSize estimateNodeSize(const WarpNodeData &data); warppipe::Client *m_client = nullptr; QtNodes::NodeId m_nextNodeId = 1; std::unordered_map m_nodes; std::unordered_map m_pwToQt; std::unordered_set m_connections; std::unordered_map m_linkIdToConn; std::unordered_map m_positions; std::unordered_map m_sizes; std::unordered_set m_ghostNodes; std::unordered_set m_ghostConnections; static constexpr double kHorizontalGap = 40.0; static constexpr double kVerticalGap = 30.0; static constexpr double kMaxRowWidth = 1400.0; double m_nextX = 0.0; double m_nextY = 0.0; double m_rowMaxHeight = 0.0; bool m_refreshing = false; struct PendingGhostConnection { std::string outNodeName; std::string outPortName; std::string inNodeName; std::string inPortName; }; std::unordered_map m_pendingPositions; std::unordered_map m_savedPositions; std::vector m_pendingGhostConnections; ViewState m_savedViewState{}; std::unordered_map m_volumeStates; std::unordered_map> m_volumeWidgets; mutable std::unordered_map m_styleCache; std::unordered_map m_peakLevels; std::unordered_map m_connectionChannels; std::unordered_map m_appGroups; std::unordered_map m_groupKeyToQt; std::unordered_map m_pwToGroupQt; struct GroupPortRef { QtNodes::NodeId groupQtId; QtNodes::PortIndex portIndex; bool isInput; }; std::unordered_map m_portToGroupPort; void rebuildGroupPortMap(QtNodes::NodeId groupQtId); void recomputeConnectionChannels(); };