new function entry/exit nodes with parameters
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled

This commit is contained in:
Anthony Rabine 2025-10-24 21:00:15 +02:00
parent 8aa18fa5af
commit c594e01912
22 changed files with 1366 additions and 140 deletions

View file

@ -27,6 +27,8 @@ struct Callback<Ret(Params...)> {
template <typename Ret, typename... Params>
std::function<Ret(Params...)> Callback<Ret(Params...)>::func;
class NodesFactory;
class IStoryManager
{
public:
@ -50,6 +52,7 @@ public:
virtual void LoadBinaryStory(const std::string &filename) = 0;
virtual void ToggleBreakpoint(int line) = 0;
virtual uint32_t GetRegister(int reg) = 0;
virtual NodesFactory& GetNodesFactory() = 0;
virtual void Play() = 0;
virtual void Step() = 0;

View file

@ -136,6 +136,38 @@ public:
m_outputPorts.clear();
}
// Clear only data input ports, keep execution ports
void ClearDataInputPorts() {
auto it = m_inputPorts.begin();
while (it != m_inputPorts.end()) {
if (it->type == Port::Type::DATA_PORT) {
it = m_inputPorts.erase(it);
} else {
++it;
}
}
}
// Clear only data output ports, keep execution ports
void ClearDataOutputPorts() {
auto it = m_outputPorts.begin();
while (it != m_outputPorts.end()) {
if (it->type == Port::Type::DATA_PORT) {
it = m_outputPorts.erase(it);
} else {
++it;
}
}
}
void ClearAllInputPorts() {
m_inputPorts.clear();
}
void ClearAllOutputPorts() {
m_outputPorts.clear();
}
// Port management
void AddInputPort(Port::Type type, const std::string& label, bool customRendering = false) {
m_inputPorts.push_back({type, label, customRendering});

View file

@ -2,28 +2,92 @@
#pragma once
#include "base_node.h"
#include <map>
class CallFunctionNode : public BaseNode
{
public:
enum InputBindingMode {
MODE_CONNECTED, // Value comes from a connected pin
MODE_CONSTANT // Value is a constant set in the UI
};
struct InputBinding {
std::string paramName;
InputBindingMode mode;
std::string constantValue;
nlohmann::json ToJson() const {
return {
{"paramName", paramName},
{"mode", mode == MODE_CONNECTED ? "connected" : "constant"},
{"constantValue", constantValue}
};
}
static InputBinding FromJson(const nlohmann::json& j) {
InputBinding ib;
ib.paramName = j.value("paramName", "");
std::string modeStr = j.value("mode", "connected");
ib.mode = (modeStr == "constant") ? MODE_CONSTANT : MODE_CONNECTED;
ib.constantValue = j.value("constantValue", "");
return ib;
}
};
struct OutputMapping {
std::string returnValueName;
std::string targetVariable; // Optional: map to a global variable
nlohmann::json ToJson() const {
return {
{"returnValueName", returnValueName},
{"targetVariable", targetVariable}
};
}
static OutputMapping FromJson(const nlohmann::json& j) {
OutputMapping om;
om.returnValueName = j.value("returnValueName", "");
om.targetVariable = j.value("targetVariable", "");
return om;
}
};
CallFunctionNode(const std::string &type)
: BaseNode(type, "Call Function Node")
, m_functionName("")
, m_functionUuid("")
{
SetBehavior(BaseNode::BEHAVIOR_EXECUTION);
SetupExecutionPorts(true, 1, true); // 1 entrée, 1 sortie
SetupExecutionPorts(true, 1, true); // 1 entrée, sorties dynamiques
}
void Initialize() override {
// Charger le nom et l'UUID de la fonction depuis les données internes
nlohmann::json j = GetInternalData();
if (j.contains("functionName")) {
m_functionName = j["functionName"].get<std::string>();
}
if (j.contains("functionUuid")) {
m_functionUuid = j["functionUuid"].get<std::string>();
}
// Load input bindings
m_inputBindings.clear();
if (j.contains("inputBindings") && j["inputBindings"].is_array()) {
for (const auto& ibJson : j["inputBindings"]) {
m_inputBindings.push_back(InputBinding::FromJson(ibJson));
}
}
// Load output mappings
m_outputMappings.clear();
if (j.contains("outputMappings") && j["outputMappings"].is_array()) {
for (const auto& omJson : j["outputMappings"]) {
m_outputMappings.push_back(OutputMapping::FromJson(omJson));
}
}
}
std::string GetFunctionName() const {
@ -37,15 +101,113 @@ public:
void SetFunction(const std::string& uuid, const std::string& name) {
m_functionUuid = uuid;
m_functionName = name;
SaveData();
}
// Input bindings management
const std::vector<InputBinding>& GetInputBindings() const {
return m_inputBindings;
}
void SetInputBindingMode(const std::string& paramName, InputBindingMode mode, const std::string& constantValue = "") {
// Find or create binding
auto it = std::find_if(m_inputBindings.begin(), m_inputBindings.end(),
[&paramName](const InputBinding& ib) { return ib.paramName == paramName; });
// Sauvegarder dans les données internes pour la persistance
nlohmann::json j;
j["functionName"] = m_functionName;
j["functionUuid"] = m_functionUuid;
SetInternalData(j);
if (it != m_inputBindings.end()) {
it->mode = mode;
it->constantValue = constantValue;
} else {
InputBinding ib;
ib.paramName = paramName;
ib.mode = mode;
ib.constantValue = constantValue;
m_inputBindings.push_back(ib);
}
SaveData();
}
InputBinding* GetInputBinding(const std::string& paramName) {
auto it = std::find_if(m_inputBindings.begin(), m_inputBindings.end(),
[&paramName](const InputBinding& ib) { return ib.paramName == paramName; });
return (it != m_inputBindings.end()) ? &(*it) : nullptr;
}
// Output mappings management
const std::vector<OutputMapping>& GetOutputMappings() const {
return m_outputMappings;
}
void SetOutputMapping(const std::string& returnValueName, const std::string& targetVariable) {
auto it = std::find_if(m_outputMappings.begin(), m_outputMappings.end(),
[&returnValueName](const OutputMapping& om) { return om.returnValueName == returnValueName; });
if (it != m_outputMappings.end()) {
it->targetVariable = targetVariable;
} else {
OutputMapping om;
om.returnValueName = returnValueName;
om.targetVariable = targetVariable;
m_outputMappings.push_back(om);
}
SaveData();
}
// Rebuild ports based on the module's interface
void RebuildPortsFromModule(const std::vector<std::pair<std::string, std::string>>& parameters,
const std::vector<std::pair<std::string, std::string>>& exitLabels,
const std::map<std::string, std::vector<std::pair<std::string, std::string>>>& returnValuesByExit) {
// Clear all ports except the main execution input
ClearDataInputPorts();
ClearAllOutputPorts();
// Add data input ports for each parameter
for (const auto& param : parameters) {
AddInputPort(Port::Type::DATA_PORT, param.first, false);
}
// Add execution output ports for each exit
for (const auto& exitLabel : exitLabels) {
AddOutputPort(Port::Type::EXECUTION_PORT, exitLabel.first, true);
}
// Add data output ports for all unique return values across all exits
std::set<std::string> allReturnValues;
for (const auto& exitPair : returnValuesByExit) {
for (const auto& rv : exitPair.second) {
allReturnValues.insert(rv.first);
}
}
for (const auto& rvName : allReturnValues) {
AddOutputPort(Port::Type::DATA_PORT, rvName, false);
}
}
private:
std::string m_functionName;
std::string m_functionUuid;
std::vector<InputBinding> m_inputBindings;
std::vector<OutputMapping> m_outputMappings;
void SaveData() {
nlohmann::json j;
j["functionName"] = m_functionName;
j["functionUuid"] = m_functionUuid;
j["inputBindings"] = nlohmann::json::array();
for (const auto& ib : m_inputBindings) {
j["inputBindings"].push_back(ib.ToJson());
}
j["outputMappings"] = nlohmann::json::array();
for (const auto& om : m_outputMappings) {
j["outputMappings"].push_back(om.ToJson());
}
SetInternalData(j);
}
};

View file

@ -1,11 +1,33 @@
#pragma once
#include "base_node.h"
#include <vector>
class FunctionEntryNode : public BaseNode
{
public:
struct Parameter {
std::string name;
std::string type; // "int", "string", "bool", "float"
std::string defaultValue;
nlohmann::json ToJson() const {
return {
{"name", name},
{"type", type},
{"defaultValue", defaultValue}
};
}
static Parameter FromJson(const nlohmann::json& j) {
Parameter p;
p.name = j.value("name", "");
p.type = j.value("type", "int");
p.defaultValue = j.value("defaultValue", "");
return p;
}
};
FunctionEntryNode(const std::string &type)
: BaseNode(type, "Function Entry Node")
{
@ -15,12 +37,78 @@ public:
}
void Initialize() override {
// Initialisation spécifique pour FunctionEntryNode
// Par exemple, préparer les entrées nécessaires pour la fonction
// Load parameters from internal data
nlohmann::json j = GetInternalData();
m_parameters.clear();
if (j.contains("parameters") && j["parameters"].is_array()) {
for (const auto& paramJson : j["parameters"]) {
m_parameters.push_back(Parameter::FromJson(paramJson));
}
}
// Rebuild output ports for parameters
RebuildParameterPorts();
}
// Ajoutez des méthodes spécifiques pour gérer l'entrée de la fonction
void PrepareFunctionEntry() {
// Logique pour préparer l'entrée de la fonction
void AddParameter(const std::string& name, const std::string& type, const std::string& defaultValue = "") {
Parameter param;
param.name = name;
param.type = type;
param.defaultValue = defaultValue;
m_parameters.push_back(param);
SaveParameters();
RebuildParameterPorts();
}
};
void RemoveParameter(size_t index) {
if (index < m_parameters.size()) {
m_parameters.erase(m_parameters.begin() + index);
SaveParameters();
RebuildParameterPorts();
}
}
void UpdateParameter(size_t index, const std::string& name, const std::string& type, const std::string& defaultValue) {
if (index < m_parameters.size()) {
m_parameters[index].name = name;
m_parameters[index].type = type;
m_parameters[index].defaultValue = defaultValue;
SaveParameters();
RebuildParameterPorts();
}
}
const std::vector<Parameter>& GetParameters() const {
return m_parameters;
}
size_t GetParameterCount() const {
return m_parameters.size();
}
private:
std::vector<Parameter> m_parameters;
void SaveParameters() {
nlohmann::json j;
j["parameters"] = nlohmann::json::array();
for (const auto& param : m_parameters) {
j["parameters"].push_back(param.ToJson());
}
SetInternalData(j);
}
void RebuildParameterPorts() {
// Clear all data output ports (keep execution port)
ClearDataOutputPorts();
// Add a data output port for each parameter
for (const auto& param : m_parameters) {
AddOutputPort(Port::Type::DATA_PORT, param.name, false);
}
}
};

View file

@ -0,0 +1,123 @@
#pragma once
#include "base_node.h"
#include <vector>
class FunctionExitNode : public BaseNode
{
public:
struct ReturnValue {
std::string name;
std::string type; // "int", "string", "bool", "float"
nlohmann::json ToJson() const {
return {
{"name", name},
{"type", type}
};
}
static ReturnValue FromJson(const nlohmann::json& j) {
ReturnValue rv;
rv.name = j.value("name", "");
rv.type = j.value("type", "int");
return rv;
}
};
FunctionExitNode(const std::string &type)
: BaseNode(type, "Function Exit Node")
, m_exitLabel("Return")
{
SetWeight(900); // High weight, near the end
SetBehavior(BaseNode::BEHAVIOR_EXECUTION);
SetupExecutionPorts(true, 0, true); // Has input, no output (it's the end)
}
void Initialize() override {
// Load return values and exit label from internal data
nlohmann::json j = GetInternalData();
m_returnValues.clear();
m_exitLabel = j.value("exitLabel", "Return");
if (j.contains("returnValues") && j["returnValues"].is_array()) {
for (const auto& rvJson : j["returnValues"]) {
m_returnValues.push_back(ReturnValue::FromJson(rvJson));
}
}
// Rebuild input ports for return values
RebuildReturnValuePorts();
}
void SetExitLabel(const std::string& label) {
m_exitLabel = label;
SaveData();
}
std::string GetExitLabel() const {
return m_exitLabel;
}
void AddReturnValue(const std::string& name, const std::string& type) {
ReturnValue rv;
rv.name = name;
rv.type = type;
m_returnValues.push_back(rv);
SaveData();
RebuildReturnValuePorts();
}
void RemoveReturnValue(size_t index) {
if (index < m_returnValues.size()) {
m_returnValues.erase(m_returnValues.begin() + index);
SaveData();
RebuildReturnValuePorts();
}
}
void UpdateReturnValue(size_t index, const std::string& name, const std::string& type) {
if (index < m_returnValues.size()) {
m_returnValues[index].name = name;
m_returnValues[index].type = type;
SaveData();
RebuildReturnValuePorts();
}
}
const std::vector<ReturnValue>& GetReturnValues() const {
return m_returnValues;
}
size_t GetReturnValueCount() const {
return m_returnValues.size();
}
private:
std::string m_exitLabel;
std::vector<ReturnValue> m_returnValues;
void SaveData() {
nlohmann::json j;
j["exitLabel"] = m_exitLabel;
j["returnValues"] = nlohmann::json::array();
for (const auto& rv : m_returnValues) {
j["returnValues"].push_back(rv.ToJson());
}
SetInternalData(j);
}
void RebuildReturnValuePorts() {
// Clear all data input ports (keep execution port)
ClearDataInputPorts();
// Add a data input port for each return value
for (const auto& rv : m_returnValues) {
AddInputPort(Port::Type::DATA_PORT, rv.name, false);
}
}
};

View file

@ -24,21 +24,24 @@
#include "while_loop_node.h"
#include "break_node.h"
#include "continue_node.h"
#include "function_exit_node.h"
static const std::string OperatorNodeUuid = "0226fdac-8f7a-47d7-8584-b23aceb712ec";
static const std::string CallFunctionNodeUuid = "02745f38-9b11-49fe-94b1-b2a6b78249fb";
static const std::string VariableNodeUuid = "020cca4e-9cdc-47e7-a6a5-53e4c9152ed0";
static const std::string PrintNodeUuid = "02ee27bc-ff1d-4f94-b700-eab55052ad1c";
static const std::string FunctionEntryNodeUuid = "02fd145a-b3a6-43c2-83ce-6a187e6d4b5b";
static const std::string BranchNodeUuid = "027b723d-2327-4646-a17a-79ddc2e016e4";
static const std::string WaitEventNodeUuid = "02225cff-4975-400e-8130-41524d8af773";
static const std::string WaitDelayNodeUuid = "02455ef0-4975-4546-94de-720cae6baae3";
static const std::string PlayMediaNodeUuid = "0285e90a-2eb7-4605-baa9-b3712a14dff8";
static const std::string SendSignalNodeUuid = "02c2ce4b-8783-47cb-a55f-90056bebd64b";
static const std::string ForLoopNodeUuid = "02a1b2c3-4d5e-6f7a-8b9c-0d1e2f3a4b5c";
static const std::string WhileLoopNodeUuid = "02b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
static const std::string BreakNodeUuid = "02c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e";
static const std::string ContinueNodeUuid = "02d4e5f6-7a8b-9c0d-1e2f-3a4b5c6d7e8f";
static constexpr const char* OperatorNodeUuid = "0226fdac-8f7a-47d7-8584-b23aceb712ec";
static constexpr const char* CallFunctionNodeUuid = "02745f38-9b11-49fe-94b1-b2a6b78249fb";
static constexpr const char* VariableNodeUuid = "020cca4e-9cdc-47e7-a6a5-53e4c9152ed0";
static constexpr const char* PrintNodeUuid = "02ee27bc-ff1d-4f94-b700-eab55052ad1c";
static constexpr const char* FunctionEntryNodeUuid = "02fd145a-b3a6-43c2-83ce-6a187e6d4b5b";
static constexpr const char* BranchNodeUuid = "027b723d-2327-4646-a17a-79ddc2e016e4";
static constexpr const char* WaitEventNodeUuid = "02225cff-4975-400e-8130-41524d8af773";
static constexpr const char* WaitDelayNodeUuid = "02455ef0-4975-4546-94de-720cae6baae3";
static constexpr const char* PlayMediaNodeUuid = "0285e90a-2eb7-4605-baa9-b3712a14dff8";
static constexpr const char* SendSignalNodeUuid = "02c2ce4b-8783-47cb-a55f-90056bebd64b";
static constexpr const char* ForLoopNodeUuid = "02a1b2c3-4d5e-6f7a-8b9c-0d1e2f3a4b5c";
static constexpr const char* WhileLoopNodeUuid = "02b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
static constexpr const char* BreakNodeUuid = "02c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e";
static constexpr const char* ContinueNodeUuid = "02d4e5f6-7a8b-9c0d-1e2f-3a4b5c6d7e8f";
static constexpr const char* FunctionExitNodeUuid = "02d78b65-9246-4108-91fc-03dfc142d9ee";
typedef std::shared_ptr<BaseNode> (*GenericCreator)(const std::string &type);
@ -66,6 +69,8 @@ public:
registerNode<WhileLoopNode>(WhileLoopNodeUuid, std::make_shared<StoryPrimitive>("While Loop"));
registerNode<BreakNode>(BreakNodeUuid, std::make_shared<StoryPrimitive>("Break"));
registerNode<ContinueNode>(ContinueNodeUuid, std::make_shared<StoryPrimitive>("Continue"));
registerNode<FunctionExitNode>(FunctionExitNodeUuid, std::make_shared<StoryPrimitive>("Function exit"));
}
~NodesFactory() = default;

View file

@ -7,7 +7,6 @@
#include "story_project.h"
#include "json.hpp"
// #include "media_node.h"
#include "variable_node.h"
#include "operator_node.h"
#include "print_node.h"
@ -234,6 +233,18 @@ void StoryProject::ScanVariable(const std::function<bool(std::shared_ptr<Variabl
}
}
void StoryProject::ScanNodes(const std::function<bool(std::shared_ptr<BaseNode>)>& callback) {
for (auto& page : m_pages) {
auto [nodesBegin, nodesEnd] = page->Nodes();
std::vector<std::shared_ptr<BaseNode>> pageNodes(nodesBegin, nodesEnd);
for (auto& node : pageNodes) {
if (!callback(node)) {
return; // Stop scanning if callback returns false
}
}
}
}
void StoryProject::AddVariable()
{
auto v = std::make_shared<Variable>("var_" + std::to_string(m_variables.size()));
@ -271,6 +282,8 @@ bool StoryProject::ModelFromJson(const nlohmann::json &model, NodesFactory &fact
std::string type = element["type"].get<std::string>();
std::cout << "!!!!!!!!!!!!!!!!!" << type << std::endl;
auto n = factory.CreateNode(type);
if (n)
{
@ -301,16 +314,13 @@ bool StoryProject::ModelFromJson(const nlohmann::json &model, NodesFactory &fact
p->AddLink(std::make_shared<Connection>(connection.get<Connection>()));
}
}
}
success = true;
}
catch(nlohmann::json::exception &e)
{
std::cout << "(NodeEditorWindow::Load) " << e.what() << std::endl;
std::cout << "(StoryProject::ModelFromJson) " << e.what() << std::endl;
}
return success;
@ -533,8 +543,14 @@ bool StoryProject::Load(ResourceManager &manager, NodesFactory &factory)
if (j.contains("pages"))
{
ModelFromJson(j, factory);
m_initialized = true;
if (ModelFromJson(j, factory))
{
m_initialized = true;
}
else
{
throw std::logic_error("Model read error");
}
}
if (j.contains("variables"))
@ -575,7 +591,11 @@ bool StoryProject::Load(ResourceManager &manager, NodesFactory &factory)
}
catch(nlohmann::json::exception &e)
{
std::cout << e.what() << std::endl;
m_log.Log(e.what(), true);
}
catch(const std::exception &e)
{
m_log.Log(e.what(), true);
}
if (m_pages.size() == 0)

View file

@ -5,7 +5,7 @@
#include <string>
#include <filesystem>
#include <unordered_set>
#include "json.hpp"
#include "json.hpp"
#include "resource_manager.h"
@ -103,7 +103,7 @@ public:
std::pair<std::list<std::shared_ptr<BaseNode>>::iterator, std::list<std::shared_ptr<BaseNode>>::iterator> Nodes(const std::string_view &page_uuid);
std::pair<std::list<std::shared_ptr<Connection>>::iterator, std::list<std::shared_ptr<Connection>>::iterator> Links(const std::string_view &page_uuid);
void ScanNodes(const std::function<bool(std::shared_ptr<BaseNode>)>& callback);
void ScanVariable(const std::function<bool(std::shared_ptr<Variable> element)>& operation) override;
void AddVariable() override;
void DeleteVariable(int i) override;

View file

@ -0,0 +1,186 @@
#include "json_wrapper.h"
#include "json.hpp"
#include <stdexcept>
#include <sstream>
#include <exception>
#include <iostream>
using json = nlohmann::json;
class JsonWrapper::Impl {
public:
json j;
};
JsonWrapper::JsonWrapper() : impl(std::make_unique<Impl>()) {
impl->j = json::object();
}
JsonWrapper::JsonWrapper(JsonWrapper&& other) noexcept : impl(std::move(other.impl)) {}
JsonWrapper& JsonWrapper::operator=(JsonWrapper&& other) noexcept {
if (this != &other) {
impl = std::move(other.impl);
}
return *this;
}
JsonWrapper::JsonWrapper(const std::string& jsonString) : impl(std::make_unique<Impl>()) {
try {
impl->j = json::parse(jsonString);
} catch (...) {
impl->j = json::object();
}
}
JsonWrapper::~JsonWrapper() = default;
bool JsonWrapper::hasKey(const std::string& key) const {
return impl->j.contains(key);
}
std::optional<std::string> JsonWrapper::getString(const std::string& key, std::string& errorKey) const {
try {
return impl->j.at(key).get<std::string>();
} catch (...) {
errorKey = key;
return std::nullopt;
}
}
std::optional<int> JsonWrapper::getInt(const std::string& key, std::string& errorKey) const {
try {
return impl->j.at(key).get<int>();
} catch (...) {
errorKey = key;
return std::nullopt;
}
}
std::optional<double> JsonWrapper::getDouble(const std::string& key, std::string& errorKey) const {
try {
return impl->j.at(key).get<double>();
} catch (...) {
errorKey = key;
return std::nullopt;
}
}
std::optional<bool> JsonWrapper::getBool(const std::string& key, std::string& errorKey) const {
try {
return impl->j.at(key).get<bool>();
} catch (...) {
errorKey = key;
return std::nullopt;
}
}
void JsonWrapper::setString(const std::string& key, const std::string& value) {
impl->j[key] = value;
}
void JsonWrapper::setInt(const std::string& key, int value) {
impl->j[key] = value;
}
void JsonWrapper::setDouble(const std::string& key, double value) {
impl->j[key] = value;
}
void JsonWrapper::setBool(const std::string& key, bool value) {
impl->j[key] = value;
}
void JsonWrapper::setArrayImpl(const std::string& key, const void* values, size_t typeHash) {
// Ici on doit caster et stocker dans json, mais sans RTTI avancé on limite
// Par exemple on peut specialiser cette méthode pour les types courants
if (typeHash == typeid(int).hash_code()) {
const auto& v = *reinterpret_cast<const std::vector<int>*>(values);
impl->j[key] = v;
} else if (typeHash == typeid(double).hash_code()) {
const auto& v = *reinterpret_cast<const std::vector<double>*>(values);
impl->j[key] = v;
} else if (typeHash == typeid(std::string).hash_code()) {
const auto& v = *reinterpret_cast<const std::vector<std::string>*>(values);
impl->j[key] = v;
} else if (typeHash == typeid(bool).hash_code()) {
const auto& v = *reinterpret_cast<const std::vector<bool>*>(values);
impl->j[key] = v;
} else {
// Pour les types non pris en charge, on peut lever une exception ou ignorer
throw std::runtime_error("Type non supporté pour setArray");
}
}
template <typename T>
std::optional<std::vector<T>> JsonWrapper::getArrayImpl(const std::string& key, std::string& errorKey) const {
try {
return impl->j.at(key).get<std::vector<T>>();
} catch (...) {
errorKey = key;
return std::nullopt;
}
}
template <typename T>
T JsonWrapper::getImpl(const std::string& key, std::string& errorKey) const {
try {
return impl->j.at(key).get<T>();
} catch (...) {
errorKey = key;
throw;
}
}
template <typename T>
T JsonWrapper::asImpl(std::string& errorKey) const {
try {
return impl->j.get<T>();
} catch (...) {
errorKey = "[root]";
throw;
}
}
JsonWrapper JsonWrapper::fromImpl(const void* value, size_t typeHash) {
JsonWrapper wrapper;
if (typeHash == typeid(int).hash_code()) {
wrapper.impl->j = *reinterpret_cast<const int*>(value);
} else if (typeHash == typeid(double).hash_code()) {
wrapper.impl->j = *reinterpret_cast<const double*>(value);
} else if (typeHash == typeid(std::string).hash_code()) {
wrapper.impl->j = *reinterpret_cast<const std::string*>(value);
} else if (typeHash == typeid(bool).hash_code()) {
wrapper.impl->j = *reinterpret_cast<const bool*>(value);
} else {
// Si cest un type complexe, il faut une surcharge ou un specialization dans le .cpp
// Exemple: si T a to_json/from_json, on peut faire une conversion nlohmann::json value = T;
// Ici on fait un cast "générique" (moins safe)
// On peut faire un throw ici pour forcer lutilisateur à spécialiser.
throw std::runtime_error("Type non supporté pour from()");
}
return std::move(wrapper);
}
// Explicit instantiations for getArrayImpl, getImpl, asImpl for common types
template std::optional<std::vector<int>> JsonWrapper::getArrayImpl<int>(const std::string&, std::string&) const;
template std::optional<std::vector<double>> JsonWrapper::getArrayImpl<double>(const std::string&, std::string&) const;
template std::optional<std::vector<std::string>> JsonWrapper::getArrayImpl<std::string>(const std::string&, std::string&) const;
template std::optional<std::vector<bool>> JsonWrapper::getArrayImpl<bool>(const std::string&, std::string&) const;
template int JsonWrapper::getImpl<int>(const std::string&, std::string&) const;
template double JsonWrapper::getImpl<double>(const std::string&, std::string&) const;
template std::string JsonWrapper::getImpl<std::string>(const std::string&, std::string&) const;
template bool JsonWrapper::getImpl<bool>(const std::string&, std::string&) const;
template int JsonWrapper::asImpl<int>(std::string&) const;
template double JsonWrapper::asImpl<double>(std::string&) const;
template std::string JsonWrapper::asImpl<std::string>(std::string&) const;
template bool JsonWrapper::asImpl<bool>(std::string&) const;
std::string JsonWrapper::dump(int indent) const {
if (indent < 0) return impl->j.dump();
return impl->j.dump(indent);
}

View file

@ -0,0 +1,82 @@
#pragma once
#include <string>
#include <vector>
#include <optional>
#include <memory>
class JsonWrapper {
public:
JsonWrapper();
explicit JsonWrapper(const std::string& jsonString);
~JsonWrapper();
// Ajout move constructor et move assign
JsonWrapper(JsonWrapper&&) noexcept;
JsonWrapper& operator=(JsonWrapper&&) noexcept;
// Supprimer copie pour éviter erreur
JsonWrapper(const JsonWrapper&) = delete;
JsonWrapper& operator=(const JsonWrapper&) = delete;
bool hasKey(const std::string& key) const;
// Valeurs simples
std::optional<std::string> getString(const std::string& key, std::string& errorKey) const;
std::optional<int> getInt(const std::string& key, std::string& errorKey) const;
std::optional<double> getDouble(const std::string& key, std::string& errorKey) const;
std::optional<bool> getBool(const std::string& key, std::string& errorKey) const;
void setString(const std::string& key, const std::string& value);
void setInt(const std::string& key, int value);
void setDouble(const std::string& key, double value);
void setBool(const std::string& key, bool value);
// Array génériques
template <typename T>
void setArray(const std::string& key, const std::vector<T>& values) {
setArrayImpl(key, reinterpret_cast<const void*>(&values), typeid(T).hash_code());
}
template <typename T>
std::optional<std::vector<T>> getArray(const std::string& key, std::string& errorKey) const {
return getArrayImpl<T>(key, errorKey);
}
// Sérialisation/Désérialisation générique
template <typename T>
T get(const std::string& key, std::string& errorKey) const {
return getImpl<T>(key, errorKey);
}
template <typename T>
T as(std::string& errorKey) const {
return asImpl<T>(errorKey);
}
template <typename T>
static JsonWrapper from(const T& value) {
return fromImpl(reinterpret_cast<const void*>(&value), typeid(T).hash_code());
}
// Dump JSON
std::string dump(int indent = -1) const;
private:
class Impl;
std::unique_ptr<Impl> impl;
// Implémentations privées non template (définies dans .cpp)
void setArrayImpl(const std::string& key, const void* values, size_t typeHash);
template <typename T>
std::optional<std::vector<T>> getArrayImpl(const std::string& key, std::string& errorKey) const;
template <typename T>
T getImpl(const std::string& key, std::string& errorKey) const;
template <typename T>
T asImpl(std::string& errorKey) const;
static JsonWrapper fromImpl(const void* value, size_t typeHash);
};

View file

@ -32,6 +32,7 @@ add_executable(${PROJECT_NAME}
test_print_node.cpp
test_branch.cpp
test_loops.cpp
test_json.cpp
../story-manager/src/nodes/base_node.cpp
../story-manager/src/nodes/branch_node.cpp
@ -47,6 +48,7 @@ add_executable(${PROJECT_NAME}
../story-manager/src/nodes/break_node.cpp
../story-manager/src/nodes/continue_node.cpp
../story-manager/src/utils/json_wrapper.cpp
../chip32/chip32_assembler.cpp
../chip32/chip32_vm.c
@ -58,6 +60,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE
../story-manager/src
../story-manager/src/nodes
../story-manager/src/compiler
../story-manager/src/utils
../story-manager/interfaces
../../shared
)

101
core/tests/test_json.cpp Normal file
View file

@ -0,0 +1,101 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include "json_wrapper.h"
#include "json.hpp"
using Catch::Matchers::WithinRel;
using namespace nlohmann;
TEST_CASE("JsonWrapper basic types", "[json]") {
JsonWrapper json;
json.setString("name", "Alice");
json.setInt("age", 30);
json.setDouble("pi", 3.14159);
json.setBool("is_valid", true);
std::string err;
SECTION("Get string") {
auto name = json.getString("name", err);
REQUIRE(name.has_value());
REQUIRE(name.value() == "Alice");
}
SECTION("Get int") {
auto age = json.getInt("age", err);
REQUIRE(age.has_value());
REQUIRE(age.value() == 30);
}
SECTION("Get double") {
auto pi = json.getDouble("pi", err);
REQUIRE(pi.has_value());
REQUIRE_THAT(pi.value(), WithinRel(3.14159, 1e-6));
}
SECTION("Get bool") {
auto valid = json.getBool("is_valid", err);
REQUIRE(valid.has_value());
REQUIRE(valid.value() == true);
}
}
TEST_CASE("JsonWrapper arrays", "[json]") {
JsonWrapper json;
json.setArray<int>("scores", {10, 20, 30});
json.setArray<std::string>("tags", {"news", "tech"});
json.setArray<double>("measurements", {1.1, 2.2, 3.3});
json.setArray<bool>("flags", {true, false, true});
std::string err;
auto scores = json.getArray<int>("scores", err);
REQUIRE(scores.has_value());
REQUIRE(scores->size() == 3);
REQUIRE(scores->at(1) == 20);
auto tags = json.getArray<std::string>("tags", err);
REQUIRE(tags.has_value());
REQUIRE(tags->at(0) == "news");
auto measurements = json.getArray<double>("measurements", err);
REQUIRE(measurements.has_value());
REQUIRE_THAT(measurements->at(2), WithinRel(3.3, 1e-6));
auto flags = json.getArray<bool>("flags", err);
REQUIRE(flags.has_value());
REQUIRE(flags->at(0) == true);
}
struct Connection {
std::string host;
int port;ccM9XAGZ$mz^b*52T5p&sMA@ujPbCUNW
};
// from_json / to_json must be defined for Connection
inline void to_json(nlohmann::json& j, const Connection& c) {
j = nlohmann::json{{"host", c.host}, {"port", c.port}};
}
inline void from_json(const nlohmann::json& j, Connection& c) {
j.at("host").get_to(c.host);
j.at("port").get_to(c.port);
}
template Connection JsonWrapper::asImpl<Connection>(std::string&) const;
template Connection JsonWrapper::getImpl<Connection>(const std::string&, std::string&) const;
template std::optional<std::vector<Connection>> JsonWrapper::getArrayImpl<Connection>(const std::string&, std::string&) const;
TEST_CASE("JsonWrapper generic serialization/deserialization", "[json][struct]") {
Connection original{"127.0.0.1", 5000};
auto wrapper = JsonWrapper::from(original);
std::string err;
auto restored = wrapper.as<Connection>(err);
REQUIRE(restored.host == "127.0.0.1");
REQUIRE(restored.port == 5000);
}

View file

@ -292,6 +292,7 @@ target_include_directories(${STORY_EDITOR_PROJECT} PUBLIC
../core/story-manager/src
../core/story-manager/src/nodes
../core/story-manager/src/compiler
../core/story-manager/src/utils
../core/story-manager/interfaces
)
@ -300,7 +301,7 @@ add_definitions(
-DVERSION_MAJOR=${PROJECT_VERSION_MAJOR}
-DVERSION_MINOR=${PROJECT_VERSION_MINOR}
-DVERSION_PATCH=${PROJECT_VERSION_PATCH}
-DJSON_DIAGNOSTICS=1
-DRAUDIO_STANDALONE
-DSUPPORT_MODULE_RAUDIO
)

View file

@ -1,6 +1,6 @@
[Window][WindowOverViewport_11111111]
Pos=60,26
Size=1860,982
Size=1220,694
Collapsed=0
[Window][Debug##Default]
@ -9,34 +9,34 @@ Size=400,400
Collapsed=0
[Window][Library Manager]
Pos=1060,26
Size=860,653
Pos=730,26
Size=550,441
Collapsed=0
DockId=0x00000002,0
[Window][Console]
Pos=60,681
Size=805,327
Pos=60,469
Size=527,251
Collapsed=0
DockId=0x00000004,0
[Window][Emulator]
Pos=1060,26
Size=860,653
Collapsed=0
DockId=0x00000002,5
[Window][Code viewer]
Pos=1060,26
Size=860,653
Pos=730,26
Size=550,441
Collapsed=0
DockId=0x00000002,4
[Window][Resources]
Pos=1060,26
Size=860,653
[Window][Code viewer]
Pos=730,26
Size=550,441
Collapsed=0
DockId=0x00000002,1
DockId=0x00000002,3
[Window][Resources]
Pos=589,469
Size=691,251
Collapsed=0
DockId=0x00000005,1
[Window][Node editor]
Pos=60,26
@ -50,36 +50,36 @@ Size=150,42
Collapsed=0
[Window][Variables]
Pos=867,681
Size=1053,327
Pos=589,469
Size=691,251
Collapsed=0
DockId=0x00000005,0
[Window][CPU]
Pos=1060,26
Size=860,653
Pos=730,26
Size=550,441
Collapsed=0
DockId=0x00000002,1
[Window][RAM view]
Pos=730,26
Size=550,441
Collapsed=0
DockId=0x00000002,2
[Window][RAM view]
Pos=1060,26
Size=860,653
Collapsed=0
DockId=0x00000002,3
[Window][Properties]
Pos=867,681
Size=1053,327
Pos=730,26
Size=550,441
Collapsed=0
DockId=0x00000005,1
DockId=0x00000002,5
[Window][ToolBar]
Pos=0,26
Size=60,982
Size=60,694
Collapsed=0
[Window][QuitConfirm]
Pos=828,456
Pos=508,312
Size=264,96
Collapsed=0
@ -90,13 +90,13 @@ Collapsed=0
[Window][Module editor]
Pos=60,26
Size=998,653
Size=668,441
Collapsed=0
DockId=0x00000001,0
[Window][Story editor]
Pos=60,26
Size=998,653
Size=668,441
Collapsed=0
DockId=0x00000001,1
@ -106,11 +106,21 @@ Size=687,422
Collapsed=0
[Window][Error List]
Pos=60,681
Size=805,327
Pos=60,469
Size=527,251
Collapsed=0
DockId=0x00000004,1
[Window][AboutPopup]
Pos=436,235
Size=408,249
Collapsed=0
[Window][ModulePropertiesPopup]
Pos=424,289
Size=432,142
Collapsed=0
[Table][0x7728942D,5]
RefScale=20
Column 0 Width=44 Sort=0v
@ -155,11 +165,11 @@ Column 2 Weight=1.0000
Column 3 Width=60
[Docking][Data]
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=60,26 Size=1860,982 Split=Y
DockNode ID=0x00000007 Parent=0x08BD597D SizeRef=1220,365 Split=X
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=998,694 CentralNode=1 Selected=0x93ADCAAB
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=860,694 Selected=0x4B07C626
DockNode ID=0x00000008 Parent=0x08BD597D SizeRef=1220,327 Split=X Selected=0xEA83D666
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=60,26 Size=1220,694 Split=Y
DockNode ID=0x00000007 Parent=0x08BD597D SizeRef=1220,441 Split=X
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=668,694 CentralNode=1 Selected=0x93ADCAAB
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=550,694 Selected=0x63869CAF
DockNode ID=0x00000008 Parent=0x08BD597D SizeRef=1220,251 Split=X Selected=0xEA83D666
DockNode ID=0x00000004 Parent=0x00000008 SizeRef=621,192 Selected=0xEA83D666
DockNode ID=0x00000005 Parent=0x00000008 SizeRef=813,192 Selected=0x8C72BEA8
DockNode ID=0x00000005 Parent=0x00000008 SizeRef=813,192 Selected=0x30401527

View file

@ -852,6 +852,11 @@ void AppController::ImportProject(const std::string &filePathName, int format)
}
NodesFactory& AppController::GetNodesFactory()
{
return m_nodesFactory;
}
std::shared_ptr<IStoryProject> AppController::GetCurrentProject()
{
return m_story;

View file

@ -77,6 +77,7 @@ public:
virtual void LoadBinaryStory(const std::string &filename) override;
virtual void ToggleBreakpoint(int line) override;
virtual uint32_t GetRegister(int reg) override;
virtual NodesFactory& GetNodesFactory() override;
virtual void Play() override;
virtual void Step() override;
virtual void Run() override;
@ -113,7 +114,6 @@ public:
// Getters pour les managers gérés par AppController
ResourceManager& GetResourceManager() { return m_resources; }
LibraryManager& GetLibraryManager() { return m_libraryManager; }
NodesFactory& GetNodesFactory() { return m_nodesFactory; }
AudioPlayer& GetAudioPlayer() { return m_player; }
WebServer& GetWebServer() { return m_webServer; }
const std::vector<std::string>& GetRecentProjects() const { return m_recentProjects; }

View file

@ -35,7 +35,7 @@
#include "variable_node_widget.h"
#include "operator_node_widget.h"
#include "print_node_widget.h"
#include "function_entry_widget.h"
#include "function_entry_node_widget.h"
#include "branch_node_widget.h"
#include "play_media_node_widget.h"
#include "send_signal_node_widget.h"
@ -45,6 +45,8 @@
#include "while_loop_node_widget.h"
#include "break_node_widget.h"
#include "continue_node_widget.h"
#include "function_exit_node_widget.h"
MainWindow::MainWindow(ILogger& logger, EventBus& eventBus, AppController& appController)
: m_logger(logger)
@ -74,7 +76,7 @@ MainWindow::MainWindow(ILogger& logger, EventBus& eventBus, AppController& appCo
// m_widgetFactory.registerNode<ModuleNodeWidget>("module-node");
m_widgetFactory.registerNode<VariableNodeWidget>(VariableNodeUuid);
m_widgetFactory.registerNode<PrintNodeWidget>(PrintNodeUuid);
m_widgetFactory.registerNode<FunctionEntryWidget>(FunctionEntryNodeUuid);
m_widgetFactory.registerNode<FunctionEntryNodeWidget>(FunctionEntryNodeUuid);
m_widgetFactory.registerNode<BranchNodeWidget>(BranchNodeUuid);
m_widgetFactory.registerNode<WaitEventNodeWidget>(WaitEventNodeUuid);
m_widgetFactory.registerNode<WaitDelayNodeWidget>(WaitDelayNodeUuid);
@ -84,6 +86,8 @@ MainWindow::MainWindow(ILogger& logger, EventBus& eventBus, AppController& appCo
m_widgetFactory.registerNode<WhileLoopNodeWidget>(WhileLoopNodeUuid);
m_widgetFactory.registerNode<BreakNodeWidget>(BreakNodeUuid);
m_widgetFactory.registerNode<ContinueNodeWidget>(ContinueNodeUuid);
m_widgetFactory.registerNode<FunctionExitNodeWidget>(FunctionExitNodeUuid);
m_eventBus.Subscribe<OpenProjectEvent>([this](const OpenProjectEvent &event) {
OpenProject(event.GetUuid());

View file

@ -9,7 +9,10 @@
#include "i_story_manager.h"
#include "i_story_project.h"
#include "call_function_node.h"
#include "function_entry_node.h"
#include "function_exit_node.h"
#include "gui.h"
#include "IconsMaterialDesignIcons.h"
class CallFunctionNodeWidget : public BaseNodeWidget
{
@ -26,16 +29,25 @@ public:
BaseNodeWidget::Initialize();
m_functionName = m_callFunctionNode->GetFunctionName();
m_functionUuid = m_callFunctionNode->GetFunctionUuid();
// If a function is selected, rebuild ports
if (!m_functionUuid.empty()) {
RebuildPortsFromModule();
}
}
void DrawProperties(std::shared_ptr<IStoryProject> story) override {
ImGui::AlignTextToFramePadding();
// Liste déroulante des fonctions disponibles
if (ImGui::BeginCombo("Function", m_functionName.empty() ? "<Select function>" : m_functionName.c_str())) {
ImGui::TextColored(ImVec4(0.2f, 0.6f, 1.0f, 1.0f), ICON_MDI_FUNCTION " Call Function");
ImGui::Separator();
ImGui::Spacing();
// Récupérer la liste des fonctions du projet
auto functions = story->GetFunctionsList(); // À implémenter dans IStoryProject
// Function selection
ImGui::Text("Select function/module:");
ImGui::Spacing();
if (ImGui::BeginCombo("##function", m_functionName.empty() ? "<Select function>" : m_functionName.c_str())) {
// Get list of available functions/modules
auto functions = story->GetFunctionsList();
for (size_t i = 0; i < functions.size(); ++i) {
const bool is_selected = (m_functionUuid == functions[i].uuid);
@ -44,6 +56,9 @@ public:
m_functionUuid = functions[i].uuid;
m_functionName = functions[i].name;
m_callFunctionNode->SetFunction(m_functionUuid, m_functionName);
// Rebuild ports based on the selected module
RebuildPortsFromModule();
}
if (is_selected)
@ -52,25 +67,38 @@ public:
ImGui::EndCombo();
}
// Bouton pour ouvrir la fonction dans l'éditeur
ImGui::Spacing();
// Désactiver le bouton si aucune fonction n'est sélectionnée
// Open function button
if (m_functionUuid.empty()) {
ImGui::BeginDisabled();
}
if (ImGui::Button("> Open function")) {
if (ImGui::Button(ICON_MDI_OPEN_IN_NEW " Open function")) {
m_manager.OpenFunction(m_functionUuid, m_functionName);
}
if (m_functionUuid.empty()) {
ImGui::EndDisabled();
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// If a function is selected, show input/output configuration
if (!m_functionUuid.empty() && !m_moduleParameters.empty()) {
DrawInputBindingsUI();
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
DrawOutputMappingsUI(story);
}
}
void Draw() override {
// Afficher le nom de la fonction dans le noeud
ImGui::TextUnformatted(m_functionName.empty()
? "<No function>"
: m_functionName.c_str());
@ -81,4 +109,143 @@ private:
std::shared_ptr<CallFunctionNode> m_callFunctionNode;
std::string m_functionName;
std::string m_functionUuid;
// Cache of module interface
std::vector<std::pair<std::string, std::string>> m_moduleParameters; // name, type
std::vector<std::string> m_exitLabels;
std::map<std::string, std::vector<std::pair<std::string, std::string>>> m_returnValuesByExit;
void RebuildPortsFromModule() {
// Get the module from NodesFactory
auto module = m_manager.GetNodesFactory().GetModule(m_functionUuid);
if (!module) {
return;
}
// Find FunctionEntryNode in the module
m_moduleParameters.clear();
std::shared_ptr<FunctionEntryNode> entryNode = nullptr;
module->ScanNodes([&](std::shared_ptr<BaseNode> node) {
if (node->GetType() == "function-entry-node") {
entryNode = std::dynamic_pointer_cast<FunctionEntryNode>(node);
return false;
}
return true;
});
if (entryNode) {
for (const auto& param : entryNode->GetParameters()) {
m_moduleParameters.push_back({param.name, param.type});
}
}
// Find all FunctionExitNodes in the module
m_exitLabels.clear();
m_returnValuesByExit.clear();
std::vector<std::shared_ptr<FunctionExitNode>> exitNodes;
module->ScanNodes([&](std::shared_ptr<BaseNode> node) {
if (node->GetType() == "function-exit-node") {
auto exitNode = std::dynamic_pointer_cast<FunctionExitNode>(node);
if (exitNode) {
exitNodes.push_back(exitNode);
m_exitLabels.push_back(exitNode->GetExitLabel());
std::vector<std::pair<std::string, std::string>> returnValues;
for (const auto& rv : exitNode->GetReturnValues()) {
returnValues.push_back({rv.name, rv.type});
}
m_returnValuesByExit[exitNode->GetExitLabel()] = returnValues;
}
}
return true;
});
// Rebuild the node's ports
std::vector<std::pair<std::string, std::string>> exitLabelsWithType;
for (const auto& label : m_exitLabels) {
exitLabelsWithType.push_back({label, "execution"});
}
m_callFunctionNode->RebuildPortsFromModule(m_moduleParameters, exitLabelsWithType, m_returnValuesByExit);
}
void DrawInputBindingsUI() {
ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), ICON_MDI_ARROW_RIGHT " Input Parameters");
ImGui::Spacing();
ImGui::Text("Configure how each parameter receives its value:");
ImGui::Spacing();
if (ImGui::BeginTable("input_bindings_table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("Parameter", ImGuiTableColumnFlags_WidthFixed, 120.0f);
ImGui::TableSetupColumn("Mode", ImGuiTableColumnFlags_WidthFixed, 100.0f);
ImGui::TableSetupColumn("Constant Value", ImGuiTableColumnFlags_WidthFixed, 150.0f);
ImGui::TableHeadersRow();
for (const auto& param : m_moduleParameters) {
ImGui::TableNextRow();
ImGui::PushID(param.first.c_str());
// Parameter name
ImGui::TableNextColumn();
ImGui::Text("%s (%s)", param.first.c_str(), param.second.c_str());
// Mode selection
ImGui::TableNextColumn();
auto binding = m_callFunctionNode->GetInputBinding(param.first);
int currentMode = (binding && binding->mode == CallFunctionNode::MODE_CONSTANT) ? 1 : 0;
const char* modes[] = {"Connected", "Constant"};
if (ImGui::Combo("##mode", &currentMode, modes, 2)) {
auto mode = (currentMode == 1) ? CallFunctionNode::MODE_CONSTANT : CallFunctionNode::MODE_CONNECTED;
std::string value = binding ? binding->constantValue : "";
m_callFunctionNode->SetInputBindingMode(param.first, mode, value);
}
// Constant value (only if mode is constant)
ImGui::TableNextColumn();
if (currentMode == 1) {
char valueBuf[256] = "";
if (binding) {
strncpy(valueBuf, binding->constantValue.c_str(), sizeof(valueBuf) - 1);
}
if (ImGui::InputText("##value", valueBuf, sizeof(valueBuf))) {
m_callFunctionNode->SetInputBindingMode(param.first, CallFunctionNode::MODE_CONSTANT, valueBuf);
}
} else {
ImGui::TextDisabled("(from pin)");
}
ImGui::PopID();
}
ImGui::EndTable();
}
ImGui::Spacing();
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), ICON_MDI_INFORMATION_OUTLINE " Use 'Connected' to get values from connected pins, 'Constant' to set a fixed value.");
}
void DrawOutputMappingsUI(std::shared_ptr<IStoryProject> story) {
ImGui::TextColored(ImVec4(0.8f, 0.4f, 0.2f, 1.0f), ICON_MDI_ARROW_LEFT " Return Values");
ImGui::Spacing();
// Show all return values grouped by exit
for (const auto& exitPair : m_returnValuesByExit) {
ImGui::Text("Exit: %s", exitPair.first.c_str());
ImGui::Indent();
for (const auto& rv : exitPair.second) {
ImGui::BulletText("%s (%s)", rv.first.c_str(), rv.second.c_str());
}
ImGui::Unindent();
ImGui::Spacing();
}
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), ICON_MDI_INFORMATION_OUTLINE " Return values are available on output pins.");
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Connect them to use the returned data.");
}
};

View file

@ -0,0 +1,136 @@
#pragma once
#include <vector>
#include <map>
#include <mutex>
#include <set>
#include "base_node_widget.h"
#include "i_story_manager.h"
#include "i_story_project.h"
#include "function_entry_node.h"
#include "gui.h"
#include "IconsMaterialDesignIcons.h"
class FunctionEntryNodeWidget : public BaseNodeWidget
{
public:
FunctionEntryNodeWidget(IStoryManager &manager, std::shared_ptr<BaseNode> node)
: BaseNodeWidget(manager, node)
, m_manager(manager)
{
m_functionEntryNode = std::dynamic_pointer_cast<FunctionEntryNode>(node);
SetTitle("Function Entry");
}
void Draw() override {
ImGui::TextUnformatted("Entry Point");
if (m_functionEntryNode->GetParameterCount() > 0) {
ImGui::Text(ICON_MDI_ARROW_RIGHT " %zu parameter(s)", m_functionEntryNode->GetParameterCount());
}
}
virtual bool HasSync() const override {
return false;
}
virtual void DrawProperties(std::shared_ptr<IStoryProject> story) override {
ImGui::TextColored(ImVec4(0.2f, 0.8f, 1.0f, 1.0f), ICON_MDI_IMPORT " Function Entry");
ImGui::Separator();
ImGui::Spacing();
ImGui::Text("This node defines the entry point of the function/module.");
ImGui::Text("Add parameters that will be passed when this function is called.");
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// Parameters list
ImGui::Text(ICON_MDI_FORMAT_LIST_BULLETED " Parameters:");
ImGui::Spacing();
const auto& parameters = m_functionEntryNode->GetParameters();
// Table for parameters
if (ImGui::BeginTable("parameters_table", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 120.0f);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 80.0f);
ImGui::TableSetupColumn("Default", ImGuiTableColumnFlags_WidthFixed, 100.0f);
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 80.0f);
ImGui::TableHeadersRow();
// Display existing parameters
for (size_t i = 0; i < parameters.size(); ++i) {
ImGui::TableNextRow();
ImGui::PushID(static_cast<int>(i));
// Name
ImGui::TableNextColumn();
char nameBuf[128];
strncpy(nameBuf, parameters[i].name.c_str(), sizeof(nameBuf) - 1);
nameBuf[sizeof(nameBuf) - 1] = '\0';
if (ImGui::InputText("##name", nameBuf, sizeof(nameBuf))) {
m_functionEntryNode->UpdateParameter(i, nameBuf, parameters[i].type, parameters[i].defaultValue);
}
// Type
ImGui::TableNextColumn();
const char* types[] = {"int", "float", "string", "bool"};
int currentType = 0;
for (int t = 0; t < 4; ++t) {
if (parameters[i].type == types[t]) {
currentType = t;
break;
}
}
if (ImGui::Combo("##type", &currentType, types, 4)) {
m_functionEntryNode->UpdateParameter(i, parameters[i].name, types[currentType], parameters[i].defaultValue);
}
// Default value
ImGui::TableNextColumn();
char defaultBuf[128];
strncpy(defaultBuf, parameters[i].defaultValue.c_str(), sizeof(defaultBuf) - 1);
defaultBuf[sizeof(defaultBuf) - 1] = '\0';
if (ImGui::InputText("##default", defaultBuf, sizeof(defaultBuf))) {
m_functionEntryNode->UpdateParameter(i, parameters[i].name, parameters[i].type, defaultBuf);
}
// Actions
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MDI_DELETE "##delete")) {
m_functionEntryNode->RemoveParameter(i);
}
ImGui::PopID();
}
ImGui::EndTable();
}
ImGui::Spacing();
// Add new parameter
if (ImGui::Button(ICON_MDI_PLUS " Add Parameter")) {
m_functionEntryNode->AddParameter("newParam", "int", "0");
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// Info
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), ICON_MDI_INFORMATION_OUTLINE " Each parameter creates an output port on this node.");
}
virtual void Initialize() override {
BaseNodeWidget::Initialize();
}
private:
IStoryManager &m_manager;
std::shared_ptr<FunctionEntryNode> m_functionEntryNode;
};

