deargui-vpl/examples/blueprints-example/app.cpp

307 lines
9.6 KiB
C++

#define IMGUI_DEFINE_MATH_OPERATORS
#include "app.h"
#include "nodes.h"
#include "containers/root_container.h"
#include "Logging.h"
#include <imgui_node_editor.h>
#include <imgui_node_editor_internal.h>
namespace ed = ax::NodeEditor;
ed::EditorContext* m_Editor = nullptr;
void App::OnStart()
{
// Determine desired log level from CLI args
std::string logLevelArg = "debug";
auto logLevelIt = m_Args.find("log-level");
if (logLevelIt != m_Args.end() && logLevelIt->second.Type == ArgValue::Type::String && !logLevelIt->second.String.empty())
{
logLevelArg = logLevelIt->second.String;
}
bool logLevelValid = true;
auto parsedLogLevel = ParseLogLevel(logLevelArg, &logLevelValid);
// Initialize spdlog logger system
InitLogger("blueprints", true, true, "blueprints.log");
SetLoggerLevel(parsedLogLevel);
if (parsedLogLevel != spdlog::level::off)
{
spdlog::log(parsedLogLevel, "Logger level set to {}", spdlog::level::to_string_view(parsedLogLevel));
}
if (!logLevelValid)
{
LOG_WARN("Unknown log level '{}', defaulting to 'debug'", logLevelArg);
}
LOG_TRACE("[CHECKPOINT] OnStart: Beginning");
// Get graph filename from CLI args (--graph), default to BlueprintsGraph.json
// Supports both relative and absolute paths
auto it = m_Args.find("graph");
if (it != m_Args.end() && it->second.Type == ArgValue::Type::String)
{
m_GraphFilename = it->second.String;
LOG_INFO("Using graph file: {}", m_GraphFilename);
}
else
{
LOG_INFO("Using default graph file: {}", m_GraphFilename);
}
LOG_TRACE("[CHECKPOINT] OnStart: About to create root container");
// Create default root container from graph filename
AddRootContainer(m_GraphFilename);
LOG_TRACE("[CHECKPOINT] OnStart: Root container created, active={:p}",
static_cast<const void*>(GetActiveRootContainer()));
ed::Config config;
// Use custom settings callbacks instead of SettingsFile
// Graph data (nodes, links, positions, control points) → specified file or BlueprintsGraph.json (handled in SaveGraph/LoadGraph)
// View state only (scroll, zoom, selection) → Blueprints.json (handled via callbacks)
config.SettingsFile = "Blueprints.json";
config.UserPointer = this;
config.LoadSettings = [](char* data, void* userPointer) -> size_t
{
auto self = static_cast<App*>(userPointer);
return self->LoadViewSettings(data);
};
config.SaveSettings = [](const char* data, size_t size, ed::SaveReasonFlags reason, void* userPointer) -> bool
{
auto self = static_cast<App*>(userPointer);
return self->SaveViewSettings(data, size);
};
// Disable per-node settings - we handle everything in BlueprintsGraph.json
config.SaveNodeSettings = nullptr;
config.LoadNodeSettings = nullptr;
// Provide callbacks to access container nodes/links for BuildControl
config.GetContainerNodeIds = [](void* userPointer, ed::NodeId* nodeIdsOut, int maxNodes) -> int
{
auto self = static_cast<App*>(userPointer);
auto* container = self->GetActiveRootContainer();
if (!container)
return 0;
// Get all container nodes
auto nodes = container->GetNodes(self);
if (nodeIdsOut == nullptr)
return static_cast<int>(nodes.size()); // Return count only
// Fill node IDs array
int count = 0;
for (auto* node : nodes)
{
if (node && count < maxNodes)
{
nodeIdsOut[count] = node->ID;
count++;
}
}
return count;
};
config.GetContainerLinkIds = [](void* userPointer, ed::LinkId* linkIdsOut, int maxLinks) -> int
{
auto self = static_cast<App*>(userPointer);
auto* container = self->GetActiveRootContainer();
if (!container)
return 0;
// Get all container links
auto links = container->GetLinks(self);
if (linkIdsOut == nullptr)
return static_cast<int>(links.size()); // Return count only
// Fill link IDs array
int count = 0;
for (auto* link : links)
{
if (link && count < maxLinks)
{
linkIdsOut[count] = link->ID;
count++;
}
}
return count;
};
// Provide callback to mark links as user-manipulated when user edits waypoints
config.MarkLinkUserManipulated = [](ed::LinkId linkId, void* userPointer) -> void
{
auto self = static_cast<App*>(userPointer);
self->MarkLinkUserManipulated(linkId);
};
// Provide callback when block display mode changes (triggers link auto-adjustment)
config.OnBlockDisplayModeChanged = [](ed::NodeId nodeId, void* userPointer) -> void
{
auto self = static_cast<App*>(userPointer);
// When display mode changes, the node size changes immediately
// Call AutoAdjustLinkWaypoints immediately - it will detect the size change
// by comparing current size with m_LastNodeSizes (which has the old size from last frame)
self->AutoAdjustLinkWaypoints();
// Update the last known size for this node after adjustment
self->UpdateLastNodeSize(nodeId);
LOG_TRACE("[CHECKPOINT] OnBlockDisplayModeChanged: AutoAdjustLinkWaypoints called");
};
LOG_TRACE("[CHECKPOINT] OnStart: About to create editor");
m_Editor = ed::CreateEditor(&config);
ed::SetCurrentEditor(m_Editor);
// Load custom node styles from JSON
if (m_StyleManager.LoadFromFile("styles.json"))
{
LOG_TRACE("[CHECKPOINT] OnStart: Custom styles loaded from styles.json");
}
else
{
}
m_StyleManager.ApplyToEditorStyle(m_Editor);
// Register runtime callback for block execution
ed::Detail::EditorContext::RegisterRuntimeCallback(this, [](void* app) {
static_cast<App*>(app)->ExecuteRuntimeStep();
});
LOG_TRACE("[CHECKPOINT] OnStart: Runtime callback registered");
// Set global style - no arrows on any pins
auto& style = ed::GetStyle();
style.PinArrowSize = 0.0f;
style.PinArrowWidth = 0.0f;
LOG_TRACE("[CHECKPOINT] OnStart: About to load graph");
// Load saved graph (nodes and links from BlueprintsGraph.json)
auto* activeContainer = GetActiveRootContainer();
if (activeContainer)
{
LoadGraph(m_GraphFilename, activeContainer);
}
LOG_TRACE("[CHECKPOINT] OnStart: Graph loaded, about to build nodes");
BuildNodes();
LOG_TRACE("[CHECKPOINT] OnStart: Nodes built, about to load textures");
m_HeaderBackground = LoadTexture("data/BlueprintBackground.png");
m_SaveIcon = LoadTexture("data/ic_save_white_24dp.png");
m_RestoreIcon = LoadTexture("data/ic_restore_white_24dp.png");
LOG_TRACE("[CHECKPOINT] OnStart: Textures loaded, about to navigate to content");
// Only navigate to content if we don't have saved view settings
// (m_NeedsInitialZoom is set to false in LoadViewSettings if Blueprints.json exists)
if (m_NeedsInitialZoom)
{
LOG_TRACE("[CHECKPOINT] OnStart: No saved view state, navigating to content");
ed::NavigateToContent(0.0f);
}
else
{
LOG_TRACE("[CHECKPOINT] OnStart: Saved view state will be restored, skipping initial zoom");
}
LOG_TRACE("[CHECKPOINT] OnStart: Complete");
}
void App::OnStop()
{
// Save graph before shutdown
auto* activeContainer = GetActiveRootContainer();
if (activeContainer)
{
SaveGraph(m_GraphFilename, activeContainer);
}
auto releaseTexture = [this](ImTextureID& id)
{
if (id)
{
DestroyTexture(id);
id = nullptr;
}
};
releaseTexture(m_RestoreIcon);
releaseTexture(m_SaveIcon);
releaseTexture(m_HeaderBackground);
if (m_Editor)
{
ed::DestroyEditor(m_Editor);
m_Editor = nullptr;
}
// Shutdown spdlog logger
ShutdownLogger();
}
// UUID Generation Methods (32-bit)
uint32_t App::GenerateRandomUuid()
{
return m_UuidGenerator.GenerateRandom();
}
uint32_t App::GenerateSequentialUuid()
{
return m_UuidGenerator.GenerateSequential();
}
std::string App::UuidToHexString(uint32_t uuid)
{
return UuidGenerator::ToHexString(uuid);
}
uint32_t App::HexStringToUuid(const std::string& hexString)
{
return UuidGenerator::FromHexString(hexString);
}
// UUID Generation Methods (64-bit using dual 32-bit words)
Uuid64 App::GenerateRandomUuid64()
{
return m_UuidGenerator.GenerateRandom64();
}
Uuid64 App::GenerateSequentialUuid64()
{
return m_UuidGenerator.GenerateSequential64();
}
std::string App::UuidToHexString64(const Uuid64& uuid)
{
return UuidGenerator::ToHexString64(uuid);
}
Uuid64 App::HexStringToUuid64(const std::string& hexString)
{
return UuidGenerator::FromHexString64(hexString);
}
// Standard UUID Format Conversion Methods
std::string App::UuidToStandardString(const Uuid64& uuid)
{
return uuid.ToStandardUuidString();
}
Uuid64 App::StandardStringToUuid(const std::string& uuidStr, bool takeLast64)
{
return Uuid64::FromStandardUuidString(uuidStr, takeLast64);
}