Fix project loading duplicate nodes, formats moved

This commit is contained in:
anthony@rabine.fr 2024-05-14 11:05:17 +02:00
parent fcbc802265
commit 9accbbeea7
16 changed files with 164 additions and 67 deletions

View file

@ -17,10 +17,36 @@ You'll need:
- A C++ compiler - A C++ compiler
- CMake build utility - CMake build utility
Here is a list of packages for Ubuntu-like systems:
```
sudo apt install cmake mesa-utils mesa-common-dev ninja-build libxext-dev
```
## How to build ## How to build
Open the CMakeLists.txt with your favorite IDE (ie, QtCreator, Visual Studio Code) or build from the command line. Open the CMakeLists.txt with your favorite IDE (ie, QtCreator, Visual Studio Code) or build from the command line.
## How to generate a Windows executable and setup executable on Ubuntu
The build system uses a Docker environment image for reproductible builds.
Run `build_win32.sh` script.
Output file is located here: `story-editor/build-win32/Open-Story-Editor-1.0.0-win64.exe`
## Linux build
```
cd story-editor
mkdir build
cd build
cmake ..
make -j4
make package
```
# Architecture # Architecture
![arch](./images/story-editor-architecture.png) ![arch](./images/story-editor-architecture.png)
@ -28,3 +54,5 @@ Open the CMakeLists.txt with your favorite IDE (ie, QtCreator, Visual Studio Cod

View file

@ -85,8 +85,8 @@ std::shared_ptr<StoryProject> LibraryManager::NewProject()
story->New(uuid, m_library_path); story->New(uuid, m_library_path);
story->SetDisplayFormat(320, 240); story->SetDisplayFormat(320, 240);
story->SetImageFormat(StoryProject::IMG_FORMAT_QOIF); story->SetImageFormat(Resource::IMG_FORMAT_QOIF);
story->SetSoundFormat(StoryProject::SND_FORMAT_WAV); story->SetSoundFormat(Resource::SND_FORMAT_WAV);
story->SetName("New project"); story->SetName("New project");
return story; return story;
} }
@ -171,6 +171,7 @@ void LibraryManager::CopyToDevice(const std::string &outputDir)
{ {
if (p->IsSelected()) if (p->IsSelected())
{ {
std::cout << "Copying " << p->GetName() << std::endl;
p->CopyToDevice(outputDir); p->CopyToDevice(outputDir);
} }
} }

29
shared/resource.cpp Normal file
View file

@ -0,0 +1,29 @@
#include "resource.h"
std::string Resource::ImageFormatToString(ImageFormat format)
{
std::string text = "SAME";
switch (format)
{
case IMG_FORMAT_QOIF:
text = "QOIF";
break;
}
return text;
}
std::string Resource::SoundFormatToString(SoundFormat format)
{
std::string text = "SAME";
switch (format)
{
case SND_FORMAT_WAV:
text = "WAV";
break;
case SND_FORMAT_QOAF:
text = "QOAF";
break;
}
return text;
}

View file

@ -6,9 +6,14 @@
#include <vector> #include <vector>
#include <iostream> #include <iostream>
#include <list> #include <list>
#include <unordered_map>
struct Resource struct Resource
{ {
enum ImageFormat { IMG_SAME_FORMAT, IMG_FORMAT_QOIF, IMG_FORMAT_COUNT };
enum SoundFormat { SND_SAME_FORMAT, SND_FORMAT_WAV, SND_FORMAT_QOAF, SND_FORMAT_COUNT };
std::string file; std::string file;
std::string description; std::string description;
std::string format; std::string format;
@ -17,6 +22,9 @@ struct Resource
~Resource() { ~Resource() {
// std::cout << "Res deleted" << std::endl; // std::cout << "Res deleted" << std::endl;
} }
static std::string ImageFormatToString(ImageFormat format);
static std::string SoundFormatToString(SoundFormat format);
}; };
// Itérateur pour parcourir les éléments filtrés // Itérateur pour parcourir les éléments filtrés

View file

