Compare commits

...

4 commits

Author SHA1 Message Date
anthony@rabine.fr
8111f0a362 Merge remote-tracking branch 'origin/fix-build' + better toolbar
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-01 18:09:13 +02:00
Anthony Rabine
31e76ce6d7 fix build of modules 2025-10-01 17:38:52 +02:00
anthony@rabine.fr
479497f1df load/save module success, better toaster 2025-10-01 17:25:56 +02:00
anthony@rabine.fr
883257fd78 custom socket styling 2025-10-01 11:00:31 +02:00
18 changed files with 746 additions and 290 deletions

View file

@ -42,9 +42,6 @@ public:
virtual std::shared_ptr<IStoryProject> GetCurrentProject() = 0;
// Modules
virtual std::shared_ptr<IStoryProject> OpenModule(const std::string &uuid) = 0;
// Node interaction
virtual void CompileNodes(bool compileonly) = 0;
virtual void BuildCode(bool compileonly) = 0;

View file

@ -114,14 +114,19 @@ public:
void SaveAllModules(ResourceManager &manager)
{
for (const auto &entry : m_registry)
for (const auto &n : m_registry)
{
// Only modules
auto module = std::dynamic_pointer_cast<StoryProject>(entry.second.first);
if (module)
auto p = dynamic_cast<StoryProject*>(n.second.first.get());
if (p)
{
module->Save(manager);
p->Save(manager);
}
// Only modules
// auto module = std::dynamic_pointer_cast<StoryProject>(entry.second.first);
// if (module)
// {
// module->Save(manager);
// }
}
}

View file

@ -637,10 +637,6 @@ void StoryProject::Save(ResourceManager &manager)
void StoryProject::Clear()
{
m_uuid = "";
m_working_dir = "";
m_project_file_path = "";
m_initialized = false;
m_variables.clear();
m_pages.clear();
}

@ -1 +1 @@
Subproject commit 1d06616de63ab497f18e9403b128b6eccef3115d
Subproject commit e4984a42d3939d361f977e8d0a56bbfd49d0a702

View file

