Compare commits

...

4 commits

Author SHA1 Message Date
anthony@rabine.fr
663fa47004 cleaning + label from node base
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
Deploy-Documentation / deploy (push) Waiting to run
2025-10-02 18:07:14 +02:00
anthony@rabine.fr
0be3ff2203 missing files 2025-10-02 17:48:25 +02:00
anthony@rabine.fr
6d544f5879 removed exit node, nex function widget 2025-10-02 17:41:42 +02:00
anthony@rabine.fr
3e00fb1c83 Add error list dock window, cleaned some nodes 2025-10-02 14:10:50 +02:00
37 changed files with 823 additions and 726 deletions

View file

@ -17,6 +17,11 @@ public:
PROJECT_TYPE_PRIMITIVE = 2
};
struct FunctionInfo {
std::string uuid;
std::string name;
};
virtual ~IStoryProject() {};
virtual std::string GetName() const = 0;
@ -26,6 +31,7 @@ public:
virtual std::string GetUuid() const = 0;
virtual std::string GetTitleImage() const = 0;
virtual std::string GetTitleSound() const = 0;
virtual std::vector<FunctionInfo> GetFunctionsList() const = 0;
virtual void SetTitleImage(const std::string &titleImage) = 0;
virtual void SetTitleSound(const std::string &titleSound) = 0;

View file

@ -2,12 +2,14 @@
#include "ast_builder.h"
#include "assembly_generator.h"
#include "call_function_node.h"
class AssemblyGeneratorChip32 : public AssemblyGenerator
{
public:
AssemblyGeneratorChip32(const GeneratorContext& context)
: AssemblyGenerator(context)
, m_currentContext(FunctionContext::MAIN_PROGRAM)
{
}
@ -28,6 +30,13 @@ public:
GenerateOperatorNode(node);
}
else if (node->IsType<FunctionEntryNode>()) {
// Détecter si c'est le main ou une sous-fonction
// Weight 100 = fonction principale (main)
auto* entry = node->GetAs<FunctionEntryNode>();
m_currentContext = (entry->GetWeight() >= 100)
? FunctionContext::MAIN_PROGRAM
: FunctionContext::SUB_FUNCTION;
GenerateFunctionEntry(node);
}
else if (node->IsType<BranchNode>()) {
@ -36,12 +45,20 @@ public:
else if (node->IsType<PrintNode>()) {
GeneratePrintNode(node);
}
else if (node->IsType<CallFunctionNode>()) {
GenerateCallFunctionNode(node);
}
// // If there is no any children, put an halt
// Détection automatique des fins de fonction/programme
if (node->GetChildCount() == 0)
{
AddComment("Program exit");
m_assembly << " halt\n";
if (m_currentContext == FunctionContext::MAIN_PROGRAM) {
AddComment("Program exit (automatic)");
m_assembly << " halt\n";
} else {
AddComment("Function return (automatic)");
m_assembly << " ret\n";
}
}
}
@ -55,6 +72,12 @@ public:
}
private:
enum class FunctionContext {
MAIN_PROGRAM,
SUB_FUNCTION
};
FunctionContext m_currentContext;
virtual void GenerateMain() override {
// Program entry point
@ -62,7 +85,39 @@ private:
}
void GenerateFunctionEntry(std::shared_ptr<ASTNode> node) {
AddComment("Function Entry");
auto* entry = node->GetAs<FunctionEntryNode>();
if (m_currentContext == FunctionContext::MAIN_PROGRAM) {
AddComment("Main function entry");
} else {
AddComment("Function entry");
// Si nécessaire, sauvegarder les registres
// m_assembly << " push r0\n";
// m_assembly << " push r1\n";
// etc.
}
}
void GenerateCallFunctionNode(std::shared_ptr<ASTNode> node) {
auto* callNode = node->GetAs<CallFunctionNode>();
if (!callNode) return;
std::string functionName = callNode->GetFunctionName();
AddComment("Call function: " + functionName);
m_depth++;
// Préparer les arguments si nécessaire
// Dans votre système, les variables globales sont utilisées
// donc pas besoin de passer des arguments sur la pile
// Appel de la fonction
m_assembly << " call " << GetFunctionLabel(functionName) << "\n";
// Après le retour de la fonction, les variables globales
// ont potentiellement été modifiées et sont directement accessibles
m_depth--;
}
void GenerateBranchNode(std::shared_ptr<ASTNode> node)
@ -88,24 +143,20 @@ private:
std::string label = printNode->GetLabel();
m_assembly << " push r0\n"
<< " push r1\n"
<< " lcons r0, $" << label << "\n"
<< " lcons r1, 0 ; number of arguments\n" // FIXME: handle arguments
<< " syscall 4\n"
<< " pop r1\n"
<< " pop r0\n";
// << ""mov r2, %2 // arguments are in r2, r3, r4 etc.
m_assembly << " push r0\n"
<< " push r1\n"
<< " lcons r0, $" << label << "\n"
<< " lcons r1, 0 ; number of arguments\n" // FIXME: handle arguments
<< " syscall 4\n"
<< " pop r1\n"
<< " pop r0\n";
}
void GenerateOperatorNode(std::shared_ptr<ASTNode> node) {
auto* opNode = node->GetAs<OperatorNode>();
if (!opNode) return;
AddComment("Operator: " + std::to_string(static_cast<int>(opNode->GetOperationType())));
AddComment("Operator: " + opNode->GetOperatorSymbol());
m_depth++;
// Generate code for variables usage
@ -125,7 +176,7 @@ private:
{
// Generate code to load the variable value
// FIXME: hardcoded 4 bytes, replace by actual real variable size
m_assembly << " load r" << reg << ", $" << var->GetLabel() << ", 4" << "; Load variable " << var->GetVariableName() << "\n";
m_assembly << " load r" << reg << ", $" << var->GetLabel() << ", 4" << " ; Load variable " << var->GetVariableName() << "\n";
m_assembly << " push r" << reg << "\n";
}
else
@ -135,13 +186,9 @@ private:
}
reg++;
}
// m_assembly << " load r0, " << inputNode.node->GetId() << "\n";
}
// Generate operator code
// Generate operator code based on type
switch (opNode->GetOperationType()) {
case OperatorNode::OperationType::ADD:
m_assembly << " pop r0\n"
@ -167,28 +214,133 @@ private:
<< " div r0, r1\n"
<< " push r0\n";
break;
case OperatorNode::OperationType::MODULO:
m_assembly << " pop r0\n"
<< " pop r1\n"
<< " mod r0, r1\n"
<< " push r0\n";
break;
case OperatorNode::OperationType::EQUAL:
m_assembly << " pop r0\n"
<< " pop r1\n"
<< " cmp r0, r1\n"
<< " lcons r0, 1\n"
<< " skipz\n"
<< " lcons r0, 0\n"
<< " push r0\n";
break;
case OperatorNode::OperationType::NOT_EQUAL:
m_assembly << " pop r0\n"
<< " pop r1\n"
<< " cmp r0, r1\n"
<< " lcons r0, 0\n"
<< " skipz\n"
<< " lcons r0, 1\n"
<< " push r0\n";
break;
case OperatorNode::OperationType::GREATER_THAN:
m_assembly << " pop r0\n"
<< " pop r1\n"
<< " cmp r0, r1\n"
<< " lcons r0, 1\n"
<< " skipgt\n"
<< " lcons r0, 0\n"
<< " push r0\n";
break;
case OperatorNode::OperationType::LESS_THAN:
m_assembly << " pop r0\n"
<< " pop r1\n"
<< " cmp r0, r1\n"
<< " lcons r0, 1\n"
<< " skiplt\n"
<< " lcons r0, 0\n"
<< " push r0\n";
break;
case OperatorNode::OperationType::GREATER_EQUAL:
m_assembly << " pop r0\n"
<< " pop r1\n"
<< " cmp r0, r1\n"
<< " lcons r0, 1\n"
<< " skipge\n"
<< " lcons r0, 0\n"
<< " push r0\n";
break;
case OperatorNode::OperationType::LESS_EQUAL:
m_assembly << " pop r0\n"
<< " pop r1\n"
<< " cmp r0, r1\n"
<< " lcons r0, 1\n"
<< " skiple\n"
<< " lcons r0, 0\n"
<< " push r0\n";
break;
case OperatorNode::OperationType::AND:
m_assembly << " pop r0\n"
<< " pop r1\n"
<< " and r0, r1\n"
<< " push r0\n";
break;
case OperatorNode::OperationType::GREATER_THAN:
case OperatorNode::OperationType::OR:
m_assembly << " pop r0\n"
<< " pop r1\n"
<< " gt r0, r0, r1\n"
<< " or r0, r1\n"
<< " push r0\n";
break;
case OperatorNode::OperationType::NOT:
m_assembly << " pop r0\n"
<< " not r0\n"
<< " push r0\n";
break;
case OperatorNode::OperationType::BITWISE_AND:
m_assembly << " pop r0\n"
<< " pop r1\n"
<< " and r0, r1\n"
<< " push r0\n";
break;
case OperatorNode::OperationType::BITWISE_OR:
m_assembly << " pop r0\n"
<< " pop r1\n"
<< " or r0, r1\n"
<< " push r0\n";
break;
case OperatorNode::OperationType::BITWISE_XOR:
m_assembly << " pop r0\n"
<< " pop r1\n"
<< " xor r0, r1\n"
<< " push r0\n";
break;
case OperatorNode::OperationType::BITWISE_NOT:
m_assembly << " pop r0\n"
<< " not r0\n"
<< " push r0\n";
break;
case OperatorNode::OperationType::LEFT_SHIFT:
m_assembly << " pop r0\n"
<< " pop r1\n"
<< " shl r0, r1\n"
<< " push r0\n";
break;
case OperatorNode::OperationType::RIGHT_SHIFT:
m_assembly << " pop r0\n"
<< " pop r1\n"
<< " shr r0, r1\n"
<< " push r0\n";
break;
default:
// Make voluntary bad assembly
m_assembly << "------>>>> OPERATOR NOT IMPLEMENTED: " << opNode->GetOperatorSymbol() << "\n";
break;
throw std::runtime_error("Unsupported operator type");
}
m_depth--;
}
virtual void Visit(const std::shared_ptr<Variable> v) override
// Helper pour générer le label d'une fonction à partir de son nom
std::string GetFunctionLabel(const std::string& functionName) {
// Convertir le nom de fonction en label valide
// Par exemple: "MyFunction" -> ".func_MyFunction"
return ".func_" + functionName;
}
virtual void Visit(const std::shared_ptr<Variable> v) override
{
if (v->IsConstant())
{
@ -232,7 +384,4 @@ private:
m_assembly << "$" << v->GetLabel() << " DVB, " << (v->GetValue<bool>() ? "1" : "0") << " ; " << v->GetVariableName() << "\n";
}
}
};
};

