// SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023-2099 Anthony Rabine #pragma once #include "src/story_project.h" #include #include #include #include #include #include #include #include #include #include #include #include using QtNodes::ConnectionStyle; using QtNodes::NodeRole; using QtNodes::StyleCollection; using QtNodes::NodeDelegateModelRegistry; using ConnectionId = QtNodes::ConnectionId; using ConnectionPolicy = QtNodes::ConnectionPolicy; using NodeFlag = QtNodes::NodeFlag; using NodeId = QtNodes::NodeId; using NodeRole = QtNodes::NodeRole; using PortIndex = QtNodes::PortIndex; using PortRole = QtNodes::PortRole; using PortType = QtNodes::PortType; using StyleCollection = QtNodes::StyleCollection; using QtNodes::NodeDelegateModel; using QtNodes::InvalidNodeId; class PortAddRemoveWidget; #include "story_node_base.h" #include "story_project.h" template class ThreadSafeQueue { std::mutex mutex; std::condition_variable cond_var; std::queue queue; public: void push(T&& item) { { std::lock_guard lock(mutex); queue.push(item); } cond_var.notify_one(); } T& front() { std::unique_lock lock(mutex); cond_var.wait(lock, [&]{ return !queue.empty(); }); return queue.front(); } void pop() { std::lock_guard lock(mutex); queue.pop(); } }; struct AudioCommand { std::string order; std::string filename; }; /** * The class implements a bare minimum required to demonstrate a model-based * graph. */ class StoryGraphModel : public QtNodes::AbstractGraphModel { Q_OBJECT public: public: StoryGraphModel(StoryProject &project); ~StoryGraphModel() override; std::set categories() { return m_categories; } void addModel(const QString &name, const QString &category) { m_modelNames[name] = category; m_categories.insert(category); } std::unordered_map models() { return m_modelNames; } std::unordered_set allNodeIds() const override; std::unordered_set allConnectionIds(NodeId const nodeId) const override; std::unordered_set connections(NodeId nodeId, PortType portType, PortIndex portIndex) const override; bool connectionExists(ConnectionId const connectionId) const override; NodeId addNode(QString const nodeType = QString()) override; NodeId addNode(std::shared_ptr model); /** * Connection is possible when graph contains no connectivity data * in both directions `Out -> In` and `In -> Out`. */ bool connectionPossible(ConnectionId const connectionId) const override; void addConnection(ConnectionId const connectionId) override; bool nodeExists(NodeId const nodeId) const override; QVariant nodeData(NodeId nodeId, NodeRole role) const override; bool setNodeData(NodeId nodeId, NodeRole role, QVariant value) override; QVariant portData(NodeId nodeId, PortType portType, PortIndex portIndex, PortRole role) const override; bool setPortData(NodeId nodeId, PortType portType, PortIndex portIndex, QVariant const &value, PortRole role = PortRole::Data) override; bool deleteConnection(ConnectionId const connectionId) override; bool deleteNode(NodeId const nodeId) override; void addPort(NodeId nodeId, PortType portType, PortIndex portIndex); void removePort(NodeId nodeId, PortType portType, PortIndex first); NodeId newNodeId() override { return _nextNodeId++; } template void registerNode(const std::string& name) { m_registry.insert(typename Registry::value_type(name, Factory::create_func)); } std::shared_ptr createNode(const std::string& key) { typename Registry::const_iterator i = m_registry.find(key); if (i == m_registry.end()) { throw std::invalid_argument(std::string(__PRETTY_FUNCTION__) + ": key not registered"); } else return i->second(*this); } StoryProject &GetProject() { return m_project; }; QString GetImagesDir() const; QString GetSoundsDir() const; QString BuildFullImagePath(const QString &fileName) const; QString BuildFullSoundPath(const QString &fileName) const; void Clear(); void SetInternalData(NodeId nodeId, nlohmann::json &j); nlohmann::json Save() const; void Load(const nlohmann::json &j); nlohmann::json SaveNode(NodeId const) const; void LoadNode(const nlohmann::json &nodeJson); // Creates a new node std::string Build(); // Centralized for wide usage void PlaySound(const QString &fileName); NodeId FindFirstNode() const; signals: void sigChooseFile(NodeId id, const QString &type); void sigAudioStopped(); private: StoryProject &m_project; std::unordered_set _nodeIds; // Key: widget name, value: category std::unordered_map m_modelNames; std::set m_categories; template struct Factory { static std::shared_ptr create_func(StoryGraphModel &model) { return std::make_shared(model); } }; typedef std::shared_ptr (*GenericCreator)(StoryGraphModel &model); typedef std::map Registry; Registry m_registry; /// [Important] This is a user defined data structure backing your model. /// In your case it could be anything else representing a graph, for example, a /// table. Or a collection of structs with pointers to each other. Or an /// abstract syntax tree, you name it. std::unordered_set _connectivity; std::unordered_map> _models; /// A convenience variable needed for generating unique node ids. unsigned int _nextNodeId{0}; std::thread m_audioThread; ThreadSafeQueue m_audioQueue; void AudioThread(); };