@ -9,32 +9,32 @@ Size=400,400
Collapsed=0
[Window][Library Manager]
Pos=591,26
Size=689,224
Pos=435,26
Size=845,388
Collapsed=0
DockId=0x00000003,0
[Window][Console]
Pos=60,533
Size=610,187
Pos=60,450
Size=610,270
Collapsed=0
DockId=0x00000004,0
[Window][Emulator]
Pos=591,26
Size=689,224
Pos=435,26
Size=845,388
Collapsed=0
DockId=0x00000003,5
[Window][Code viewer]
Pos=591,26
Size=689,224
Pos=435,26
Size=845,388
Collapsed=0
DockId=0x00000003,4
[Window][Resources]
Pos=591,26
Size=689,224
Pos=435,26
Size=845,388
Collapsed=0
DockId=0x00000003,1
@ -50,26 +50,26 @@ Size=150,42
Collapsed=0
[Window][Variables]
Pos=672,533
Size=608,187
Pos=672,450
Size=608,270
Collapsed=0
DockId=0x00000005,0
[Window][CPU]
Pos=591,26
Size=689,224
Pos=435,26
Size=845,388
Collapsed=0
DockId=0x00000003,2
[Window][RAM view]
Pos=591,26
Size=689,224
Pos=435,26
Size=845,388
Collapsed=0
DockId=0x00000003,3
[Window][Properties]
Pos=591,252
Size=689,279
Pos=435,416
Size=845,32
Collapsed=0
DockId=0x00000006,0
@ -90,15 +90,15 @@ Collapsed=0
[Window][Module editor]
Pos=60,26
Size=529,505
Size=373,422
Collapsed=0
DockId=0x00000001,1
DockId=0x00000001,0
[Window][Story editor]
Pos=60,26
Size=529,505
Size=373,422
Collapsed=0
DockId=0x00000001,0
DockId=0x00000001,1
[Window][Choose a library directory##ChooseLibraryDirDialog]
Pos=490,260
@ -143,12 +143,12 @@ 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,505 Split=X
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=721,694 CentralNode=1 Selected=0x93ADCAAB
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=689,694 Split=Y Selected=0x52EB28B5
DockNode ID=0x00000003 Parent=0x00000002 SizeRef=718,224 Selected=0x63869CAF
DockNode ID=0x00000006 Parent=0x00000002 SizeRef=718,279 Selected=0x8C72BEA8
DockNode ID=0x00000008 Parent=0x08BD597D SizeRef=1220,187 Split=X Selected=0xEA83D666
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=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

View file

@ -247,10 +247,6 @@ void AppController::CompileNodes(IStoryProject::Type type)
{
if (m_story->GenerateScript(m_storyAssembly))
{
// La GUI (DebuggerWindow) doit être notifiée de cette mise à jour.
// Au lieu de appeler m_debuggerWindow.SetScript(m_storyAssembly); directement,
// AppController pourrait émettre un événement ou un callback.
// Pour l'instant, on suppose une notification ou que la GUI tire les données.
m_logger.Log("Nodes script generated for story.");
Build(true); // Compile seulement par défaut
}
@ -261,9 +257,11 @@ void AppController::CompileNodes(IStoryProject::Type type)
}
else if (type == IStoryProject::Type::PROJECT_TYPE_MODULE && m_module)
{
if (m_module->GenerateScript(m_storyAssembly))
if (m_module->GenerateScript(m_moduleAssembly))
{
m_logger.Log("Nodes script generated for module.");
m_eventBus.Emit(std::make_shared<ModuleEvent>(ModuleEvent::Type::BuildSuccess, m_module->GetUuid()));
Build(true); // Compile seulement par défaut
}
else
{
@ -880,23 +878,21 @@ void AppController::SaveModule()
m_logger.Log("Modules saved.");
}
std::shared_ptr<IStoryProject> AppController::OpenModule(const std::string &uuid)
std::shared_ptr<StoryProject> AppController::OpenModule(const std::string &uuid)
{
m_module = m_nodesFactory.GetModule(uuid);
if (!m_module)
{
m_eventBus.Emit(std::make_shared<GenericResultEvent>(false, "Cannot find module: " + uuid));
m_logger.Log("Cannot find module: " + uuid, true);
}
else if (m_module->Load(m_resources, m_nodesFactory))
{
m_eventBus.Emit(std::make_shared<ModuleEvent>(ModuleEvent::Type::Opened, uuid));
m_logger.Log("Open module success: " + uuid);
m_eventBus.Emit(std::make_shared<GenericResultEvent>(true, "Open module success: " + uuid));
}
else
{
m_eventBus.Emit(std::make_shared<GenericResultEvent>(false, "Failed to open module: " + uuid));
m_logger.Log("Open module error: " + uuid, true);
}
return m_module;
}

View file

@ -61,7 +61,7 @@ public:
std::shared_ptr<StoryProject> NewModule();
void SaveModule();
void CloseModule();
std::shared_ptr<IStoryProject> OpenModule(const std::string &uuid);
std::shared_ptr<StoryProject> OpenModule(const std::string &uuid);
void OpenStory(const std::string &path = "");
void SaveStory(const std::string &path = "");
void ExportStory(const std::string &filename);
@ -104,6 +104,8 @@ public:
void ProcessStory();
void StepInstruction();
void StopAudio() { m_player.Stop(); }
std::string GetModuleAssembly() const { return m_moduleAssembly; }
std::string GetStoryAssembly() const { return m_storyAssembly; }
bool IsLibraryManagerInitialized() const { return m_libraryManager.IsInitialized(); }

View file

@ -60,8 +60,10 @@ class ModuleEvent : public Event
public:
enum class Type
{
Opened,
Open,
Closed,
BuildSuccess,
BuildFailure
};
ModuleEvent(Type type, const std::string &uuid)
@ -70,9 +72,17 @@ public:
Type GetType() const { return m_type; }
const std::string& GetUuid() const { return m_uuid; }
const std::string& GetScript() const { return m_script; }
bool IsSuccess() const { return success; }
void SetScript(const std::string& script) { m_script = script; }
void SetSuccess(bool s) { success = s; }
private:
Type m_type;
std::string m_uuid;
std::string m_script;
bool success{false};
};
#endif // ALL_EVENTS_H

View file

@ -629,7 +629,6 @@ void Gui::ApplyTheme()
ImVec4* colors = ImGui::GetStyle().Colors;
ImGuiStyle & style = ImGui::GetStyle();
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f);
@ -663,11 +662,19 @@ void Gui::ApplyTheme()
colors[ImGuiCol_ResizeGrip] = ImVec4(0.13f, 0.75f, 0.55f, 0.40f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.13f, 0.75f, 0.75f, 0.60f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.13f, 0.75f, 1.00f, 0.80f);
colors[ImGuiCol_Tab] = ImVec4(0.13f, 0.75f, 0.55f, 0.80f);
colors[ImGuiCol_TabHovered] = ImVec4(0.13f, 0.75f, 0.75f, 0.80f);
colors[ImGuiCol_TabActive] = ImVec4(0.13f, 0.75f, 1.00f, 0.80f);
colors[ImGuiCol_TabUnfocused] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f);
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.36f, 0.36f, 0.36f, 0.54f);
// ===== AMÉLIORATION DES ONGLETS =====
// Onglet inactif (non sélectionné)
colors[ImGuiCol_Tab] = ImVec4(0.15f, 0.15f, 0.15f, 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
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);
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
@ -684,6 +691,8 @@ void Gui::ApplyTheme()
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.38f, 0.38f, 0.38f, 1.00f);
colors[ImGuiCol_DockingPreview] = ImVec4(0.20f, 0.65f, 0.85f, 0.50f);
style.ChildRounding = 4.0f;
style.FrameBorderSize = 1.0f;
@ -692,103 +701,12 @@ void Gui::ApplyTheme()
style.PopupRounding = 2.0f;
style.ScrollbarRounding = 12.0f;
style.ScrollbarSize = 13.0f;
style.TabBorderSize = 1.0f;
style.TabRounding = 0.0f;
style.TabBorderSize = 0.0f; // Pas de bordure autour des onglets
style.TabRounding = 4.0f; // Coins arrondis pour les onglets
style.WindowRounding = 4.0f;
/*
/// 0 = FLAT APPEARENCE
/// 1 = MORE "3D" LOOK
int is3D = 1;
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.40f, 0.40f, 0.40f, 1.00f);
colors[ImGuiCol_ChildBg] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
colors[ImGuiCol_WindowBg] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
colors[ImGuiCol_Border] = ImVec4(0.12f, 0.12f, 0.12f, 0.71f);
colors[ImGuiCol_BorderShadow] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f);
colors[ImGuiCol_FrameBg] = ImVec4(0.42f, 0.42f, 0.42f, 0.54f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.42f, 0.42f, 0.42f, 0.40f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.56f, 0.56f, 0.56f, 0.67f);
colors[ImGuiCol_TitleBg] = ImVec4(0.19f, 0.19f, 0.19f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.17f, 0.17f, 0.17f, 0.90f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.335f, 0.335f, 0.335f, 1.000f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.24f, 0.24f, 0.24f, 0.53f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.52f, 0.52f, 0.52f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.76f, 0.76f, 0.76f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.65f, 0.65f, 0.65f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.52f, 0.52f, 0.52f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.64f, 0.64f, 0.64f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.54f, 0.54f, 0.54f, 0.35f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.52f, 0.52f, 0.52f, 0.59f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.76f, 0.76f, 0.76f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.38f, 0.38f, 0.38f, 1.00f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.47f, 0.47f, 0.47f, 1.00f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.76f, 0.76f, 0.76f, 0.77f);
colors[ImGuiCol_Separator] = ImVec4(0.000f, 0.000f, 0.000f, 0.137f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.700f, 0.671f, 0.600f, 0.290f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.702f, 0.671f, 0.600f, 0.674f);
colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f);
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.73f, 0.73f, 0.73f, 0.35f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
style.PopupRounding = 3;
style.WindowPadding = ImVec2(4, 4);
style.FramePadding = ImVec2(6, 4);
style.ItemSpacing = ImVec2(6, 2);
style.ScrollbarSize = 18;
style.WindowBorderSize = 1;
style.ChildBorderSize = 1;
style.PopupBorderSize = 1;
style.FrameBorderSize = is3D;
style.WindowRounding = 3;
style.ChildRounding = 3;
style.FrameRounding = 3;
style.ScrollbarRounding = 2;
style.GrabRounding = 3;
style.TabBorderSize = is3D;
style.TabRounding = 3;
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.38f, 0.38f, 0.38f, 1.00f);
colors[ImGuiCol_Tab] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
colors[ImGuiCol_TabHovered] = ImVec4(0.40f, 0.40f, 0.40f, 1.00f);
colors[ImGuiCol_TabActive] = ImVec4(0.33f, 0.33f, 0.33f, 1.00f);
colors[ImGuiCol_TabUnfocused] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.33f, 0.33f, 0.33f, 1.00f);
colors[ImGuiCol_DockingPreview] = ImVec4(0.85f, 0.85f, 0.85f, 0.28f);
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
style.WindowRounding = 0.0f;
style.Colors[ImGuiCol_WindowBg].w = 1.0f;
}
*/
}
namespace ImGui {
void LoadingIndicatorCircle(const char* label, const float indicator_radius,
const ImVec4& main_color, const ImVec4& backdrop_color,

View file

@ -26,6 +26,8 @@ struct Toast {
class ImGuiToastNotifier {
private:
std::vector<Toast> toasts;
const float TOAST_WIDTH = 300.0f;
const float TOAST_PADDING = 10.0f;
public:
void addToast(const std::string& title, const std::string& text, ToastType type, float duration = 3.0f) {
@ -37,56 +39,101 @@ public:
return;
}
ImGuiIO& io = ImGui::GetIO();
ImVec2 viewport_pos = ImGui::GetMainViewport()->Pos;
ImVec2 viewport_size = ImGui::GetMainViewport()->Size;
ImVec2 window_pos = ImVec2(viewport_pos.x + viewport_size.x - 10.0f, viewport_pos.y + 10.0f);
// Get the main viewport work area (excludes menu bar, status bar, etc.)
ImGuiViewport* viewport = ImGui::GetMainViewport();
ImVec2 work_pos = viewport->WorkPos;
ImVec2 work_size = viewport->WorkSize;
// Position in top-right corner of the work area
ImVec2 window_pos = ImVec2(
work_pos.x + work_size.x - TOAST_PADDING,
work_pos.y + TOAST_PADDING
);
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, ImVec2(1.0f, 0.0f));
ImGui::SetNextWindowBgAlpha(0.75f);
if (ImGui::Begin("##ToastWindow", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing)) {
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 |
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_AlwaysAutoResize;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12, 12));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 8));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
if (ImGui::Begin("##ToastWindow", nullptr, window_flags)) {
auto now = std::chrono::steady_clock::now();
auto it = toasts.begin();
while (it != toasts.end()) {
auto elapsed = std::chrono::duration<float>(now - it->startTime).count();
if (elapsed > it->duration) {
it = toasts.erase(it);
continue;
}
// Calculate fade out effect in the last 0.5 seconds
float alpha = 1.0f;
if (elapsed > it->duration - 0.5f) {
alpha = (it->duration - elapsed) / 0.5f;
}
ImGui::PushID(it->title.c_str());
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
ImVec4 color;
const char* icon;
switch (it->type) {
case Success:
color = ImVec4(0.18f, 0.80f, 0.44f, 1.0f);
icon = ICON_FA_CHECK_CIRCLE; // Font Awesome 5 success icon (check-circle)
icon = ICON_FA_CHECK_CIRCLE;
break;
case Warning:
color = ImVec4(1.0f, 0.84f, 0.0f, 1.0f);
icon = ICON_FA_EXCLAMATION_TRIANGLE; // Font Awesome 5 warning icon (exclamation-triangle)
icon = ICON_FA_EXCLAMATION_TRIANGLE;
break;
case Error:
color = ImVec4(0.94f, 0.31f, 0.31f, 1.0f);
icon = ICON_FA_TIMES_CIRCLE; // Font Awesome 5 error icon (times-circle)
icon = ICON_FA_TIMES_CIRCLE;
break;
}
// Draw icon and title on the same line
ImGui::PushStyleColor(ImGuiCol_Text, color);
ImGui::Text("%s", icon);
ImGui::PopStyleColor();
ImGui::SameLine();
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); // Use default font for title
ImGui::TextUnformatted(it->title.c_str());
ImGui::TextWrapped("%s", it->text.c_str());
ImGui::PopFont();
ImGui::Separator();
// Draw message text with wrapping
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + TOAST_WIDTH - 24.0f);
ImGui::TextUnformatted(it->text.c_str());
ImGui::PopTextWrapPos();
ImGui::PopStyleVar(); // Alpha
// Add separator between toasts if not the last one
if (std::next(it) != toasts.end()) {
ImGui::Separator();
}
ImGui::PopID();
++it;
}
ImGui::End();
}
ImGui::PopStyleVar(3); // WindowPadding, ItemSpacing, WindowRounding
}
};

