Build binary, code display, errors management

This commit is contained in:
Anthony Rabine 2023-12-21 22:08:09 +01:00
parent 83d8542002
commit 09aab7241a
32 changed files with 657 additions and 522 deletions

View file

@ -94,15 +94,13 @@ static inline void leu16_put(std::vector<std::uint8_t> &container, uint16_t data
} }
#define GET_REG(name, ra) if (!GetRegister(name, ra)) {\ #define GET_REG(name, ra) if (!GetRegister(name, ra)) {\
std::stringstream ss; \ m_lastError.line -1; \
ss << "ERROR! Bad register name: " << name << std::endl;\ m_lastError.message = "ERROR! Bad register name: " + name; \
m_lastError = ss.str(); \
return false; } return false; }
#define CHIP32_CHECK(instr, cond, error) if (!(cond)) { \ #define CHIP32_CHECK(instr, cond, error) if (!(cond)) { \
std::stringstream ss; \ m_lastError.line = instr.line; \
ss << "error line: " << instr.line << ": " << error << std::endl; \ m_lastError.message = error; \
m_lastError = ss.str(); \
return false; } \ return false; } \
std::vector<std::string> Split(std::string line) std::vector<std::string> Split(std::string line)
@ -242,7 +240,7 @@ bool Assembler::CompileMnemonicArguments(Instr &instr)
instr.compiledArgs.push_back(static_cast<uint32_t>(strtol(instr.args[2].c_str(), NULL, 0))); instr.compiledArgs.push_back(static_cast<uint32_t>(strtol(instr.args[2].c_str(), NULL, 0)));
break; break;
default: default:
CHIP32_CHECK(instr, false, "Unsupported mnemonic: " << instr.mnemonic); CHIP32_CHECK(instr, false, "Unsupported mnemonic: " + instr.mnemonic);
break; break;
} }
return true; return true;
@ -286,7 +284,7 @@ bool Assembler::CompileConstantArgument(Instr &instr, const std::string &a)
((intVal <= UINT32_MAX) && (instr.dataTypeSize == 32))) { ((intVal <= UINT32_MAX) && (instr.dataTypeSize == 32))) {
sizeOk = true; 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) { if (instr.dataTypeSize == 8) {
instr.compiledArgs.push_back(intVal); instr.compiledArgs.push_back(intVal);
} else if (instr.dataTypeSize == 16) { } else if (instr.dataTypeSize == 16) {
@ -362,7 +360,7 @@ bool Assembler::Parse(const std::string &data)
instr.mnemonic = opcode; instr.mnemonic = opcode;
instr.isLabel = true; instr.isLabel = true;
instr.addr = code_addr; 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_labels[opcode] = instr;
m_instructions.push_back(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()); instr.args.insert(instr.args.begin(), lineParts.begin() + 1, lineParts.end());
CHIP32_CHECK(instr, instr.args.size() == instr.code.nbAargs, CHIP32_CHECK(instr, instr.args.size() == instr.code.nbAargs,
"Bad number of parameters. Required: " << static_cast<int>(instr.code.nbAargs) << ", got: " << instr.args.size()); "Bad number of parameters. Required: " + std::to_string(static_cast<int>(instr.code.nbAargs)) + ", got: " + std::to_string(instr.args.size()));
nbArgsSuccess = true; nbArgsSuccess = true;
} }
else 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.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, (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.isRomData = type[1] == 'C' ? true : false;
instr.isRamData = type[1] == 'V' ? true : false; instr.isRamData = type[1] == 'V' ? true : false;
@ -445,7 +443,8 @@ bool Assembler::Parse(const std::string &data)
} }
else 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; return false;
} }
} }
@ -458,7 +457,7 @@ bool Assembler::Parse(const std::string &data)
// label is the first argument for jump, second position for LCONS // label is the first argument for jump, second position for LCONS
uint16_t argsIndex = instr.code.opcode == OP_LCONS ? 1 : 0; uint16_t argsIndex = instr.code.opcode == OP_LCONS ? 1 : 0;
std::string label = instr.args[argsIndex]; 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; uint16_t addr = m_labels[label].addr;
std::cout << "LABEL: " << label << " , addr: " << addr << std::endl; std::cout << "LABEL: " << label << " , addr: " << addr << std::endl;
instr.compiledArgs[argsIndex] = addr & 0xFF; instr.compiledArgs[argsIndex] = addr & 0xFF;

View file

