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
- 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
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
![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->SetDisplayFormat(320, 240);
story->SetImageFormat(StoryProject::IMG_FORMAT_QOIF);
story->SetSoundFormat(StoryProject::SND_FORMAT_WAV);
story->SetImageFormat(Resource::IMG_FORMAT_QOIF);
story->SetSoundFormat(Resource::SND_FORMAT_WAV);
story->SetName("New project");
return story;
}
@ -171,6 +171,7 @@ void LibraryManager::CopyToDevice(const std::string &outputDir)
{
if (p->IsSelected())
{
std::cout << "Copying " << p->GetName() << std::endl;
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 <iostream>
#include <list>
#include <unordered_map>
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 description;
std::string format;
@ -17,6 +22,9 @@ struct Resource
~Resource() {
// 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

View file

@ -5,10 +5,7 @@
#include "resource_manager.h"
#include "media_converter.h"
#include "sys_lib.h"
struct Media {
std::string type;
std::string format;
};
static const std::unordered_map<std::string, Media> mediaTypes {
{".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 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();
for (auto it = b; it != e; ++it)
@ -49,18 +47,39 @@ void ResourceManager::ConvertResources(const std::filesystem::path &assetsPath,
{
if ((*it)->format == "PNG")
{
outputfile += ".qoi"; // FIXME: prendre la config en cours désirée
retCode = MediaConverter::ImageToQoi(inputfile.generic_string(), outputfile.generic_string());
if (imageFormat == Resource::IMG_FORMAT_QOIF)
{
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")
{
outputfile += ".wav"; // FIXME: prendre la config en cours désirée
retCode = MediaConverter::Mp3ToWav(inputfile.generic_string(), outputfile.generic_string());
if (soundFormat == Resource::SND_FORMAT_WAV)
{
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")
{
outputfile += ".wav"; // FIXME: prendre la config en cours désirée
retCode = MediaConverter::OggToWav(inputfile.generic_string(), outputfile.generic_string());
if (soundFormat == Resource::SND_FORMAT_WAV)
{
outputfile += ".wav"; // FIXME: prendre la config en cours désirée
retCode = MediaConverter::OggToWav(inputfile.generic_string(), outputfile.generic_string());
}
else
{
outputfile += ".ogg";
}
}
else
{

View file

@ -11,21 +11,25 @@
#include "resource.h"
#include "i_logger.h"
struct Media {
std::string type;
std::string format;
};
class ResourceManager
{
public:
public:
ResourceManager()
: m_images(filter("image"))
, m_sounds(filter("sound"))
{
}
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() {
@ -48,7 +52,6 @@ public:
UpdateIterators();
}
// 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 {
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);
// 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
{
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)
{
n->FromJson(element);
m_nodes.push_back(n);
}
else
{
@ -215,12 +216,15 @@ bool StoryProject::ModelFromJson(const nlohmann::json &model)
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;
}
catch(nlohmann::json::exception &e)
{
std::cout << "(NodeEditorWindow::Load) " << e.what() << std::endl;
}
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;
}
void StoryProject::SetSoundFormat(SoundFormat format)
void StoryProject::SetSoundFormat(Resource::SoundFormat format)
{
m_soundFormat = format;
}

View file

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

View file

@ -232,6 +232,8 @@ set(SRCS
../shared/miniaudio.h
../shared/stb_vorbis.c
../shared/uuid.h
../shared/resource.h
../shared/resource.cpp
../shared/resource_manager.h
../shared/resource_manager.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;
}
bool PackArchive::ImportStudioFormat(const std::string &fileName, const std::string &outputDir)
{
auto uuid = Uuid().String();
std::string basePath = outputDir + "/" + uuid;
Unzip(fileName, basePath);
bool PackArchive::ConvertJsonStudioToOst(const std::string &basePath, const std::string &uuid, const std::string &outputDir)
{
try
{
@ -436,12 +432,7 @@ bool PackArchive::ImportStudioFormat(const std::string &fileName, const std::str
stageActionLink[n["okTransition"]["actionNode"]] = node_uuid;
}
/*
"okTransition":{
"actionNode":"19d7328f-d0d2-4443-a7a2-25270dafe52c",
"optionIndex":0
},
*/
}
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());
}
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;
}

View file

@ -32,6 +32,7 @@ public:
void Previous();
void Unzip(const std::string &filePath, 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);
private:

View file

@ -10,11 +10,12 @@ BaseNodeWidget::BaseNodeWidget(IStoryManager &manager, std::shared_ptr<BaseNode
, m_base(base)
{
m_node = std::make_unique<Node>(GetNextId(), ""); // ImGui internal ID
std::cout << " --> Created node widget: " << (int)m_node->ID.Get() << std::endl;
}
BaseNodeWidget::~BaseNodeWidget()
{
std::cout << "Deleted node widget" << std::endl;
std::cout << " <-- Deleted node widget: " << (int)m_node->ID.Get() << std::endl;
}
void BaseNodeWidget::AddInput()
@ -71,6 +72,14 @@ void BaseNodeWidget::FrameStart()
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;
}

View file

@ -106,6 +106,8 @@ void NodeEditorWindow::Load(std::shared_ptr<StoryProject> story)
auto [node_begin, node_end] = m_story->Nodes();
int i = 0;
for (auto it = node_begin; it != node_end; ++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());
}
std::cout << "Created " << ++i << " node" << std::endl;
}
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 << "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)
{

View file

@ -54,6 +54,7 @@ public:
void Initialize();
void Clear();
void Load(std::shared_ptr<StoryProject> story);
void SaveNodePositions();
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)
: BaseNode(type)
{
nlohmann::json j{ {"image", ""}, {"sound", ""}};
SetInternalData(j);
}