View file

@ -46,7 +46,7 @@ MainWindow::MainWindow(ILogger& logger, EventBus& eventBus, AppController& appCo
, m_resourcesDock(appController.GetResourceManager(), appController)
, m_nodeEditorWindow(appController, appController.GetNodesFactory(), m_widgetFactory, IStoryProject::PROJECT_TYPE_STORY)
, m_moduleEditorWindow(appController, appController.GetNodesFactory(), m_widgetFactory, IStoryProject::PROJECT_TYPE_MODULE)
, m_libraryWindow(appController, appController.GetLibraryManager(), appController.GetNodesFactory())
, m_libraryWindow(appController, appController.GetLibraryManager(), appController.GetNodesFactory(), eventBus)
, m_projectPropertiesDialog(appController, appController.GetResourceManager())
{
CloseProject();
@ -96,10 +96,15 @@ MainWindow::MainWindow(ILogger& logger, EventBus& eventBus, AppController& appCo
});
m_eventBus.Subscribe<ModuleEvent>([this](const ModuleEvent &event) {
if (event.GetType() == ModuleEvent::Type::Opened) {
if (event.GetType() == ModuleEvent::Type::Open) {
OpenModule(event.GetUuid());
} else if (event.GetType() == ModuleEvent::Type::Closed) {
CloseModule();
} else if (event.GetType() == ModuleEvent::Type::BuildSuccess) {
m_toastNotifier.addToast("Module", "Module built successfully", ToastType::Success);
m_debuggerWindow.SetScript(m_appController.GetModuleAssembly());
} else if (event.GetType() == ModuleEvent::Type::BuildFailure) {
m_toastNotifier.addToast("Module", "Module build failed", ToastType::Error);
}
});
}
@ -327,6 +332,7 @@ void MainWindow::NewModule()
void MainWindow::SaveModule()
{
m_moduleEditorWindow.SaveNodesToProject();
m_appController.SaveModule();
m_logger.Log("Modules saved");
m_toastNotifier.addToast("Module", "Module saved", ToastType::Success);
@ -334,7 +340,7 @@ void MainWindow::SaveModule()
void MainWindow::OpenModule(const std::string &uuid)
{
auto module = m_appController.GetCurrentModule();
auto module = m_appController.OpenModule(uuid);
if (module)
{
m_moduleEditorWindow.Load(module);
@ -410,45 +416,80 @@ void MainWindow::DrawToolBar(float topPadding)
ImGuiWindowFlags_NoDocking;
// Définit la taille et la position de la barre d'outils
ImVec2 size = ImVec2(60, ImGui::GetIO().DisplaySize.y - topPadding); // Largeur de 60 pixels et hauteur égale à celle de l'écran
ImVec2 size = ImVec2(60, ImGui::GetIO().DisplaySize.y - topPadding);
ImGui::SetNextWindowSize(size);
ImGui::SetNextWindowPos(ImVec2(0, topPadding)); // Positionné à gauche et en haut
ImGui::SetNextWindowPos(ImVec2(0, topPadding));
// Style pour les coins arrondis des boutons
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 4));
// Création de la fenêtre pour la barre d'outils
ImGui::Begin("ToolBar", nullptr, window_flags);
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); // rouge
float old_size = ImGui::GetFont()->Scale;
ImGui::GetFont()->Scale *= 2.5;
ImGui::PushFont(ImGui::GetFont());
// Ajouter des boutons à la barre d'outils
if (ImGui::Button(ICON_MDI_SPEAKER_STOP "##stop_sound", ImVec2(-1, 50))) { // Le bouton prend toute la largeur de la fenêtre et a une hauteur de 50 pixels
m_appController.StopAudio();
}
if (ImGui::Button(ICON_MDI_HAMMER "##build_project", ImVec2(-1, 50))) { // Le bouton prend toute la largeur de la fenêtre et a une hauteur de 50 pixels
if (ImGui::Begin("ToolBar", nullptr, window_flags))
{
// Taille réduite des boutons
float buttonSize = 36.0f;
float windowWidth = ImGui::GetContentRegionAvail().x;
float offsetX = (windowWidth - buttonSize) * 0.5f;
// Compile story if window focused, otherwise module
if (m_nodeEditorWindow.IsFocused())
{
m_appController.CompileNodes(IStoryProject::PROJECT_TYPE_STORY);
// Style pour les couleurs des boutons
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.2f, 0.2f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.15f, 0.15f, 0.15f, 1.0f));
// Taille de fonte réduite pour les icônes
float old_size = ImGui::GetFont()->Scale;
ImGui::GetFont()->Scale *= 1.2f;
ImGui::PushFont(ImGui::GetFont());
// Bouton Stop Sound
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX);
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.9f, 0.3f, 0.3f, 1.0f));
if (ImGui::Button(ICON_MDI_SPEAKER_STOP "##stop_sound", ImVec2(buttonSize, buttonSize))) {
m_appController.StopAudio();
}
else
{
m_appController.CompileNodes(IStoryProject::PROJECT_TYPE_MODULE);
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Stop Sound");
}
// Espacement vertical
ImGui::Dummy(ImVec2(0, 8));
// Bouton Build
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX);
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.3f, 0.8f, 0.9f, 1.0f));
if (ImGui::Button(ICON_MDI_HAMMER "##build_project", ImVec2(buttonSize, buttonSize))) {
// Compile story if window focused, otherwise module
if (m_nodeEditorWindow.IsFocused())
{
m_appController.CompileNodes(IStoryProject::PROJECT_TYPE_STORY);
}
else
{
m_appController.CompileNodes(IStoryProject::PROJECT_TYPE_MODULE);
}
}
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Build Project");
}
// Restaurer le scale de la fonte
ImGui::GetFont()->Scale = old_size;
ImGui::PopFont();
// Pop les couleurs des boutons
ImGui::PopStyleColor(3); // Button, ButtonHovered, ButtonActive
// Fermeture de la fenêtre ImGui
ImGui::End();
}
ImGui::GetFont()->Scale = old_size;
ImGui::PopFont();
ImGui::PopStyleColor();
// Fermeture de la fenêtre ImGui
ImGui::End();
// Pop les styles de frame
ImGui::PopStyleVar(2); // FrameRounding, FramePadding
}
#include "imgui_internal.h"