View file

@ -102,6 +102,22 @@ public:
return m_weight;
}
void SetupExecutionPorts(bool hasInput = true,
int numOutputs = 1,
bool customRendering = true)
{
if (m_behavior != BEHAVIOR_EXECUTION) return;
if (hasInput) {
AddInputPort(Port::Type::EXECUTION_PORT, ">", customRendering);
}
for (int i = 0; i < numOutputs; ++i) {
std::string label = (numOutputs == 1) ? ">" : std::to_string(i);
AddOutputPort(Port::Type::EXECUTION_PORT, label, customRendering);
}
}
void SetId(const std::string &id) { m_uuid = id; }
std::string GetId() const { return m_uuid; }
@ -121,8 +137,8 @@ public:
}
// Port management
void AddInputPort(Port::Type type, const std::string& label) {
m_inputPorts.push_back({type, label});
void AddInputPort(Port::Type type, const std::string& label, bool customRendering = false) {
m_inputPorts.push_back({type, label, customRendering});
}
void AddOutputPort(Port::Type type, const std::string& label, bool customRendering = false) {

View file

@ -1,20 +0,0 @@
#include "call_function_node.h"
#include "story_project.h"
#include "connection.h"
#include "sys_lib.h"
CallFunctionNode::CallFunctionNode(const std::string &type)
: BaseNode(type, "Call Function Node")
{
nlohmann::json j{ {"function", ""} };
SetInternalData(j);
}
void CallFunctionNode::Initialize()
{
nlohmann::json j = GetInternalData();
// m_image = j["image"].get<std::string>();
// m_sound = j["sound"].get<std::string>();
}

View file

@ -1,19 +1,51 @@
// call_function_node.h
#pragma once
#include <string>
#include "i_story_manager.h"
#include "base_node.h"
#include "i_script_node.h"
#include "i_story_project.h"
class CallFunctionNode : public BaseNode
{
public:
CallFunctionNode(const std::string &type);
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
}
virtual void Initialize() override;
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>();
}
}
std::string GetFunctionName() const {
return m_functionName;
}
std::string GetFunctionUuid() const {
return m_functionUuid;
}
void SetFunction(const std::string& uuid, const std::string& name) {
m_functionUuid = uuid;
m_functionName = name;
// Sauvegarder dans les données internes pour la persistance
nlohmann::json j;
j["functionName"] = m_functionName;
j["functionUuid"] = m_functionUuid;
SetInternalData(j);
}
private:
nlohmann::json m_content;
};
std::string m_functionName;
std::string m_functionUuid;
};

View file

@ -11,7 +11,7 @@ public:
{
SetWeight(100);
SetBehavior(BaseNode::BEHAVIOR_EXECUTION);
AddOutputPort(BaseNode::Port::Type::EXECUTION_PORT, ">", true);
SetupExecutionPorts(false, 1, true);
}
void Initialize() override {

View file

@ -1,26 +0,0 @@
#pragma once
#include "base_node.h"
class FunctionExitNode : public BaseNode
{
public:
FunctionExitNode(const std::string &type)
: BaseNode(type, "Function Exit Node")
{
SetBehavior(BaseNode::BEHAVIOR_EXECUTION);
}
void Initialize() override {
// Initialisation spécifique pour FunctionExitNode
// Par exemple, préparer les sorties nécessaires pour la fonction
}
// Ajoutez des méthodes spécifiques pour gérer la sortie de la fonction
void FinalizeFunctionExit() {
// Logique pour finaliser la sortie de la fonction
}
};

View file

@ -7,14 +7,20 @@
PrintNode::PrintNode(const std::string &type)
: BaseNode(type, "Print Node")
{
// Create empty variable in memory
// Create empty variable in memory for the format string
auto v = std::make_shared<Variable>(m_label);
v->SetConstant(true);
m_label = v->GetLabel();
m_variables[m_label] = v;
SetBehavior(BaseNode::BEHAVIOR_EXECUTION);
SetupExecutionPorts(true, 1, true);
// Add 4 data input ports for arguments
for (int i = 0; i < MAX_INPUT_COUNT; ++i) {
AddInputPort(Port::DATA_PORT, "arg" + std::to_string(i));
}
SetText("");
}
@ -24,11 +30,9 @@ void PrintNode::Initialize()
m_variables.at(m_label)->SetTextValue(j["text"].get<std::string>());
}
void PrintNode::SetText(const std::string &text)
{
m_variables.at(m_label)->SetValue<std::string>(text);
SetInternalData({{"text", text}});
}
@ -40,5 +44,4 @@ std::string PrintNode::GetLabel() const
std::string PrintNode::GetText() const
{
return m_variables.at(m_label)->GetValue<std::string>();
}
}

View file

@ -16,9 +16,9 @@ public:
void SetText(const std::string &text);
std::string GetLabel() const;
std::string GetText() const;
static constexpr int MAX_INPUT_COUNT = 4;
private:
std::string m_label; // Label for the string literal
uint32_t m_arguments{0}; // number of arguments
};
};

View file

@ -3,19 +3,19 @@
#include "connection.h"
#include "sys_lib.h"
VariableNode::VariableNode(const std::string &type)
: BaseNode(type, "Variable Node")
: BaseNode(type, "Variable Node", BaseNode::BEHAVIOR_DATA)
{
nlohmann::json j{ {"uuid", ""} };
SetInternalData(j);
SetBehavior(BaseNode::BEHAVIOR_DATA);
// Add data output port
AddOutputPort(Port::DATA_PORT, "value");
}
void VariableNode::Initialize()
{
nlohmann::json j = GetInternalData();
m_variableUuid = j["uuid"].get<std::string>();
}
@ -33,5 +33,16 @@ std::string VariableNode::GetVariableUuid() const
return m_variableUuid;
}
void VariableNode::SetVariable(std::shared_ptr<Variable> var)
{
m_variable = var;
if (var) {
SetVariableUuid(var->GetUuid());
SetTitle(var->GetLabel());
}
}
std::shared_ptr<Variable> VariableNode::GetVariable() const
{
return m_variable;
}

View file

@ -1,27 +1,26 @@
#pragma once
#include <string>
#include "variable.h"
#include "i_story_manager.h"
#include "base_node.h"
#include "i_script_node.h"
#include "i_story_project.h"
#include "variable.h"
class VariableNode : public BaseNode
{
public:
VariableNode(const std::string &type = "variable-node");
VariableNode(const std::string &type);
virtual void Initialize() override;
void SetVariableUuid(const std::string &uuid);
std::string GetVariableUuid() const;
void SetVariable(std::shared_ptr<Variable> var);
std::shared_ptr<Variable> GetVariable() const;
private:
std::string m_variableUuid;
};
std::shared_ptr<Variable> m_variable;
};

View file

@ -16,7 +16,7 @@
#include "story_project.h"
#include "story_primitive.h"
#include "function_entry_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";
@ -24,7 +24,7 @@ static const std::string VariableNodeUuid = "020cca4e-9cdc-47e7-a6a5-53e4c9152ed
static const std::string PrintNodeUuid = "02ee27bc-ff1d-4f94-b700-eab55052ad1c";
static const std::string SyscallNodeUuid = "02225cff-4975-400e-8130-41524d8af773";
static const std::string FunctionEntryNodeUuid = "02fd145a-b3a6-43c2-83ce-6a187e6d4b5b";
static const std::string FunctionExitNodeUuid = "027b723d-2327-4646-a17a-79ddc2e016e4";
static const std::string DUMMY_a_prendre_Uuid = "027b723d-2327-4646-a17a-79ddc2e016e4";
typedef std::shared_ptr<BaseNode> (*GenericCreator)(const std::string &type);
@ -44,7 +44,6 @@ public:
registerNode<PrintNode>(PrintNodeUuid, std::make_shared<StoryPrimitive>("Print"));
registerNode<SyscallNode>(SyscallNodeUuid, std::make_shared<StoryPrimitive>("System call"));
registerNode<FunctionEntryNode>(FunctionEntryNodeUuid, std::make_shared<StoryPrimitive>("Function entry"));
registerNode<FunctionExitNode>(FunctionExitNodeUuid, std::make_shared<StoryPrimitive>("Function exit"));
}
~NodesFactory() = default;

