mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-08 09:43:32 +01:00
Compare commits
3 commits
d58967710a
...
88b9bc6b3e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88b9bc6b3e | ||
|
|
07f4288748 | ||
|
|
20bb0aca57 |
12 changed files with 454 additions and 193 deletions
2
story-editor/externals/ImNodeFlow
vendored
2
story-editor/externals/ImNodeFlow
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit e4984a42d3939d361f977e8d0a56bbfd49d0a702
|
||||
Subproject commit 4634021545dfdf6b4621426576d8c9f05a71b1fd
|
||||
|
|
@ -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>(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>(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)
|
||||
{
|
||||
// Note: Dans le code original, BuildCode lisait m_externalSourceFileName.
|
||||
|
|
@ -772,16 +829,22 @@ uint8_t AppController::Syscall(chip32_ctx_t *ctx, uint8_t code)
|
|||
{
|
||||
std::string text = GetStringFromMemory(ctx->registers[R0]);
|
||||
int arg_count = ctx->registers[R1];
|
||||
char working_buf[200] = {0};
|
||||
char working_buf[400] = {0};
|
||||
|
||||
// Simplified printf logic for logging
|
||||
switch(arg_count){
|
||||
case 0: m_logger.Log(text); break;
|
||||
case 1: snprintf(working_buf, sizeof(working_buf), text.c_str(), ctx->registers[R2]); m_logger.Log(working_buf); break;
|
||||
case 2: snprintf(working_buf, sizeof(working_buf), text.c_str(), ctx->registers[R2], ctx->registers[R3]); m_logger.Log(working_buf); break;
|
||||
case 3: snprintf(working_buf, sizeof(working_buf), text.c_str(), ctx->registers[R2], ctx->registers[R3], ctx->registers[R4]); m_logger.Log(working_buf); break;
|
||||
case 0: strcpy(working_buf, text.c_str()); break;
|
||||
case 1: snprintf(working_buf, sizeof(working_buf), text.c_str(), ctx->registers[R2]); break;
|
||||
case 2: snprintf(working_buf, sizeof(working_buf), text.c_str(), ctx->registers[R2], ctx->registers[R3]); break;
|
||||
case 3: snprintf(working_buf, sizeof(working_buf), text.c_str(), ctx->registers[R2], ctx->registers[R3], ctx->registers[R4]); break;
|
||||
default: m_logger.Log("Printf with unsupported arg_count: " + std::to_string(arg_count) + " text: " + text, true); break;
|
||||
}
|
||||
|
||||
// Send event to UI
|
||||
auto evObj = std::make_shared<VmStateEvent>();
|
||||
evObj->type = VmStateEvent::Type::PrintEvent;
|
||||
evObj->printOutput = working_buf;
|
||||
m_eventBus.Emit(evObj);
|
||||
}
|
||||
else if (code == 5) // WAIT (sleep)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ public:
|
|||
std::shared_ptr<StoryProject> GetCurrentModule() const { return m_module; }
|
||||
void CompileNodes(IStoryProject::Type type);
|
||||
void Build(bool compileonly);
|
||||
void BuildModule(bool compileonly);
|
||||
void BuildCode(std::shared_ptr<StoryProject> story, bool compileonly, bool force = false);
|
||||
|
||||
// --- Fonctions de IStoryManager ---
|
||||
|
|
@ -147,8 +148,7 @@ private:
|
|||
ThreadSafeQueue<VmEvent> 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
|
||||
|
|
|
|||
|
|
@ -7,32 +7,35 @@ EmulatorDock::EmulatorDock(IStoryManager &proj)
|
|||
: WindowBase("Emulator")
|
||||
, m_story(proj)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void EmulatorDock::Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
void EmulatorDock::AddVmOutput(const std::string& text)
|
||||
{
|
||||
std::scoped_lock<std::mutex> lock(m_vmOutputMutex);
|
||||
m_vmOutput.push_back({text, 0});
|
||||
|
||||
// Limiter à 1000 lignes
|
||||
if (m_vmOutput.size() > 1000) {
|
||||
m_vmOutput.erase(m_vmOutput.begin());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void EmulatorDock::ClearVmOutput()
|
||||
{
|
||||
std::scoped_lock<std::mutex> lock(m_vmOutputMutex);
|
||||
m_vmOutput.clear();
|
||||
}
|
||||
|
||||
void EmulatorDock::Draw()
|
||||
{
|
||||
// if (!IsVisible())
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
static ImVec2 size(320.0f, 240.0f);
|
||||
|
||||
WindowBase::BeginDraw();
|
||||
ImGui::SetWindowSize(ImVec2(626, 744), ImGuiCond_FirstUseEver);
|
||||
|
||||
// ImGui::Image((void*)(intptr_t)my_image_texture, ImVec2(313, 367));
|
||||
|
||||
// float sz = ImGui::GetTextLineHeight();
|
||||
|
||||
// ===== ÉCRAN DE L'ÉMULATEUR =====
|
||||
ImVec2 p = ImGui::GetCursorScreenPos();
|
||||
|
||||
if (m_image.Valid())
|
||||
|
|
@ -41,44 +44,59 @@ void EmulatorDock::Draw()
|
|||
}
|
||||
else
|
||||
{
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + 320, p.y + 240), ImGui::GetColorU32(ImVec4(1.0, 1.0, 1.0, 1.0)));
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + 320, p.y + 240),
|
||||
ImGui::GetColorU32(ImVec4(0.1f, 0.1f, 0.1f, 1.0f)));
|
||||
|
||||
// Texte centré "No Image"
|
||||
ImGui::SetCursorScreenPos(ImVec2(p.x + 110, p.y + 110));
|
||||
ImGui::TextDisabled("No Image");
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(p.x, p.y + 244));
|
||||
|
||||
// ===== BOUTONS DE CONTRÔLE =====
|
||||
float old_size = ImGui::GetFont()->Scale;
|
||||
ImGui::GetFont()->Scale *= 2;
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont());
|
||||
|
||||
if (ImGui::Button(ICON_MDI_CHECK_CIRCLE_OUTLINE, ImVec2(50, 50)))
|
||||
{
|
||||
m_story.Ok();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("OK");
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_MDI_PAUSE, ImVec2(50, 50)))
|
||||
{
|
||||
m_story.Pause();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Pause");
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_MDI_HOME, ImVec2(50, 50)))
|
||||
{
|
||||
m_story.Home();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Home");
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_MDI_ARROW_LEFT_BOLD_CIRCLE_OUTLINE, ImVec2(50, 50)))
|
||||
{
|
||||
m_story.Previous();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Previous");
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_MDI_ARROW_RIGHT_BOLD_CIRCLE_OUTLINE, ImVec2(50, 50)))
|
||||
{
|
||||
m_story.Next();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Next");
|
||||
|
||||
ImGui::GetFont()->Scale = old_size;
|
||||
ImGui::PopFont();
|
||||
|
||||
// ===== CONTRÔLES DE SCRIPT ET DEBUG =====
|
||||
ImGui::SeparatorText("Script control and debug");
|
||||
|
||||
if (ImGui::Button("Build nodes"))
|
||||
|
|
@ -96,19 +114,16 @@ void EmulatorDock::Draw()
|
|||
m_story.Log("Play story");
|
||||
m_story.Play();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Stop"))
|
||||
{
|
||||
m_story.Log("Stop story");
|
||||
m_story.Stop();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::Text("VM state: %s", m_story.VmState().c_str());
|
||||
|
||||
|
||||
// ===== BOUTONS DE CHARGEMENT =====
|
||||
if (ImGui::Button("Load binary story (.c32)"))
|
||||
{
|
||||
IGFD::FileDialogConfig config;
|
||||
|
|
@ -130,46 +145,108 @@ void EmulatorDock::Draw()
|
|||
config.countSelectionMax = 1;
|
||||
config.flags = ImGuiFileDialogFlags_Modal;
|
||||
ImGuiFileDialog::Instance()->OpenDialog("SetSourceScriptDlgKey",
|
||||
"Choose a binary story",
|
||||
"Choose a source file",
|
||||
".chip32",
|
||||
config
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------- Load Binary story
|
||||
// ===== DIALOGS =====
|
||||
// Load Binary story
|
||||
if (ImGuiFileDialog::Instance()->Display("LoadBinaryStoryDlgKey"))
|
||||
{
|
||||
if (ImGuiFileDialog::Instance()->IsOk())
|
||||
{
|
||||
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
|
||||
std::string filePath = ImGuiFileDialog::Instance()->GetCurrentPath();
|
||||
std::string filter = ImGuiFileDialog::Instance()->GetCurrentFilter();
|
||||
|
||||
m_story.LoadBinaryStory(filePathName);
|
||||
}
|
||||
// close
|
||||
ImGuiFileDialog::Instance()->Close();
|
||||
}
|
||||
|
||||
// ---------------- External source file
|
||||
// External source file
|
||||
if (ImGuiFileDialog::Instance()->Display("SetSourceScriptDlgKey"))
|
||||
{
|
||||
if (ImGuiFileDialog::Instance()->IsOk())
|
||||
{
|
||||
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
|
||||
std::string filePath = ImGuiFileDialog::Instance()->GetCurrentPath();
|
||||
std::string filter = ImGuiFileDialog::Instance()->GetCurrentFilter();
|
||||
|
||||
m_story.SetExternalSourceFile(filePathName);
|
||||
}
|
||||
// close
|
||||
ImGuiFileDialog::Instance()->Close();
|
||||
}
|
||||
|
||||
// ===== CONSOLE VM (NOUVEAU) =====
|
||||
DrawVmConsole();
|
||||
|
||||
WindowBase::EndDraw();
|
||||
}
|
||||
|
||||
void EmulatorDock::DrawVmConsole()
|
||||
{
|
||||
ImGui::Separator();
|
||||
|
||||
// Header de la console
|
||||
ImGui::Text(ICON_MDI_CONSOLE " VM Output");
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::Checkbox("Auto-scroll##vm", &m_autoScrollVm);
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::SmallButton("Clear##vm"))
|
||||
{
|
||||
ClearVmOutput();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Zone de console scrollable
|
||||
ImGui::BeginChild("VmConsoleRegion", ImVec2(0, 150), true,
|
||||
ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||
|
||||
{
|
||||
std::scoped_lock<std::mutex> lock(m_vmOutputMutex);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 2));
|
||||
ImGui::PushFont(ImGui::GetFont()); // Police monospace si disponible
|
||||
|
||||
if (m_vmOutput.empty())
|
||||
{
|
||||
ImGui::TextDisabled("(No VM output yet - run the program to see print statements)");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& entry : m_vmOutput)
|
||||
{
|
||||
// Colorer les erreurs en rouge
|
||||
if (entry.type == 1) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.4f, 0.4f, 1.0f));
|
||||
}
|
||||
|
||||
ImGui::TextUnformatted(entry.text.c_str());
|
||||
|
||||
if (entry.type == 1) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopFont();
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
// Auto-scroll intelligent (comme la console principale)
|
||||
float scrollY = ImGui::GetScrollY();
|
||||
float scrollMaxY = ImGui::GetScrollMaxY();
|
||||
const float BOTTOM_THRESHOLD = 5.0f;
|
||||
bool wasAtBottom = (scrollMaxY == 0) || (scrollY >= scrollMaxY - BOTTOM_THRESHOLD);
|
||||
|
||||
if (m_autoScrollVm && wasAtBottom)
|
||||
{
|
||||
ImGui::SetScrollHereY(1.0f);
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void EmulatorDock::ClearImage()
|
||||
{
|
||||
m_image.Clear();
|
||||
|
|
@ -179,4 +256,4 @@ void EmulatorDock::SetImage(const std::string &image)
|
|||
{
|
||||
m_imageFileName = image;
|
||||
m_image.Load(m_story.BuildFullAssetsPath(image));
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,9 @@
|
|||
#include "window_base.h"
|
||||
#include "i_story_manager.h"
|
||||
#include "gui.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
|
||||
class EmulatorDock : public WindowBase
|
||||
{
|
||||
|
|
@ -14,10 +17,25 @@ public:
|
|||
|
||||
void ClearImage();
|
||||
void SetImage(const std::string &image);
|
||||
|
||||
// NEW: Console VM pour afficher la sortie des prints
|
||||
void AddVmOutput(const std::string& text);
|
||||
void ClearVmOutput();
|
||||
|
||||
private:
|
||||
IStoryManager &m_story;
|
||||
Gui::Image m_image;
|
||||
std::string m_imageFileName;
|
||||
};
|
||||
|
||||
|
||||
// NEW: Buffer pour la console VM
|
||||
struct VmOutputEntry {
|
||||
std::string text;
|
||||
uint32_t type; // 0 = normal, 1 = error
|
||||
};
|
||||
|
||||
std::vector<VmOutputEntry> m_vmOutput;
|
||||
std::mutex m_vmOutputMutex;
|
||||
bool m_autoScrollVm = true;
|
||||
|
||||
void DrawVmConsole();
|
||||
};
|
||||
|
|
@ -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<int>(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();
|
||||
|
|
|
|||
|
|
@ -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<CompilationError> m_errors;
|
||||
bool m_shouldShow = false;
|
||||
};
|
||||
|
|
@ -13,11 +13,15 @@ struct VmStateEvent : public Event
|
|||
int currentLine;
|
||||
chip32_result_t vmResult;
|
||||
std::set<int> breakpoints;
|
||||
std::string printOutput;
|
||||
// Ajoutez d'autres données nécessaires, ex: std::vector<std::shared_ptr<Variable>> variables;
|
||||
|
||||
// Constructeur pour faciliter la création de l'événement
|
||||
VmStateEvent(const chip32_ctx_t& ctx, int line, chip32_result_t result, const std::set<int>& bps)
|
||||
: vmContext(ctx), currentLine(line), vmResult(result), breakpoints(bps) {}
|
||||
enum class Type
|
||||
{
|
||||
PrintEvent,
|
||||
};
|
||||
|
||||
Type type;
|
||||
};
|
||||
|
||||
class OpenProjectEvent : public Event
|
||||
|
|
@ -78,11 +82,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
|
||||
|
|
@ -83,6 +83,15 @@ MainWindow::MainWindow(ILogger& logger, EventBus& eventBus, AppController& appCo
|
|||
OpenFunction(event.GetUuid(), event.GetName());
|
||||
});
|
||||
|
||||
m_eventBus.Subscribe<VmStateEvent>([this](const VmStateEvent &event) {
|
||||
|
||||
if (event.type == VmStateEvent::Type::PrintEvent)
|
||||
{
|
||||
m_logger.Log("Print from VM: " + event.printOutput);
|
||||
m_emulatorDock.AddVmOutput(event.printOutput);
|
||||
}
|
||||
});
|
||||
|
||||
m_eventBus.Subscribe<GenericResultEvent>([this](const GenericResultEvent &event) {
|
||||
|
||||
if (event.IsSuccess()) {
|
||||
|
|
@ -100,10 +109,32 @@ 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);
|
||||
|
||||
m_emulatorDock.ClearVmOutput();
|
||||
|
||||
// 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 +622,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();
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ void BaseNodeWidget::DrawSocket(const Nw::Pin &pin)
|
|||
{
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
|
||||
// Taille du socket
|
||||
// Taille du socket
|
||||
float socket_size = 4.0f;
|
||||
|
||||
// Définir les 5 points du polygone (flèche pointant vers la droite pour Output)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ public:
|
|||
if (port.customSocketIcon)
|
||||
{
|
||||
ImFlow::BaseNode::addIN<int>(label, 0, ImFlow::ConnectionFilter::SameType())->renderer([this, i](ImFlow::Pin* p) {
|
||||
ImGui::Dummy(ImVec2(20.0f, 20.0f)); // Ajustez la taille selon vos besoins
|
||||
Nw::Pin pin;
|
||||
pin.index = i;
|
||||
pin.isConnected = p->isConnected();
|
||||
|
|
@ -62,17 +63,23 @@ public:
|
|||
std::string label = (port.type == ::BaseNode::Port::Type::EXECUTION_PORT) ? "" : port.label;
|
||||
if (port.customSocketIcon)
|
||||
{
|
||||
ImFlow::BaseNode::addOUT<int>(label, nullptr)->renderer([this, i](ImFlow::Pin* p) {
|
||||
ImFlow::BaseNode::addOUT<int>(label, nullptr)->renderer([this, i, cached_pin = Nw::Pin{}](ImFlow::Pin* p) mutable {
|
||||
|
||||
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();
|
||||
if (p->getName().empty()) {
|
||||
ImGui::Dummy(ImVec2(1.0f, 10.0f));
|
||||
} else {
|
||||
ImGui::Text("%s", p->getName().c_str());
|
||||
}
|
||||
|
||||
m_widget->DrawSocket(pin);
|
||||
|
||||
cached_pin.index = i;
|
||||
cached_pin.isConnected = p->isConnected();
|
||||
cached_pin.pinKind = Nw::PinKind::Output;
|
||||
cached_pin.pinPoint = p->pinPoint();
|
||||
cached_pin.pos = p->getPos();
|
||||
cached_pin.size = p->getSize();
|
||||
|
||||
m_widget->DrawSocket(cached_pin);
|
||||
});
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
#include "console_window.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
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<std::mutex> 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<std::mutex> 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();
|
||||
}
|
||||
Loading…
Reference in a new issue