@ -5,10 +5,7 @@
#include "resource_manager.h" #include "resource_manager.h"
#include "media_converter.h" #include "media_converter.h"
#include "sys_lib.h" #include "sys_lib.h"
struct Media {
std::string type;
std::string format;
};
static const std::unordered_map<std::string, Media> mediaTypes { static const std::unordered_map<std::string, Media> mediaTypes {
{".mp3", {"sound", "MP3"}}, {".mp3", {"sound", "MP3"}},
@ -22,6 +19,7 @@ static const std::unordered_map<std::string, Media> mediaTypes {
}; };
std::string ResourceManager::ExtentionInfo(std::string extension, int info_type) std::string ResourceManager::ExtentionInfo(std::string extension, int info_type)
{ {
std::string lowerExtension = extension; std::string lowerExtension = extension;
@ -36,7 +34,7 @@ std::string ResourceManager::ExtentionInfo(std::string extension, int info_type)
} }
} }
void ResourceManager::ConvertResources(const std::filesystem::path &assetsPath, const std::filesystem::path &destAssetsPath) void ResourceManager::ConvertResources(const std::filesystem::path &assetsPath, const std::filesystem::path &destAssetsPath, Resource::ImageFormat imageFormat, Resource::SoundFormat soundFormat)
{ {
auto [b, e] = Items(); auto [b, e] = Items();
for (auto it = b; it != e; ++it) for (auto it = b; it != e; ++it)
@ -49,18 +47,39 @@ void ResourceManager::ConvertResources(const std::filesystem::path &assetsPath,
{ {
if ((*it)->format == "PNG") if ((*it)->format == "PNG")
{ {
outputfile += ".qoi"; // FIXME: prendre la config en cours désirée if (imageFormat == Resource::IMG_FORMAT_QOIF)
retCode = MediaConverter::ImageToQoi(inputfile.generic_string(), outputfile.generic_string()); {
outputfile += ".qoi"; // FIXME: prendre la config en cours désirée
retCode = MediaConverter::ImageToQoi(inputfile.generic_string(), outputfile.generic_string());
}
else
{
outputfile += ".png";
}
} }
else if ((*it)->format == "MP3") else if ((*it)->format == "MP3")
{ {
outputfile += ".wav"; // FIXME: prendre la config en cours désirée if (soundFormat == Resource::SND_FORMAT_WAV)
retCode = MediaConverter::Mp3ToWav(inputfile.generic_string(), outputfile.generic_string()); {
outputfile += ".wav"; // FIXME: prendre la config en cours désirée
retCode = MediaConverter::Mp3ToWav(inputfile.generic_string(), outputfile.generic_string());
}
else
{
outputfile += ".mp3";
}
} }
else if ((*it)->format == "OGG") else if ((*it)->format == "OGG")
{ {
outputfile += ".wav"; // FIXME: prendre la config en cours désirée if (soundFormat == Resource::SND_FORMAT_WAV)
retCode = MediaConverter::OggToWav(inputfile.generic_string(), outputfile.generic_string()); {
outputfile += ".wav"; // FIXME: prendre la config en cours désirée
retCode = MediaConverter::OggToWav(inputfile.generic_string(), outputfile.generic_string());
}
else
{
outputfile += ".ogg";
}
} }
else else
{ {

View file

@ -11,21 +11,25 @@
#include "resource.h" #include "resource.h"
#include "i_logger.h" #include "i_logger.h"
struct Media {
std::string type;
std::string format;
};
class ResourceManager class ResourceManager
{ {
public: public:
ResourceManager() ResourceManager()
: m_images(filter("image")) : m_images(filter("image"))
, m_sounds(filter("sound")) , m_sounds(filter("sound"))
{ {
} }
static std::string ExtentionInfo(std::string extension, int info_type); static std::string ExtentionInfo(std::string extension, int info_type);
void ConvertResources(const std::filesystem::path &assetsPath, const std::filesystem::path &destAssetsPath); void ConvertResources(const std::filesystem::path &assetsPath, const std::filesystem::path &destAssetsPath, Resource::ImageFormat imageFormat, Resource::SoundFormat soundFormat);
~ResourceManager() { ~ResourceManager() {
@ -48,7 +52,6 @@ public:
UpdateIterators(); UpdateIterators();
} }
// Fonction pour créer un itérateur de début et de fin pour les éléments filtrés // Fonction pour créer un itérateur de début et de fin pour les éléments filtrés
std::pair<FilterIterator, FilterIterator> filter(const std::string &type) const { std::pair<FilterIterator, FilterIterator> filter(const std::string &type) const {
auto begin = std::begin(m_items); auto begin = std::begin(m_items);

View file

@ -52,7 +52,7 @@ void StoryProject::CopyToDevice(const std::string &outputDir)
std::filesystem::copy(BinaryFileName(), destRootDir, std::filesystem::copy_options::overwrite_existing); std::filesystem::copy(BinaryFileName(), destRootDir, std::filesystem::copy_options::overwrite_existing);
// Convert resources (if necessary) and copy them to destination assets // Convert resources (if necessary) and copy them to destination assets
manager.ConvertResources(AssetsPath(), destAssetsDir); manager.ConvertResources(AssetsPath(), destAssetsDir, m_imageFormat, m_soundFormat);
} }
} }
@ -136,7 +136,9 @@ std::shared_ptr<BaseNode> StoryProject::CreateNode(const std::string &type)
} }
else else
{ {
return i->second(type); auto n = i->second(type);
m_nodes.push_back(n);
return n;
} }
} }
@ -191,7 +193,6 @@ bool StoryProject::ModelFromJson(const nlohmann::json &model)
if (n) if (n)
{ {
n->FromJson(element); n->FromJson(element);
m_nodes.push_back(n);
} }
else else
{ {
@ -215,12 +216,15 @@ bool StoryProject::ModelFromJson(const nlohmann::json &model)
m_links.push_back(std::make_shared<Connection>(connection.get<Connection>())); m_links.push_back(std::make_shared<Connection>(connection.get<Connection>()));
} }
} }
std::cout << "From model, loded nodes: " << m_nodes.size() << ", links: " << m_links.size() << std::endl;
success = true; success = true;
} }
catch(nlohmann::json::exception &e) catch(nlohmann::json::exception &e)
{ {
std::cout << "(NodeEditorWindow::Load) " << e.what() << std::endl; std::cout << "(NodeEditorWindow::Load) " << e.what() << std::endl;
} }
return success; return success;
} }
@ -517,12 +521,12 @@ void StoryProject::SetTitleSound(const std::string &titleSound)
void StoryProject::SetImageFormat(ImageFormat format) void StoryProject::SetImageFormat(Resource::ImageFormat format)
{ {
m_imageFormat = format; m_imageFormat = format;
} }
void StoryProject::SetSoundFormat(SoundFormat format) void StoryProject::SetSoundFormat(Resource::SoundFormat format)
{ {
m_soundFormat = format; m_soundFormat = format;
} }

