deargui-vpl/external/imgui_node/imgui_node_editor_store.cpp
2026-02-03 18:25:25 +01:00

346 lines
9.6 KiB
C++

//------------------------------------------------------------------------------
// VERSION 0.9.1
//
// LICENSE
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
//
// CREDITS
// Written by Michal Cichon
//------------------------------------------------------------------------------
# include "imgui_node_editor_internal.h"
# include <cstdio>
# include <string>
# include <fstream>
# include <bitset>
# include <climits>
# include <algorithm>
# include <sstream>
# include <streambuf>
# include <type_traits>
namespace ed = ax::NodeEditor::Detail;
//------------------------------------------------------------------------------
//
// Node Settings
//
//------------------------------------------------------------------------------
void ed::NodeSettings::ClearDirty()
{
m_IsDirty = false;
m_DirtyReason = SaveReasonFlags::None;
}
void ed::NodeSettings::MakeDirty(SaveReasonFlags reason)
{
m_IsDirty = true;
m_DirtyReason = m_DirtyReason | reason;
}
ed::json::value ed::NodeSettings::Serialize()
{
json::value result;
result["location"]["x"] = m_Location.x;
result["location"]["y"] = m_Location.y;
if (m_GroupSize.x > 0 || m_GroupSize.y > 0)
{
result["group_size"]["x"] = m_GroupSize.x;
result["group_size"]["y"] = m_GroupSize.y;
}
return result;
}
bool ed::NodeSettings::Parse(const std::string& string, NodeSettings& settings)
{
auto settingsValue = json::value::parse(string);
if (settingsValue.is_discarded())
return false;
return Parse(settingsValue, settings);
}
bool ed::NodeSettings::Parse(const json::value& data, NodeSettings& result)
{
if (!data.is_object())
return false;
auto tryParseVector = [](const json::value& v, ImVec2& result) -> bool
{
if (v.is_object())
{
auto xValue = v["x"];
auto yValue = v["y"];
if (xValue.is_number() && yValue.is_number())
{
result.x = static_cast<float>(xValue.get<double>());
result.y = static_cast<float>(yValue.get<double>());
return true;
}
}
return false;
};
if (!tryParseVector(data["location"], result.m_Location))
return false;
if (data.contains("group_size") && !tryParseVector(data["group_size"], result.m_GroupSize))
return false;
return true;
}
//------------------------------------------------------------------------------
//
// Settings
//
//------------------------------------------------------------------------------
ed::NodeSettings* ed::Settings::AddNode(NodeId id)
{
m_Nodes.push_back(NodeSettings(id));
return &m_Nodes.back();
}
ed::NodeSettings* ed::Settings::FindNode(NodeId id)
{
for (auto& settings : m_Nodes)
if (settings.m_ID == id)
return &settings;
return nullptr;
}
void ed::Settings::RemoveNode(NodeId id)
{
auto node = FindNode(id);
if (!node)
return;
*node = NodeSettings(id);
}
ed::LinkSettings* ed::Settings::AddLink(LinkId id)
{
m_Links.push_back(LinkSettings(id));
return &m_Links.back();
}
ed::LinkSettings* ed::Settings::FindLink(LinkId id)
{
for (auto& settings : m_Links)
if (settings.m_ID == id)
return &settings;
return nullptr;
}
void ed::Settings::RemoveLink(LinkId id)
{
auto link = FindLink(id);
if (!link)
return;
*link = LinkSettings(id);
}
void ed::Settings::ClearDirty(Node* node)
{
if (node)
{
auto settings = FindNode(node->m_ID);
IM_ASSERT(settings);
settings->ClearDirty();
}
else
{
m_IsDirty = false;
m_DirtyReason = SaveReasonFlags::None;
for (auto& knownNode : m_Nodes)
knownNode.ClearDirty();
}
}
void ed::Settings::MakeDirty(SaveReasonFlags reason, Node* node)
{
m_IsDirty = true;
m_DirtyReason = m_DirtyReason | reason;
if (node)
{
auto settings = FindNode(node->m_ID);
IM_ASSERT(settings);
settings->MakeDirty(reason);
}
}
std::string ed::Settings::Serialize()
{
json::value result;
auto serializeObjectId = [](ObjectId id)
{
auto value = std::to_string(reinterpret_cast<uintptr_t>(id.AsPointer()));
switch (id.Type())
{
default:
case NodeEditor::Detail::ObjectType::None: return value;
case NodeEditor::Detail::ObjectType::Node: return "node:" + value;
case NodeEditor::Detail::ObjectType::Link: return "link:" + value;
case NodeEditor::Detail::ObjectType::Pin: return "pin:" + value;
}
};
auto& nodes = result["nodes"];
for (auto& node : m_Nodes)
{
if (node.m_WasUsed)
nodes[serializeObjectId(node.m_ID)] = node.Serialize();
}
auto& links = result["links"];
for (auto& link : m_Links)
{
// Save all non-Auto modes (Straight, Guided)
if (link.m_Mode != LinkMode::Auto)
links[serializeObjectId(link.m_ID)] = link.Serialize();
}
auto& selection = result["selection"];
for (auto& id : m_Selection)
selection.push_back(serializeObjectId(id));
auto& view = result["view"];
view["scroll"]["x"] = m_ViewScroll.x;
view["scroll"]["y"] = m_ViewScroll.y;
view["zoom"] = m_ViewZoom;
view["visible_rect"]["min"]["x"] = m_VisibleRect.Min.x;
view["visible_rect"]["min"]["y"] = m_VisibleRect.Min.y;
view["visible_rect"]["max"]["x"] = m_VisibleRect.Max.x;
view["visible_rect"]["max"]["y"] = m_VisibleRect.Max.y;
return result.dump(4); // Pretty print with 4 space indent
}
bool ed::Settings::Parse(const std::string& string, Settings& settings)
{
Settings result = settings;
auto settingsValue = json::value::parse(string);
if (settingsValue.is_discarded())
return false;
if (!settingsValue.is_object())
return false;
auto tryParseVector = [](const json::value& v, ImVec2& result) -> bool
{
if (v.is_object() && v.contains("x") && v.contains("y"))
{
auto xValue = v["x"];
auto yValue = v["y"];
if (xValue.is_number() && yValue.is_number())
{
result.x = static_cast<float>(xValue.get<double>());
result.y = static_cast<float>(yValue.get<double>());
return true;
}
}
return false;
};
auto deserializeObjectId = [](const std::string& str)
{
auto separator = str.find_first_of(':');
auto idStart = str.c_str() + ((separator != std::string::npos) ? separator + 1 : 0);
auto id = reinterpret_cast<void*>(strtoull(idStart, nullptr, 10));
if (str.compare(0, separator, "node") == 0)
return ObjectId(NodeId(id));
else if (str.compare(0, separator, "link") == 0)
return ObjectId(LinkId(id));
else if (str.compare(0, separator, "pin") == 0)
return ObjectId(PinId(id));
else
// fallback to old format
return ObjectId(NodeId(id)); //return ObjectId();
};
//auto& settingsObject = settingsValue.get<json::object>();
auto& nodesValue = settingsValue["nodes"];
if (nodesValue.is_object())
{
for (auto& node : nodesValue.get<json::object>())
{
auto id = deserializeObjectId(node.first.c_str()).AsNodeId();
auto nodeSettings = result.FindNode(id);
if (!nodeSettings)
nodeSettings = result.AddNode(id);
NodeSettings::Parse(node.second, *nodeSettings);
}
}
auto& linksValue = settingsValue["links"];
if (linksValue.is_object())
{
for (auto& link : linksValue.get<json::object>())
{
auto id = deserializeObjectId(link.first.c_str()).AsLinkId();
auto linkSettings = result.FindLink(id);
if (!linkSettings)
linkSettings = result.AddLink(id);
LinkSettings::Parse(link.second, *linkSettings);
}
}
auto& selectionValue = settingsValue["selection"];
if (selectionValue.is_array())
{
const auto selectionArray = selectionValue.get<json::array>();
result.m_Selection.reserve(selectionArray.size());
result.m_Selection.resize(0);
for (auto& selection : selectionArray)
{
if (selection.is_string())
result.m_Selection.push_back(deserializeObjectId(selection.get<json::string>()));
}
}
auto& viewValue = settingsValue["view"];
if (viewValue.is_object())
{
auto& viewScrollValue = viewValue["scroll"];
auto& viewZoomValue = viewValue["zoom"];
if (!tryParseVector(viewScrollValue, result.m_ViewScroll))
result.m_ViewScroll = ImVec2(0, 0);
result.m_ViewZoom = viewZoomValue.is_number() ? static_cast<float>(viewZoomValue.get<double>()) : 1.0f;
if (!viewValue.contains("visible_rect") || !tryParseVector(viewValue["visible_rect"]["min"], result.m_VisibleRect.Min) || !tryParseVector(viewValue["visible_rect"]["max"], result.m_VisibleRect.Max))
result.m_VisibleRect = {};
}
settings = std::move(result);
return true;
}