Compilation and load of module

This commit is contained in:
anthony@rabine.fr 2025-10-03 17:34:15 +02:00
parent 20bb0aca57
commit 07f4288748
8 changed files with 280 additions and 142 deletions

@ -1 +1 @@
Subproject commit e4984a42d3939d361f977e8d0a56bbfd49d0a702 Subproject commit 4634021545dfdf6b4621426576d8c9f05a71b1fd

View file

@ -260,12 +260,15 @@ void AppController::CompileNodes(IStoryProject::Type type)
if (m_module->GenerateScript(m_moduleAssembly)) if (m_module->GenerateScript(m_moduleAssembly))
{ {
m_logger.Log("Nodes script generated for module."); m_logger.Log("Nodes script generated for module.");
m_eventBus.Emit(std::make_shared<ModuleEvent>(ModuleEvent::Type::BuildSuccess, m_module->GetUuid())); BuildModule(true); // NEW: Use BuildModule instead
Build(true); // Compile seulement par défaut
} }
else else
{ {
m_logger.Log("Failed to generate script for module.", true); 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>(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>(ModuleEvent::Type::BuildSuccess, m_module->GetUuid()));
}
else
{
auto errObj = std::make_shared<ModuleEvent>(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>(ModuleEvent::Type::BuildFailure, m_module->GetUuid());
errObj->SetFailure(errorMsg, err.line);
m_eventBus.Emit(errObj);
}
}
void AppController::BuildCode(std::shared_ptr<StoryProject> story, bool compileonly, bool force) void AppController::BuildCode(std::shared_ptr<StoryProject> story, bool compileonly, bool force)
{ {
// Note: Dans le code original, BuildCode lisait m_externalSourceFileName. // Note: Dans le code original, BuildCode lisait m_externalSourceFileName.

View file

@ -69,6 +69,7 @@ public:
std::shared_ptr<StoryProject> GetCurrentModule() const { return m_module; } std::shared_ptr<StoryProject> GetCurrentModule() const { return m_module; }
void CompileNodes(IStoryProject::Type type); void CompileNodes(IStoryProject::Type type);
void Build(bool compileonly); void Build(bool compileonly);
void BuildModule(bool compileonly);
void BuildCode(std::shared_ptr<StoryProject> story, bool compileonly, bool force = false); void BuildCode(std::shared_ptr<StoryProject> story, bool compileonly, bool force = false);
// --- Fonctions de IStoryManager --- // --- Fonctions de IStoryManager ---
@ -147,8 +148,7 @@ private:
ThreadSafeQueue<VmEvent> m_eventQueue; // File d'événements de la VM ThreadSafeQueue<VmEvent> m_eventQueue; // File d'événements de la VM
WebServer m_webServer; // Serveur web intégré WebServer m_webServer; // Serveur web intégré
// Fonctions privées utilitaires pour la logique métier std::string WrapModuleWithMain(const std::string& moduleCode);
void SetupVM(int start_line, uint32_t entry_point);
}; };
#endif // APP_CONTROLLER_H #endif // APP_CONTROLLER_H

View file

@ -2,91 +2,137 @@
#include "imgui.h" #include "imgui.h"
void ErrorListDock::Draw() { void ErrorListDock::Draw() {
// Auto-show when errors are added
if (m_shouldShow) {
ImGui::SetNextWindowFocus();
m_shouldShow = false; // Reset flag after showing once
}
WindowBase::BeginDraw(); WindowBase::BeginDraw();
ImGui::SetWindowSize(ImVec2(800, 250), ImGuiCond_FirstUseEver);
ImGui::SetWindowSize(ImVec2(800, 200), ImGuiCond_FirstUseEver); // Header avec compteur et barre de couleur selon le type d'erreur
// Header avec compteur
size_t errorCount = GetErrorCount(); size_t errorCount = GetErrorCount();
size_t warningCount = GetWarningCount(); size_t warningCount = GetWarningCount();
// Barre de statut colorée en haut
if (errorCount > 0) { if (errorCount > 0) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.9f, 0.2f, 0.2f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.9f, 0.2f, 0.2f, 0.15f));
ImGui::Text("%s %zu", ICON_FA_TIMES_CIRCLE, errorCount); } else if (warningCount > 0) {
ImGui::PopStyleColor(); ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 0.8f, 0.0f, 0.15f));
} else { } 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(); ImGui::BeginChild("StatusBar", ImVec2(0, 42), true);
if (warningCount > 0) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.0f, 1.0f)); // Errors count
ImGui::Text("%s %zu", ICON_FA_EXCLAMATION_TRIANGLE, warningCount); 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(); ImGui::PopStyleColor();
} else { } 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::SameLine();
ImGui::Spacing(); ImGui::Spacing();
ImGui::SameLine(); 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")) { if (ImGui::Button(ICON_FA_TRASH " Clear")) {
Clear(); Clear();
} }
ImGui::EndChild();
ImGui::PopStyleColor(); // StatusBar background
ImGui::Separator(); ImGui::Separator();
// AJOUT du BeginChild pour la zone scrollable // Main content area with scrolling
ImGui::BeginChild("ErrorListContent", ImVec2(0, 0), false, ImGuiWindowFlags_AlwaysVerticalScrollbar); ImGui::BeginChild("ErrorListContent", ImVec2(0, 0), false, ImGuiWindowFlags_AlwaysVerticalScrollbar);
// Afficher un message si pas d'erreurs // Afficher un message si pas d'erreurs
if (m_errors.empty()) { if (m_errors.empty()) {
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), ImGui::Dummy(ImVec2(0, 20)); // Spacing
"%s No errors or warnings", ICON_FA_CHECK_CIRCLE); 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 { } else {
// Table des erreurs // Table des erreurs avec colonnes redimensionnables
if (ImGui::BeginTable("ErrorTable", 3, if (ImGui::BeginTable("ErrorTable", 4,
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders |
ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) ImGuiTableFlags_RowBg |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_ScrollY |
ImGuiTableFlags_SizingStretchProp))
{ {
ImGui::TableSetupColumn("##icon", ImGuiTableColumnFlags_WidthFixed, 30);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 80);
ImGui::TableSetupColumn("Message", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Message", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Node", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn("Line", ImGuiTableColumnFlags_WidthFixed, 60);
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
for (size_t i = 0; i < m_errors.size(); ++i) { for (size_t i = 0; i < m_errors.size(); ++i) {
const auto& error = m_errors[i]; const auto& error = m_errors[i];
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::PushID(static_cast<int>(i));
// Type column // Icon column
ImGui::TableSetColumnIndex(0); ImGui::TableSetColumnIndex(0);
ImGui::PushStyleColor(ImGuiCol_Text, error.GetTypeColor()); ImGui::PushStyleColor(ImGuiCol_Text, error.GetTypeColor());
ImGui::Text("%s %s", error.GetTypeIcon().c_str(), ImGui::Text("%s", error.GetTypeIcon().c_str());
error.type == CompilationError::ERROR ? "Error" : ImGui::PopStyleColor();
error.type == CompilationError::WARNING ? "Warning" : "Info");
// 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(); ImGui::PopStyleColor();
// Message column // Message column
ImGui::TableSetColumnIndex(1); ImGui::TableSetColumnIndex(2);
ImGui::TextWrapped("%s", error.message.c_str()); ImGui::TextWrapped("%s", error.message.c_str());
// Node column (clickable to navigate) // Line column
ImGui::TableSetColumnIndex(2); ImGui::TableSetColumnIndex(3);
if (!error.nodeId.empty()) { if (error.line > 0) {
if (ImGui::SmallButton(("Go##" + std::to_string(i)).c_str())) { ImGui::Text("%d", error.line);
// TODO: Emit event to navigate to node
}
ImGui::SameLine();
ImGui::TextDisabled("?");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Node: %s", error.nodeId.c_str());
}
} else { } else {
ImGui::TextDisabled("-"); 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(); ImGui::EndTable();

View file

@ -43,14 +43,26 @@ public:
void Draw() override; void Draw() override;
void Clear() { m_errors.clear(); } void Clear() {
void AddError(const CompilationError& error) { m_errors.push_back(error); } 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) { void AddError(const std::string& message, const std::string& nodeId = "", int line = 0) {
m_errors.push_back({CompilationError::ERROR, message, nodeId, line}); 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) { void AddWarning(const std::string& message, const std::string& nodeId = "", int line = 0) {
m_errors.push_back({CompilationError::WARNING, message, nodeId, line}); 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) { void AddInfo(const std::string& message, const std::string& nodeId = "", int line = 0) {
m_errors.push_back({CompilationError::INFO, message, nodeId, line}); m_errors.push_back({CompilationError::INFO, message, nodeId, line});
} }
@ -70,6 +82,13 @@ public:
[](const auto& e) { return e.type == CompilationError::WARNING; }); [](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: private:
std::vector<CompilationError> m_errors; std::vector<CompilationError> m_errors;
bool m_shouldShow = false;
}; };

View file

@ -78,11 +78,21 @@ public:
void SetScript(const std::string& script) { m_script = script; } void SetScript(const std::string& script) { m_script = script; }
void SetSuccess(bool s) { success = s; } 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: private:
Type m_type; Type m_type;
std::string m_uuid; std::string m_uuid;
std::string m_script; std::string m_script;
bool success{false}; bool success{false};
std::string m_message;
int m_line{0};
}; };
#endif // ALL_EVENTS_H #endif // ALL_EVENTS_H

View file

@ -100,10 +100,30 @@ MainWindow::MainWindow(ILogger& logger, EventBus& eventBus, AppController& appCo
} else if (event.GetType() == ModuleEvent::Type::Closed) { } else if (event.GetType() == ModuleEvent::Type::Closed) {
CloseModule(); CloseModule();
} else if (event.GetType() == ModuleEvent::Type::BuildSuccess) { } 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()); 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); 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(); m_aboutDialog.Draw();

View file

@ -1,8 +1,6 @@
#include "console_window.h" #include "console_window.h"
#include <stdio.h> #include <stdio.h>
ConsoleWindow::ConsoleWindow() ConsoleWindow::ConsoleWindow()
: WindowBase("Console") : WindowBase("Console")
{ {
@ -30,124 +28,96 @@ void ConsoleWindow::Draw()
ImGui::SetWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); ImGui::SetWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
ImGui::TextWrapped("Console view"); ImGui::TextWrapped("Console view");
// ImGui::TextWrapped("Enter 'HELP' for help.");
// TODO: display items starting from the bottom if (ImGui::SmallButton("Clear")) {
ClearLog();
// 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(); }
ImGui::SameLine(); ImGui::SameLine();
// Option pour activer/désactiver l'auto-scroll
ImGui::Checkbox("Auto-scroll", &AutoScroll);
ImGui::SameLine();
bool copy_to_clipboard = ImGui::SmallButton("Copy"); 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(); ImGui::Separator();
// Reserve enough left-over height for 1 separator + 1 input text // Reserve enough left-over height for 1 separator + 1 input text
const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar);
if (ImGui::BeginPopupContextWindow()) if (ImGui::BeginPopupContextWindow())
{ {
if (ImGui::Selectable("Clear")) ClearLog(); if (ImGui::Selectable("Clear")) ClearLog();
ImGui::EndPopup(); ImGui::EndPopup();
} }
// Display every line as a separate entry so we can change their color or add custom widgets. // Afficher les logs avec thread-safety
// 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 std::scoped_lock<std::mutex> mutex(mLogMutex);
// 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 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing
if (copy_to_clipboard) if (copy_to_clipboard)
ImGui::LogToClipboard(); ImGui::LogToClipboard();
ImGuiListClipper clipper; for (const auto& entry : Items)
std::scoped_lock<std::mutex> mutex(mLogMutex);
clipper.Begin(Items.size());
while (clipper.Step())
{ {
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) const char* item = entry.text.c_str();
{
if (Items[i].type > 0)
{
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255,0,0,255));
}
ImGui::TextUnformatted(Items[i].text.c_str());
if (Items[i].type > 0) // Coloration selon le type
{
ImGui::PopStyleColor();
}
}
}
/*
for (int i = 0; i < Items.Size; i++)
{
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; ImVec4 color;
bool has_color = false; 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 (entry.type == 1) { // Error
color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f);
has_color = true;
}
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) if (has_color)
ImGui::PushStyleColor(ImGuiCol_Text, color); ImGui::PushStyleColor(ImGuiCol_Text, color);
ImGui::TextUnformatted(item); ImGui::TextUnformatted(item);
if (has_color) if (has_color)
ImGui::PopStyleColor(); ImGui::PopStyleColor();
} }
*/
if (copy_to_clipboard) if (copy_to_clipboard)
ImGui::LogFinish(); ImGui::LogFinish();
if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) ImGui::PopStyleVar();
}
// 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))
{
ImGui::SetScrollHereY(1.0f); ImGui::SetScrollHereY(1.0f);
}
ScrollToBottom = false; ScrollToBottom = false;
ImGui::PopStyleVar();
ImGui::EndChild(); ImGui::EndChild();
ImGui::Separator(); ImGui::Separator();
WindowBase::EndDraw(); WindowBase::EndDraw();
} }