View file

@ -49,8 +49,7 @@ struct StoryProject : public IStoryProject
{ {
public: public:
enum ImageFormat { IMG_SAME_FORMAT, IMG_FORMAT_QOIF, IMG_FORMAT_COUNT };
enum SoundFormat { SND_SAME_FORMAT, SND_FORMAT_WAV, SND_FORMAT_QOAF, SND_FORMAT_COUNT };
StoryProject(ILogger &log); StoryProject(ILogger &log);
~StoryProject(); ~StoryProject();
@ -98,8 +97,12 @@ public:
void Select(bool selected) { m_selected = selected; } void Select(bool selected) { m_selected = selected; }
bool IsSelected() const { return m_selected; } bool IsSelected() const { return m_selected; }
void SetImageFormat(ImageFormat format); void SetImageFormat(Resource::ImageFormat format);
void SetSoundFormat(SoundFormat format); void SetSoundFormat(Resource::SoundFormat format);
Resource::ImageFormat GetImageFormat() const { return m_imageFormat; }
Resource::SoundFormat GetSoundFormat() const { return m_soundFormat; }
void SetDisplayFormat(int w, int h); void SetDisplayFormat(int w, int h);
void SetName(const std::string &name) { m_name = name; } void SetName(const std::string &name) { m_name = name; }
void SetUuid(const std::string &uuid) { m_uuid = uuid; } void SetUuid(const std::string &uuid) { m_uuid = uuid; }
@ -167,9 +170,8 @@ private:
int m_display_w{320}; int m_display_w{320};
int m_display_h{240}; int m_display_h{240};
ImageFormat m_imageFormat{IMG_SAME_FORMAT}; Resource::ImageFormat m_imageFormat{Resource::IMG_SAME_FORMAT};
SoundFormat m_soundFormat{SND_FORMAT_WAV}; Resource::SoundFormat m_soundFormat{Resource::SND_SAME_FORMAT};
template<class NodeType> template<class NodeType>
struct Factory { struct Factory {
@ -187,8 +189,6 @@ private:
m_registry.insert(typename Registry::value_type(key, Factory<Derived>::create_func)); m_registry.insert(typename Registry::value_type(key, Factory<Derived>::create_func));
} }
}; };
#endif // STORY_PROJECT_H #endif // STORY_PROJECT_H