@ -83,10 +83,16 @@ struct Result
} }
}; };
class Assembler class Assembler
{ {
public: 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 // Separated parser to allow only code check
bool Parse(const std::string &data); bool Parse(const std::string &data);
// Generate the executable binary after the parse pass // Generate the executable binary after the parse pass
@ -104,7 +110,7 @@ public:
bool GetRegister(const std::string &regName, uint8_t &reg); bool GetRegister(const std::string &regName, uint8_t &reg);
bool GetRegisterName(uint8_t reg, std::string &regName); bool GetRegisterName(uint8_t reg, std::string &regName);
std::string GetLastError() { return m_lastError; } Error GetLastError() { return m_lastError; }
private: private:
bool CompileMnemonicArguments(Instr &instr); bool CompileMnemonicArguments(Instr &instr);
@ -112,7 +118,7 @@ private:
// label, address // label, address
std::map<std::string, Instr> m_labels; std::map<std::string, Instr> m_labels;
std::string m_lastError; Error m_lastError;
std::vector<Instr> m_instructions; std::vector<Instr> m_instructions;
bool CompileConstantArgument(Instr &instr, const std::string &a); bool CompileConstantArgument(Instr &instr, const std::string &a);

View file

@ -119,8 +119,8 @@ set(SRCS
src/resources_window.cpp src/resources_window.cpp
src/resources_window.h src/resources_window.h
src/node_properties_window.cpp src/properties_window.cpp
src/node_properties_window.h src/properties_window.h
src/gui.h src/gui.h
src/gui.cpp src/gui.cpp
@ -128,6 +128,9 @@ set(SRCS
src/code_editor.cpp src/code_editor.cpp
src/code_editor.h src/code_editor.h
src/connection.cpp
src/connection.h
src/uuid.h src/uuid.h
src/resource_manager.h src/resource_manager.h
src/i_story_project.h src/i_story_project.h

1
story-editor/icon.rc Normal file
View file

@ -0,0 +1 @@
IDI_ICON1 ICON DISCARDABLE "story-editor-logo.ico"

View file

@ -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

View file

@ -105,8 +105,10 @@ public:
virtual void Draw() = 0; virtual void Draw() = 0;
virtual void DrawProperties() = 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); void SetPosition(float x, float y);
@ -135,8 +137,8 @@ public:
unsigned long GetId() const { return m_id; } unsigned long GetId() const { return m_id; }
unsigned long GetInternalId() const { return m_node->ID.Get(); } unsigned long GetInternalId() const { return m_node->ID.Get(); }
void seTitle(const std::string &title) { m_title = title; } void SeTitle(const std::string &title) { m_title = title; }
std::string getTitle() const { return m_title; } std::string GetTitle() const { return m_title; }
virtual void FromJson(const nlohmann::json &) = 0; virtual void FromJson(const nlohmann::json &) = 0;
virtual void ToJson(nlohmann::json &) = 0; virtual void ToJson(nlohmann::json &) = 0;

View file

@ -1,6 +1,7 @@
#include "code_editor.h" #include "code_editor.h"
#include <fstream> #include <fstream>
#include <memory>
CodeEditor::CodeEditor() CodeEditor::CodeEditor()
: WindowBase("Code editor") : WindowBase("Code editor")
@ -11,27 +12,32 @@ CodeEditor::CodeEditor()
void CodeEditor::Initialize() void CodeEditor::Initialize()
{ {
// error markers // error markers
TextEditor::ErrorMarkers markers;
markers.insert(std::make_pair<int, std::string>(6, "Example error here:\nInclude file not found: \"TextEditor.h\"")); //
markers.insert(std::make_pair<int, std::string>(41, "Another example error")); // markers.insert(std::make_pair<int, std::string>(41, "Another example error"));
mEditor.SetErrorMarkers(markers);
// "breakpoint" markers // "breakpoint" markers
//TextEditor::Breakpoints bpts; m_breakPoints.insert(42);
//bpts.insert(24); mEditor.SetBreakpoints(m_breakPoints);
//bpts.insert(47);
//editor.SetBreakpoints(bpts);
mFileToEdit = "test/test_zebra7500.js"; }
{ void CodeEditor::ClearErrors()
std::ifstream t(mFileToEdit); {
if (t.good()) m_markers.clear();
{ }
std::string str((std::istreambuf_iterator<char>(t)), std::istreambuf_iterator<char>());
mEditor.SetText(str); 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() void CodeEditor::Draw()
@ -99,10 +105,9 @@ void CodeEditor::Draw()
ImGui::EndMenuBar(); 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.IsOverwrite() ? "Ovr" : "Ins",
mEditor.CanUndo() ? "*" : " ", mEditor.CanUndo() ? "*" : " ");
mEditor.GetLanguageDefinition().mName.c_str(), mFileToEdit.c_str());
mEditor.Render("TextEditor"); mEditor.Render("TextEditor");
WindowBase::EndDraw(); WindowBase::EndDraw();

View file

@ -11,7 +11,12 @@ public:
virtual void Draw() override; virtual void Draw() override;
void Initialize(); void Initialize();
void SetScript(const std::string &txt);
void ClearErrors();
void AddError(int line, const std::string &text);
private: private:
TextEditor mEditor; TextEditor mEditor;
std::string mFileToEdit; TextEditor::Breakpoints m_breakPoints;
TextEditor::ErrorMarkers m_markers;
}; };

View file

@ -0,0 +1,17 @@
#include "connection.h"
void to_json(nlohmann::json &j, const Connection &p) {
j = nlohmann::json{
{"outNodeId", static_cast<int64_t>(p.outNodeId)},
{"outPortIndex", static_cast<int64_t>(p.outPortIndex)},
{"inNodeId", static_cast<int64_t>(p.inNodeId)},
{"inPortIndex", static_cast<int64_t>(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);
}

View file

@ -0,0 +1,53 @@
#pragma once
#include <algorithm>
#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);

View file

@ -8,13 +8,7 @@ ConsoleWindow::ConsoleWindow()
{ {
ClearLog(); ClearLog();
memset(InputBuf, 0, sizeof(InputBuf)); 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; AutoScroll = true;
ScrollToBottom = false; ScrollToBottom = false;
} }
@ -22,8 +16,6 @@ ConsoleWindow::ConsoleWindow()
ConsoleWindow::~ConsoleWindow() ConsoleWindow::~ConsoleWindow()
{ {
ClearLog(); ClearLog();
for (int i = 0; i < History.Size; i++)
free(History[i]);
} }
void ConsoleWindow::ClearLog() void ConsoleWindow::ClearLog()
@ -113,7 +105,16 @@ void ConsoleWindow::Draw()
{ {
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) 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(); 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<const char*> 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;
}

View file

@ -25,42 +25,34 @@ public:
void ClearLog(); void ClearLog();
void AddMessage(const std::string &message) { AddLog("%s", message.c_str()); } void AddLog(const std::string &text, uint32_t type)
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)
{ {
// FIXME-OPT // FIXME-OPT
char buf[1024]; Entry e{text, type};
va_list args;
va_start(args, fmt);
vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args);
buf[IM_ARRAYSIZE(buf)-1] = 0;
va_end(args);
std::scoped_lock<std::mutex> mutex(mLogMutex); std::scoped_lock<std::mutex> mutex(mLogMutex);
Items.push_back(Strdup(buf)); Items.push_back(e);
if (Items.size() > 100) if (Items.size() > 100)
{ {
Items.erase(Items.begin()); Items.erase(Items.begin());
} }
} }
virtual void Draw() override;
private:
struct Entry {
std::string text;
uint32_t type;
};
std::mutex mLogMutex; std::mutex mLogMutex;
char InputBuf[256]; char InputBuf[256];
std::vector<std::string> Items; std::vector<Entry> Items;
ImVector<const char*> Commands;
ImVector<char*> History;
int HistoryPos; // -1: new line, 0..History.Size-1 browsing history.
ImGuiTextFilter Filter; ImGuiTextFilter Filter;
bool AutoScroll; bool AutoScroll;
bool ScrollToBottom; bool ScrollToBottom;

View file

@ -1,17 +1,18 @@
#include "emulator_window.h" #include "emulator_window.h"
#include "gui.h" #include "gui.h"
#include "IconsMaterialDesignIcons.h"
EmulatorWindow::EmulatorWindow() EmulatorWindow::EmulatorWindow(IStoryProject &proj)
: WindowBase("Emulator") : 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(); 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::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::SetCursorScreenPos(ImVec2(p.x, p.y + 244));
ImGui::ImageButton("play", m_playImage.texture, ImVec2(45, 45));
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(); WindowBase::EndDraw();
} }