View file

@ -1,45 +0,0 @@
#pragma once
#include <vector>
#include <map>
#include <mutex>
#include <set>
#include "base_node_widget.h"
#include "i_story_manager.h"
#include "i_story_project.h"
#include "function_entry_node.h"
#include "gui.h"
class FunctionEntryWidget : public BaseNodeWidget
{
public:
FunctionEntryWidget(IStoryManager &manager, std::shared_ptr<BaseNode> node)
: BaseNodeWidget(manager, node)
, m_manager(manager)
{
m_functionEntryNode = std::dynamic_pointer_cast<FunctionEntryNode>(node);
SetTitle("Function Entry");
}
void Draw() override {
ImGui::SetNextItemWidth(100.f);
}
virtual bool HasSync() const override {
return false;
}
virtual void DrawProperties(std::shared_ptr<IStoryProject> story) override {
}
virtual void Initialize() override {
}
private:
IStoryManager &m_manager;
std::shared_ptr<FunctionEntryNode> m_functionEntryNode;
};

View file

@ -0,0 +1,143 @@
#pragma once
#include <vector>
#include <map>
#include <mutex>
#include <set>
#include "base_node_widget.h"
#include "i_story_manager.h"
#include "i_story_project.h"
#include "function_exit_node.h"
#include "gui.h"
#include "IconsMaterialDesignIcons.h"
class FunctionExitNodeWidget : public BaseNodeWidget
{
public:
FunctionExitNodeWidget(IStoryManager &manager, std::shared_ptr<BaseNode> node)
: BaseNodeWidget(manager, node)
, m_manager(manager)
{
m_functionExitNode = std::dynamic_pointer_cast<FunctionExitNode>(node);
SetTitle("Function Exit");
}
void Draw() override {
ImGui::TextUnformatted(m_functionExitNode->GetExitLabel().c_str());
if (m_functionExitNode->GetReturnValueCount() > 0) {
ImGui::Text(ICON_MDI_ARROW_LEFT " %zu return value(s)", m_functionExitNode->GetReturnValueCount());
}
}
virtual bool HasSync() const override {
return false;
}
virtual void DrawProperties(std::shared_ptr<IStoryProject> story) override {
ImGui::TextColored(ImVec4(0.8f, 0.2f, 0.2f, 1.0f), ICON_MDI_EXPORT " Function Exit");
ImGui::Separator();
ImGui::Spacing();
ImGui::Text("This node defines an exit point for the function/module.");
ImGui::Text("It returns values and terminates the function execution.");
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// Exit label
ImGui::Text("Exit Label:");
ImGui::SameLine();
ImGui::SetNextItemWidth(200.0f);
char labelBuf[128];
strncpy(labelBuf, m_functionExitNode->GetExitLabel().c_str(), sizeof(labelBuf) - 1);
labelBuf[sizeof(labelBuf) - 1] = '\0';
if (ImGui::InputTextWithHint("##exit_label", "Success / Error / Cancel...", labelBuf, sizeof(labelBuf))) {
m_functionExitNode->SetExitLabel(labelBuf);
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// Return values list
ImGui::Text(ICON_MDI_ARROW_LEFT_BOLD " Return Values:");
ImGui::Spacing();
const auto& returnValues = m_functionExitNode->GetReturnValues();
// Table for return values
if (ImGui::BeginTable("return_values_table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 150.0f);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 100.0f);
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 80.0f);
ImGui::TableHeadersRow();
// Display existing return values
for (size_t i = 0; i < returnValues.size(); ++i) {
ImGui::TableNextRow();
ImGui::PushID(static_cast<int>(i));
// Name
ImGui::TableNextColumn();
char nameBuf[128];
strncpy(nameBuf, returnValues[i].name.c_str(), sizeof(nameBuf) - 1);
nameBuf[sizeof(nameBuf) - 1] = '\0';
if (ImGui::InputText("##name", nameBuf, sizeof(nameBuf))) {
m_functionExitNode->UpdateReturnValue(i, nameBuf, returnValues[i].type);
}
// Type
ImGui::TableNextColumn();
const char* types[] = {"int", "float", "string", "bool"};
int currentType = 0;
for (int t = 0; t < 4; ++t) {
if (returnValues[i].type == types[t]) {
currentType = t;
break;
}
}
if (ImGui::Combo("##type", &currentType, types, 4)) {
m_functionExitNode->UpdateReturnValue(i, returnValues[i].name, types[currentType]);
}
// Actions
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MDI_DELETE "##delete")) {
m_functionExitNode->RemoveReturnValue(i);
}
ImGui::PopID();
}
ImGui::EndTable();
}
ImGui::Spacing();
// Add new return value
if (ImGui::Button(ICON_MDI_PLUS " Add Return Value")) {
m_functionExitNode->AddReturnValue("result", "int");
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// Info
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), ICON_MDI_INFORMATION_OUTLINE " Each return value creates an input port on this node.");
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "The exit label will be the name of the execution output port on CallFunctionNode.");
}
virtual void Initialize() override {
BaseNodeWidget::Initialize();
}
private:
IStoryManager &m_manager;
std::shared_ptr<FunctionExitNode> m_functionExitNode;
};