View file

@ -16,6 +16,7 @@ class StoryPage : public IStoryPage
public:
StoryPage(const std::string_view uuid)
: m_uuid(uuid)
, m_name("Unnamed Page")
{
}
~StoryPage() {
@ -26,6 +27,9 @@ public:
std::string_view Uuid() const { return m_uuid; }
std::string_view GetName() const { return m_name; }
void SetName(const std::string& name) { m_name = name; }
void AddNode(std::shared_ptr<BaseNode> node) {
m_nodes.push_back(node);
}
@ -125,6 +129,7 @@ public:
{
nlohmann::json model;
model["uuid"] = m_uuid;
model["name"] = m_name;
nlohmann::json nodes = nlohmann::json::array();
for (const auto & n : m_nodes)

View file

@ -36,6 +36,9 @@ public:
return m_uuid;
}
virtual std::vector<FunctionInfo> GetFunctionsList() const override {
return std::vector<FunctionInfo>();
}
virtual std::string GetTitleImage() const {

View file

@ -179,7 +179,7 @@ void StoryProject::DeleteNode(const std::string_view &page_uuid, const std::stri
}
}
std::shared_ptr<StoryPage> StoryProject::GetPage(const std::string &uuid)
std::shared_ptr<StoryPage> StoryProject::GetPage(const std::string_view &uuid)
{
for (const auto & p : m_pages)
{
@ -268,6 +268,7 @@ bool StoryProject::ModelFromJson(const nlohmann::json &model, NodesFactory &fact
// 1. Create the page in memory
auto p = std::make_shared<StoryPage>(pageModel["uuid"].get<std::string>());
m_pages.push_back(p);
p->SetName(pageModel.value("name", "Unnamed Page"));
// 2. Load the nodes
nlohmann::json nodesJsonArray = pageModel["nodes"];
@ -332,6 +333,30 @@ bool StoryProject::CopyProgramTo(uint8_t *memory, uint32_t size)
return success;
}
std::vector<IStoryProject::FunctionInfo> StoryProject::GetFunctionsList() const
{
std::vector<IStoryProject::FunctionInfo> functions;
// Parcourir toutes les pages du projet
for (const auto& page : m_pages)
{
// Exclure la page main (MainUuid)
if (page->Uuid() == MainUuid())
{
continue;
}
// Ajouter la page à la liste des fonctions disponibles
IStoryProject::FunctionInfo info;
info.uuid = page->Uuid();
info.name = page->GetName();
functions.push_back(info);
}
return functions;
}
bool StoryProject::GetAssemblyLine(uint32_t pointer_counter, uint32_t &line)
{
bool success = false;

View file

@ -99,10 +99,11 @@ public:
virtual void SetName(const std::string &name) override { m_name = name; }
virtual void SetUuid(const std::string &uuid) override { m_uuid = uuid; }
virtual Type GetProjectType() const override { return m_projectType; }
std::vector<FunctionInfo> GetFunctionsList() const override;
// Node interaction
std::shared_ptr<StoryPage> CreatePage(const std::string_view uuid);
std::shared_ptr<StoryPage> GetPage(const std::string &uuid);
std::shared_ptr<StoryPage> GetPage(const std::string_view &uuid);
void AddNode(const std::string_view &page, std::shared_ptr<BaseNode> node);
void AddConnection(const std::string_view &page, std::shared_ptr<Connection> c);

View file

@ -147,6 +147,21 @@ public:
return GetValue<bool>();
}
std::string GetValueAsString() const {
switch (m_valueType) {
case ValueType::INTEGER:
return std::to_string(GetValue<int>());
case ValueType::FLOAT:
return std::to_string(GetValue<float>());
case ValueType::BOOL:
return GetValue<bool>() ? "true" : "false";
case ValueType::STRING:
return GetValue<std::string>();
default:
return "<unknown>";
}
}
using VariableValue = std::variant<int, float, bool, std::string>;
std::string GetUuid() const {

View file

@ -138,11 +138,11 @@ set(SRCS
## Docks
src/docks/emulator_dock.cpp
src/docks/resources_dock.cpp
src/docks/error_list_dock.cpp
# src/node_editor/media_node_widget.cpp
src/node_editor/base_node_widget.cpp
src/node_editor/node_editor_window.cpp
src/node_editor/call_function_node_widget.cpp
src/node_editor/module_node_widget.cpp
src/node_editor/variable_node_widget.cpp
src/node_editor/operator_node_widget.cpp
@ -184,7 +184,6 @@ set(SRCS
../core/story-manager/src/nodes/compare_node.cpp
../core/story-manager/src/nodes/branch_node.cpp
../core/story-manager/src/nodes/variable_node.cpp
../core/story-manager/src/nodes/call_function_node.cpp
../core/story-manager/src/nodes/module_node.cpp
../core/story-manager/src/nodes/print_node.cpp
../core/story-manager/src/nodes/syscall_node.cpp

View file

@ -9,32 +9,32 @@ Size=400,400
Collapsed=0
[Window][Library Manager]
Pos=435,26
Size=845,388
Pos=656,26
Size=624,313
Collapsed=0
DockId=0x00000003,0
[Window][Console]
Pos=60,450
Size=610,270
Size=475,270
Collapsed=0
DockId=0x00000004,0
[Window][Emulator]
Pos=435,26
Size=845,388
Pos=656,26
Size=624,313
Collapsed=0
DockId=0x00000003,5
[Window][Code viewer]
Pos=435,26
Size=845,388
Pos=656,26
Size=624,313
Collapsed=0
DockId=0x00000003,4
[Window][Resources]
Pos=435,26
Size=845,388
Pos=656,26
Size=624,313
Collapsed=0
DockId=0x00000003,1
@ -50,26 +50,26 @@ Size=150,42
Collapsed=0
[Window][Variables]
Pos=672,450
Size=608,270
Pos=537,450
Size=743,270
Collapsed=0
DockId=0x00000005,0
[Window][CPU]
Pos=435,26
Size=845,388
Pos=656,26
Size=624,313
Collapsed=0
DockId=0x00000003,2
[Window][RAM view]
Pos=435,26
Size=845,388
Pos=656,26
Size=624,313
Collapsed=0
DockId=0x00000003,3
[Window][Properties]
Pos=435,416
Size=845,32
Pos=656,341
Size=624,107
Collapsed=0
DockId=0x00000006,0
@ -90,13 +90,13 @@ Collapsed=0
[Window][Module editor]
Pos=60,26
Size=373,422
Size=594,422
Collapsed=0
DockId=0x00000001,0
[Window][Story editor]
Pos=60,26
Size=373,422
Size=594,422
Collapsed=0
DockId=0x00000001,1
@ -105,6 +105,12 @@ Pos=490,260
Size=687,422
Collapsed=0
[Window][Error List]
Pos=60,450
Size=475,270
Collapsed=0
DockId=0x00000004,1
[Table][0x7728942D,5]
RefScale=20
Column 0 Width=44 Sort=0v
@ -144,11 +150,11 @@ Column 0 Sort=0v
[Docking][Data]
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=60,26 Size=1220,694 Split=Y
DockNode ID=0x00000007 Parent=0x08BD597D SizeRef=1220,422 Split=X
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=373,694 CentralNode=1 Selected=0x93ADCAAB
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=845,694 Split=Y Selected=0x52EB28B5
DockNode ID=0x00000003 Parent=0x00000002 SizeRef=718,388 Selected=0x63869CAF
DockNode ID=0x00000006 Parent=0x00000002 SizeRef=718,32 Selected=0x8C72BEA8
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=790,694 CentralNode=1 Selected=0x93ADCAAB
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=624,694 Split=Y Selected=0x52EB28B5
DockNode ID=0x00000003 Parent=0x00000002 SizeRef=718,476 Selected=0x63869CAF
DockNode ID=0x00000006 Parent=0x00000002 SizeRef=718,163 Selected=0x8C72BEA8
DockNode ID=0x00000008 Parent=0x08BD597D SizeRef=1220,270 Split=X Selected=0xEA83D666
DockNode ID=0x00000004 Parent=0x00000008 SizeRef=610,192 Selected=0xEA83D666
DockNode ID=0x00000005 Parent=0x00000008 SizeRef=608,192 Selected=0x6DE9B20C
DockNode ID=0x00000004 Parent=0x00000008 SizeRef=475,192 Selected=0xD246D6BE
DockNode ID=0x00000005 Parent=0x00000008 SizeRef=743,192 Selected=0x6DE9B20C

View file

@ -0,0 +1,99 @@
#include "error_list_dock.h"
#include "imgui.h"
void ErrorListDock::Draw() {
WindowBase::BeginDraw();
ImGui::SetWindowSize(ImVec2(800, 200), ImGuiCond_FirstUseEver);
// Header avec compteur
size_t errorCount = GetErrorCount();
size_t warningCount = GetWarningCount();
if (errorCount > 0) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.9f, 0.2f, 0.2f, 1.0f));
ImGui::Text("%s %zu", ICON_FA_TIMES_CIRCLE, errorCount);
ImGui::PopStyleColor();
} else {
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "%s 0", ICON_FA_TIMES_CIRCLE);
}
ImGui::SameLine();
if (warningCount > 0) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.0f, 1.0f));
ImGui::Text("%s %zu", ICON_FA_EXCLAMATION_TRIANGLE, warningCount);
ImGui::PopStyleColor();
} else {
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "%s 0", ICON_FA_EXCLAMATION_TRIANGLE);
}
ImGui::SameLine();
ImGui::Spacing();
ImGui::SameLine();
if (ImGui::Button(ICON_FA_TRASH " Clear")) {
Clear();
}
ImGui::Separator();
// AJOUT du BeginChild pour la zone scrollable
ImGui::BeginChild("ErrorListContent", ImVec2(0, 0), false, ImGuiWindowFlags_AlwaysVerticalScrollbar);
// Afficher un message si pas d'erreurs
if (m_errors.empty()) {
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f),
"%s No errors or warnings", ICON_FA_CHECK_CIRCLE);
} else {
// Table des erreurs
if (ImGui::BeginTable("ErrorTable", 3,
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY))
{
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 80);
ImGui::TableSetupColumn("Message", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Node", ImGuiTableColumnFlags_WidthFixed, 80);
ImGui::TableHeadersRow();
for (size_t i = 0; i < m_errors.size(); ++i) {
const auto& error = m_errors[i];
ImGui::TableNextRow();
// Type column
ImGui::TableSetColumnIndex(0);
ImGui::PushStyleColor(ImGuiCol_Text, error.GetTypeColor());
ImGui::Text("%s %s", error.GetTypeIcon().c_str(),
error.type == CompilationError::ERROR ? "Error" :
error.type == CompilationError::WARNING ? "Warning" : "Info");
ImGui::PopStyleColor();
// Message column
ImGui::TableSetColumnIndex(1);
ImGui::TextWrapped("%s", error.message.c_str());
// Node column (clickable to navigate)
ImGui::TableSetColumnIndex(2);
if (!error.nodeId.empty()) {
if (ImGui::SmallButton(("Go##" + std::to_string(i)).c_str())) {
// TODO: Emit event to navigate to node
}
ImGui::SameLine();
ImGui::TextDisabled("?");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Node: %s", error.nodeId.c_str());
}
} else {
ImGui::TextDisabled("-");
}
}
ImGui::EndTable();
}
}
ImGui::EndChild();
WindowBase::EndDraw();
}