View file

@ -1,20 +1,17 @@
#pragma once #pragma once
#include "window_base.h" #include "window_base.h"
#include "gui.h" #include "i_story_project.h"
class EmulatorWindow : public WindowBase class EmulatorWindow : public WindowBase
{ {
public: public:
EmulatorWindow(); EmulatorWindow(IStoryProject &proj);
void Initialize(); void Initialize();
virtual void Draw() override; virtual void Draw() override;
private: private:
Gui::Image m_playImage; IStoryProject &m_project;
Gui::Image m_pauseImage;
Gui::Image m_homeImage;
}; };

View file

@ -5,14 +5,17 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <list>
#include "resource.h" #include "resource.h"
#include "connection.h"
class IStoryProject class IStoryProject
{ {
public: public:
virtual ~IStoryProject() {} virtual ~IStoryProject() {}
virtual void Log(const std::string &txt, bool critical = false) = 0;
virtual void PlaySoundFile(const std::string &fileName) = 0; virtual void PlaySoundFile(const std::string &fileName) = 0;
virtual std::string BuildFullAssetsPath(const std::string &fileName) const = 0; virtual std::string BuildFullAssetsPath(const std::string &fileName) const = 0;
@ -22,7 +25,12 @@ public:
virtual std::pair<FilterIterator, FilterIterator> Resources() = 0; virtual std::pair<FilterIterator, FilterIterator> Resources() = 0;
virtual void AddResource(std::shared_ptr<Resource> res) = 0; virtual void AddResource(std::shared_ptr<Resource> res) = 0;
virtual void ClearResources() = 0; virtual void ClearResources() = 0;
virtual void DeleteResource(FilterIterator &it) = 0;
// Node interaction
virtual void Build() = 0;
virtual std::list<std::shared_ptr<Connection>> GetNodeConnections(unsigned long nodeId) = 0;
virtual std::string GetNodeEntryLabel(unsigned long nodeId) = 0;
}; };

