From 09aab7241aede7430c1a794ae61fea7c624172c7 Mon Sep 17 00:00:00 2001 From: Anthony Rabine Date: Thu, 21 Dec 2023 22:08:09 +0100 Subject: [PATCH] Build binary, code display, errors management --- software/chip32/chip32_assembler.cpp | 25 +- software/chip32/chip32_assembler.h | 12 +- story-editor/CMakeLists.txt | 7 +- story-editor/icon.rc | 1 + story-editor/nsis-installer.nsi | 53 ++++ story-editor/src/base_node.h | 8 +- story-editor/src/code_editor.cpp | 45 ++-- story-editor/src/code_editor.h | 7 +- story-editor/src/connection.cpp | 17 ++ story-editor/src/connection.h | 53 ++++ story-editor/src/console_window.cpp | 167 +------------ story-editor/src/console_window.h | 44 ++-- story-editor/src/emulator_window.cpp | 39 ++- story-editor/src/emulator_window.h | 9 +- story-editor/src/i_story_project.h | 8 + story-editor/src/main_window.cpp | 227 ++++++++++-------- story-editor/src/main_window.h | 34 ++- story-editor/src/media_node.cpp | 60 +++-- story-editor/src/media_node.h | 6 +- story-editor/src/node_editor_window.cpp | 149 +++++++++--- story-editor/src/node_editor_window.h | 31 ++- story-editor/src/node_properties_window.cpp | 42 ---- story-editor/src/properties_window.cpp | 47 ++++ ...roperties_window.h => properties_window.h} | 4 +- story-editor/src/resource.h | 16 +- story-editor/src/resource_manager.h | 7 +- story-editor/src/resources_window.cpp | 10 +- story-editor/src/story_project.cpp | 14 -- story-editor/src/story_project.h | 29 --- story-editor/story-editor-logo.ico | Bin 0 -> 224990 bytes story-editor/story-editor-logo.png | Bin 0 -> 39945 bytes story-editor/story-editor.desktop | 8 + 32 files changed, 657 insertions(+), 522 deletions(-) create mode 100644 story-editor/icon.rc create mode 100644 story-editor/nsis-installer.nsi create mode 100644 story-editor/src/connection.cpp create mode 100644 story-editor/src/connection.h delete mode 100644 story-editor/src/node_properties_window.cpp create mode 100644 story-editor/src/properties_window.cpp rename story-editor/src/{node_properties_window.h => properties_window.h} (78%) create mode 100644 story-editor/story-editor-logo.ico create mode 100644 story-editor/story-editor-logo.png create mode 100644 story-editor/story-editor.desktop diff --git a/software/chip32/chip32_assembler.cpp b/software/chip32/chip32_assembler.cpp index 1021720..8b39c4b 100644 --- a/software/chip32/chip32_assembler.cpp +++ b/software/chip32/chip32_assembler.cpp @@ -94,15 +94,13 @@ static inline void leu16_put(std::vector &container, uint16_t data } #define GET_REG(name, ra) if (!GetRegister(name, ra)) {\ - std::stringstream ss; \ - ss << "ERROR! Bad register name: " << name << std::endl;\ - m_lastError = ss.str(); \ + m_lastError.line -1; \ + m_lastError.message = "ERROR! Bad register name: " + name; \ return false; } #define CHIP32_CHECK(instr, cond, error) if (!(cond)) { \ - std::stringstream ss; \ - ss << "error line: " << instr.line << ": " << error << std::endl; \ - m_lastError = ss.str(); \ + m_lastError.line = instr.line; \ + m_lastError.message = error; \ return false; } \ std::vector Split(std::string line) @@ -242,7 +240,7 @@ bool Assembler::CompileMnemonicArguments(Instr &instr) instr.compiledArgs.push_back(static_cast(strtol(instr.args[2].c_str(), NULL, 0))); break; default: - CHIP32_CHECK(instr, false, "Unsupported mnemonic: " << instr.mnemonic); + CHIP32_CHECK(instr, false, "Unsupported mnemonic: " + instr.mnemonic); break; } return true; @@ -286,7 +284,7 @@ bool Assembler::CompileConstantArgument(Instr &instr, const std::string &a) ((intVal <= UINT32_MAX) && (instr.dataTypeSize == 32))) { sizeOk = true; } - CHIP32_CHECK(instr, sizeOk, "integer too high: " << intVal); + CHIP32_CHECK(instr, sizeOk, "integer too high: " + std::to_string(intVal)); if (instr.dataTypeSize == 8) { instr.compiledArgs.push_back(intVal); } else if (instr.dataTypeSize == 16) { @@ -362,7 +360,7 @@ bool Assembler::Parse(const std::string &data) instr.mnemonic = opcode; instr.isLabel = true; instr.addr = code_addr; - CHIP32_CHECK(instr, m_labels.count(opcode) == 0, "duplicated label : " << opcode); + CHIP32_CHECK(instr, m_labels.count(opcode) == 0, "duplicated label : " + opcode); m_labels[opcode] = instr; m_instructions.push_back(instr); } @@ -383,7 +381,7 @@ bool Assembler::Parse(const std::string &data) { instr.args.insert(instr.args.begin(), lineParts.begin() + 1, lineParts.end()); CHIP32_CHECK(instr, instr.args.size() == instr.code.nbAargs, - "Bad number of parameters. Required: " << static_cast(instr.code.nbAargs) << ", got: " << instr.args.size()); + "Bad number of parameters. Required: " + std::to_string(static_cast(instr.code.nbAargs)) + ", got: " + std::to_string(instr.args.size())); nbArgsSuccess = true; } else @@ -412,7 +410,7 @@ bool Assembler::Parse(const std::string &data) CHIP32_CHECK(instr, (type.size() >= 3), "bad data type size"); CHIP32_CHECK(instr, (type[0] == 'D') && ((type[1] == 'C') || (type[1] == 'V')), "bad data type (must be DCxx or DVxx"); - CHIP32_CHECK(instr, m_labels.count(opcode) == 0, "duplicated label : " << opcode); + CHIP32_CHECK(instr, m_labels.count(opcode) == 0, "duplicated label : " + opcode); instr.isRomData = type[1] == 'C' ? true : false; instr.isRamData = type[1] == 'V' ? true : false; @@ -445,7 +443,8 @@ bool Assembler::Parse(const std::string &data) } else { - m_lastError = "Unknown mnemonic or bad formatted line: " + std::to_string(lineNum); + m_lastError.message = "Unknown mnemonic or badly formatted line"; + m_lastError.line = lineNum; return false; } } @@ -458,7 +457,7 @@ bool Assembler::Parse(const std::string &data) // label is the first argument for jump, second position for LCONS uint16_t argsIndex = instr.code.opcode == OP_LCONS ? 1 : 0; std::string label = instr.args[argsIndex]; - CHIP32_CHECK(instr, m_labels.count(label) > 0, "label not found: " << label); + CHIP32_CHECK(instr, m_labels.count(label) > 0, "label not found: " + label); uint16_t addr = m_labels[label].addr; std::cout << "LABEL: " << label << " , addr: " << addr << std::endl; instr.compiledArgs[argsIndex] = addr & 0xFF; diff --git a/software/chip32/chip32_assembler.h b/software/chip32/chip32_assembler.h index 4e92a3e..8bc54e6 100644 --- a/software/chip32/chip32_assembler.h +++ b/software/chip32/chip32_assembler.h @@ -83,10 +83,16 @@ struct Result } }; - class Assembler { public: + + struct Error { + std::string message; + int line; + std::string ToString() const { return "Error line " + std::to_string(line) + ", " + message; } + }; + // Separated parser to allow only code check bool Parse(const std::string &data); // Generate the executable binary after the parse pass @@ -104,7 +110,7 @@ public: bool GetRegister(const std::string ®Name, uint8_t ®); bool GetRegisterName(uint8_t reg, std::string ®Name); - std::string GetLastError() { return m_lastError; } + Error GetLastError() { return m_lastError; } private: bool CompileMnemonicArguments(Instr &instr); @@ -112,7 +118,7 @@ private: // label, address std::map m_labels; - std::string m_lastError; + Error m_lastError; std::vector m_instructions; bool CompileConstantArgument(Instr &instr, const std::string &a); diff --git a/story-editor/CMakeLists.txt b/story-editor/CMakeLists.txt index 6377592..a42339d 100644 --- a/story-editor/CMakeLists.txt +++ b/story-editor/CMakeLists.txt @@ -119,8 +119,8 @@ set(SRCS src/resources_window.cpp src/resources_window.h - src/node_properties_window.cpp - src/node_properties_window.h + src/properties_window.cpp + src/properties_window.h src/gui.h src/gui.cpp @@ -128,6 +128,9 @@ set(SRCS src/code_editor.cpp src/code_editor.h + src/connection.cpp + src/connection.h + src/uuid.h src/resource_manager.h src/i_story_project.h diff --git a/story-editor/icon.rc b/story-editor/icon.rc new file mode 100644 index 0000000..28fac41 --- /dev/null +++ b/story-editor/icon.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "story-editor-logo.ico" diff --git a/story-editor/nsis-installer.nsi b/story-editor/nsis-installer.nsi new file mode 100644 index 0000000..7330313 --- /dev/null +++ b/story-editor/nsis-installer.nsi @@ -0,0 +1,53 @@ +# define installer name +!define APPNAME "StoryEditor" +!define COMPANYNAME "OpenStoryTeller" +!define DESCRIPTION "A story editor using graphical nodes, for the OpenStoryTeller project. http://openstoryteller.org" + +!define VERSIONMAJOR 1 +!define VERSIONMINOR 3 +!define VERSIONBUILD 4 +OutFile "build/story-editor-setup.exe" + +# set desktop as install directory +InstallDir "$PROGRAMFILES64\${APPNAME}" +Name "${COMPANYNAME} - ${APPNAME}" + +# default section start +Section + +# define output path +SetOutPath $INSTDIR + +# specify file to go in output path +File /r "build/story-editor\*" +File "story-editor-logo.ico" + +# define uninstaller name +WriteUninstaller $INSTDIR\uninstaller.exe + +# Create shortcut +SetShellVarContext all +CreateDirectory "$SMPROGRAMS\${COMPANYNAME}" +CreateShortCut "$SMPROGRAMS\${COMPANYNAME}\${APPNAME}.lnk" "$INSTDIR\story-editor.exe" "" "$INSTDIR\story-editor-logo.ico" +SetShellVarContext current + + +#------- +# default section end +SectionEnd + +# create a section to define what the uninstaller does. +# the section will always be named "Uninstall" +Section "Uninstall" + +# Always delete uninstaller first +Delete $INSTDIR\uninstaller.exe +Delete "$SMPROGRAMS\${COMPANYNAME}\${APPNAME}.lnk" +Delete $INSTDIR\story-editor-logo.ico + +# now delete installed file +Delete $INSTDIR\* + +# Delete the directory +RMDir /r $INSTDIR +SectionEnd diff --git a/story-editor/src/base_node.h b/story-editor/src/base_node.h index b37a53f..4565ab9 100644 --- a/story-editor/src/base_node.h +++ b/story-editor/src/base_node.h @@ -105,8 +105,10 @@ public: virtual void Draw() = 0; - virtual void DrawProperties() = 0; + virtual std::string GenerateConstants() = 0; + virtual std::string Build() = 0; + virtual std::string GetEntryLabel() = 0; void SetPosition(float x, float y); @@ -135,8 +137,8 @@ public: unsigned long GetId() const { return m_id; } unsigned long GetInternalId() const { return m_node->ID.Get(); } - void seTitle(const std::string &title) { m_title = title; } - std::string getTitle() const { return m_title; } + void SeTitle(const std::string &title) { m_title = title; } + std::string GetTitle() const { return m_title; } virtual void FromJson(const nlohmann::json &) = 0; virtual void ToJson(nlohmann::json &) = 0; diff --git a/story-editor/src/code_editor.cpp b/story-editor/src/code_editor.cpp index 3a85e36..37b07ce 100644 --- a/story-editor/src/code_editor.cpp +++ b/story-editor/src/code_editor.cpp @@ -1,6 +1,7 @@ #include "code_editor.h" #include +#include CodeEditor::CodeEditor() : WindowBase("Code editor") @@ -11,27 +12,32 @@ CodeEditor::CodeEditor() void CodeEditor::Initialize() { // error markers - TextEditor::ErrorMarkers markers; - markers.insert(std::make_pair(6, "Example error here:\nInclude file not found: \"TextEditor.h\"")); - markers.insert(std::make_pair(41, "Another example error")); - mEditor.SetErrorMarkers(markers); + +// +// markers.insert(std::make_pair(41, "Another example error")); + // "breakpoint" markers - //TextEditor::Breakpoints bpts; - //bpts.insert(24); - //bpts.insert(47); - //editor.SetBreakpoints(bpts); + m_breakPoints.insert(42); + mEditor.SetBreakpoints(m_breakPoints); - mFileToEdit = "test/test_zebra7500.js"; +} - { - std::ifstream t(mFileToEdit); - if (t.good()) - { - std::string str((std::istreambuf_iterator(t)), std::istreambuf_iterator()); - mEditor.SetText(str); - } - } +void CodeEditor::ClearErrors() +{ + m_markers.clear(); +} + +void CodeEditor::AddError(int line, const std::string &text) +{ + m_markers.insert(std::make_pair(line, text)); + mEditor.SetErrorMarkers(m_markers); +} + + +void CodeEditor::SetScript(const std::string &txt) +{ + mEditor.SetText(txt); } void CodeEditor::Draw() @@ -99,10 +105,9 @@ void CodeEditor::Draw() ImGui::EndMenuBar(); } - ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1, cpos.mColumn + 1, mEditor.GetTotalLines(), + ImGui::Text("%6d/%-6d %6d lines | %s | %s ", cpos.mLine + 1, cpos.mColumn + 1, mEditor.GetTotalLines(), mEditor.IsOverwrite() ? "Ovr" : "Ins", - mEditor.CanUndo() ? "*" : " ", - mEditor.GetLanguageDefinition().mName.c_str(), mFileToEdit.c_str()); + mEditor.CanUndo() ? "*" : " "); mEditor.Render("TextEditor"); WindowBase::EndDraw(); diff --git a/story-editor/src/code_editor.h b/story-editor/src/code_editor.h index 273da5a..86b06e8 100644 --- a/story-editor/src/code_editor.h +++ b/story-editor/src/code_editor.h @@ -11,7 +11,12 @@ public: virtual void Draw() override; void Initialize(); + + void SetScript(const std::string &txt); + void ClearErrors(); + void AddError(int line, const std::string &text); private: TextEditor mEditor; - std::string mFileToEdit; + TextEditor::Breakpoints m_breakPoints; + TextEditor::ErrorMarkers m_markers; }; diff --git a/story-editor/src/connection.cpp b/story-editor/src/connection.cpp new file mode 100644 index 0000000..6b5ad8a --- /dev/null +++ b/story-editor/src/connection.cpp @@ -0,0 +1,17 @@ +#include "connection.h" + +void to_json(nlohmann::json &j, const Connection &p) { + j = nlohmann::json{ + {"outNodeId", static_cast(p.outNodeId)}, + {"outPortIndex", static_cast(p.outPortIndex)}, + {"inNodeId", static_cast(p.inNodeId)}, + {"inPortIndex", static_cast(p.inPortIndex)}, + }; +} + +void from_json(const nlohmann::json &j, Connection &p) { + j.at("outNodeId").get_to(p.outNodeId); + j.at("outPortIndex").get_to(p.outPortIndex); + j.at("inNodeId").get_to(p.inNodeId); + j.at("inPortIndex").get_to(p.inPortIndex); +} diff --git a/story-editor/src/connection.h b/story-editor/src/connection.h new file mode 100644 index 0000000..12ccfff --- /dev/null +++ b/story-editor/src/connection.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include "json.hpp" + +struct Connection +{ + Connection() + : outNodeId(0) + , outPortIndex(0) + , inNodeId(0) + , inPortIndex(0) + { + + } + unsigned int outNodeId{0}; + unsigned int outPortIndex{0}; + unsigned int inNodeId{0}; + unsigned int inPortIndex{0}; + + Connection& operator=(const Connection& other) { + this->outNodeId = other.outNodeId; + this->outPortIndex = other.outPortIndex; + this->inNodeId = other.inNodeId; + this->inPortIndex = other.inPortIndex; + return *this; + } +}; + +inline bool operator==(Connection const &a, Connection const &b) +{ + return a.outNodeId == b.outNodeId && a.outPortIndex == b.outPortIndex + && a.inNodeId == b.inNodeId && a.inPortIndex == b.inPortIndex; +} + +inline bool operator!=(Connection const &a, Connection const &b) +{ + return !(a == b); +} + + +inline void invertConnection(Connection &id) +{ + std::swap(id.outNodeId, id.inNodeId); + std::swap(id.outPortIndex, id.inPortIndex); +} + +void to_json(nlohmann::json& j, const Connection& p); + +void from_json(const nlohmann::json& j, Connection& p); + + + diff --git a/story-editor/src/console_window.cpp b/story-editor/src/console_window.cpp index 7a6b893..7100d14 100644 --- a/story-editor/src/console_window.cpp +++ b/story-editor/src/console_window.cpp @@ -8,13 +8,7 @@ ConsoleWindow::ConsoleWindow() { ClearLog(); memset(InputBuf, 0, sizeof(InputBuf)); - HistoryPos = -1; - // "CLASSIFY" is here to provide the test case where "C"+[tab] completes to "CL" and display multiple matches. - Commands.push_back("HELP"); - Commands.push_back("HISTORY"); - Commands.push_back("CLEAR"); - Commands.push_back("CLASSIFY"); AutoScroll = true; ScrollToBottom = false; } @@ -22,8 +16,6 @@ ConsoleWindow::ConsoleWindow() ConsoleWindow::~ConsoleWindow() { ClearLog(); - for (int i = 0; i < History.Size; i++) - free(History[i]); } void ConsoleWindow::ClearLog() @@ -113,7 +105,16 @@ void ConsoleWindow::Draw() { for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { - ImGui::TextUnformatted(Items[i].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) + { + ImGui::PopStyleColor(); + } } } /* @@ -168,151 +169,3 @@ void ConsoleWindow::Draw() WindowBase::EndDraw(); } -void ConsoleWindow::ExecCommand(const char *command_line) -{ - AddLog("# %s\n", command_line); - - // Insert into history. First find match and delete it so it can be pushed to the back. - // This isn't trying to be smart or optimal. - HistoryPos = -1; - for (int i = History.Size - 1; i >= 0; i--) - if (Stricmp(History[i], command_line) == 0) - { - free(History[i]); - History.erase(History.begin() + i); - break; - } - History.push_back(Strdup(command_line)); - - // Process command - if (Stricmp(command_line, "CLEAR") == 0) - { - ClearLog(); - } - else if (Stricmp(command_line, "HELP") == 0) - { - AddLog("Commands:"); - for (int i = 0; i < Commands.Size; i++) - AddLog("- %s", Commands[i]); - } - else if (Stricmp(command_line, "HISTORY") == 0) - { - int first = History.Size - 10; - for (int i = first > 0 ? first : 0; i < History.Size; i++) - AddLog("%3d: %s\n", i, History[i]); - } - else - { - AddLog("Unknown command: '%s'\n", command_line); - } - - // On command input, we scroll to bottom even if AutoScroll==false - ScrollToBottom = true; -} - -int ConsoleWindow::TextEditCallbackStub(ImGuiInputTextCallbackData *data) -{ - ConsoleWindow* console = (ConsoleWindow*)data->UserData; - return console->TextEditCallback(data); -} - -int ConsoleWindow::TextEditCallback(ImGuiInputTextCallbackData *data) -{ - //AddLog("cursor: %d, selection: %d-%d", data->CursorPos, data->SelectionStart, data->SelectionEnd); - switch (data->EventFlag) - { - case ImGuiInputTextFlags_CallbackCompletion: - { - // Example of TEXT COMPLETION - - // Locate beginning of current word - const char* word_end = data->Buf + data->CursorPos; - const char* word_start = word_end; - while (word_start > data->Buf) - { - const char c = word_start[-1]; - if (c == ' ' || c == '\t' || c == ',' || c == ';') - break; - word_start--; - } - - // Build a list of candidates - ImVector candidates; - for (int i = 0; i < Commands.Size; i++) - if (Strnicmp(Commands[i], word_start, (int)(word_end - word_start)) == 0) - candidates.push_back(Commands[i]); - - if (candidates.Size == 0) - { - // No match - AddLog("No match for \"%.*s\"!\n", (int)(word_end - word_start), word_start); - } - else if (candidates.Size == 1) - { - // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing. - data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start)); - data->InsertChars(data->CursorPos, candidates[0]); - data->InsertChars(data->CursorPos, " "); - } - else - { - // Multiple matches. Complete as much as we can.. - // So inputing "C"+Tab will complete to "CL" then display "CLEAR" and "CLASSIFY" as matches. - int match_len = (int)(word_end - word_start); - for (;;) - { - int c = 0; - bool all_candidates_matches = true; - for (int i = 0; i < candidates.Size && all_candidates_matches; i++) - if (i == 0) - c = toupper(candidates[i][match_len]); - else if (c == 0 || c != toupper(candidates[i][match_len])) - all_candidates_matches = false; - if (!all_candidates_matches) - break; - match_len++; - } - - if (match_len > 0) - { - data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start)); - data->InsertChars(data->CursorPos, candidates[0], candidates[0] + match_len); - } - - // List matches - AddLog("Possible matches:\n"); - for (int i = 0; i < candidates.Size; i++) - AddLog("- %s\n", candidates[i]); - } - - break; - } - case ImGuiInputTextFlags_CallbackHistory: - { - // Example of HISTORY - const int prev_history_pos = HistoryPos; - if (data->EventKey == ImGuiKey_UpArrow) - { - if (HistoryPos == -1) - HistoryPos = History.Size - 1; - else if (HistoryPos > 0) - HistoryPos--; - } - else if (data->EventKey == ImGuiKey_DownArrow) - { - if (HistoryPos != -1) - if (++HistoryPos >= History.Size) - HistoryPos = -1; - } - - // A better implementation would preserve the data on the current input line along with cursor position. - if (prev_history_pos != HistoryPos) - { - const char* history_str = (HistoryPos >= 0) ? History[HistoryPos] : ""; - data->DeleteChars(0, data->BufTextLen); - data->InsertChars(0, history_str); - } - } - } - return 0; -} diff --git a/story-editor/src/console_window.h b/story-editor/src/console_window.h index 57c0086..48a5de8 100644 --- a/story-editor/src/console_window.h +++ b/story-editor/src/console_window.h @@ -25,42 +25,34 @@ public: void ClearLog(); - void AddMessage(const std::string &message) { AddLog("%s", message.c_str()); } - - virtual void Draw() override; - - void ExecCommand(const char* command_line); - - // In C++11 you'd be better off using lambdas for this sort of forwarding callbacks - static int TextEditCallbackStub(ImGuiInputTextCallbackData* data); - - int TextEditCallback(ImGuiInputTextCallbackData* data); - -private: - - void AddLog(const char* fmt, ...) IM_FMTARGS(2) + void AddLog(const std::string &text, uint32_t type) { + + // FIXME-OPT - char buf[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args); - buf[IM_ARRAYSIZE(buf)-1] = 0; - va_end(args); + Entry e{text, type}; std::scoped_lock mutex(mLogMutex); - Items.push_back(Strdup(buf)); - if (Items.size() > 100) + Items.push_back(e); + if (Items.size() > 100) { Items.erase(Items.begin()); } } + virtual void Draw() override; + + +private: + + struct Entry { + std::string text; + uint32_t type; + }; + std::mutex mLogMutex; char InputBuf[256]; - std::vector Items; - ImVector Commands; - ImVector History; - int HistoryPos; // -1: new line, 0..History.Size-1 browsing history. + std::vector Items; + ImGuiTextFilter Filter; bool AutoScroll; bool ScrollToBottom; diff --git a/story-editor/src/emulator_window.cpp b/story-editor/src/emulator_window.cpp index 9abedbb..d4a5eb0 100644 --- a/story-editor/src/emulator_window.cpp +++ b/story-editor/src/emulator_window.cpp @@ -1,17 +1,18 @@ #include "emulator_window.h" #include "gui.h" +#include "IconsMaterialDesignIcons.h" -EmulatorWindow::EmulatorWindow() +EmulatorWindow::EmulatorWindow(IStoryProject &proj) : WindowBase("Emulator") + , m_project(proj) { - Gui::LoadRawImage("assets/play.png", m_playImage); } -void EmulatorWindow::Initialize() { +void EmulatorWindow::Initialize() +{ + - int my_image_width = 0; - int my_image_height = 0; } @@ -33,8 +34,32 @@ void EmulatorWindow::Draw() ImVec2 p = ImGui::GetCursorScreenPos(); ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + 320, p.y + 240), ImGui::GetColorU32(ImVec4(1.0, 1.0, 1.0, 1.0))); - ImGui::SetCursorScreenPos(ImVec2(p.x, p.y + 240)); - ImGui::ImageButton("play", m_playImage.texture, ImVec2(45, 45)); + ImGui::SetCursorScreenPos(ImVec2(p.x, p.y + 244)); + + float old_size = ImGui::GetFont()->Scale; + ImGui::GetFont()->Scale *= 2; + + ImGui::PushFont(ImGui::GetFont()); + + + ImGui::Button(ICON_MDI_PLAY_CIRCLE_OUTLINE, ImVec2(50, 50)); + ImGui::SameLine(); + ImGui::Button(ICON_MDI_STOP_CIRCLE_OUTLINE, ImVec2(50, 50)); + ImGui::SameLine(); + ImGui::Button(ICON_MDI_ARROW_LEFT_BOLD_CIRCLE_OUTLINE, ImVec2(50, 50)); + ImGui::SameLine(); + ImGui::Button(ICON_MDI_ARROW_RIGHT_BOLD_CIRCLE_OUTLINE, ImVec2(50, 50)); + + ImGui::GetFont()->Scale = old_size; + ImGui::PopFont(); + + ImGui::SeparatorText("Script control and debug"); + + if (ImGui::Button("Build")) + { + m_project.Build(); + } + ImGui::SameLine(); WindowBase::EndDraw(); } diff --git a/story-editor/src/emulator_window.h b/story-editor/src/emulator_window.h index 2ed1d61..d2c5594 100644 --- a/story-editor/src/emulator_window.h +++ b/story-editor/src/emulator_window.h @@ -1,20 +1,17 @@ #pragma once #include "window_base.h" -#include "gui.h" +#include "i_story_project.h" class EmulatorWindow : public WindowBase { public: - EmulatorWindow(); + EmulatorWindow(IStoryProject &proj); void Initialize(); virtual void Draw() override; private: - Gui::Image m_playImage; - Gui::Image m_pauseImage; - Gui::Image m_homeImage; - + IStoryProject &m_project; }; diff --git a/story-editor/src/i_story_project.h b/story-editor/src/i_story_project.h index 2679e94..f51ccf3 100644 --- a/story-editor/src/i_story_project.h +++ b/story-editor/src/i_story_project.h @@ -5,14 +5,17 @@ #include #include #include +#include #include "resource.h" +#include "connection.h" class IStoryProject { public: virtual ~IStoryProject() {} + virtual void Log(const std::string &txt, bool critical = false) = 0; virtual void PlaySoundFile(const std::string &fileName) = 0; virtual std::string BuildFullAssetsPath(const std::string &fileName) const = 0; @@ -22,7 +25,12 @@ public: virtual std::pair Resources() = 0; virtual void AddResource(std::shared_ptr res) = 0; virtual void ClearResources() = 0; + virtual void DeleteResource(FilterIterator &it) = 0; + // Node interaction + virtual void Build() = 0; + virtual std::list> GetNodeConnections(unsigned long nodeId) = 0; + virtual std::string GetNodeEntryLabel(unsigned long nodeId) = 0; }; diff --git a/story-editor/src/main_window.cpp b/story-editor/src/main_window.cpp index b427933..b193011 100644 --- a/story-editor/src/main_window.cpp +++ b/story-editor/src/main_window.cpp @@ -19,8 +19,10 @@ #include "ImGuiFileDialog.h" MainWindow::MainWindow() - : m_resourcesWindow(*this) + : m_emulatorWindow(*this) + , m_resourcesWindow(*this) , m_nodeEditorWindow(*this) + { m_project.Clear(); } @@ -168,109 +170,15 @@ void MainWindow::Initialize() gui.Initialize(); // gui.ApplyTheme(); - editor.Initialize(); + m_editor.Initialize(); m_emulatorWindow.Initialize(); m_nodeEditorWindow.Initialize(); - m_nodePropertiesWindow.Initialize(); + m_PropertiesWindow.Initialize(); LoadParams(); } -void MainWindow::ShowOptionsWindow() -{ - static int pingState = 0; - - // Always center this window when appearing - ImVec2 center = ImGui::GetMainViewport()->GetCenter(); - ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - ImGui::SetNextWindowSize(ImVec2(600, 0.0f)); - if (ImGui::BeginPopupModal("Options", NULL, ImGuiWindowFlags_AlwaysAutoResize)) - { - ImGui::PushItemWidth(-1.0f); - - ImGui::Text("Adresse du serveur"); - ImGui::SameLine(); - ImGui::InputText("##addr", mBufAddress, sizeof(mBufAddress)); - - ImGui::Text("Chemin de récupération"); - ImGui::SameLine(); - ImGui::InputText("##rec_path", mBufReceivePath, sizeof(mBufReceivePath)); - - ImGui::Text("Chemin d'envoi des données"); - ImGui::SameLine(); - ImGui::InputText("##send_path", mBufSendPath, sizeof(mBufSendPath)); - - ImGui::PushItemWidth(100); - ImGui::Text("Port"); - ImGui::SameLine(); - ImGui::InputText("##port", mBufPort, sizeof(mBufPort), ImGuiInputTextFlags_CharsDecimal); - - float width = 50; - ImGui::BeginGroup(); - ImGui::PushID("Zebra7500"); - ImGui::TextUnformatted("Adresse IP Zebra7500"); - ImGui::SameLine(); - for (int i = 0; i < 4; i++) { - ImGui::PushItemWidth(width); - ImGui::PushID(i); - bool invalid_octet = false; - if (octets[i] > 255) { - // Make values over 255 red, and when focus is lost reset it to 255. - octets[i] = 255; - invalid_octet = true; - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); - } - if (octets[i] < 0) { - // Make values below 0 yellow, and when focus is lost reset it to 0. - octets[i] = 0; - invalid_octet = true; - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 0.0f, 1.0f)); - } - ImGui::InputInt("##v", &octets[i], 0, 0, ImGuiInputTextFlags_CharsDecimal); - if (invalid_octet) { - ImGui::PopStyleColor(); - } - ImGui::SameLine(); - ImGui::PopID(); - ImGui::PopItemWidth(); - } - ImGui::PopID(); - ImGui::EndGroup(); - - // Example action button and way to build the IP string - ImGui::SameLine(); - - ImGui::SameLine(); - if (pingState == 1) - { - ImGui::TextUnformatted("Ping en cours..."); - } - else if (pingState == 2) - { - ImGui::TextUnformatted("Ping succès!"); - } - else if (pingState == 3) - { - ImGui::TextUnformatted("Ping erreur :("); - } - else - { - ImGui::TextUnformatted(""); - } - - ImGui::Separator(); - - - ImGui::SetItemDefaultFocus(); - ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(120, 0))) - { - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } -} bool MainWindow::ShowQuitConfirm() { @@ -319,13 +227,13 @@ void MainWindow::OpenProjectDialog() if (m_project.Load(filePathName, model, m_resources)) { - m_consoleWindow.AddMessage("Open project success"); + Log("Open project success"); m_nodeEditorWindow.Load(model); EnableProject(); } else { - m_consoleWindow.AddMessage("Open project error"); + Log("Open project error"); } @@ -600,14 +508,12 @@ void MainWindow::Loop() // ------------ Draw all windows m_consoleWindow.Draw(); m_emulatorWindow.Draw(); - editor.Draw(); + m_editor.Draw(); m_resourcesWindow.Draw(); m_nodeEditorWindow.Draw(); - m_nodePropertiesWindow.SetSelectedNode(m_nodeEditorWindow.GetSelectedNode()); - m_nodePropertiesWindow.Draw(); - - ShowOptionsWindow(); + m_PropertiesWindow.SetSelectedNode(m_nodeEditorWindow.GetSelectedNode()); + m_PropertiesWindow.Draw(); NewProjectPopup(); OpenProjectDialog(); @@ -629,9 +535,14 @@ void MainWindow::Loop() gui.Destroy(); } +void MainWindow::Log(const std::string &txt, bool critical) +{ + m_consoleWindow.AddLog(txt, critical ? 1 : 0); +} + void MainWindow::PlaySoundFile(const std::string &fileName) { - m_consoleWindow.AddMessage("Play sound file: " + fileName); + Log("Play sound file: " + fileName); m_project.PlaySoundFile(fileName); } @@ -665,6 +576,112 @@ std::pair MainWindow::Resources() return m_resources.Items(); } +void MainWindow::DeleteResource(FilterIterator &it) +{ + return m_resources.Delete(it); +} + +void MainWindow::Build() +{ + // 1. First compile nodes to assembly + CompileToAssembler(); + + // 2. Compile the assembly to machine binary + GenerateBinary(); + + // 3. Convert all media to desired type format + ConvertResources(); +} + +std::string MainWindow::GetNodeEntryLabel(unsigned long nodeId) +{ + return m_nodeEditorWindow.GetNodeEntryLabel(nodeId); +} + +std::list> MainWindow::GetNodeConnections(unsigned long nodeId) +{ + return m_nodeEditorWindow.GetNodeConnections(nodeId); +} + +bool MainWindow::CompileToAssembler() +{ + // 1. Check if the model can be compiled, check for errors and report + // FIXME + + // 2. Generate the assembly code from the model + m_currentCode = m_nodeEditorWindow.Build(); + + // Add global functions + { + std::string buffer; + + std::ifstream f("scripts/media.asm"); + f.seekg(0, std::ios::end); + buffer.resize(f.tellg()); + f.seekg(0); + f.read(buffer.data(), buffer.size()); + m_currentCode += buffer; + } + + m_editor.SetScript(m_currentCode); + + return true; +} + +void MainWindow::GenerateBinary() +{ + m_dbg.run_result = VM_FINISHED; + m_dbg.free_run = false; + + if (m_assembler.Parse(m_currentCode) == true ) + { + if (m_assembler.BuildBinary(m_program, m_result) == true) + { + m_result.Print(); + + Log("Binary successfully generated."); + + // Update ROM memory + std::copy(m_program.begin(), m_program.end(), m_rom_data); + + // FIXME +// m_ramView->SetMemory(m_ram_data, sizeof(m_ram_data)); +// m_romView->SetMemory(m_rom_data, m_program.size()); + m_project.SaveStory(m_program); + chip32_initialize(&m_chip32_ctx); + m_dbg.run_result = VM_OK; + UpdateVmView(); + // DebugContext::DumpCodeAssembler(m_assembler); + } + else + { + Chip32::Assembler::Error err = m_assembler.GetLastError(); + Log(err.ToString(), true); + m_editor.AddError(err.line, err.message); // show also the error in the code editor + } + } + else + { + Chip32::Assembler::Error err = m_assembler.GetLastError(); + Log(err.ToString(), true); + m_editor.AddError(err.line, err.message); // show also the error in the code editor + } +} + +void MainWindow::UpdateVmView() +{ + // FIXME +// m_vmDock->updateRegistersView(m_chip32_ctx); +// highlightNextLine(); + // Refresh RAM content +// m_ramView->SetMemory(m_ram_data, m_chip32_ctx.ram.size); +} + +void MainWindow::ConvertResources() +{ + +} + void MainWindow::SaveParams() { diff --git a/story-editor/src/main_window.h b/story-editor/src/main_window.h index a33ab81..5e3e773 100644 --- a/story-editor/src/main_window.h +++ b/story-editor/src/main_window.h @@ -8,7 +8,7 @@ #include "emulator_window.h" #include "resources_window.h" #include "node_editor_window.h" -#include "node_properties_window.h" +#include "properties_window.h" #include "chip32_assembler.h" #include "chip32_vm.h" @@ -92,7 +92,8 @@ private: std::vector m_program; Chip32::Assembler m_assembler; Chip32::Result m_result; - // DebugContext m_dbg; + DebugContext m_dbg; + std::string m_currentCode; std::vector m_recentProjects; @@ -101,29 +102,17 @@ private: Gui gui; EmulatorWindow m_emulatorWindow; ConsoleWindow m_consoleWindow; - CodeEditor editor; + CodeEditor m_editor; ResourcesWindow m_resourcesWindow; NodeEditorWindow m_nodeEditorWindow; - NodePropertiesWindow m_nodePropertiesWindow; - - - char mBufAddress[200]; - char mBufReceivePath[200]; - char mBufSendPath[200]; - char mBufPort[10]; - - int octets[4]; - - std::string mServerAddr; - std::string mServerRecUrl; - std::string mServerSndUrl; - int mServerPort; + PropertiesWindow m_PropertiesWindow; // From IStoryProject (proxy to StoryProject class) + virtual void Log(const std::string &txt, bool critical = false) override; virtual void PlaySoundFile(const std::string &fileName) override;; virtual std::string BuildFullAssetsPath(const std::string &fileName) const override; virtual std::pair Images() override; @@ -132,13 +121,15 @@ private: virtual void AddResource(std::shared_ptr res) override; virtual void ClearResources() override; virtual std::pair Resources() override; - + virtual void DeleteResource(FilterIterator &it) override; + virtual void Build() override; + virtual std::list> GetNodeConnections(unsigned long nodeId) override; + virtual std::string GetNodeEntryLabel(unsigned long nodeId) override; void SaveParams(); void LoadParams(); void DrawMainMenuBar(); - void ShowOptionsWindow(); bool ShowQuitConfirm(); void NewProjectPopup(); @@ -147,6 +138,11 @@ private: void CloseProject(); void OpenProjectDialog(); void DrawStatusBar(); + + bool CompileToAssembler(); + void ConvertResources(); + void GenerateBinary(); + void UpdateVmView(); }; #endif // MAINWINDOW_H diff --git a/story-editor/src/media_node.cpp b/story-editor/src/media_node.cpp index c590683..48db78c 100644 --- a/story-editor/src/media_node.cpp +++ b/story-editor/src/media_node.cpp @@ -2,7 +2,7 @@ namespace ed = ax::NodeEditor; #include "IconsMaterialDesignIcons.h" - +#include "story_project.h" MediaNode::MediaNode(const std::string &title, IStoryProject &proj) : BaseNode(title, proj) @@ -128,6 +128,11 @@ void MediaNode::DrawProperties() ImGui::OpenPopup("popup_button"); isImage = true; } + ImGui::SameLine(); + if (ImGui::Button(ICON_MDI_CLOSE_BOX_OUTLINE "##delimage")) { + SetImage(""); + } + ImGui::AlignTextToFramePadding(); ImGui::Text("Sound"); @@ -149,6 +154,11 @@ void MediaNode::DrawProperties() isImage = false; } + ImGui::SameLine(); + if (ImGui::Button(ICON_MDI_CLOSE_BOX_OUTLINE "##delsound")) { + SetSound(""); + } + // This is the actual popup Gui drawing section. if (ImGui::BeginPopup("popup_button")) { ImGui::SeparatorText(isImage ? "Images" : "Sounds"); @@ -189,38 +199,36 @@ void MediaNode::SetSound(const std::string &f) -/* -std::string NodeEditorWindow::ChoiceLabel() const + +std::string MediaNode::ChoiceLabel() const { std::stringstream ss; ss << "mediaChoice" << std::setw(4) << std::setfill('0') << GetId(); return ss.str(); } -std::string NodeEditorWindow::EntryLabel() const +std::string MediaNode::GetEntryLabel() { std::stringstream ss; - ss << ".mediaEntry" << std::setw(4) << std::setfill('0') << getNodeId(); + ss << ".mediaEntry" << std::setw(4) << std::setfill('0') << GetId(); return ss.str(); } -std::string NodeEditorWindow::GenerateConstants() +std::string MediaNode::GenerateConstants() { std::string s; - std::string image = m_mediaData["image"].get(); - std::string sound = m_mediaData["sound"].get(); - if (image.size() > 0) + if (m_image.name.size() > 0) { - s = StoryProject::FileToConstant(image, ".qoi"); // FIXME: Generate the extension setup in user option of output format + s = StoryProject::FileToConstant(m_image.name, ".qoi"); // FIXME: Generate the extension setup in user option of output format } - if (sound.size() > 0) + if (m_soundName.size() > 0) { - s += StoryProject::FileToConstant(sound, ".wav"); // FIXME: Generate the extension setup in user option of output format + s += StoryProject::FileToConstant(m_soundName, ".wav"); // FIXME: Generate the extension setup in user option of output format } - int nb_out_conns = ComputeOutputConnections(); + int nb_out_conns = Outputs(); if (nb_out_conns > 1) { // Generate choice table if needed (out ports > 1) @@ -230,14 +238,14 @@ std::string NodeEditorWindow::GenerateConstants() << " DC32, " << nb_out_conns << ", "; - std::unordered_set conns = m_model.allConnectionIds(getNodeId()); + std::list> conns = m_project.GetNodeConnections(GetId()); int i = 0; for (auto & c : conns) { std::stringstream ssChoice; // On va chercher le label d'entrée du noeud connecté à l'autre bout - ss << m_model.GetNodeEntryLabel(c.inNodeId); + ss << m_project.GetNodeEntryLabel(c->inNodeId); if (i < (nb_out_conns - 1)) { ss << ", "; @@ -255,22 +263,22 @@ std::string NodeEditorWindow::GenerateConstants() return s; } -std::string NodeEditorWindow::Build() +std::string MediaNode::Build() { std::stringstream ss; - int nb_out_conns = ComputeOutputConnections(); + int nb_out_conns = Outputs(); ss << R"(; ---------------------------- )" - << GetNodeTitle() + << GetTitle() << " Type: " << (nb_out_conns == 0 ? "End" : nb_out_conns == 1 ? "Transition" : "Choice") << "\n"; - std::string image = StoryProject::RemoveFileExtension(m_mediaData["image"].get()); - std::string sound = StoryProject::RemoveFileExtension(m_mediaData["sound"].get()); + std::string image = StoryProject::RemoveFileExtension(m_image.name); + std::string sound = StoryProject::RemoveFileExtension(m_soundName); // Le label de ce noeud est généré de la façon suivante : // "media" + Node ID + id du noeud parent. Si pas de noeud parent, alors rien - ss << EntryLabel() << ":\n"; + ss << GetEntryLabel() << ":\n"; if (image.size() > 0) { @@ -301,18 +309,18 @@ std::string NodeEditorWindow::Build() { ss << "halt\n"; } - else if (nb_out_conns == 1) // Transition node + else if (nb_out_conns == 1) // it is a transition node { - std::unordered_set conns = m_model.allConnectionIds(getNodeId()); + std::list> conns = m_project.GetNodeConnections(GetId()); for (auto c : conns) { - if (c.outNodeId == getNodeId()) + if (c->outNodeId == GetId()) { // On place dans R0 le prochain noeud à exécuter en cas de OK ss << "lcons r0, " - << m_model.GetNodeEntryLabel(c.inNodeId) << "\n" + << m_project.GetNodeEntryLabel(c->inNodeId) << "\n" << "ret\n"; } } @@ -325,4 +333,4 @@ std::string NodeEditorWindow::Build() } return ss.str(); } -*/ + diff --git a/story-editor/src/media_node.h b/story-editor/src/media_node.h index 6dea129..5198512 100644 --- a/story-editor/src/media_node.h +++ b/story-editor/src/media_node.h @@ -20,9 +20,10 @@ public: virtual void FromJson(const nlohmann::json &j) override; virtual void ToJson(nlohmann::json &j) override; - virtual void DrawProperties() override; - + virtual std::string Build() override; + virtual std::string GetEntryLabel() override; + virtual std::string GenerateConstants() override; private: IStoryProject &m_project; Gui::Image m_image; @@ -34,4 +35,5 @@ private: std::string m_buttonUniqueName; void SetImage(const std::string &f); void SetSound(const std::string &f); + std::string ChoiceLabel() const; }; diff --git a/story-editor/src/node_editor_window.cpp b/story-editor/src/node_editor_window.cpp index b49e95d..e6cb600 100644 --- a/story-editor/src/node_editor_window.cpp +++ b/story-editor/src/node_editor_window.cpp @@ -57,7 +57,7 @@ void NodeEditorWindow::LoadNode(const nlohmann::json &nodeJson) n->SetPosition(posJson["x"].get(), posJson["y"].get()); n->FromJson(internalDataJson); - m_nodes[n->GetInternalId()] = n; + m_nodes.push_back(n); } else { @@ -78,9 +78,9 @@ ed::PinId NodeEditorWindow::GetInputPin(unsigned long modelNodeId, int pinIndex) for (auto & n : m_nodes) { - if (n.second->GetId() == modelNodeId) + if (n->GetId() == modelNodeId) { - id = n.second->GetInputPinAt(pinIndex); + id = n->GetInputPinAt(pinIndex); } } @@ -98,9 +98,9 @@ ed::PinId NodeEditorWindow::GetOutputPin(unsigned long modelNodeId, int pinIndex for (auto & n : m_nodes) { - if (n.second->GetId() == modelNodeId) + if (n->GetId() == modelNodeId) { - id = n.second->GetOutputPinAt(pinIndex); + id = n->GetOutputPinAt(pinIndex); } } @@ -133,13 +133,13 @@ void NodeEditorWindow::Load(const nlohmann::json &model) auto conn = std::make_shared(); // our model - conn->model = connection.get(); + *conn->model = connection.get(); // ImGui stuff for links - conn->Id = BaseNode::GetNextId(); - conn->InputId = GetInputPin(conn->model.inNodeId, conn->model.inPortIndex); - conn->OutputId = GetOutputPin(conn->model.outNodeId, conn->model.outPortIndex); + conn->ed_link->Id = BaseNode::GetNextId(); + conn->ed_link->InputId = GetInputPin(conn->model->inNodeId, conn->model->inPortIndex); + conn->ed_link->OutputId = GetOutputPin(conn->model->outNodeId, conn->model->outPortIndex); // Since we accepted new link, lets add one to our list of links. m_links.push_back(conn); @@ -155,18 +155,18 @@ void NodeEditorWindow::Save(nlohmann::json &model) for (const auto & n : m_nodes) { nlohmann::json node; - node["id"] = n.second->GetId(); - node["type"] = n.second->GetType(); - node["outPortCount"] = n.second->Outputs(); - node["inPortCount"] = n.second->Inputs(); + node["id"] = n->GetId(); + node["type"] = n->GetType(); + node["outPortCount"] = n->Outputs(); + node["inPortCount"] = n->Inputs(); nlohmann::json position; - position["x"] = n.second->GetX(); - position["y"] = n.second->GetY(); + position["x"] = n->GetX(); + position["y"] = n->GetY(); nlohmann::json internalData; - n.second->ToJson(internalData); + n->ToJson(internalData); node["position"] = position; node["internal-data"] = internalData; @@ -185,27 +185,116 @@ void NodeEditorWindow::Save(nlohmann::json &model) int index; for (const auto & n : m_nodes) { - if (n.second->HasOnputPinId(linkInfo->OutputId, index)) + if (n->HasOnputPinId(linkInfo->ed_link->OutputId, index)) { - c["outNodeId"] = n.second->GetId(); + c["outNodeId"] = n->GetId(); c["outPortIndex"] = index; } - if (n.second->HasInputPinId(linkInfo->InputId, index)) + if (n->HasInputPinId(linkInfo->ed_link->InputId, index)) { - c["inNodeId"] = n.second->GetId(); + c["inNodeId"] = n->GetId(); c["inPortIndex"] = index; } } connections.push_back(c); - ed::Link(linkInfo->Id, linkInfo->OutputId, linkInfo->InputId); } model["connections"] = connections; ed::SetCurrentEditor(nullptr); } +uint32_t NodeEditorWindow::FindFirstNode() const +{ + uint32_t id = 0; + + // First node is the one without connection on its input port + + for (const auto & n : m_nodes) + { + bool foundConnection = false; + for (const auto& l : m_links) + { + if (l->model->inNodeId == n->GetId()) + { + foundConnection = true; + } + } + + if (!foundConnection) + { + id = n->GetId(); + m_project.Log("First node is: " + std::to_string(id)); + break; + } + } + + return id; +} + +std::string NodeEditorWindow::Build() +{ + std::stringstream code; + ed::SetCurrentEditor(m_context); + + + std::stringstream chip32; + + uint32_t firstNode = FindFirstNode(); + + code << "\tjump " << GetNodeEntryLabel(firstNode) << "\r\n"; + + // First generate all constants + for (const auto & n : m_nodes) + { + code << n->GenerateConstants() << "\n"; + } + + for (const auto & n : m_nodes) + { + code << n->Build() << "\n"; + } + + ed::SetCurrentEditor(nullptr); + return code.str(); +} + +std::list> NodeEditorWindow::GetNodeConnections(unsigned long nodeId) +{ + std::list> c; + ed::SetCurrentEditor(m_context); + + for (const auto & l : m_links) + { + if (l->model->outNodeId == nodeId) + { + c.push_back(l->model); + } + } + + ed::SetCurrentEditor(nullptr); + return c; +} + +std::string NodeEditorWindow::GetNodeEntryLabel(unsigned long nodeId) +{ + std::string label; + ed::SetCurrentEditor(m_context); + + for (const auto & n : m_nodes) + { + if (n->GetId() == nodeId) + { + label = n->GetEntryLabel(); + break; + } + } + + ed::SetCurrentEditor(nullptr); + return label; +} + std::shared_ptr NodeEditorWindow::GetSelectedNode() { @@ -219,9 +308,12 @@ std::shared_ptr NodeEditorWindow::GetSelectedNode() if (nodeCount > 0) { - if (m_nodes.contains(nId.Get())) + for (auto & n : m_nodes) { - selected = m_nodes[nId.Get()]; + if (n->GetInternalId() == nId.Get()) + { + selected = n; + } } } } @@ -242,14 +334,14 @@ void NodeEditorWindow::Draw() for (const auto & n : m_nodes) { - ImGui::PushID(n.first); - n.second->Draw(); + ImGui::PushID(n->GetInternalId()); + n->Draw(); ImGui::PopID(); } for (const auto& linkInfo : m_links) { - ed::Link(linkInfo->Id, linkInfo->OutputId, linkInfo->InputId); + ed::Link(linkInfo->ed_link->Id, linkInfo->ed_link->OutputId, linkInfo->ed_link->InputId); } // Handle creation action, returns true if editor want to create new object (node or link) @@ -304,7 +396,10 @@ void NodeEditorWindow::Draw() m_links.erase(std::remove_if(m_links.begin(), m_links.end(), - [deletedLinkId](std::shared_ptr inf) { return inf->Id == deletedLinkId; })); + [deletedLinkId](std::shared_ptr inf) + { + return inf->ed_link->Id == deletedLinkId; + })); } // You may reject link deletion by calling: diff --git a/story-editor/src/node_editor_window.h b/story-editor/src/node_editor_window.h index 7a3a5c1..60788b3 100644 --- a/story-editor/src/node_editor_window.h +++ b/story-editor/src/node_editor_window.h @@ -28,15 +28,24 @@ namespace ed = ax::NodeEditor; class NodeEditorWindow : public WindowBase { public: - struct LinkInfo - { - // Stuff from ImGuiNodeEditor + struct EditorLink { ed::LinkId Id; ed::PinId InputId; ed::PinId OutputId; + }; - // Stuff from the project.json file, our model - Connection model; + // Stuff from ImGuiNodeEditor, each element has a unique ID within one editor + struct LinkInfo + { + + LinkInfo() + { + ed_link = std::make_shared(); + model = std::make_shared(); + } + + std::shared_ptr ed_link; + std::shared_ptr model; }; NodeEditorWindow(IStoryProject &proj); @@ -47,17 +56,20 @@ public: void Clear(); void Load(const nlohmann::json &model); void Save(nlohmann::json &model); + std::string Build(); + std::list > GetNodeConnections(unsigned long nodeId); + std::string GetNodeEntryLabel(unsigned long nodeId); std::shared_ptr GetSelectedNode(); - std::string GenerateConstants(); + private: IStoryProject &m_project; ed::EditorContext* m_context = nullptr; // key: Id - std::map> m_nodes; - std::vector> m_links; // List of live links. It is dynamic unless you want to create read-only view over nodes. + std::list> m_nodes; + std::list> m_links; // List of live links. It is dynamic unless you want to create read-only view over nodes. void ToolbarUI(); @@ -104,7 +116,6 @@ private: void LoadNode(const nlohmann::json &nodeJson); ed::PinId GetInputPin(unsigned long modelNodeId, int pinIndex); ed::PinId GetOutputPin(unsigned long modelNodeId, int pinIndex); - std::string ChoiceLabel() const; - std::string EntryLabel() const; + uint32_t FindFirstNode() const; }; diff --git a/story-editor/src/node_properties_window.cpp b/story-editor/src/node_properties_window.cpp deleted file mode 100644 index 42ca70a..0000000 --- a/story-editor/src/node_properties_window.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "node_properties_window.h" -#include "gui.h" - -NodePropertiesWindow::NodePropertiesWindow() - : WindowBase("Properties") -{ - -} - -void NodePropertiesWindow::Initialize() { - - int my_image_width = 0; - int my_image_height = 0; - -} - -void NodePropertiesWindow::Draw() -{ -// if (!IsVisible()) -// { -// return; -// } - - - WindowBase::BeginDraw(); - ImGui::SetWindowSize(ImVec2(626, 744), ImGuiCond_FirstUseEver); - - - static char buf1[32] = ""; ImGui::InputText("Title", buf1, 32); - - if (m_selectedNode) - { - m_selectedNode->DrawProperties(); - } - - WindowBase::EndDraw(); -} - -void NodePropertiesWindow::SetSelectedNode(std::shared_ptr node) -{ - m_selectedNode = node; -} diff --git a/story-editor/src/properties_window.cpp b/story-editor/src/properties_window.cpp new file mode 100644 index 0000000..99a4f8c --- /dev/null +++ b/story-editor/src/properties_window.cpp @@ -0,0 +1,47 @@ +#include "properties_window.h" +#include "gui.h" + +PropertiesWindow::PropertiesWindow() + : WindowBase("Properties") +{ + +} + +void PropertiesWindow::Initialize() { + + int my_image_width = 0; + int my_image_height = 0; + +} + +void PropertiesWindow::Draw() +{ +// if (!IsVisible()) +// { +// return; +// } + + + WindowBase::BeginDraw(); + ImGui::SetWindowSize(ImVec2(626, 744), ImGuiCond_FirstUseEver); + + + ImGui::SeparatorText("Project"); + + ImGui::SeparatorText("Selected node"); + + + if (m_selectedNode) + { + static char buf1[32] = ""; ImGui::InputText("Title", buf1, 32); + ImGui::Text("Node ID: %lu", m_selectedNode->GetId()); + m_selectedNode->DrawProperties(); + } + + WindowBase::EndDraw(); +} + +void PropertiesWindow::SetSelectedNode(std::shared_ptr node) +{ + m_selectedNode = node; +} diff --git a/story-editor/src/node_properties_window.h b/story-editor/src/properties_window.h similarity index 78% rename from story-editor/src/node_properties_window.h rename to story-editor/src/properties_window.h index 5c4fb04..09d0ee3 100644 --- a/story-editor/src/node_properties_window.h +++ b/story-editor/src/properties_window.h @@ -5,10 +5,10 @@ #include "base_node.h" -class NodePropertiesWindow : public WindowBase +class PropertiesWindow : public WindowBase { public: - NodePropertiesWindow(); + PropertiesWindow(); void Initialize(); virtual void Draw() override; diff --git a/story-editor/src/resource.h b/story-editor/src/resource.h index 65f495b..f4ff26a 100644 --- a/story-editor/src/resource.h +++ b/story-editor/src/resource.h @@ -5,6 +5,7 @@ #include #include #include +#include struct Resource { @@ -20,11 +21,8 @@ struct Resource // Itérateur pour parcourir les éléments filtrés class FilterIterator { -private: - using Iterator = std::vector>::const_iterator; - Iterator current; - Iterator end; - std::string filterType; +public: + using Iterator = std::list>::const_iterator; public: FilterIterator(Iterator start, Iterator end, const std::string &type) @@ -37,6 +35,10 @@ public: return *current; } + const Iterator Current() const { + return current; + } + // Surcharge de l'opérateur d'incrémentation FilterIterator& operator++() { ++current; @@ -55,6 +57,10 @@ public: } private: + Iterator current; + Iterator end; + std::string filterType; + // Fonction pour trouver le prochain élément qui correspond au filtre void searchNext() { diff --git a/story-editor/src/resource_manager.h b/story-editor/src/resource_manager.h index fa1621d..0836381 100644 --- a/story-editor/src/resource_manager.h +++ b/story-editor/src/resource_manager.h @@ -30,6 +30,11 @@ public: UpdateIterators(); } + void Delete(FilterIterator &it) + { + m_items.erase(it.Current()); + } + void Clear() { m_items.clear(); @@ -61,7 +66,7 @@ public: private: - std::vector> m_items; + std::list> m_items; std::pair m_images; std::pair m_sounds; diff --git a/story-editor/src/resources_window.cpp b/story-editor/src/resources_window.cpp index e5b3b71..20ff73e 100644 --- a/story-editor/src/resources_window.cpp +++ b/story-editor/src/resources_window.cpp @@ -109,6 +109,7 @@ void ResourcesWindow::Draw() int id = 1000; for (auto it = b; it != e; ++it) { + bool quitLoop = false; ImGui::PushID(id); ImGui::TableNextColumn(); ImGui::Text("%s", (*it)->file.c_str()); @@ -132,7 +133,6 @@ void ResourcesWindow::Draw() strncpy(description, (*it)->description.c_str(), sizeof(description)); init = false; } -// ImGui::PushID(id); ImGui::InputText("Description", description, sizeof(description)); if (ImGui::Button("Close")) { @@ -140,7 +140,6 @@ void ResourcesWindow::Draw() ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); -// ImGui::PopID(); } ImGui::SameLine(); ImGui::Text("%s", (*it)->description.c_str()); @@ -153,9 +152,16 @@ void ResourcesWindow::Draw() ImGui::TableNextColumn(); if (ImGui::SmallButton("Delete")) { + m_project.DeleteResource(it); + quitLoop = true; } ImGui::PopID(); id++; + + if (quitLoop) + { + break; + } } ImGui::EndTable(); diff --git a/story-editor/src/story_project.cpp b/story-editor/src/story_project.cpp index 2b38acd..eab9b18 100644 --- a/story-editor/src/story_project.cpp +++ b/story-editor/src/story_project.cpp @@ -422,18 +422,4 @@ std::string StoryProject::BuildFullAssetsPath(const std::string &fileName) const } -void to_json(nlohmann::json &j, const Connection &p) { - j = nlohmann::json{ - {"outNodeId", static_cast(p.outNodeId)}, - {"outPortIndex", static_cast(p.outPortIndex)}, - {"inNodeId", static_cast(p.inNodeId)}, - {"inPortIndex", static_cast(p.inPortIndex)}, - }; -} -void from_json(const nlohmann::json &j, Connection &p) { - j.at("outNodeId").get_to(p.outNodeId); - j.at("outPortIndex").get_to(p.outPortIndex); - j.at("inNodeId").get_to(p.inNodeId); - j.at("inPortIndex").get_to(p.inPortIndex); -} diff --git a/story-editor/src/story_project.h b/story-editor/src/story_project.h index fb0cb94..392b035 100644 --- a/story-editor/src/story_project.h +++ b/story-editor/src/story_project.h @@ -48,35 +48,6 @@ struct AudioCommand { }; -struct Connection -{ - int outNodeId; - int outPortIndex; - int inNodeId; - int inPortIndex; -}; - -inline bool operator==(Connection const &a, Connection const &b) -{ - return a.outNodeId == b.outNodeId && a.outPortIndex == b.outPortIndex - && a.inNodeId == b.inNodeId && a.inPortIndex == b.inPortIndex; -} - -inline bool operator!=(Connection const &a, Connection const &b) -{ - return !(a == b); -} - -inline void invertConnection(Connection &id) -{ - std::swap(id.outNodeId, id.inNodeId); - std::swap(id.outPortIndex, id.inPortIndex); -} - -void to_json(nlohmann::json& j, const Connection& p); - -void from_json(const nlohmann::json& j, Connection& p); - // FIXME : Structure très Lunii style, à utiliser pour la conversion peut-être ... struct StoryNode diff --git a/story-editor/story-editor-logo.ico b/story-editor/story-editor-logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..667cf6015e86a651adf031c5a91fa53ccda295fa GIT binary patch literal 224990 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 U&e6OQhk45h!JnU;0RFlE3n+0fm;e9( literal 0 HcmV?d00001 diff --git a/story-editor/story-editor-logo.png b/story-editor/story-editor-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f50d31deea8c4d4b440a922dd7742671214bc275 GIT binary patch literal 39945 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