View file

@ -0,0 +1,75 @@
#pragma once
#include "window_base.h"
#include "IconsFontAwesome5_c.h"
#include <vector>
#include <string>
#include <algorithm>
struct CompilationError {
enum Type {
ERROR,
WARNING,
INFO
};
Type type;
std::string message;
std::string nodeId; // UUID du nœud concerné
int line;
std::string GetTypeIcon() const {
switch(type) {
case ERROR: return ICON_FA_TIMES_CIRCLE;
case WARNING: return ICON_FA_EXCLAMATION_TRIANGLE;
case INFO: return ICON_FA_INFO_CIRCLE;
}
return "";
}
ImVec4 GetTypeColor() const {
switch(type) {
case ERROR: return ImVec4(0.9f, 0.2f, 0.2f, 1.0f);
case WARNING: return ImVec4(1.0f, 0.8f, 0.0f, 1.0f);
case INFO: return ImVec4(0.3f, 0.7f, 1.0f, 1.0f);
}
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
}
};
class ErrorListDock : public WindowBase {
public:
ErrorListDock() : WindowBase("Error List") {}
void Draw() override;
void Clear() { m_errors.clear(); }
void AddError(const CompilationError& error) { m_errors.push_back(error); }
void AddError(const std::string& message, const std::string& nodeId = "", int line = 0) {
m_errors.push_back({CompilationError::ERROR, message, nodeId, line});
}
void AddWarning(const std::string& message, const std::string& nodeId = "", int line = 0) {
m_errors.push_back({CompilationError::WARNING, message, nodeId, line});
}
void AddInfo(const std::string& message, const std::string& nodeId = "", int line = 0) {
m_errors.push_back({CompilationError::INFO, message, nodeId, line});
}
bool HasErrors() const {
return std::any_of(m_errors.begin(), m_errors.end(),
[](const auto& e) { return e.type == CompilationError::ERROR; });
}
size_t GetErrorCount() const {
return std::count_if(m_errors.begin(), m_errors.end(),
[](const auto& e) { return e.type == CompilationError::ERROR; });
}
size_t GetWarningCount() const {
return std::count_if(m_errors.begin(), m_errors.end(),
[](const auto& e) { return e.type == CompilationError::WARNING; });
}
private:
std::vector<CompilationError> m_errors;
};

View file

@ -664,16 +664,16 @@ void Gui::ApplyTheme()
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.13f, 0.75f, 1.00f, 0.80f);
// ===== AMÉLIORATION DES ONGLETS =====
// Onglet inactif (non sélectionné)
colors[ImGuiCol_Tab] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f);
// Onglet inactif (non sélectionné) - TRÈS FONCÉ
colors[ImGuiCol_Tab] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f);
// Onglet survolé
colors[ImGuiCol_TabHovered] = ImVec4(0.25f, 0.60f, 0.80f, 1.00f);
// Onglet actif (sélectionné) - couleur vive et contrastée
// Onglet actif (sélectionné dans fenêtre focusée) - BLEU VIF
colors[ImGuiCol_TabActive] = ImVec4(0.20f, 0.65f, 0.85f, 1.00f);
// Onglet inactif dans une fenêtre non-focusée
colors[ImGuiCol_TabUnfocused] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f);
// Onglet actif dans une fenêtre non-focusée
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.18f, 0.45f, 0.60f, 1.00f);
// Onglet inactif dans une fenêtre non-focusée - TRÈS FONCÉ
colors[ImGuiCol_TabUnfocused] = ImVec4(0.06f, 0.06f, 0.06f, 1.00f);
// Onglet actif dans une fenêtre non-focusée - BEAUCOUP PLUS FONCÉ
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.10f, 0.10f, 0.12f, 1.00f);
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
@ -701,8 +701,8 @@ void Gui::ApplyTheme()
style.PopupRounding = 2.0f;
style.ScrollbarRounding = 12.0f;
style.ScrollbarSize = 13.0f;
style.TabBorderSize = 0.0f; // Pas de bordure autour des onglets
style.TabRounding = 4.0f; // Coins arrondis pour les onglets
style.TabBorderSize = 0.0f;
style.TabRounding = 4.0f;
style.WindowRounding = 4.0f;
}

View file

@ -12,7 +12,8 @@
enum ToastType {
Success,
Warning,
Error
Error,
Info
};
struct Toast {
@ -26,7 +27,7 @@ struct Toast {
class ImGuiToastNotifier {
private:
std::vector<Toast> toasts;
const float TOAST_WIDTH = 300.0f;
const float TOAST_WIDTH = 350.0f;
const float TOAST_PADDING = 10.0f;
public:
@ -34,12 +35,43 @@ public:
toasts.push_back({title, text, type, std::chrono::steady_clock::now(), duration});
}
// Helper methods pour les cas communs
void success(const std::string& message) {
addToast("Success", message, ToastType::Success, 3.0f);
}
void error(const std::string& message, float duration = 5.0f) {
addToast("Error", message, ToastType::Error, duration);
}
void warning(const std::string& message) {
addToast("Warning", message, ToastType::Warning, 4.0f);
}
void info(const std::string& message) {
addToast("Info", message, ToastType::Info, 3.0f);
}
// Pour les erreurs de compilation
void compilationFailed(size_t errorCount, size_t warningCount = 0) {
std::string msg = std::to_string(errorCount) + " error(s) found";
if (warningCount > 0) {
msg += ", " + std::to_string(warningCount) + " warning(s)";
}
msg += ". Check Error List for details.";
addToast("Compilation Failed", msg, ToastType::Error, 6.0f);
}
void compilationSuccess(float duration = 2.5f) {
addToast("Compilation Success", "Build completed successfully", ToastType::Success, duration);
}
void render() {
if (toasts.empty()) {
return;
}
// Get the main viewport work area (excludes menu bar, status bar, etc.)
// Get the main viewport work area
ImGuiViewport* viewport = ImGui::GetMainViewport();
ImVec2 work_pos = viewport->WorkPos;
ImVec2 work_size = viewport->WorkSize;
@ -54,7 +86,6 @@ public:
ImGui::SetNextWindowSize(ImVec2(TOAST_WIDTH, 0), ImGuiCond_Always);
ImGui::SetNextWindowBgAlpha(0.90f);
// Add NoNav to prevent interfering with other windows
ImGuiWindowFlags window_flags =
ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoMove |
@ -103,6 +134,10 @@ public:
color = ImVec4(0.94f, 0.31f, 0.31f, 1.0f);
icon = ICON_FA_TIMES_CIRCLE;
break;
case Info:
color = ImVec4(0.3f, 0.7f, 1.0f, 1.0f);
icon = ICON_FA_INFO_CIRCLE;
break;
}
// Draw icon and title on the same line
@ -111,13 +146,13 @@ public:
ImGui::PopStyleColor();
ImGui::SameLine();
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); // Use default font for title
ImGui::TextUnformatted(it->title.c_str());
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]);
ImGui::Text("%s", it->title.c_str());
ImGui::PopFont();
// Draw message text with wrapping
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + TOAST_WIDTH - 24.0f);
ImGui::TextUnformatted(it->text.c_str());
ImGui::TextWrapped("%s", it->text.c_str());
ImGui::PopTextWrapPos();
ImGui::PopStyleVar(); // Alpha

