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 0000000..667cf60 Binary files /dev/null and b/story-editor/story-editor-logo.ico differ diff --git a/story-editor/story-editor-logo.png b/story-editor/story-editor-logo.png new file mode 100644 index 0000000..f50d31d Binary files /dev/null and b/story-editor/story-editor-logo.png differ diff --git a/story-editor/story-editor.desktop b/story-editor/story-editor.desktop new file mode 100644 index 0000000..b31dc74 --- /dev/null +++ b/story-editor/story-editor.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Name=StoryEditor +Comment=A tool to create stories with graphical nodes (OpenStoryTeller project) +Exec=story-editor +Icon=story-editor-logo +Categories=Graphics;2DGraphics; +