diff --git a/story-editor/externals/ImNodeFlow b/story-editor/externals/ImNodeFlow index e4984a4..4634021 160000 --- a/story-editor/externals/ImNodeFlow +++ b/story-editor/externals/ImNodeFlow @@ -1 +1 @@ -Subproject commit e4984a42d3939d361f977e8d0a56bbfd49d0a702 +Subproject commit 4634021545dfdf6b4621426576d8c9f05a71b1fd diff --git a/story-editor/src/app/app_controller.cpp b/story-editor/src/app/app_controller.cpp index 2cfbd6f..fb2c607 100644 --- a/story-editor/src/app/app_controller.cpp +++ b/story-editor/src/app/app_controller.cpp @@ -260,12 +260,15 @@ void AppController::CompileNodes(IStoryProject::Type type) if (m_module->GenerateScript(m_moduleAssembly)) { m_logger.Log("Nodes script generated for module."); - m_eventBus.Emit(std::make_shared(ModuleEvent::Type::BuildSuccess, m_module->GetUuid())); - Build(true); // Compile seulement par défaut + BuildModule(true); // NEW: Use BuildModule instead } else { m_logger.Log("Failed to generate script for module.", true); + // if (m_errorListDock) { + // m_errorListDock->AddError("Failed to generate assembly code from nodes"); + // } + m_eventBus.Emit(std::make_shared(ModuleEvent::Type::BuildFailure, m_module->GetUuid())); } } } @@ -317,6 +320,60 @@ void AppController::Build(bool compileonly) } } + +void AppController::BuildModule(bool compileonly) +{ + if (!m_module) { + m_logger.Log("No module loaded to build.", true); + return; + } + + // Le code du module est déjà complet avec .main: et halt + // Pas besoin de wrapper ! + + m_logger.Log("=== Module Assembly Code ==="); + m_logger.Log(m_moduleAssembly); + m_logger.Log("============================"); + + // Try to compile the module code directly + Chip32::Assembler::Error err; + + if (m_module->GenerateBinary(m_moduleAssembly, err)) + { + m_logger.Log("Module compiled successfully!"); + + // Save the binary to disk + m_module->SaveBinary(); + + // Load into VM for testing + if (m_module->CopyProgramTo(m_rom_data, sizeof(m_rom_data))) + { + chip32_initialize(&m_chip32_ctx); + m_dbg.run_result = VM_READY; + UpdateVmView(); + + m_logger.Log("Module binary ready for testing."); + m_eventBus.Emit(std::make_shared(ModuleEvent::Type::BuildSuccess, m_module->GetUuid())); + } + else + { + auto errObj = std::make_shared(ModuleEvent::Type::BuildFailure, m_module->GetUuid()); + errObj->SetFailure("Module program too big. Expand ROM memory.", -1); + m_eventBus.Emit(errObj); + } + } + else + { + // Compilation failed - show error + std::string errorMsg = err.ToString(); + + auto errObj = std::make_shared(ModuleEvent::Type::BuildFailure, m_module->GetUuid()); + errObj->SetFailure(errorMsg, err.line); + m_eventBus.Emit(errObj); + } +} + + void AppController::BuildCode(std::shared_ptr story, bool compileonly, bool force) { // Note: Dans le code original, BuildCode lisait m_externalSourceFileName. diff --git a/story-editor/src/app/app_controller.h b/story-editor/src/app/app_controller.h index 0068016..22a6b71 100644 --- a/story-editor/src/app/app_controller.h +++ b/story-editor/src/app/app_controller.h @@ -69,6 +69,7 @@ public: std::shared_ptr GetCurrentModule() const { return m_module; } void CompileNodes(IStoryProject::Type type); void Build(bool compileonly); + void BuildModule(bool compileonly); void BuildCode(std::shared_ptr story, bool compileonly, bool force = false); // --- Fonctions de IStoryManager --- @@ -147,8 +148,7 @@ private: ThreadSafeQueue m_eventQueue; // File d'événements de la VM WebServer m_webServer; // Serveur web intégré - // Fonctions privées utilitaires pour la logique métier - void SetupVM(int start_line, uint32_t entry_point); + std::string WrapModuleWithMain(const std::string& moduleCode); }; #endif // APP_CONTROLLER_H diff --git a/story-editor/src/docks/error_list_dock.cpp b/story-editor/src/docks/error_list_dock.cpp index e2134f9..b0b7038 100644 --- a/story-editor/src/docks/error_list_dock.cpp +++ b/story-editor/src/docks/error_list_dock.cpp @@ -2,91 +2,137 @@ #include "imgui.h" void ErrorListDock::Draw() { + // Auto-show when errors are added + if (m_shouldShow) { + ImGui::SetNextWindowFocus(); + m_shouldShow = false; // Reset flag after showing once + } + WindowBase::BeginDraw(); - - ImGui::SetWindowSize(ImVec2(800, 200), ImGuiCond_FirstUseEver); + ImGui::SetWindowSize(ImVec2(800, 250), ImGuiCond_FirstUseEver); - // Header avec compteur + // Header avec compteur et barre de couleur selon le type d'erreur size_t errorCount = GetErrorCount(); size_t warningCount = GetWarningCount(); + // Barre de statut colorée en haut 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(); + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.9f, 0.2f, 0.2f, 0.15f)); + } else if (warningCount > 0) { + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 0.8f, 0.0f, 0.15f)); } else { - ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "%s 0", ICON_FA_TIMES_CIRCLE); + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.3f, 0.8f, 0.3f, 0.15f)); } - 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::BeginChild("StatusBar", ImVec2(0, 42), true); + + // Errors count + if (errorCount > 0) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.9f, 0.2f, 0.2f, 1.0f)); + ImGui::Text("%s %zu Error%s", ICON_FA_TIMES_CIRCLE, errorCount, errorCount > 1 ? "s" : ""); ImGui::PopStyleColor(); } else { - ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "%s 0", ICON_FA_EXCLAMATION_TRIANGLE); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "%s 0 Errors", ICON_FA_TIMES_CIRCLE); } ImGui::SameLine(); ImGui::Spacing(); ImGui::SameLine(); + // Warnings count + if (warningCount > 0) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.0f, 1.0f)); + ImGui::Text("%s %zu Warning%s", ICON_FA_EXCLAMATION_TRIANGLE, warningCount, warningCount > 1 ? "s" : ""); + ImGui::PopStyleColor(); + } else { + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "%s 0 Warnings", ICON_FA_EXCLAMATION_TRIANGLE); + } + + // Clear button on the right + ImGui::SameLine(ImGui::GetWindowWidth() - 100); if (ImGui::Button(ICON_FA_TRASH " Clear")) { Clear(); } + ImGui::EndChild(); + ImGui::PopStyleColor(); // StatusBar background + ImGui::Separator(); - // AJOUT du BeginChild pour la zone scrollable + // Main content area with scrolling 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); + ImGui::Dummy(ImVec2(0, 20)); // Spacing + ImGui::Indent(20); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.3f, 0.8f, 0.3f, 1.0f)); + ImGui::Text("%s", ICON_FA_CHECK_CIRCLE); + ImGui::SameLine(); + ImGui::TextWrapped("No compilation errors or warnings. Your code is ready to run!"); + ImGui::PopStyleColor(); + ImGui::Unindent(20); } else { - // Table des erreurs - if (ImGui::BeginTable("ErrorTable", 3, - ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | - ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) + // Table des erreurs avec colonnes redimensionnables + if (ImGui::BeginTable("ErrorTable", 4, + ImGuiTableFlags_Borders | + ImGuiTableFlags_RowBg | + ImGuiTableFlags_Resizable | + ImGuiTableFlags_ScrollY | + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("##icon", ImGuiTableColumnFlags_WidthFixed, 30); ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn("Message", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("Node", ImGuiTableColumnFlags_WidthFixed, 80); + ImGui::TableSetupColumn("Line", ImGuiTableColumnFlags_WidthFixed, 60); ImGui::TableHeadersRow(); for (size_t i = 0; i < m_errors.size(); ++i) { const auto& error = m_errors[i]; ImGui::TableNextRow(); + ImGui::PushID(static_cast(i)); - // Type column + // Icon 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::Text("%s", error.GetTypeIcon().c_str()); + ImGui::PopStyleColor(); + + // Type column + ImGui::TableSetColumnIndex(1); + ImGui::PushStyleColor(ImGuiCol_Text, error.GetTypeColor()); + const char* typeStr = (error.type == CompilationError::ERROR) ? "Error" : + (error.type == CompilationError::WARNING) ? "Warning" : "Info"; + ImGui::Text("%s", typeStr); ImGui::PopStyleColor(); // Message column - ImGui::TableSetColumnIndex(1); + ImGui::TableSetColumnIndex(2); 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()); - } + // Line column + ImGui::TableSetColumnIndex(3); + if (error.line > 0) { + ImGui::Text("%d", error.line); } else { ImGui::TextDisabled("-"); } + + // Optional: Node navigation button + if (!error.nodeId.empty()) { + ImGui::SameLine(); + if (ImGui::SmallButton(ICON_FA_ARROW_RIGHT)) { + // TODO: Emit event to navigate to node + // m_eventBus.Emit(NavigateToNodeEvent(error.nodeId)); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Go to node: %s", error.nodeId.c_str()); + } + } + + ImGui::PopID(); } ImGui::EndTable(); diff --git a/story-editor/src/docks/error_list_dock.h b/story-editor/src/docks/error_list_dock.h index 8fd8aac..2b19ae2 100644 --- a/story-editor/src/docks/error_list_dock.h +++ b/story-editor/src/docks/error_list_dock.h @@ -43,14 +43,26 @@ public: void Draw() override; - void Clear() { m_errors.clear(); } - void AddError(const CompilationError& error) { m_errors.push_back(error); } + void Clear() { + m_errors.clear(); + m_shouldShow = false; // Hide when cleared + } + + void AddError(const CompilationError& error) { + m_errors.push_back(error); + m_shouldShow = true; // Show when error added + } + void AddError(const std::string& message, const std::string& nodeId = "", int line = 0) { m_errors.push_back({CompilationError::ERROR, message, nodeId, line}); + m_shouldShow = true; } + void AddWarning(const std::string& message, const std::string& nodeId = "", int line = 0) { m_errors.push_back({CompilationError::WARNING, message, nodeId, line}); + m_shouldShow = true; } + void AddInfo(const std::string& message, const std::string& nodeId = "", int line = 0) { m_errors.push_back({CompilationError::INFO, message, nodeId, line}); } @@ -70,6 +82,13 @@ public: [](const auto& e) { return e.type == CompilationError::WARNING; }); } + // Force the window to be visible (used when errors occur) + void Show() { + m_shouldShow = true; + Enable(); + } + private: std::vector m_errors; + bool m_shouldShow = false; }; \ No newline at end of file diff --git a/story-editor/src/events/all_events.h b/story-editor/src/events/all_events.h index ed05f84..48e4383 100644 --- a/story-editor/src/events/all_events.h +++ b/story-editor/src/events/all_events.h @@ -78,11 +78,21 @@ public: void SetScript(const std::string& script) { m_script = script; } void SetSuccess(bool s) { success = s; } + void SetFailure(const std::string &message, int line) { + m_message = message; + m_line = line; + } + + const std::string &GetMessage() const { return m_message; } + int GetLine() const { return m_line; } + private: Type m_type; std::string m_uuid; std::string m_script; bool success{false}; + std::string m_message; + int m_line{0}; }; #endif // ALL_EVENTS_H \ No newline at end of file diff --git a/story-editor/src/main_window.cpp b/story-editor/src/main_window.cpp index 6cf9cd3..d9c6bbb 100644 --- a/story-editor/src/main_window.cpp +++ b/story-editor/src/main_window.cpp @@ -100,10 +100,30 @@ MainWindow::MainWindow(ILogger& logger, EventBus& eventBus, AppController& appCo } 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_toastNotifier.addToast("Module", "Module built successfully! Binary ready for testing.", ToastType::Success); m_debuggerWindow.SetScript(m_appController.GetModuleAssembly()); - } else if (event.GetType() == ModuleEvent::Type::BuildFailure) { - m_toastNotifier.addToast("Module", "Module build failed", ToastType::Error); + + // Show success message if no errors + if (!m_errorListDock.HasErrors()) { + // You can also open a popup if desired + m_logger.Log("✓ Module compilation successful - Binary saved and loaded into VM"); + } + } + else if (event.GetType() == ModuleEvent::Type::BuildFailure) { + + m_logger.Log("Module compilation failed: " + event.GetMessage(), true); + m_toastNotifier.addToast("Module", "Module build failed - Check Error List", ToastType::Error); + + // Make sure error list is visible + m_errorListDock.Enable(); + m_errorListDock.Show(); + + static CompilationError err; + err.line = event.GetLine(); + err.message = event.GetMessage(); + + // Add error to dock + m_errorListDock.AddError(err); } }); @@ -591,6 +611,22 @@ bool MainWindow::Loop() m_logger.Log("Tentative de sauvegarde sans projet ouvert", true); } } + + if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_B, false)) + { + bool moduleEditorFocused = m_moduleEditorWindow.IsFocused(); + + if (moduleEditorFocused && m_module) + { + m_logger.Log("Building module..."); + m_appController.CompileNodes(IStoryProject::PROJECT_TYPE_MODULE); + } + else if (m_story) + { + m_logger.Log("Building story..."); + m_appController.CompileNodes(IStoryProject::PROJECT_TYPE_STORY); + } + } } m_aboutDialog.Draw(); diff --git a/story-editor/src/windows/console_window.cpp b/story-editor/src/windows/console_window.cpp index 62a507e..10ec5ab 100644 --- a/story-editor/src/windows/console_window.cpp +++ b/story-editor/src/windows/console_window.cpp @@ -1,8 +1,6 @@ #include "console_window.h" - #include - ConsoleWindow::ConsoleWindow() : WindowBase("Console") { @@ -30,124 +28,96 @@ void ConsoleWindow::Draw() ImGui::SetWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); - ImGui::TextWrapped("Console view"); -// ImGui::TextWrapped("Enter 'HELP' for help."); - - // TODO: display items starting from the bottom - -// if (ImGui::SmallButton("Add Debug Text")) { AddLog("%d some text", Items.Size); AddLog("some more text"); AddLog("display very important message here!"); } -// ImGui::SameLine(); -// if (ImGui::SmallButton("Add Debug Error")) { AddLog("[error] something went wrong"); } -// ImGui::SameLine(); - if (ImGui::SmallButton("Clear")) { ClearLog(); } + + if (ImGui::SmallButton("Clear")) { + ClearLog(); + } ImGui::SameLine(); + + // Option pour activer/désactiver l'auto-scroll + ImGui::Checkbox("Auto-scroll", &AutoScroll); + ImGui::SameLine(); + bool copy_to_clipboard = ImGui::SmallButton("Copy"); - //static float t = 0.0f; if (ImGui::GetTime() - t > 0.02f) { t = ImGui::GetTime(); AddLog("Spam %f", t); } - -// ImGui::Separator(); - -// // Options menu -// if (ImGui::BeginPopup("Options")) -// { -// ImGui::Checkbox("Auto-scroll", &AutoScroll); -// ImGui::EndPopup(); -// } - -// // Options, Filter -// if (ImGui::Button("Options")) -// ImGui::OpenPopup("Options"); -// ImGui::SameLine(); -// Filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180); + ImGui::Separator(); // Reserve enough left-over height for 1 separator + 1 input text const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); + if (ImGui::BeginPopupContextWindow()) { if (ImGui::Selectable("Clear")) ClearLog(); ImGui::EndPopup(); } - // Display every line as a separate entry so we can change their color or add custom widgets. - // If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); - // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping - // to only process visible items. The clipper will automatically measure the height of your first item and then - // "seek" to display only items in the visible area. - // To use the clipper we can replace your standard loop: - // for (int i = 0; i < Items.Size; i++) - // With: - // ImGuiListClipper clipper; - // clipper.Begin(Items.Size); - // while (clipper.Step()) - // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) - // - That your items are evenly spaced (same height) - // - That you have cheap random access to your elements (you can access them given their index, - // without processing all the ones before) - // You cannot this code as-is if a filter is active because it breaks the 'cheap random-access' property. - // We would need random-access on the post-filtered list. - // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices - // or offsets of items that passed the filtering test, recomputing this array when user changes the filter, - // and appending newly elements as they are inserted. This is left as a task to the user until we can manage - // to improve this example code! - // If your items are of variable height: - // - Split them into same height items would be simpler and facilitate random-seeking into your list. - // - Consider using manual call to IsRectVisible() and skipping extraneous decoration from your items. - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing - if (copy_to_clipboard) - ImGui::LogToClipboard(); - - ImGuiListClipper clipper; - std::scoped_lock mutex(mLogMutex); - clipper.Begin(Items.size()); - while (clipper.Step()) + // Afficher les logs avec thread-safety { - for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + std::scoped_lock mutex(mLogMutex); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing + + if (copy_to_clipboard) + ImGui::LogToClipboard(); + + for (const auto& entry : Items) { - if (Items[i].type > 0) - { - ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255,0,0,255)); + const char* item = entry.text.c_str(); + + // Coloration selon le type + ImVec4 color; + bool has_color = false; + + if (entry.type == 1) { // Error + color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); + has_color = true; } - ImGui::TextUnformatted(Items[i].text.c_str()); - - if (Items[i].type > 0) - { + else if (strstr(item, "[error]")) { + color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); + has_color = true; + } + else if (strncmp(item, "# ", 2) == 0) { + color = ImVec4(1.0f, 0.8f, 0.6f, 1.0f); + has_color = true; + } + + if (has_color) + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(item); + if (has_color) ImGui::PopStyleColor(); - } } + + if (copy_to_clipboard) + ImGui::LogFinish(); + + ImGui::PopStyleVar(); } - /* - for (int i = 0; i < Items.Size; i++) + + // Solution simple : ne forcer le scroll QUE quand on ajoute un nouveau message + // et que l'utilisateur était déjà en bas (ou si ScrollToBottom est explicitement demandé) + + float scrollY = ImGui::GetScrollY(); + float scrollMaxY = ImGui::GetScrollMaxY(); + + // On considère qu'on est "en bas" avec une petite tolérance + const float BOTTOM_THRESHOLD = 5.0f; + bool wasAtBottom = (scrollMaxY == 0) || (scrollY >= scrollMaxY - BOTTOM_THRESHOLD); + + // Seulement scroller si : + // 1. ScrollToBottom est explicitement demandé (nouveau message ajouté), OU + // 2. AutoScroll est activé ET on était déjà en bas + if (ScrollToBottom || (AutoScroll && wasAtBottom)) { - const char* item = Items[i]; - if (!Filter.PassFilter(item)) - continue; - - // Normally you would store more information in your item than just a string. - // (e.g. make Items[] an array of structure, store color/type etc.) - ImVec4 color; - bool has_color = false; - if (strstr(item, "[error]")) { color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); has_color = true; } - else if (strncmp(item, "# ", 2) == 0) { color = ImVec4(1.0f, 0.8f, 0.6f, 1.0f); has_color = true; } - if (has_color) - ImGui::PushStyleColor(ImGuiCol_Text, color); - ImGui::TextUnformatted(item); - if (has_color) - ImGui::PopStyleColor(); - } - */ - if (copy_to_clipboard) - ImGui::LogFinish(); - - if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) ImGui::SetScrollHereY(1.0f); + } + ScrollToBottom = false; - ImGui::PopStyleVar(); ImGui::EndChild(); ImGui::Separator(); - WindowBase::EndDraw(); -} - + WindowBase::EndDraw(); +} \ No newline at end of file