View file

@ -19,8 +19,10 @@
#include "ImGuiFileDialog.h" #include "ImGuiFileDialog.h"
MainWindow::MainWindow() MainWindow::MainWindow()
: m_resourcesWindow(*this) : m_emulatorWindow(*this)
, m_resourcesWindow(*this)
, m_nodeEditorWindow(*this) , m_nodeEditorWindow(*this)
{ {
m_project.Clear(); m_project.Clear();
} }
@ -168,109 +170,15 @@ void MainWindow::Initialize()
gui.Initialize(); gui.Initialize();
// gui.ApplyTheme(); // gui.ApplyTheme();
editor.Initialize(); m_editor.Initialize();
m_emulatorWindow.Initialize(); m_emulatorWindow.Initialize();
m_nodeEditorWindow.Initialize(); m_nodeEditorWindow.Initialize();
m_nodePropertiesWindow.Initialize(); m_PropertiesWindow.Initialize();
LoadParams(); 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() bool MainWindow::ShowQuitConfirm()
{ {
@ -319,13 +227,13 @@ void MainWindow::OpenProjectDialog()
if (m_project.Load(filePathName, model, m_resources)) if (m_project.Load(filePathName, model, m_resources))
{ {
m_consoleWindow.AddMessage("Open project success"); Log("Open project success");
m_nodeEditorWindow.Load(model); m_nodeEditorWindow.Load(model);
EnableProject(); EnableProject();
} }
else else
{ {
m_consoleWindow.AddMessage("Open project error"); Log("Open project error");
} }
@ -600,14 +508,12 @@ void MainWindow::Loop()
// ------------ Draw all windows // ------------ Draw all windows
m_consoleWindow.Draw(); m_consoleWindow.Draw();
m_emulatorWindow.Draw(); m_emulatorWindow.Draw();
editor.Draw(); m_editor.Draw();
m_resourcesWindow.Draw(); m_resourcesWindow.Draw();
m_nodeEditorWindow.Draw(); m_nodeEditorWindow.Draw();
m_nodePropertiesWindow.SetSelectedNode(m_nodeEditorWindow.GetSelectedNode()); m_PropertiesWindow.SetSelectedNode(m_nodeEditorWindow.GetSelectedNode());
m_nodePropertiesWindow.Draw(); m_PropertiesWindow.Draw();
ShowOptionsWindow();
NewProjectPopup(); NewProjectPopup();
OpenProjectDialog(); OpenProjectDialog();
@ -629,9 +535,14 @@ void MainWindow::Loop()
gui.Destroy(); 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) void MainWindow::PlaySoundFile(const std::string &fileName)
{ {
m_consoleWindow.AddMessage("Play sound file: " + fileName); Log("Play sound file: " + fileName);
m_project.PlaySoundFile(fileName); m_project.PlaySoundFile(fileName);
} }
@ -665,6 +576,112 @@ std::pair<FilterIterator, FilterIterator> MainWindow::Resources()
return m_resources.Items(); 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<std::shared_ptr<Connection>> 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() void MainWindow::SaveParams()
{ {

View file

@ -8,7 +8,7 @@
#include "emulator_window.h" #include "emulator_window.h"
#include "resources_window.h" #include "resources_window.h"
#include "node_editor_window.h" #include "node_editor_window.h"
#include "node_properties_window.h" #include "properties_window.h"
#include "chip32_assembler.h" #include "chip32_assembler.h"
#include "chip32_vm.h" #include "chip32_vm.h"
@ -92,7 +92,8 @@ private:
std::vector<uint8_t> m_program; std::vector<uint8_t> m_program;
Chip32::Assembler m_assembler; Chip32::Assembler m_assembler;
Chip32::Result m_result; Chip32::Result m_result;
// DebugContext m_dbg; DebugContext m_dbg;
std::string m_currentCode;
std::vector<std::string> m_recentProjects; std::vector<std::string> m_recentProjects;
@ -101,29 +102,17 @@ private:
Gui gui; Gui gui;
EmulatorWindow m_emulatorWindow; EmulatorWindow m_emulatorWindow;
ConsoleWindow m_consoleWindow; ConsoleWindow m_consoleWindow;
CodeEditor editor; CodeEditor m_editor;
ResourcesWindow m_resourcesWindow; ResourcesWindow m_resourcesWindow;
NodeEditorWindow m_nodeEditorWindow; NodeEditorWindow m_nodeEditorWindow;
NodePropertiesWindow m_nodePropertiesWindow; PropertiesWindow m_PropertiesWindow;
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;
// From IStoryProject (proxy to StoryProject class) // 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 void PlaySoundFile(const std::string &fileName) override;;
virtual std::string BuildFullAssetsPath(const std::string &fileName) const override; virtual std::string BuildFullAssetsPath(const std::string &fileName) const override;
virtual std::pair<FilterIterator, FilterIterator> Images() override; virtual std::pair<FilterIterator, FilterIterator> Images() override;
@ -132,13 +121,15 @@ private:
virtual void AddResource(std::shared_ptr<Resource> res) override; virtual void AddResource(std::shared_ptr<Resource> res) override;
virtual void ClearResources() override; virtual void ClearResources() override;
virtual std::pair<FilterIterator, FilterIterator> Resources() override; virtual std::pair<FilterIterator, FilterIterator> Resources() override;
virtual void DeleteResource(FilterIterator &it) override;
virtual void Build() override;
virtual std::list<std::shared_ptr<Connection>> GetNodeConnections(unsigned long nodeId) override;
virtual std::string GetNodeEntryLabel(unsigned long nodeId) override;
void SaveParams(); void SaveParams();
void LoadParams(); void LoadParams();
void DrawMainMenuBar(); void DrawMainMenuBar();
void ShowOptionsWindow();
bool ShowQuitConfirm(); bool ShowQuitConfirm();
void NewProjectPopup(); void NewProjectPopup();
@ -147,6 +138,11 @@ private:
void CloseProject(); void CloseProject();
void OpenProjectDialog(); void OpenProjectDialog();
void DrawStatusBar(); void DrawStatusBar();
bool CompileToAssembler();
void ConvertResources();
void GenerateBinary();
void UpdateVmView();
}; };
#endif // MAINWINDOW_H #endif // MAINWINDOW_H

View file

@ -2,7 +2,7 @@
namespace ed = ax::NodeEditor; namespace ed = ax::NodeEditor;
#include "IconsMaterialDesignIcons.h" #include "IconsMaterialDesignIcons.h"
#include "story_project.h"
MediaNode::MediaNode(const std::string &title, IStoryProject &proj) MediaNode::MediaNode(const std::string &title, IStoryProject &proj)
: BaseNode(title, proj) : BaseNode(title, proj)
@ -128,6 +128,11 @@ void MediaNode::DrawProperties()
ImGui::OpenPopup("popup_button"); ImGui::OpenPopup("popup_button");
isImage = true; isImage = true;
} }
ImGui::SameLine();
if (ImGui::Button(ICON_MDI_CLOSE_BOX_OUTLINE "##delimage")) {
SetImage("");
}
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::Text("Sound"); ImGui::Text("Sound");
@ -149,6 +154,11 @@ void MediaNode::DrawProperties()
isImage = false; isImage = false;
} }
ImGui::SameLine();
if (ImGui::Button(ICON_MDI_CLOSE_BOX_OUTLINE "##delsound")) {
SetSound("");
}
// This is the actual popup Gui drawing section. // This is the actual popup Gui drawing section.
if (ImGui::BeginPopup("popup_button")) { if (ImGui::BeginPopup("popup_button")) {
ImGui::SeparatorText(isImage ? "Images" : "Sounds"); 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; std::stringstream ss;
ss << "mediaChoice" << std::setw(4) << std::setfill('0') << GetId(); ss << "mediaChoice" << std::setw(4) << std::setfill('0') << GetId();
return ss.str(); return ss.str();
} }
std::string NodeEditorWindow::EntryLabel() const std::string MediaNode::GetEntryLabel()
{ {
std::stringstream ss; std::stringstream ss;
ss << ".mediaEntry" << std::setw(4) << std::setfill('0') << getNodeId(); ss << ".mediaEntry" << std::setw(4) << std::setfill('0') << GetId();
return ss.str(); return ss.str();
} }
std::string NodeEditorWindow::GenerateConstants() std::string MediaNode::GenerateConstants()
{ {
std::string s; std::string s;
std::string image = m_mediaData["image"].get<std::string>(); if (m_image.name.size() > 0)
std::string sound = m_mediaData["sound"].get<std::string>();
if (image.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) if (nb_out_conns > 1)
{ {
// Generate choice table if needed (out ports > 1) // Generate choice table if needed (out ports > 1)
@ -230,14 +238,14 @@ std::string NodeEditorWindow::GenerateConstants()
<< " DC32, " << " DC32, "
<< nb_out_conns << ", "; << nb_out_conns << ", ";
std::unordered_set<ConnectionId> conns = m_model.allConnectionIds(getNodeId()); std::list<std::shared_ptr<Connection>> conns = m_project.GetNodeConnections(GetId());
int i = 0; int i = 0;
for (auto & c : conns) for (auto & c : conns)
{ {
std::stringstream ssChoice; std::stringstream ssChoice;
// On va chercher le label d'entrée du noeud connecté à l'autre bout // 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)) if (i < (nb_out_conns - 1))
{ {
ss << ", "; ss << ", ";
@ -255,22 +263,22 @@ std::string NodeEditorWindow::GenerateConstants()
return s; return s;
} }
std::string NodeEditorWindow::Build() std::string MediaNode::Build()
{ {
std::stringstream ss; std::stringstream ss;
int nb_out_conns = ComputeOutputConnections(); int nb_out_conns = Outputs();
ss << R"(; ---------------------------- )" ss << R"(; ---------------------------- )"
<< GetNodeTitle() << GetTitle()
<< " Type: " << " Type: "
<< (nb_out_conns == 0 ? "End" : nb_out_conns == 1 ? "Transition" : "Choice") << (nb_out_conns == 0 ? "End" : nb_out_conns == 1 ? "Transition" : "Choice")
<< "\n"; << "\n";
std::string image = StoryProject::RemoveFileExtension(m_mediaData["image"].get<std::string>()); std::string image = StoryProject::RemoveFileExtension(m_image.name);
std::string sound = StoryProject::RemoveFileExtension(m_mediaData["sound"].get<std::string>()); std::string sound = StoryProject::RemoveFileExtension(m_soundName);
// Le label de ce noeud est généré de la façon suivante : // 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 // "media" + Node ID + id du noeud parent. Si pas de noeud parent, alors rien
ss << EntryLabel() << ":\n"; ss << GetEntryLabel() << ":\n";
if (image.size() > 0) if (image.size() > 0)
{ {
@ -301,18 +309,18 @@ std::string NodeEditorWindow::Build()
{ {
ss << "halt\n"; 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<ConnectionId> conns = m_model.allConnectionIds(getNodeId()); std::list<std::shared_ptr<Connection>> conns = m_project.GetNodeConnections(GetId());
for (auto c : conns) 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 // On place dans R0 le prochain noeud à exécuter en cas de OK
ss << "lcons r0, " ss << "lcons r0, "
<< m_model.GetNodeEntryLabel(c.inNodeId) << "\n" << m_project.GetNodeEntryLabel(c->inNodeId) << "\n"
<< "ret\n"; << "ret\n";
} }
} }
@ -325,4 +333,4 @@ std::string NodeEditorWindow::Build()
} }
return ss.str(); return ss.str();
} }
*/

View file

@ -20,9 +20,10 @@ public:
virtual void FromJson(const nlohmann::json &j) override; virtual void FromJson(const nlohmann::json &j) override;
virtual void ToJson(nlohmann::json &j) override; virtual void ToJson(nlohmann::json &j) override;
virtual void DrawProperties() override; virtual void DrawProperties() override;
virtual std::string Build() override;
virtual std::string GetEntryLabel() override;
virtual std::string GenerateConstants() override;
private: private:
IStoryProject &m_project; IStoryProject &m_project;
Gui::Image m_image; Gui::Image m_image;
@ -34,4 +35,5 @@ private:
std::string m_buttonUniqueName; std::string m_buttonUniqueName;
void SetImage(const std::string &f); void SetImage(const std::string &f);
void SetSound(const std::string &f); void SetSound(const std::string &f);
std::string ChoiceLabel() const;
}; };

View file

@ -57,7 +57,7 @@ void NodeEditorWindow::LoadNode(const nlohmann::json &nodeJson)
n->SetPosition(posJson["x"].get<float>(), posJson["y"].get<float>()); n->SetPosition(posJson["x"].get<float>(), posJson["y"].get<float>());
n->FromJson(internalDataJson); n->FromJson(internalDataJson);
m_nodes[n->GetInternalId()] = n; m_nodes.push_back(n);
} }
else else
{ {
@ -78,9 +78,9 @@ ed::PinId NodeEditorWindow::GetInputPin(unsigned long modelNodeId, int pinIndex)
for (auto & n : m_nodes) 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) 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<LinkInfo>(); auto conn = std::make_shared<LinkInfo>();
// our model // our model
conn->model = connection.get<Connection>(); *conn->model = connection.get<Connection>();
// ImGui stuff for links // ImGui stuff for links
conn->Id = BaseNode::GetNextId(); conn->ed_link->Id = BaseNode::GetNextId();
conn->InputId = GetInputPin(conn->model.inNodeId, conn->model.inPortIndex); conn->ed_link->InputId = GetInputPin(conn->model->inNodeId, conn->model->inPortIndex);
conn->OutputId = GetOutputPin(conn->model.outNodeId, conn->model.outPortIndex); conn->ed_link->OutputId = GetOutputPin(conn->model->outNodeId, conn->model->outPortIndex);
// Since we accepted new link, lets add one to our list of links. // Since we accepted new link, lets add one to our list of links.
m_links.push_back(conn); m_links.push_back(conn);
@ -155,18 +155,18 @@ void NodeEditorWindow::Save(nlohmann::json &model)
for (const auto & n : m_nodes) for (const auto & n : m_nodes)
{ {
nlohmann::json node; nlohmann::json node;
node["id"] = n.second->GetId(); node["id"] = n->GetId();
node["type"] = n.second->GetType(); node["type"] = n->GetType();
node["outPortCount"] = n.second->Outputs(); node["outPortCount"] = n->Outputs();
node["inPortCount"] = n.second->Inputs(); node["inPortCount"] = n->Inputs();
nlohmann::json position; nlohmann::json position;
position["x"] = n.second->GetX(); position["x"] = n->GetX();
position["y"] = n.second->GetY(); position["y"] = n->GetY();
nlohmann::json internalData; nlohmann::json internalData;
n.second->ToJson(internalData); n->ToJson(internalData);
node["position"] = position; node["position"] = position;
node["internal-data"] = internalData; node["internal-data"] = internalData;
@ -185,27 +185,116 @@ void NodeEditorWindow::Save(nlohmann::json &model)
int index; int index;
for (const auto & n : m_nodes) 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; 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; c["inPortIndex"] = index;
} }
} }
connections.push_back(c); connections.push_back(c);
ed::Link(linkInfo->Id, linkInfo->OutputId, linkInfo->InputId);
} }
model["connections"] = connections; model["connections"] = connections;
ed::SetCurrentEditor(nullptr); 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<std::shared_ptr<Connection>> NodeEditorWindow::GetNodeConnections(unsigned long nodeId)
{
std::list<std::shared_ptr<Connection>> 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<BaseNode> NodeEditorWindow::GetSelectedNode() std::shared_ptr<BaseNode> NodeEditorWindow::GetSelectedNode()
{ {
@ -219,9 +308,12 @@ std::shared_ptr<BaseNode> NodeEditorWindow::GetSelectedNode()
if (nodeCount > 0) 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) for (const auto & n : m_nodes)
{ {
ImGui::PushID(n.first); ImGui::PushID(n->GetInternalId());
n.second->Draw(); n->Draw();
ImGui::PopID(); ImGui::PopID();
} }
for (const auto& linkInfo : m_links) 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) // 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.erase(std::remove_if(m_links.begin(),
m_links.end(), m_links.end(),
[deletedLinkId](std::shared_ptr<LinkInfo> inf) { return inf->Id == deletedLinkId; })); [deletedLinkId](std::shared_ptr<LinkInfo> inf)
{
return inf->ed_link->Id == deletedLinkId;
}));
} }
// You may reject link deletion by calling: // You may reject link deletion by calling:

View file

@ -28,15 +28,24 @@ namespace ed = ax::NodeEditor;
class NodeEditorWindow : public WindowBase class NodeEditorWindow : public WindowBase
{ {
public: public:
struct LinkInfo struct EditorLink {
{
// Stuff from ImGuiNodeEditor
ed::LinkId Id; ed::LinkId Id;
ed::PinId InputId; ed::PinId InputId;
ed::PinId OutputId; ed::PinId OutputId;
};
// Stuff from the project.json file, our model // Stuff from ImGuiNodeEditor, each element has a unique ID within one editor
Connection model; struct LinkInfo
{
LinkInfo()
{
ed_link = std::make_shared<EditorLink>();
model = std::make_shared<Connection>();
}
std::shared_ptr<EditorLink> ed_link;
std::shared_ptr<Connection> model;
}; };
NodeEditorWindow(IStoryProject &proj); NodeEditorWindow(IStoryProject &proj);
@ -47,17 +56,20 @@ public:
void Clear(); void Clear();
void Load(const nlohmann::json &model); void Load(const nlohmann::json &model);
void Save(nlohmann::json &model); void Save(nlohmann::json &model);
std::string Build();
std::list<std::shared_ptr<Connection> > GetNodeConnections(unsigned long nodeId);
std::string GetNodeEntryLabel(unsigned long nodeId);
std::shared_ptr<BaseNode> GetSelectedNode(); std::shared_ptr<BaseNode> GetSelectedNode();
std::string GenerateConstants();
private: private:
IStoryProject &m_project; IStoryProject &m_project;
ed::EditorContext* m_context = nullptr; ed::EditorContext* m_context = nullptr;
// key: Id // key: Id
std::map<unsigned long, std::shared_ptr<BaseNode>> m_nodes; std::list<std::shared_ptr<BaseNode>> m_nodes;
std::vector<std::shared_ptr<LinkInfo>> m_links; // List of live links. It is dynamic unless you want to create read-only view over nodes. std::list<std::shared_ptr<LinkInfo>> m_links; // List of live links. It is dynamic unless you want to create read-only view over nodes.
void ToolbarUI(); void ToolbarUI();
@ -104,7 +116,6 @@ private:
void LoadNode(const nlohmann::json &nodeJson); void LoadNode(const nlohmann::json &nodeJson);
ed::PinId GetInputPin(unsigned long modelNodeId, int pinIndex); ed::PinId GetInputPin(unsigned long modelNodeId, int pinIndex);
ed::PinId GetOutputPin(unsigned long modelNodeId, int pinIndex); ed::PinId GetOutputPin(unsigned long modelNodeId, int pinIndex);
std::string ChoiceLabel() const; uint32_t FindFirstNode() const;
std::string EntryLabel() const;
}; };