View file

@ -10,6 +10,9 @@
#include "base_node.h"
#include "gui.h"
namespace Nw
{
enum class PinType
{
Flow,
@ -40,19 +43,43 @@ enum class NodeType
struct PinStyle
{
/// @brief Socket and link color
ImU32 color;
ImU32 color{IM_COL32(255,255,255,255)};
/// @brief Socket shape ID
int socket_shape;
int socket_shape{5};
/// @brief Socket radius
float socket_radius;
float socket_radius{4.f};
/// @brief Socket radius when hovered
float socket_hovered_radius;
float socket_hovered_radius{4.4f};
/// @brief Socket radius when connected
float socket_connected_radius;
float socket_connected_radius{4.2f};
/// @brief Socket outline thickness when empty
float socket_thickness;
float socket_thickness{1.f};
ImVec2 padding = ImVec2(3.f, 1.f);
/// @brief Border and background corner rounding
float bg_radius = 8.f;
/// @brief Border thickness
float border_thickness = 1.f;
/// @brief Background color
ImU32 bg_color = IM_COL32(23, 16, 16, 0);
/// @brief Background color when hovered
ImU32 bg_hover_color = IM_COL32(100, 100, 255, 70);
/// @brief Border color
ImU32 border_color = IM_COL32(255, 255, 255, 0);
};
struct Pin
{
ImVec2 pos = ImVec2(0.f, 0.f);
ImVec2 size;
ImVec2 pinPoint = ImVec2(0.f, 0.f);
bool isConnected{false};
int index{0};
PinKind pinKind{PinKind::Input};
PinStyle style;
};
} // namespace Nw
/**
* @brief Basically a wrapper class around ImGuiNodeEditor Node structure
*
@ -69,7 +96,7 @@ public:
virtual void DrawProperties(std::shared_ptr<IStoryProject> story) = 0;
virtual void DrawSocket(uint32_t index, bool isInput, ImVec2 pin_pos, bool isConnected) {}
virtual void DrawSocket(const Nw::Pin &pin) {}
virtual bool HasSync() const {

View file

@ -12,6 +12,108 @@
#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:
@ -27,27 +129,123 @@ public:
ImGui::SetNextItemWidth(100.f);
}
void DrawSocket(uint32_t index, bool isInput, ImVec2 pin_pos, bool isConnected) override
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 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);
ImVec2 tl = pin.pos - ImVec2(socket_size * 1.5f, socket_size);
ImVec2 br = pin.pos + ImVec2(socket_size * 1.5f, socket_size);
if (isConnected)
if (pin.isConnected)
{
draw_list->AddConvexPolyFilled(vertices, IM_ARRAYSIZE(vertices), IM_COL32(255,255,255,255));
}
@ -55,13 +253,15 @@ public:
{
if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br))
{
draw_list->AddPolyline(vertices, IM_ARRAYSIZE(vertices),IM_COL32(255,255,255,255), ImDrawFlags_Closed, 4.67f);
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 {

View file

@ -59,10 +59,15 @@ public:
if (port.customSocketIcon)
{
ImFlow::BaseNode::addIN<int>("In" + std::to_string(i), 0, ImFlow::ConnectionFilter::SameType())->renderer([this, i](ImFlow::Pin* p) {
ImGui::Text("C");
p->drawDecoration();
//p->drawSocket();
m_widget->DrawSocket(i, true, p->getPos(), p->isConnected());
Nw::Pin pin;
pin.index = i;
pin.isConnected = p->isConnected();
pin.pinKind = Nw::PinKind::Input;
pin.pinPoint = p->pinPoint();
pin.pos = p->getPos();
pin.size = p->getSize();
m_widget->DrawSocket(pin);
});
}
else
@ -78,10 +83,16 @@ public:
if (port.customSocketIcon)
{
ImFlow::BaseNode::addOUT<int>("Out" + std::to_string(i), nullptr)->renderer([this, i](ImFlow::Pin* p) {
ImGui::Text("C");
p->drawDecoration();
// p->drawSocket();
m_widget->DrawSocket(i, false, p->getPos(), p->isConnected());
Nw::Pin pin;
pin.index = i;
pin.isConnected = p->isConnected();
pin.pinKind = Nw::PinKind::Output;
pin.pinPoint = p->pinPoint();
pin.pos = p->getPos();
pin.size = p->getSize();
m_widget->DrawSocket(pin);
});
}
else
@ -121,10 +132,7 @@ struct NodeEditorPage : public ImFlow::BaseNode
, m_name(name)
{
mINF.setSize({500, 500});
// mINF.addNode<SimpleSum>({0, 0});
// mINF.addNode<SimpleSum>({10, 10});
mINF.setSize({0, 0});
}
~NodeEditorPage() {
@ -182,7 +190,7 @@ struct NodeEditorPage : public ImFlow::BaseNode
mINF.rightClickPopUpContent([this, openPopupPosition, &nodesFactory, &widgetFactory, &storyManager](ImFlow::BaseNode* node){
// std::cout << "Right-clicked on node: " << node->getName() << std::endl;
auto newNodePostion = openPopupPosition;
auto newNodePosition = mINF.screen2grid(openPopupPosition);
auto nodeTypes = nodesFactory.ListOfNodes();
for (auto &type : nodeTypes)
@ -196,11 +204,11 @@ struct NodeEditorPage : public ImFlow::BaseNode
if (n)
{
// Create delegate
auto delegate = mINF.addNode<NodeDelegate>({newNodePostion.x, newNodePostion.y});
auto delegate = mINF.placeNode<NodeDelegate>();
// Link with the widget
delegate->SetWidget(n);
n->Base()->SetPosition(newNodePostion.x, newNodePostion.y);
n->Base()->SetPosition(newNodePosition.x, newNodePosition.y);
n->Initialize();
// AddNode(n);
}

View file

@ -102,53 +102,140 @@ void NodeEditorWindow::LoadPage(const std::string &uuid, const std::string &name
void NodeEditorWindow::Load(std::shared_ptr<StoryProject> story)
{
m_story = story;
if (m_story)
if (!m_story)
{
try {
BaseNodeWidget::InitId();
InitializeProject();
auto [node_begin, node_end] = m_story->Nodes(m_currentPage->Uuid());
int i = 0;
for (auto it = node_begin; it != node_end; ++it)
{
auto n = m_widgetFactory.CreateNodeWidget((*it)->GetType(), m_manager, (*it));
if (n)
{
n->Initialize();
// n->SetOutputs(m_story->OutputsCount((*it)->GetId())); // il faut que les noeuds aient une bonne taille de outputs avant de créer les liens
// m_currentPage->AddNode(n);
}
else
{
throw std::logic_error(std::string("No registered model with name ") + (*it)->GetType());
}
std::cout << "Created " << ++i << " node" << std::endl;
}
auto [link_begin, link_end] = m_story->Links(m_currentPage->Uuid());
for (auto it = link_begin; it != link_end; ++it)
{
// CreateLink(*it,
// GetInputPin((*it)->inNodeId, (*it)->inPortIndex),
// GetOutputPin((*it)->outNodeId, (*it)->outPortIndex));
}
m_loaded = true;
}
catch(std::exception &e)
{
std::cout << "(NodeEditorWindow::Load) " << e.what() << std::endl;
}
std::cout << "Cannot load null story" << std::endl;
return;
}
// std::cout << "Loaded " << m_currentPage->m_nodes.size() << " nodes, " << m_currentPage->m_links.size() << " links" << std::endl;
try
{
// Clear existing pages
m_pages.clear();
m_currentPage.reset();
// Load all pages from the project
auto [nodesBegin, nodesEnd] = m_story->Nodes(m_story->MainUuid());
auto [linksBegin, linksEnd] = m_story->Links(m_story->MainUuid());
// Create the main page
auto page = std::make_shared<NodeEditorPage>(m_story->MainUuid(), "Main");
m_pages.push_back(page);
m_currentPage = page;
// Map to store node UUID -> ImNodeFlow UID mapping
std::map<std::string, ImFlow::NodeUID> nodeUidMap;
// 1. Load all nodes from the project into ImNodeFlow
for (auto it = nodesBegin; it != nodesEnd; ++it)
{
auto baseNode = *it;
if (!baseNode)
continue;
// Create the widget for this node
auto widget = m_widgetFactory.CreateNodeWidget(
baseNode->GetType(),
m_manager,
baseNode
);
if (!widget)
{
std::cout << "Failed to create widget for node type: "
<< baseNode->GetType() << std::endl;
continue;
}
// Initialize the widget
widget->Initialize();
// Create a NodeDelegate in ImNodeFlow
ImVec2 nodePos(baseNode->GetX(), baseNode->GetY());
auto delegate = page->mINF.addNode<NodeDelegate>(nodePos);
// Link the delegate with the widget
delegate->SetWidget(widget);
// Store the mapping between project node UUID and ImNodeFlow UID
nodeUidMap[baseNode->GetId()] = delegate->getUID();
std::cout << "Loaded node: " << baseNode->GetType()
<< " at (" << nodePos.x << ", " << nodePos.y << ")" << std::endl;
}
// 2. Load all connections/links
for (auto it = linksBegin; it != linksEnd; ++it)
{
auto connection = *it;
if (!connection)
continue;
// Find the source and target nodes in ImNodeFlow
auto sourceUidIt = nodeUidMap.find(connection->outNodeId);
auto targetUidIt = nodeUidMap.find(connection->inNodeId);
if (sourceUidIt == nodeUidMap.end() || targetUidIt == nodeUidMap.end())
{
std::cout << "Warning: Cannot create link - node not found" << std::endl;
continue;
}
// Get the ImNodeFlow nodes
auto& nodes = page->mINF.getNodes();
auto sourceNodeIt = nodes.find(sourceUidIt->second);
auto targetNodeIt = nodes.find(targetUidIt->second);
if (sourceNodeIt == nodes.end() || targetNodeIt == nodes.end())
{
std::cout << "Warning: Node UID not found in ImNodeFlow" << std::endl;
continue;
}
auto sourceNode = sourceNodeIt->second;
auto targetNode = targetNodeIt->second;
// Get the pins from the nodes
// Output pin from source node
auto& sourcePins = sourceNode->getOuts();
if (connection->outPortIndex >= static_cast<int>(sourcePins.size()))
{
std::cout << "Warning: Invalid output port index: "
<< connection->outPortIndex << std::endl;
continue;
}
auto* sourcePin = sourcePins[connection->outPortIndex].get();
// Input pin from target node
auto& targetPins = targetNode->getIns();
if (connection->inPortIndex >= static_cast<int>(targetPins.size()))
{
std::cout << "Warning: Invalid input port index: "
<< connection->inPortIndex << std::endl;
continue;
}
auto* targetPin = targetPins[connection->inPortIndex].get();
// Create the link in ImNodeFlow
if (sourcePin && targetPin)
{
targetPin->createLink(sourcePin);
std::cout << "Created link: " << connection->outNodeId
<< "[" << connection->outPortIndex << "] -> "
<< connection->inNodeId
<< "[" << connection->inPortIndex << "]" << std::endl;
}
}
m_loaded = true;
std::cout << "Loaded " << nodeUidMap.size() << " nodes successfully" << std::endl;
}
catch(std::exception &e)
{
std::cout << "(NodeEditorWindow::Load) Exception: " << e.what() << std::endl;
m_loaded = false;
}
}
void NodeEditorWindow::SaveNodePositions()
@ -158,28 +245,140 @@ void NodeEditorWindow::SaveNodePositions()
void NodeEditorWindow::SaveNodesToProject()
{
if (!m_story)
{
std::cout << "Cannot save: no story project loaded" << std::endl;
return;
}
// Clear current project structure
m_story->Clear();
// Pour toutes les pages
for (const auto& page : m_pages)
{
// Clear current project nodes and links
m_story->Clear();
// Create the page in the project
auto currentPage = m_story->CreatePage(page->Uuid());
// On récupère les noeuds de la page
for (const auto& node : page->GetNodes())
// 1. Save all nodes with their updated positions
for (auto &nodeEntry : page->mINF.getNodes())
{
// On les ajoute au projet
// currentPage->AddNode(node);
auto delegate = dynamic_cast<NodeDelegate*>(nodeEntry.second.get());
if (!delegate)
continue;
auto widget = delegate->GetWidget();
if (!widget)
continue;
auto baseNode = widget->Base();
if (!baseNode)
continue;
// Update node position from ImNodeFlow
ImVec2 nodePos = nodeEntry.second->getPos();
baseNode->SetPosition(nodePos.x, nodePos.y);
// Add node to project
m_story->AddNode(currentPage->Uuid(), baseNode);
std::cout << "Saved node: " << baseNode->GetId()
<< " at (" << nodePos.x << ", " << nodePos.y << ")" << std::endl;
}
// On récupère tous les liens de la page
for (const auto& link : page->GetLinks())
// 2. Save all links/connections
const auto& links = page->mINF.getLinks();
for (const auto& weakLink : links)
{
// On les ajoute au projet
// currentPage->AddLink(link);
auto link = weakLink.lock();
if (!link)
continue;
// Get left (source) and right (target) pins
auto* leftPin = link->left();
auto* rightPin = link->right();
if (!leftPin || !rightPin)
continue;
// Get the nodes that own these pins
auto* leftNode = leftPin->getParent();
auto* rightNode = rightPin->getParent();
if (!leftNode || !rightNode)
continue;
// Cast to NodeDelegate to get the widget
auto* leftDelegate = dynamic_cast<NodeDelegate*>(leftNode);
auto* rightDelegate = dynamic_cast<NodeDelegate*>(rightNode);
if (!leftDelegate || !rightDelegate)
continue;
auto leftWidget = leftDelegate->GetWidget();
auto rightWidget = rightDelegate->GetWidget();
if (!leftWidget || !rightWidget)
continue;
// Get the base nodes (model)
auto leftBaseNode = leftWidget->Base();
auto rightBaseNode = rightWidget->Base();
if (!leftBaseNode || !rightBaseNode)
continue;
// Find the pin indices
int leftPinIndex = -1;
int rightPinIndex = -1;
// Find output pin index on left node
const auto& leftOuts = leftNode->getOuts();
for (size_t i = 0; i < leftOuts.size(); ++i)
{
if (leftOuts[i].get() == leftPin)
{
leftPinIndex = static_cast<int>(i);
break;
}
}
// Find input pin index on right node
const auto& rightIns = rightNode->getIns();
for (size_t i = 0; i < rightIns.size(); ++i)
{
if (rightIns[i].get() == rightPin)
{
rightPinIndex = static_cast<int>(i);
break;
}
}
if (leftPinIndex < 0 || rightPinIndex < 0)
{
std::cout << "Warning: Could not find pin indices for connection" << std::endl;
continue;
}
// Create the connection object
auto connection = std::make_shared<Connection>();
connection->outNodeId = leftBaseNode->GetId();
connection->outPortIndex = leftPinIndex;
connection->inNodeId = rightBaseNode->GetId();
connection->inPortIndex = rightPinIndex;
// Add connection to project
m_story->AddConnection(currentPage->Uuid(), connection);
std::cout << "Saved connection: " << connection->outNodeId
<< "[" << connection->outPortIndex << "] -> "
<< connection->inNodeId
<< "[" << connection->inPortIndex << "]" << std::endl;
}
}
std::cout << "SaveNodesToProject completed successfully" << std::endl;
}
void NodeEditorWindow::OpenFunction(const std::string &uuid, const std::string &name)

View file

@ -13,6 +13,12 @@ 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, ">");
}
void PrintNodeWidget::Initialize()

View file

@ -9,6 +9,8 @@
#include "gui.h"
#include "ImGuiFileDialog.h"
#include "all_events.h"
typedef int (*xfer_callback_t)(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow);
@ -50,11 +52,12 @@ void download_file(CURL *curl,
}
LibraryWindow::LibraryWindow(IStoryManager &project, LibraryManager &library, NodesFactory &nodesFactory)
LibraryWindow::LibraryWindow(IStoryManager &project, LibraryManager &library, NodesFactory &nodesFactory, EventBus& eventBus)
: WindowBase("Library Manager")
, m_storyManager(project)
, m_libraryManager(library)
, m_nodesFactory(nodesFactory)
, m_eventBus(eventBus)
{
m_downloadThread = std::thread( std::bind(&LibraryWindow::DownloadThread, this) );
@ -678,8 +681,7 @@ void LibraryWindow::Draw()
ImGui::PushID(module.uuid.c_str());
if (ImGui::SmallButton("Open"))
{
// Open the module in the editor
m_storyManager.OpenModule(module.uuid);
m_eventBus.Emit(std::make_shared<ModuleEvent>(ModuleEvent::Type::Open, module.uuid));
}
ImGui::PopID();
}

View file

@ -7,6 +7,7 @@
#include "thread_safe_queue.h"
#include "downloader.h"
#include "nodes_factory.h"
#include "event_bus.h"
#include <curl/curl.h>
@ -65,7 +66,7 @@ struct TransferProgress {
class LibraryWindow : public WindowBase
{
public:
LibraryWindow(IStoryManager &project, LibraryManager &library, NodesFactory &nodesFactory);
LibraryWindow(IStoryManager &project, LibraryManager &library, NodesFactory &nodesFactory, EventBus& eventBus);
void Initialize();
virtual void Draw() override;
@ -75,6 +76,7 @@ private:
IStoryManager &m_storyManager;
LibraryManager &m_libraryManager;
NodesFactory &m_nodesFactory;
EventBus &m_eventBus;
Downloader m_downloader;
CURL *m_curl;