View file

@ -34,7 +34,7 @@
#include "print_node_widget.h"
#include "syscall_node_widget.h"
#include "function_entry_widget.h"
#include "function_exit_widget.h"
MainWindow::MainWindow(ILogger& logger, EventBus& eventBus, AppController& appController)
: m_logger(logger)
@ -74,7 +74,6 @@ MainWindow::MainWindow(ILogger& logger, EventBus& eventBus, AppController& appCo
m_widgetFactory.registerNode<PrintNodeWidget>(PrintNodeUuid);
m_widgetFactory.registerNode<SyscallNodeWidget>(SyscallNodeUuid);
m_widgetFactory.registerNode<FunctionEntryWidget>(FunctionEntryNodeUuid);
m_widgetFactory.registerNode<FunctionExitWidget>(FunctionExitNodeUuid);
m_eventBus.Subscribe<OpenProjectEvent>([this](const OpenProjectEvent &event) {
OpenProject(event.GetUuid());
@ -517,6 +516,7 @@ bool MainWindow::Loop()
// ------------ Draw all windows
m_libraryWindow.Draw();
m_errorListDock.Draw();
if (m_appController.IsLibraryManagerInitialized())
{

View file

@ -11,6 +11,7 @@
#include "debugger_window.h"
#include "emulator_dock.h"
#include "resources_dock.h"
#include "error_list_dock.h"
#include "node_editor_window.h"
#include "properties_window.h"
#include "variables_window.h"
@ -50,6 +51,7 @@ private:
Gui m_gui;
EmulatorDock m_emulatorDock;
ErrorListDock m_errorListDock;
ConsoleWindow m_consoleWindow;
DebuggerWindow m_debuggerWindow;
CpuWindow m_cpuWindow;

View file

@ -25,4 +25,68 @@ void BaseNodeWidget::Initialize()
}
void BaseNodeWidget::DrawSocket(const Nw::Pin &pin)
{
ImDrawList* draw_list = ImGui::GetWindowDrawList();
// Taille du socket
float socket_size = 4.0f;
// Définir les 5 points du polygone (flèche pointant vers la droite pour Output)
// Pour Input, la flèche pointerait vers la gauche
ImVec2 p1, p2, p3, p4, p5;
if (pin.pinKind == Nw::PinKind::Output) {
// Flèche pointant vers la droite (→)
p1 = ImVec2(pin.pinPoint.x - socket_size * 1.5f, pin.pinPoint.y - socket_size);
p2 = ImVec2(pin.pinPoint.x - socket_size * 0.5f, pin.pinPoint.y - socket_size);
p3 = ImVec2(pin.pinPoint.x + socket_size * 0.5f, pin.pinPoint.y); // Pointe
p4 = ImVec2(pin.pinPoint.x - socket_size * 0.5f, pin.pinPoint.y + socket_size);
p5 = ImVec2(pin.pinPoint.x - socket_size * 1.5f, pin.pinPoint.y + socket_size);
} else {
// Flèche pointant vers la gauche (←)
p1 = ImVec2(pin.pinPoint.x + socket_size * 1.5f, pin.pinPoint.y - socket_size);
p2 = ImVec2(pin.pinPoint.x + socket_size * 0.5f, pin.pinPoint.y - socket_size);
p3 = ImVec2(pin.pinPoint.x - socket_size * 0.5f, pin.pinPoint.y); // Pointe
p4 = ImVec2(pin.pinPoint.x + socket_size * 0.5f, pin.pinPoint.y + socket_size);
p5 = ImVec2(pin.pinPoint.x + socket_size * 1.5f, pin.pinPoint.y + socket_size);
}
ImVec2 vertices[] = {p1, p2, p3, p4, p5};
// Rectangle pour la détection de hover
ImVec2 tl = pin.pinPoint - ImVec2(socket_size * 1.5f, socket_size);
ImVec2 br = pin.pinPoint + ImVec2(socket_size * 1.5f, socket_size);
bool hovered = ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br);
// Dessin du socket
if (pin.isConnected) {
// Rempli quand connecté
draw_list->AddConvexPolyFilled(vertices, IM_ARRAYSIZE(vertices),
pin.style.color);
} else {
// Contour seulement quand non connecté
if (hovered) {
draw_list->AddPolyline(vertices, IM_ARRAYSIZE(vertices),
pin.style.color,
ImDrawFlags_Closed,
pin.style.socket_hovered_radius); // Épaisseur au hover
} else {
draw_list->AddPolyline(vertices, IM_ARRAYSIZE(vertices),
pin.style.color,
ImDrawFlags_Closed,
pin.style.socket_thickness); // Épaisseur normale
}
}
// Optionnel : dessiner la décoration (fond hover) si nécessaire
if (hovered) {
draw_list->AddRectFilled(pin.pos - pin.style.padding,
pin.pos + pin.size + pin.style.padding,
pin.style.bg_hover_color,
pin.style.bg_radius);
}
}

View file

@ -96,7 +96,7 @@ public:
virtual void DrawProperties(std::shared_ptr<IStoryProject> story) = 0;
virtual void DrawSocket(const Nw::Pin &pin) {}
virtual void DrawSocket(const Nw::Pin &pin);
virtual bool HasSync() const {

View file

@ -1,38 +0,0 @@
#include <sstream>
#include "call_function_node_widget.h"
#include "IconsMaterialDesignIcons.h"
#include "story_project.h"
#include "uuid.h"
CallFunctionNodeWidget::CallFunctionNodeWidget(IStoryManager &manager, std::shared_ptr<BaseNode> node)
: BaseNodeWidget(manager, node)
, m_manager(manager)
{
m_functionUuid = Uuid().String();
}
void CallFunctionNodeWidget::Draw()
{
ImGui::TextUnformatted(m_functionName.c_str());
if (ImGui::Button("> Open function"))
{
m_manager.OpenFunction(m_functionUuid, m_functionName);
}
}
void CallFunctionNodeWidget::Initialize()
{
BaseNodeWidget::Initialize();
m_functionName = "Function";
}
void CallFunctionNodeWidget::DrawProperties(std::shared_ptr<IStoryProject> story)
{
}

View file

@ -1,9 +1,9 @@
// call_function_node_widget.h
#pragma once
#include <vector>
#include <map>
#include <mutex>
#include <set>
#include <string>
#include "base_node_widget.h"
#include "i_story_manager.h"
@ -11,22 +11,74 @@
#include "call_function_node.h"
#include "gui.h"
class CallFunctionNodeWidget : public BaseNodeWidget
{
public:
CallFunctionNodeWidget(IStoryManager &manager, std::shared_ptr<BaseNode> node);
CallFunctionNodeWidget(IStoryManager &manager, std::shared_ptr<BaseNode> node)
: BaseNodeWidget(manager, node)
, m_manager(manager)
{
m_callFunctionNode = std::dynamic_pointer_cast<CallFunctionNode>(node);
SetTitle("Call Function");
}
void Draw() override;
void Initialize() override {
BaseNodeWidget::Initialize();
m_functionName = m_callFunctionNode->GetFunctionName();
m_functionUuid = m_callFunctionNode->GetFunctionUuid();
}
virtual void DrawProperties(std::shared_ptr<IStoryProject> story) override;
virtual void Initialize() override;
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())) {
// Récupérer la liste des fonctions du projet
auto functions = story->GetFunctionsList(); // À implémenter dans IStoryProject
for (size_t i = 0; i < functions.size(); ++i) {
const bool is_selected = (m_functionUuid == functions[i].uuid);
if (ImGui::Selectable(functions[i].name.c_str(), is_selected)) {
m_functionUuid = functions[i].uuid;
m_functionName = functions[i].name;
m_callFunctionNode->SetFunction(m_functionUuid, m_functionName);
}
if (is_selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
// Bouton pour ouvrir la fonction dans l'éditeur
ImGui::Spacing();
// Désactiver le bouton si aucune fonction n'est sélectionnée
if (m_functionUuid.empty()) {
ImGui::BeginDisabled();
}
if (ImGui::Button("> Open function")) {
m_manager.OpenFunction(m_functionUuid, m_functionName);
}
if (m_functionUuid.empty()) {
ImGui::EndDisabled();
}
}
void Draw() override {
// Afficher le nom de la fonction dans le noeud
ImGui::TextUnformatted(m_functionName.empty()
? "<No function>"
: m_functionName.c_str());
}
private:
IStoryManager &m_manager;
std::shared_ptr<CallFunctionNode> m_callFunctionNode;
std::string m_functionName;
std::string m_functionUuid;
};
std::string m_functionUuid;
};

View file

@ -11,109 +11,6 @@
#include "function_entry_node.h"
#include "gui.h"
void DrawBlueprintSyncSocket(ImDrawList* draw_list, const ImVec2& center, float size, ImU32 color, bool filled = true) {
const float half_size = size * 0.5f;
const float triangle_size = size * 0.6f; // Triangle légèrement plus petit que le carré
// Coordonnées du carré (partie gauche)
ImVec2 square_min = ImVec2(center.x - half_size, center.y - half_size);
ImVec2 square_max = ImVec2(center.x, center.y + half_size);
// Coordonnées du triangle (partie droite, pointant vers la droite)
ImVec2 triangle_p1 = ImVec2(center.x, center.y - triangle_size * 0.5f); // Point haut
ImVec2 triangle_p2 = ImVec2(center.x, center.y + triangle_size * 0.5f); // Point bas
ImVec2 triangle_p3 = ImVec2(center.x + triangle_size * 0.7f, center.y); // Point de la pointe
if (filled) {
// Dessiner le carré rempli
draw_list->AddRectFilled(square_min, square_max, color);
// Dessiner le triangle rempli
draw_list->AddTriangleFilled(triangle_p1, triangle_p2, triangle_p3, color);
} else {
// Dessiner les contours
const float thickness = 2.0f;
// Contour du carré
draw_list->AddRect(square_min, square_max, color, 0.0f, 0, thickness);
// Contour du triangle
draw_list->AddTriangle(triangle_p1, triangle_p2, triangle_p3, color, thickness);
}
}
// Version avec dégradé pour un effet plus moderne
void DrawBlueprintSyncSocketGradient(ImDrawList* draw_list, const ImVec2& center, float size, ImU32 color_start, ImU32 color_end) {
const float half_size = size * 0.5f;
const float triangle_size = size * 0.6f;
// Coordonnées du carré
ImVec2 square_min = ImVec2(center.x - half_size, center.y - half_size);
ImVec2 square_max = ImVec2(center.x, center.y + half_size);
// Coordonnées du triangle
ImVec2 triangle_p1 = ImVec2(center.x, center.y - triangle_size * 0.5f);
ImVec2 triangle_p2 = ImVec2(center.x, center.y + triangle_size * 0.5f);
ImVec2 triangle_p3 = ImVec2(center.x + triangle_size * 0.7f, center.y);
// Carré avec dégradé horizontal
draw_list->AddRectFilledMultiColor(
square_min, square_max,
color_start, color_end,
color_end, color_start
);
// Triangle uni (couleur de fin du dégradé)
draw_list->AddTriangleFilled(triangle_p1, triangle_p2, triangle_p3, color_end);
}
// Variante avec animation de pulsation pour indiquer l'activité
void DrawBlueprintSyncSocketAnimated(ImDrawList* draw_list, const ImVec2& center, float size, ImU32 color, float time) {
// Effet de pulsation basé sur le temps
float pulse = 0.8f + 0.2f * sinf(time * 3.0f); // Oscille entre 0.8 et 1.0
float animated_size = size * pulse;
// Socket principal
DrawBlueprintSyncSocket(draw_list, center, animated_size, color, true);
// Halo subtil autour
ImU32 halo_color = ImGui::ColorConvertFloat4ToU32(ImVec4(
((color >> IM_COL32_R_SHIFT) & 0xFF) / 255.0f,
((color >> IM_COL32_G_SHIFT) & 0xFF) / 255.0f,
((color >> IM_COL32_B_SHIFT) & 0xFF) / 255.0f,
0.3f * (pulse - 0.8f) * 5.0f // Alpha qui varie avec la pulsation
));
DrawBlueprintSyncSocket(draw_list, center, size * 1.2f, halo_color, false);
}
// Utilisation dans ImNodeFlow
void DrawSyncSocketInNode(ImDrawList* draw_list, const ImVec2& socket_pos, bool is_connected, bool is_hovered) {
const float socket_size = 16.0f;
// Couleurs selon l'état
ImU32 base_color = IM_COL32(100, 150, 255, 255); // Bleu par défaut
ImU32 connected_color = IM_COL32(50, 255, 100, 255); // Vert si connecté
ImU32 hover_color = IM_COL32(255, 200, 50, 255); // Orange au survol
ImU32 final_color = base_color;
if (is_connected) final_color = connected_color;
if (is_hovered) final_color = hover_color;
// Dessiner le socket
if (is_connected) {
// Version animée si connecté
float time = ImGui::GetTime();
DrawBlueprintSyncSocketAnimated(draw_list, socket_pos, socket_size, final_color, time);
} else {
// Version statique
DrawBlueprintSyncSocket(draw_list, socket_pos, socket_size, final_color, true);
}
}
class FunctionEntryWidget : public BaseNodeWidget
{
public:
@ -129,141 +26,6 @@ public:
ImGui::SetNextItemWidth(100.f);
}
void DrawSocket(const Nw::Pin &pin) override
{
ImDrawList* draw_list = ImGui::GetWindowDrawList();
// Taille du socket
float socket_size = 4.0f;
// Définir les 5 points du polygone (flèche pointant vers la droite pour Output)
// Pour Input, la flèche pointerait vers la gauche
ImVec2 p1, p2, p3, p4, p5;
if (pin.pinKind == Nw::PinKind::Output) {
// Flèche pointant vers la droite (→)
p1 = ImVec2(pin.pinPoint.x - socket_size * 1.5f, pin.pinPoint.y - socket_size);
p2 = ImVec2(pin.pinPoint.x - socket_size * 0.5f, pin.pinPoint.y - socket_size);
p3 = ImVec2(pin.pinPoint.x + socket_size * 0.5f, pin.pinPoint.y); // Pointe
p4 = ImVec2(pin.pinPoint.x - socket_size * 0.5f, pin.pinPoint.y + socket_size);
p5 = ImVec2(pin.pinPoint.x - socket_size * 1.5f, pin.pinPoint.y + socket_size);
} else {
// Flèche pointant vers la gauche (←)
p1 = ImVec2(pin.pinPoint.x + socket_size * 1.5f, pin.pinPoint.y - socket_size);
p2 = ImVec2(pin.pinPoint.x + socket_size * 0.5f, pin.pinPoint.y - socket_size);
p3 = ImVec2(pin.pinPoint.x - socket_size * 0.5f, pin.pinPoint.y); // Pointe
p4 = ImVec2(pin.pinPoint.x + socket_size * 0.5f, pin.pinPoint.y + socket_size);
p5 = ImVec2(pin.pinPoint.x + socket_size * 1.5f, pin.pinPoint.y + socket_size);
}
ImVec2 vertices[] = {p1, p2, p3, p4, p5};
// Rectangle pour la détection de hover
ImVec2 tl = pin.pinPoint - ImVec2(socket_size * 1.5f, socket_size);
ImVec2 br = pin.pinPoint + ImVec2(socket_size * 1.5f, socket_size);
bool hovered = ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br);
// Dessin du socket
if (pin.isConnected) {
// Rempli quand connecté
draw_list->AddConvexPolyFilled(vertices, IM_ARRAYSIZE(vertices),
pin.style.color);
} else {
// Contour seulement quand non connecté
if (hovered) {
draw_list->AddPolyline(vertices, IM_ARRAYSIZE(vertices),
pin.style.color,
ImDrawFlags_Closed,
pin.style.socket_hovered_radius); // Épaisseur au hover
} else {
draw_list->AddPolyline(vertices, IM_ARRAYSIZE(vertices),
pin.style.color,
ImDrawFlags_Closed,
pin.style.socket_thickness); // Épaisseur normale
}
}
// Optionnel : dessiner la décoration (fond hover) si nécessaire
if (hovered) {
draw_list->AddRectFilled(pin.pos - pin.style.padding,
pin.pos + pin.size + pin.style.padding,
pin.style.bg_hover_color,
pin.style.bg_radius);
}
/*
// bonne position du socket
ImDrawList* draw_list = ImGui::GetWindowDrawList();
// 1. Dessiner le socket à pin.pinPoint (pas à pin.pos !)
if (pin.isConnected) {
draw_list->AddCircleFilled(pin.pinPoint,
pin.style.socket_connected_radius,
pin.style.color);
} else {
// Gérer le hover vous-même si nécessaire
ImVec2 tl = pin.pinPoint - ImVec2(pin.style.socket_radius, pin.style.socket_radius);
ImVec2 br = pin.pinPoint + ImVec2(pin.style.socket_radius, pin.style.socket_radius);
bool hovered = ImGui::IsMouseHoveringRect(tl, br);
draw_list->AddCircle(pin.pinPoint,
hovered ? pin.style.socket_hovered_radius
: pin.style.socket_radius,
pin.style.color,
pin.style.socket_shape,
pin.style.socket_thickness);
}
*/
/*
ImDrawList* draw_list = ImGui::GetWindowDrawList();
float socket_size = 4;
// pin.pos.x = pin.pos.x + 10;
ImVec2 w_pos = ImGui::GetCursorPos();
std::cout << "x = " << w_pos.x << ", y = " << w_pos.y << std::endl;;
// Définir les points du polygone pour le symbole de synchronisation
// C'est un polygone fermé à 5 points
ImVec2 p1(pin.pos.x - socket_size * 0.5f, pin.pos.y - socket_size);
ImVec2 p2(pin.pos.x + socket_size * 0.5f, pin.pos.y - socket_size);
ImVec2 p3(pin.pos.x + socket_size * 0.5f, pin.pos.y + socket_size);
ImVec2 p4(pin.pos.x - socket_size * 0.5f, pin.pos.y + socket_size);
ImVec2 p5(pin.pos.x + socket_size * 1.5f, pin.pos.y);
ImVec2 vertices[] = {p1, p2, p5, p3, p4}; // Ordre des sommets
// Pour la détection de survol (hover) on peut toujours utiliser le rectangle englobant
ImVec2 tl = pin.pos - ImVec2(socket_size * 1.5f, socket_size);
ImVec2 br = pin.pos + ImVec2(socket_size * 1.5f, socket_size);
if (pin.isConnected)
{
draw_list->AddConvexPolyFilled(vertices, IM_ARRAYSIZE(vertices), IM_COL32(255,255,255,255));
}
else
{
if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br))
{
draw_list->AddPolyline(vertices, IM_ARRAYSIZE(vertices),IM_COL32(255,255,255,255), ImDrawFlags_Closed, 2.0f);
}
else
{
draw_list->AddPolyline(vertices, IM_ARRAYSIZE(vertices), IM_COL32(255,255,255,255), ImDrawFlags_Closed, 1.3f);
}
}
*/
}
virtual bool HasSync() const override {
return false;
}

View file

@ -1,42 +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_exit_node.h"
#include "gui.h"
class FunctionExitWidget : public BaseNodeWidget
{
public:
FunctionExitWidget(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::SetNextItemWidth(100.f);
}
virtual void DrawProperties(std::shared_ptr<IStoryProject> story) override {
}
virtual void Initialize() override {
}
private:
IStoryManager &m_manager;
std::shared_ptr<FunctionExitNode> m_functionExitNode;
};

View file

@ -10,25 +10,7 @@
#include "node_widget_factory.h"
#include "i_story_manager.h"
#include "ImNodeFlow.h"
class SimpleSum : public ImFlow::BaseNode {
public:
SimpleSum() {
setTitle("Simple sum");
setStyle(ImFlow::NodeStyle::green());
ImFlow::BaseNode::addIN<int>("In", 0, ImFlow::ConnectionFilter::SameType());
ImFlow::BaseNode::addOUT<int>("Out", nullptr)->behaviour([this]() { return getInVal<int>("In") + m_valB; });
}
void draw() override {
ImGui::SetNextItemWidth(100.f);
ImGui::InputInt("##ValB", &m_valB);
}
private:
int m_valB = 0;
};
#include "base_node.h"
// Generic delegate
class NodeDelegate : public ImFlow::BaseNode {
@ -45,20 +27,16 @@ public:
setTitle(m_widget->GetTitle());
setStyle(ImFlow::NodeStyle::green());
// Add Sync input if it is an executable node
if (m_widget->HasSync())
{
ImFlow::BaseNode::addIN<int>(">", 0, ImFlow::ConnectionFilter::SameType());
}
// Add inputs
for (int i = 0; i < m_widget->Inputs(); ++i) {
auto port = m_widget->Base()->GetInputPort(i);
std::string label = (port.type == ::BaseNode::Port::Type::EXECUTION_PORT) ? "" : port.label;
if (port.customSocketIcon)
{
ImFlow::BaseNode::addIN<int>("In" + std::to_string(i), 0, ImFlow::ConnectionFilter::SameType())->renderer([this, i](ImFlow::Pin* p) {
ImFlow::BaseNode::addIN<int>(label, 0, ImFlow::ConnectionFilter::SameType())->renderer([this, i](ImFlow::Pin* p) {
Nw::Pin pin;
pin.index = i;
pin.isConnected = p->isConnected();
@ -72,7 +50,7 @@ public:
}
else
{
ImFlow::BaseNode::addIN<int>("In" + std::to_string(i), 0, ImFlow::ConnectionFilter::SameType());
ImFlow::BaseNode::addIN<int>(label, 0, ImFlow::ConnectionFilter::SameType());
}
}
@ -80,9 +58,11 @@ public:
for (int i = 0; i < m_widget->Outputs(); ++i)
{
auto port = m_widget->Base()->GetOutputPort(i);
// Détermine le label : vide pour les ports d'exécution, utilise port.label pour les ports de données
std::string label = (port.type == ::BaseNode::Port::Type::EXECUTION_PORT) ? "" : port.label;
if (port.customSocketIcon)
{
ImFlow::BaseNode::addOUT<int>("Out" + std::to_string(i), nullptr)->renderer([this, i](ImFlow::Pin* p) {
ImFlow::BaseNode::addOUT<int>(label, nullptr)->renderer([this, i](ImFlow::Pin* p) {
Nw::Pin pin;
pin.index = i;
@ -97,7 +77,7 @@ public:
}
else
{
ImFlow::BaseNode::addOUT<int>("Out" + std::to_string(i), nullptr)->behaviour([this, i]() { return getInVal<int>("In" + std::to_string(i)) + m_valB; });
ImFlow::BaseNode::addOUT<int>(label, nullptr)->behaviour([this, i]() { return getInVal<int>("In" + std::to_string(i)) + m_valB; });
}
}
}
@ -216,41 +196,10 @@ struct NodeEditorPage : public ImFlow::BaseNode
}
}
});
/*
for (const auto & n : m_nodes)
{
ImGui::PushID(n->GetInternalId());
n->Draw();
ImGui::PopID();
}
for (const auto& linkInfo : m_links)
{
ed::Link(linkInfo->ed_link->Id, linkInfo->ed_link->OutputId, linkInfo->ed_link->InputId);
}
*/
}
// bool GetNode(const ed::NodeId &nodeId, std::shared_ptr<BaseNodeWidget> &node) {
// for (const auto & n : m_nodes)
// {
// if (n->GetInternalId() == nodeId.Get())
// {
// node = n;
// return true;
// }
// }
// return false;
// }
// void DeleteNode(const ed::NodeId &nodeId) {
// m_nodes.remove_if([nodeId](const std::shared_ptr<BaseNodeWidget>& node) {
// return node->GetInternalId() == nodeId.Get();
// });
// }
std::shared_ptr<BaseNodeWidget> GetSelectedNode() {
std::shared_ptr<BaseNodeWidget> GetSelectedNode()
{
std::shared_ptr<BaseNodeWidget> selected;
@ -275,124 +224,9 @@ struct NodeEditorPage : public ImFlow::BaseNode
}
}
// if (ed::GetSelectedObjectCount() > 0)
// {
// ed::NodeId nId;
// int nodeCount = ed::GetSelectedNodes(&nId, 1);
// if (nodeCount > 0)
// {
// for (auto & n : m_nodes)
// {
// if (n->GetInternalId() == nId.Get())
// {
// selected = n;
// }
// }
// }
// }
return selected;
}
// void AddNode(std::shared_ptr<BaseNodeWidget> node) {
// m_nodes.push_back(node);
// }
// void Clear() {
// m_nodes.clear();
// m_links.clear();
// }
/*
bool GetModel(ed::LinkId linkId, std::shared_ptr<Connection> &model) {
for (const auto& linkInfo : m_links)
{
if (linkInfo->ed_link->Id == linkId)
{
model = linkInfo->model;
return true;
}
}
return false;
}
void EraseLink(ed::LinkId linkId) {
m_links.remove_if([linkId](const std::shared_ptr<LinkInfo>& linkInfo) {
return linkInfo->ed_link->Id == linkId;
});
}
// retourne 1 si c'est une sortie, 2 une entrée, 0 pas trouvé
int FindNodeAndPin(ed::PinId pinId, int &foundIndex, std::string &foundNodeId)
{
int success = 0;
for (const auto & n : m_nodes)
{
// std::cout << "---> Node: " << n->Base()->GetId() << std::endl;
if (n->HasOnputPinId(pinId, foundIndex))
{
foundNodeId = n->Base()->GetId();
success = 1;
break;
}
if (n->HasInputPinId(pinId, foundIndex))
{
foundNodeId = n->Base()->GetId();
success = 2;
break;
}
}
return success;
}
ed::PinId GetInputPin(const std::string &modelNodeId, int pinIndex)
{
ed::PinId id = 0;
for (auto & n : m_nodes)
{
if (n->Base()->GetId() == modelNodeId)
{
id = n->GetInputPinAt(pinIndex);
break;
}
}
if (id.Get() == 0)
{
std::cout << "Invalid Id: " << modelNodeId << " input pin: " << pinIndex <<" not found" << std::endl;
}
return id;
}
ed::PinId GetOutputPin(const std::string &modelNodeId, int pinIndex)
{
ed::PinId id = 0;
for (auto & n : m_nodes)
{
if (n->Base()->GetId() == modelNodeId)
{
id = n->GetOutputPinAt(pinIndex);
break;
}
}
if (id.Get() == 0)
{
std::cout << "Invalid Id: " << modelNodeId << " output pin: " << pinIndex <<" not found" << std::endl;
}
return id;
}
*/
private:
std::string m_uuid;
std::string m_name;

View file

@ -75,6 +75,11 @@ void NodeEditorWindow::LoadPage(const std::string &uuid, const std::string &name
{
// New page
page = std::make_shared<NodeEditorPage>(uuid, name);
auto storyPage = m_story->GetPage(uuid);
if (!storyPage) {
storyPage = m_story->CreatePage(uuid);
}
storyPage->SetName(name);
}
else
{
@ -251,14 +256,20 @@ void NodeEditorWindow::SaveNodesToProject()
return;
}
// Clear current project structure
m_story->Clear();
// Pour toutes les pages
// IMPORTANT: Ne PAS appeler Clear() car cela efface aussi les variables!
// Au lieu de cela, on efface seulement les pages
for (const auto& page : m_pages)
{
// Create the page in the project
auto currentPage = m_story->CreatePage(page->Uuid());
// Récupérer la page correspondante dans le projet
auto projectPage = m_story->GetPage(page->Uuid());
if (!projectPage) {
// La page n'existe pas, la créer
projectPage = m_story->CreatePage(page->Uuid());
} else {
// La page existe, vider son contenu (nodes et links)
projectPage->Clear();
}
// 1. Save all nodes with their updated positions
for (auto &nodeEntry : page->mINF.getNodes())
@ -280,7 +291,7 @@ void NodeEditorWindow::SaveNodesToProject()
baseNode->SetPosition(nodePos.x, nodePos.y);
// Add node to project
m_story->AddNode(currentPage->Uuid(), baseNode);
m_story->AddNode(projectPage->Uuid(), baseNode);
std::cout << "Saved node: " << baseNode->GetId()
<< " at (" << nodePos.x << ", " << nodePos.y << ")" << std::endl;
@ -369,7 +380,7 @@ void NodeEditorWindow::SaveNodesToProject()
connection->inPortIndex = rightPinIndex;
// Add connection to project
m_story->AddConnection(currentPage->Uuid(), connection);
m_story->AddConnection(projectPage->Uuid(), connection);
std::cout << "Saved connection: " << connection->outNodeId
<< "[" << connection->outPortIndex << "] -> "
@ -378,7 +389,7 @@ void NodeEditorWindow::SaveNodesToProject()
}
}
std::cout << "SaveNodesToProject completed successfully" << std::endl;
std::cout << "SaveNodesToProject completed successfully (variables preserved)" << std::endl;
}
void NodeEditorWindow::OpenFunction(const std::string &uuid, const std::string &name)

View file

@ -1,4 +1,3 @@
#include <sstream>
#include "print_node_widget.h"
@ -13,40 +12,55 @@ PrintNodeWidget::PrintNodeWidget(IStoryManager &manager, std::shared_ptr<BaseNod
, m_manager(manager)
{
m_printNode = std::dynamic_pointer_cast<PrintNode>(node);
// Create defaut one input and one output
// AddInputs(2);
// SetInputPinName(0, ">");
// SetInputPinName(1, "Argument 1");
// AddOutputs(1);
// SetOutPinName(0, ">");
SetTitle("Print");
}
void PrintNodeWidget::Initialize()
{
BaseNodeWidget::Initialize();
// Copy current text to buffer
auto text = m_printNode->GetText();
if (text.size() < MAX_PRINT_SIZE) {
text.copy(m_buffer, text.size());
m_buffer[text.size()] = '\0';
}
}
void PrintNodeWidget::DrawProperties(std::shared_ptr<IStoryProject> story)
{
ImGui::AlignTextToFramePadding();
ImGui::Text("Format string:");
ImGui::PushItemWidth(200.0f);
ImGui::PushItemWidth(100.0f);
auto t = m_printNode->GetText();
t.copy(m_buffer, sizeof(m_buffer) - 1);
bool edited = ImGui::InputText("##edit", m_buffer, sizeof(m_buffer), ImGuiInputTextFlags_EnterReturnsTrue);
// if (edited)
{
// Edit the format string
if (ImGui::InputText("##format", m_buffer, sizeof(m_buffer))) {
m_printNode->SetText(m_buffer);
}
ImGui::PopItemWidth();
// Show help text
ImGui::TextDisabled("Use {0}, {1}, {2}, {3} for arguments");
// Display current text
ImGui::Separator();
ImGui::Text("Preview: %s", m_printNode->GetText().c_str());
}
void PrintNodeWidget::Draw()
{
}
// Display format string in the node body
std::string displayText = m_printNode->GetText();
if (displayText.empty()) {
displayText = "<empty>";
}
// Truncate if too long
if (displayText.length() > 30) {
displayText = displayText.substr(0, 27) + "...";
}
ImGui::TextUnformatted(displayText.c_str());
}

View file

@ -10,17 +10,15 @@
#include "i_story_project.h"
#include "gui.h"
#include "print_node.h"
#include "media_node.h"
class PrintNodeWidget : public BaseNodeWidget
{
public:
static const int MAX_PRINT_SIZE = 128;
static const int MAX_PRINT_SIZE = 256;
PrintNodeWidget(IStoryManager &manager, std::shared_ptr<BaseNode> node);
void Draw() override;
virtual void DrawProperties(std::shared_ptr<IStoryProject> story) override;
virtual void Initialize() override;
@ -29,4 +27,4 @@ private:
std::shared_ptr<PrintNode> m_printNode;
static char m_buffer[MAX_PRINT_SIZE];
};
};

View file

@ -1,4 +1,3 @@
#include <sstream>
#include "variable_node_widget.h"
@ -17,17 +16,16 @@ VariableNodeWidget::VariableNodeWidget(IStoryManager &manager, std::shared_ptr<B
void VariableNodeWidget::Initialize()
{
BaseNodeWidget::Initialize();
m_selectedVariableUuid = m_variableNode->GetVariableUuid();
}
void VariableNodeWidget::DrawProperties(std::shared_ptr<IStoryProject> story)
{
// Initialize variable name from UUID on first call
if (!m_isInitialized)
{
m_isInitialized = true;
story->ScanVariable([this] (std::shared_ptr<Variable> var) {
story->ScanVariable([this](std::shared_ptr<Variable> var) {
if (var->GetUuid() == m_selectedVariableUuid)
{
m_selectedVariableName = var->GetVariableName();
@ -36,39 +34,51 @@ void VariableNodeWidget::DrawProperties(std::shared_ptr<IStoryProject> story)
}
ImGui::AlignTextToFramePadding();
ImGui::Text("Variable:");
static ImGuiComboFlags flags = 0;
ImGui::SetNextItemOpen(true, ImGuiCond_Once);
if (ImGui::BeginCombo("Variables list", m_selectedVariableName.c_str(), flags))
if (ImGui::BeginCombo("##variables", m_selectedVariableName.c_str(), flags))
{
int i = 0;
story->ScanVariable([&i, this] (std::shared_ptr<Variable> var) {
// ImGui::PushID(static_cast<int>(i)); // Assure l'unicité des widgets
story->ScanVariable([&i, this](std::shared_ptr<Variable> var) {
const bool is_selected = (m_selectedIndex == i);
std::string l = var->GetVariableName();
if (ImGui::Selectable(l.c_str(), is_selected))
std::string label = var->GetVariableName();
if (ImGui::Selectable(label.c_str(), is_selected))
{
m_selectedIndex = i;
m_selectedVariableName = l;
m_selectedVariableName = label;
m_variableNode->SetVariableUuid(var->GetUuid());
m_variableNode->SetVariable(var);
SetTitle(label);
}
// Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
if (is_selected)
// Set the initial focus when opening the combo
if (is_selected) {
ImGui::SetItemDefaultFocus();
}
i++;
});
ImGui::EndCombo();
}
// Show variable details if selected
if (!m_selectedVariableUuid.empty()) {
ImGui::Separator();
story->ScanVariable([this](std::shared_ptr<Variable> var) {
if (var->GetUuid() == m_selectedVariableUuid) {
ImGui::Text("Type: %s", Variable::ValueTypeToString(var->GetValueType()).c_str());
ImGui::Text("Value: %s", var->GetValueAsString().c_str());
}
});
}
}
void VariableNodeWidget::Draw()
{
// Display variable name in the node
ImGui::TextUnformatted(m_selectedVariableName.c_str());
}
}

View file

@ -10,7 +10,6 @@
#include "i_story_project.h"
#include "gui.h"
#include "variable_node.h"
#include "media_node.h"
class VariableNodeWidget : public BaseNodeWidget
{
@ -18,7 +17,6 @@ public:
VariableNodeWidget(IStoryManager &manager, std::shared_ptr<BaseNode> node);
void Draw() override;
virtual void DrawProperties(std::shared_ptr<IStoryProject> story) override;
virtual void Initialize() override;
@ -28,5 +26,5 @@ private:
std::shared_ptr<VariableNode> m_variableNode;
int m_selectedIndex{-1};
std::string m_selectedVariableUuid;
std::string m_selectedVariableName;
};
std::string m_selectedVariableName{"<none>"};
};