View file

@ -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<BaseNode> node)
{
m_selectedNode = node;
}

View file

@ -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<BaseNode> node)
{
m_selectedNode = node;
}

View file

@ -5,10 +5,10 @@
#include "base_node.h" #include "base_node.h"
class NodePropertiesWindow : public WindowBase class PropertiesWindow : public WindowBase
{ {
public: public:
NodePropertiesWindow(); PropertiesWindow();
void Initialize(); void Initialize();
virtual void Draw() override; virtual void Draw() override;

View file

@ -5,6 +5,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <iostream> #include <iostream>
#include <list>
struct Resource struct Resource
{ {
@ -20,11 +21,8 @@ struct Resource
// Itérateur pour parcourir les éléments filtrés // Itérateur pour parcourir les éléments filtrés
class FilterIterator { class FilterIterator {
private: public:
using Iterator = std::vector<std::shared_ptr<Resource>>::const_iterator; using Iterator = std::list<std::shared_ptr<Resource>>::const_iterator;
Iterator current;
Iterator end;
std::string filterType;
public: public:
FilterIterator(Iterator start, Iterator end, const std::string &type) FilterIterator(Iterator start, Iterator end, const std::string &type)
@ -37,6 +35,10 @@ public:
return *current; return *current;
} }
const Iterator Current() const {
return current;
}
// Surcharge de l'opérateur d'incrémentation // Surcharge de l'opérateur d'incrémentation
FilterIterator& operator++() { FilterIterator& operator++() {
++current; ++current;
@ -55,6 +57,10 @@ public:
} }
private: private:
Iterator current;
Iterator end;
std::string filterType;
// Fonction pour trouver le prochain élément qui correspond au filtre // Fonction pour trouver le prochain élément qui correspond au filtre
void searchNext() { void searchNext() {

View file

@ -30,6 +30,11 @@ public:
UpdateIterators(); UpdateIterators();
} }
void Delete(FilterIterator &it)
{
m_items.erase(it.Current());
}
void Clear() void Clear()
{ {
m_items.clear(); m_items.clear();
@ -61,7 +66,7 @@ public:
private: private:
std::vector<std::shared_ptr<Resource>> m_items; std::list<std::shared_ptr<Resource>> m_items;
std::pair<FilterIterator, FilterIterator> m_images; std::pair<FilterIterator, FilterIterator> m_images;
std::pair<FilterIterator, FilterIterator> m_sounds; std::pair<FilterIterator, FilterIterator> m_sounds;

View file

@ -109,6 +109,7 @@ void ResourcesWindow::Draw()
int id = 1000; int id = 1000;
for (auto it = b; it != e; ++it) for (auto it = b; it != e; ++it)
{ {
bool quitLoop = false;
ImGui::PushID(id); ImGui::PushID(id);
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("%s", (*it)->file.c_str()); ImGui::Text("%s", (*it)->file.c_str());
@ -132,7 +133,6 @@ void ResourcesWindow::Draw()
strncpy(description, (*it)->description.c_str(), sizeof(description)); strncpy(description, (*it)->description.c_str(), sizeof(description));
init = false; init = false;
} }
// ImGui::PushID(id);
ImGui::InputText("Description", description, sizeof(description)); ImGui::InputText("Description", description, sizeof(description));
if (ImGui::Button("Close")) if (ImGui::Button("Close"))
{ {
@ -140,7 +140,6 @@ void ResourcesWindow::Draw()
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
} }
ImGui::EndPopup(); ImGui::EndPopup();
// ImGui::PopID();
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::Text("%s", (*it)->description.c_str()); ImGui::Text("%s", (*it)->description.c_str());
@ -153,9 +152,16 @@ void ResourcesWindow::Draw()
ImGui::TableNextColumn(); ImGui::TableNextColumn();
if (ImGui::SmallButton("Delete")) if (ImGui::SmallButton("Delete"))
{ {
m_project.DeleteResource(it);
quitLoop = true;
} }
ImGui::PopID(); ImGui::PopID();
id++; id++;
if (quitLoop)
{
break;
}
} }
ImGui::EndTable(); ImGui::EndTable();

View file

@ -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<int64_t>(p.outNodeId)},
{"outPortIndex", static_cast<int64_t>(p.outPortIndex)},
{"inNodeId", static_cast<int64_t>(p.inNodeId)},
{"inPortIndex", static_cast<int64_t>(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);
}

View file

@ -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 ... // FIXME : Structure très Lunii style, à utiliser pour la conversion peut-être ...
struct StoryNode struct StoryNode

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View file

@ -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;