View file

@ -232,6 +232,8 @@ set(SRCS
../shared/miniaudio.h ../shared/miniaudio.h
../shared/stb_vorbis.c ../shared/stb_vorbis.c
../shared/uuid.h ../shared/uuid.h
../shared/resource.h
../shared/resource.cpp
../shared/resource_manager.h ../shared/resource_manager.h
../shared/resource_manager.cpp ../shared/resource_manager.cpp
../shared/story_project.cpp ../shared/story_project.cpp

View file

@ -1,20 +0,0 @@
# Story Editor
## How to generate a Windows executable and setup executable on Ubuntu
The build system uses a Docker environment image for reproductible builds.
Run `build_win32.sh` script.
Output file is located here: `story-editor/build-win32/Open-Story-Editor-1.0.0-win64.exe`
## Linux build
```
cd story-editor
mkdir build
cd build
cmake ..
make -j4
make package
```

View file

@ -373,12 +373,8 @@ std::string PackArchive::OpenImage(const std::string &fileName)
return f; return f;
} }
bool PackArchive::ImportStudioFormat(const std::string &fileName, const std::string &outputDir) bool PackArchive::ConvertJsonStudioToOst(const std::string &basePath, const std::string &uuid, const std::string &outputDir)
{ {
auto uuid = Uuid().String();
std::string basePath = outputDir + "/" + uuid;
Unzip(fileName, basePath);
try try
{ {
@ -436,12 +432,7 @@ bool PackArchive::ImportStudioFormat(const std::string &fileName, const std::str
stageActionLink[n["okTransition"]["actionNode"]] = node_uuid; stageActionLink[n["okTransition"]["actionNode"]] = node_uuid;
} }
/*
"okTransition":{
"actionNode":"19d7328f-d0d2-4443-a7a2-25270dafe52c",
"optionIndex":0
},
*/
} }
for (const auto & n : j["actionNodes"]) for (const auto & n : j["actionNodes"])
@ -478,6 +469,16 @@ bool PackArchive::ImportStudioFormat(const std::string &fileName, const std::str
m_log.Log(std::string("Import failure: ") + e.what()); m_log.Log(std::string("Import failure: ") + e.what());
} }
return true; // FIXME
}
bool PackArchive::ImportStudioFormat(const std::string &fileName, const std::string &outputDir)
{
auto uuid = Uuid().String();
std::string basePath = outputDir + "/" + uuid;
Unzip(fileName, basePath);
ConvertJsonStudioToOst(basePath, uuid, outputDir);
return false; return false;
} }

View file

@ -32,6 +32,7 @@ public:
void Previous(); void Previous();
void Unzip(const std::string &filePath, const std::string &parent_dest_dir); void Unzip(const std::string &filePath, const std::string &parent_dest_dir);
void DecipherAll(const std::string &packFileName, const std::string &parent_dest_dir); void DecipherAll(const std::string &packFileName, const std::string &parent_dest_dir);
bool ConvertJsonStudioToOst(const std::string &basePath, const std::string &uuid, const std::string &outputDir);
std::string HexDump(const char *desc, const void *addr, int len); std::string HexDump(const char *desc, const void *addr, int len);
private: private:

View file

@ -10,11 +10,12 @@ BaseNodeWidget::BaseNodeWidget(IStoryManager &manager, std::shared_ptr<BaseNode
, m_base(base) , m_base(base)
{ {
m_node = std::make_unique<Node>(GetNextId(), ""); // ImGui internal ID m_node = std::make_unique<Node>(GetNextId(), ""); // ImGui internal ID
std::cout << " --> Created node widget: " << (int)m_node->ID.Get() << std::endl;
} }
BaseNodeWidget::~BaseNodeWidget() BaseNodeWidget::~BaseNodeWidget()
{ {
std::cout << "Deleted node widget" << std::endl; std::cout << " <-- Deleted node widget: " << (int)m_node->ID.Get() << std::endl;
} }
void BaseNodeWidget::AddInput() void BaseNodeWidget::AddInput()
@ -71,6 +72,14 @@ void BaseNodeWidget::FrameStart()
ed::SetNodePosition(m_node->ID, ImVec2(m_base->GetX(), m_base->GetY())); ed::SetNodePosition(m_node->ID, ImVec2(m_base->GetX(), m_base->GetY()));
} }
} }
else
{
// Si ce n'est pas la première frame, on synchronise la position du noeud avec l'objet
if (m_base)
{
m_base->SetPosition(GetX(), GetY());
}
}
m_firstFrame = false; m_firstFrame = false;
} }

View file

@ -106,6 +106,8 @@ void NodeEditorWindow::Load(std::shared_ptr<StoryProject> story)
auto [node_begin, node_end] = m_story->Nodes(); auto [node_begin, node_end] = m_story->Nodes();
int i = 0;
for (auto it = node_begin; it != node_end; ++it) for (auto it = node_begin; it != node_end; ++it)
{ {
auto n = CreateNodeWidget((*it)->GetType(), m_manager, (*it)); auto n = CreateNodeWidget((*it)->GetType(), m_manager, (*it));
@ -119,6 +121,8 @@ void NodeEditorWindow::Load(std::shared_ptr<StoryProject> story)
{ {
throw std::logic_error(std::string("No registered model with name ") + (*it)->GetType()); throw std::logic_error(std::string("No registered model with name ") + (*it)->GetType());
} }
std::cout << "Created " << ++i << " node" << std::endl;
} }
auto [link_begin, link_end] = m_story->Links(); auto [link_begin, link_end] = m_story->Links();
@ -136,9 +140,15 @@ void NodeEditorWindow::Load(std::shared_ptr<StoryProject> story)
std::cout << "(NodeEditorWindow::Load) " << e.what() << std::endl; std::cout << "(NodeEditorWindow::Load) " << e.what() << std::endl;
} }
} }
std::cout << "Loaded " << m_nodes.size() << " nodes, " << m_links.size() << " links" << std::endl;
} }
void NodeEditorWindow::SaveNodePositions()
{
}
void NodeEditorWindow::CreateLink(std::shared_ptr<Connection> model, ed::PinId inId, ed::PinId outId) void NodeEditorWindow::CreateLink(std::shared_ptr<Connection> model, ed::PinId inId, ed::PinId outId)
{ {

View file

@ -54,6 +54,7 @@ public:
void Initialize(); void Initialize();
void Clear(); void Clear();
void Load(std::shared_ptr<StoryProject> story); void Load(std::shared_ptr<StoryProject> story);
void SaveNodePositions();
std::shared_ptr<BaseNodeWidget> GetSelectedNode(); std::shared_ptr<BaseNodeWidget> GetSelectedNode();

View file

@ -13,7 +13,8 @@ static std::string ChoiceLabel(const std::string &id)
MediaNode::MediaNode(const std::string &type) MediaNode::MediaNode(const std::string &type)
: BaseNode(type) : BaseNode(type)
{ {
nlohmann::json j{ {"image", ""}, {"sound", ""}};
SetInternalData(j);
} }