diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 20604a93b..a74d8a9ba 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -23,8 +23,14 @@ set(CMAKE_CXX_STANDARD_REQUIRED YES) macro(add_example_executable name) project(${name}) + set(options NOLINK) + set(oneValueArgs) + set(multiValueArgs SOURCES) + cmake_parse_arguments(ADD_EXAMPLE "${options}" "${oneValueArgs}" + "${multiValueArgs}" ${ARGN}) + set(_Example_Sources - ${ARGN} + ${ADD_EXAMPLE_SOURCES} ) #source_group("" FILES ${_Example_Sources}) @@ -69,7 +75,10 @@ macro(add_example_executable name) find_package(imgui REQUIRED) find_package(imgui_node_editor REQUIRED) - target_link_libraries(${name} PRIVATE imgui imgui_node_editor application) + target_link_libraries(${name} PRIVATE imgui application) + if (NOT ADD_EXAMPLE_NOLINK) + target_link_libraries(${name} PRIVATE imgui_node_editor) + endif() set(_ExampleBinDir ${CMAKE_BINARY_DIR}/bin) @@ -132,3 +141,4 @@ add_subdirectory(simple-example) add_subdirectory(widgets-example) add_subdirectory(basic-interaction-example) add_subdirectory(blueprints-example) +add_subdirectory(idtypes-example) diff --git a/examples/application/source/renderer_ogl3.cpp b/examples/application/source/renderer_ogl3.cpp index 72a7dfc32..9aba29d98 100644 --- a/examples/application/source/renderer_ogl3.cpp +++ b/examples/application/source/renderer_ogl3.cpp @@ -4,6 +4,7 @@ # include "platform.h" # include <algorithm> +# include <cstdint> # if PLATFORM(WINDOWS) # define NOMINMAX diff --git a/examples/basic-interaction-example/CMakeLists.txt b/examples/basic-interaction-example/CMakeLists.txt index 4c79ebaac..2f70a3f33 100644 --- a/examples/basic-interaction-example/CMakeLists.txt +++ b/examples/basic-interaction-example/CMakeLists.txt @@ -1,3 +1,3 @@ add_example_executable(basic-interaction-example - basic-interaction-example.cpp -) \ No newline at end of file + SOURCES basic-interaction-example.cpp +) diff --git a/examples/blueprints-example/CMakeLists.txt b/examples/blueprints-example/CMakeLists.txt index 37ca1ec12..42a9c1552 100644 --- a/examples/blueprints-example/CMakeLists.txt +++ b/examples/blueprints-example/CMakeLists.txt @@ -1,4 +1,4 @@ -add_example_executable(blueprints-example +add_example_executable(blueprints-example SOURCES blueprints-example.cpp utilities/builders.h utilities/drawing.h diff --git a/examples/canvas-example/CMakeLists.txt b/examples/canvas-example/CMakeLists.txt index 11f1c89a3..df31b6255 100644 --- a/examples/canvas-example/CMakeLists.txt +++ b/examples/canvas-example/CMakeLists.txt @@ -1,5 +1,5 @@ -add_example_executable(canvas-example +add_example_executable(canvas-example SOURCES canvas-example.cpp ) -#target_link_libraries(Canvas PRIVATE imgui_canvas) \ No newline at end of file +#target_link_libraries(Canvas PRIVATE imgui_canvas) diff --git a/examples/idtypes-example/CMakeLists.txt b/examples/idtypes-example/CMakeLists.txt new file mode 100644 index 000000000..502108239 --- /dev/null +++ b/examples/idtypes-example/CMakeLists.txt @@ -0,0 +1,19 @@ +add_example_executable(idtypes-example NOLINK + SOURCES idtypes-example.cpp +) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED YES) + +add_library(imgui_node_editor_customids STATIC + ${IMGUI_NODE_EDITOR_SOURCES} +) + +target_include_directories(imgui_node_editor_customids PUBLIC + ${IMGUI_NODE_EDITOR_ROOT_DIR} +) + +target_link_libraries(imgui_node_editor_customids PUBLIC imgui) + +target_compile_definitions(imgui_node_editor_customids PUBLIC IMGUI_NODE_EDITOR_USER_CONFIG="${CMAKE_CURRENT_SOURCE_DIR}/edconfig.h") +target_link_libraries(idtypes-example PRIVATE imgui_node_editor_customids) diff --git a/examples/idtypes-example/edconfig.h b/examples/idtypes-example/edconfig.h new file mode 100644 index 000000000..66048622b --- /dev/null +++ b/examples/idtypes-example/edconfig.h @@ -0,0 +1,67 @@ +#ifndef EDCONFIG_H +#define EDCONFIG_H + +#include <string> + +struct CustomNodeId +{ + std::string name; + + CustomNodeId(const CustomNodeId &id) = default; + CustomNodeId &operator=(const CustomNodeId &id) = default; + + bool operator<(const CustomNodeId &id) const; + bool operator==(const CustomNodeId &id) const; + + std::string AsString() const; + static CustomNodeId FromString(const char *str, const char *end); + bool IsValid() const; + + static const CustomNodeId Invalid; +}; + +struct CustomPinId +{ + enum class Direction { + In, + Out + }; + std::string nodeName; + Direction direction; + int pinIndex; + + CustomPinId(const CustomPinId &id) = default; + CustomPinId &operator=(const CustomPinId &id) = default; + + bool operator<(const CustomPinId &id) const; + bool operator==(const CustomPinId &id) const; + + std::string AsString() const; + static CustomPinId FromString(const char *str, const char *end); + bool IsValid() const; + + static const CustomPinId Invalid; +}; + +struct CustomLinkId +{ + CustomPinId in, out; + + CustomLinkId(const CustomLinkId &id) = default; + CustomLinkId &operator=(const CustomLinkId &id) = default; + + bool operator<(const CustomLinkId &id) const; + bool operator==(const CustomLinkId &id) const; + + std::string AsString() const; + static CustomLinkId FromString(const char *str, const char *end); + bool IsValid() const; + + static const CustomLinkId Invalid; +}; + +#define IMGUI_NODE_EDITOR_CUSTOM_NODEID CustomNodeId +#define IMGUI_NODE_EDITOR_CUSTOM_PINID CustomPinId +#define IMGUI_NODE_EDITOR_CUSTOM_LINKID CustomLinkId + +#endif diff --git a/examples/idtypes-example/idtypes-example.cpp b/examples/idtypes-example/idtypes-example.cpp new file mode 100644 index 000000000..2fb4c5956 --- /dev/null +++ b/examples/idtypes-example/idtypes-example.cpp @@ -0,0 +1,212 @@ +# include <imgui.h> +# include <imgui_node_editor.h> +# include <application.h> +# include <vector> +# include <unordered_map> + +#include "edconfig.h" + +namespace ed = ax::NodeEditor; + +struct Pin +{ +}; + +struct Node +{ + std::vector<Pin> InPins; + std::vector<Pin> OutPins; +}; + +bool CustomNodeId::operator<(const CustomNodeId &id) const +{ + return name < id.name; +} + +bool CustomNodeId::operator==(const CustomNodeId &id) const +{ + return name == id.name; +} + +std::string CustomNodeId::AsString() const +{ + return name; +} + +CustomNodeId CustomNodeId::FromString(const char *str, const char *end) +{ + return CustomNodeId{ std::string(str, end) }; +} + +bool CustomNodeId::IsValid() const +{ + return !name.empty(); +} + +const CustomNodeId CustomNodeId::Invalid = {}; + +bool CustomPinId::operator<(const CustomPinId &id) const +{ + if (nodeName < id.nodeName) return true; + else if (nodeName > id.nodeName) return false; + + if (int(direction) < int(id.direction)) return true; + else if (int(direction) > int(id.direction)) return false; + + return pinIndex < id.pinIndex; +} + +bool CustomPinId::operator==(const CustomPinId &id) const +{ + return nodeName == id.nodeName && direction == id.direction && pinIndex == id.pinIndex; +} + +std::string CustomPinId::AsString() const +{ + return nodeName + ";" + std::to_string(int(direction)) + ";" + std::to_string(pinIndex); +} + +CustomPinId CustomPinId::FromString(const char *str, const char *end) +{ + std::string string(str, end); + auto sep1 = string.find(';'); + + if (sep1 == 0 || sep1 == std::string::npos) { + return Invalid; + } + + auto sep2 = string.find(';', sep1 + 1); + if (sep2 == sep1 + 1 || sep2 == std::string::npos) { + return Invalid; + } + + char *e; + auto nodeName = std::string(string.data(), sep1); + int dir = std::strtol(string.data() + sep1 + 1, &e, 10); + if (dir > 1) { + return Invalid; + } + + int pin = std::strtol(string.data() + sep2 + 1, &e, 10); + return CustomPinId{ nodeName, CustomPinId::Direction(dir), pin }; +} + +bool CustomPinId::IsValid() const +{ + return !nodeName.empty(); +} + +const CustomPinId CustomPinId::Invalid = {}; + + +bool CustomLinkId::operator<(const CustomLinkId &id) const +{ + if (in < id.in) return true; + if (in == id.in) return out < id.out; + return false; +} + +bool CustomLinkId::operator==(const CustomLinkId &id) const +{ + return in == id.in && out == id.out; +} + +std::string CustomLinkId::AsString() const +{ + return in.AsString() + "->" + out.AsString(); +} + +CustomLinkId CustomLinkId::FromString(const char *str, const char *end) +{ + std::string string(str, end); + auto sep = string.find("->"); + if (sep == 0 || sep == std::string::npos) { + return Invalid; + } + + auto in = CustomPinId::FromString(str, str + sep); + auto out = CustomPinId::FromString(str + sep + 2, end); + return { in, out }; +} + +bool CustomLinkId::IsValid() const +{ + return in.IsValid() && out.IsValid(); +} + +const CustomLinkId CustomLinkId::Invalid = {}; + +struct Example: + public Application +{ + using Application::Application; + + void OnStart() override + { + ed::Config config; + config.SettingsFile = "IdTypes.json"; + m_Context = ed::CreateEditor(&config); + + m_Nodes.insert({ "foo", Node{ { {}, {} }, { {} } } }); + m_Nodes.insert({ "bar", Node{ { {} }, { {}, {}, {} } } }); + } + + void OnStop() override + { + ed::DestroyEditor(m_Context); + } + + void OnFrame(float deltaTime) override + { + auto& io = ImGui::GetIO(); + + ImGui::Text("FPS: %.2f (%.2gms)", io.Framerate, io.Framerate ? 1000.0f / io.Framerate : 0.0f); + + ImGui::Separator(); + + ed::SetCurrentEditor(m_Context); + ed::Begin("My Editor", ImVec2(0.0, 0.0f)); + // Start drawing nodes. + for (auto &n: m_Nodes) { + ed::BeginNode(CustomNodeId{ n.first }); + ImGui::TextUnformatted(n.first.c_str()); + + ImGui::BeginGroup(); + for (int i = 0; i < n.second.InPins.size(); ++i) { + ed::BeginPin(CustomPinId{ n.first, CustomPinId::Direction::In, i }, ed::PinKind::Input); + ImGui::Text("-> In"); + ed::EndPin(); + } + ImGui::EndGroup(); + + ImGui::SameLine(); + + ImGui::BeginGroup(); + for (int i = 0; i < n.second.OutPins.size(); ++i) { + ed::BeginPin(CustomPinId{ n.first, CustomPinId::Direction::Out, i }, ed::PinKind::Output); + ImGui::Text("Out ->"); + ed::EndPin(); + } + ImGui::EndGroup(); + + ed::EndNode(); + } + ed::End(); + ed::SetCurrentEditor(nullptr); + + //ImGui::ShowMetricsWindow(); + } + + ed::EditorContext* m_Context = nullptr; + std::unordered_map<std::string, Node> m_Nodes; +}; + +int Main(int argc, char** argv) +{ + Example exampe("IdTypes", argc, argv); + + if (exampe.Create()) + return exampe.Run(); + + return 0; +} diff --git a/examples/simple-example/CMakeLists.txt b/examples/simple-example/CMakeLists.txt index 152330b68..69a61972c 100644 --- a/examples/simple-example/CMakeLists.txt +++ b/examples/simple-example/CMakeLists.txt @@ -1,3 +1,3 @@ -add_example_executable(simple-example +add_example_executable(simple-example SOURCES simple-example.cpp -) \ No newline at end of file +) diff --git a/examples/widgets-example/CMakeLists.txt b/examples/widgets-example/CMakeLists.txt index 1f1080a4f..3cbe5f5bd 100644 --- a/examples/widgets-example/CMakeLists.txt +++ b/examples/widgets-example/CMakeLists.txt @@ -1,3 +1,3 @@ -add_example_executable(widgets-example +add_example_executable(widgets-example SOURCES widgets-example.cpp -) \ No newline at end of file +) diff --git a/imgui_node_editor.cpp b/imgui_node_editor.cpp index 3e1539286..7be349d43 100644 --- a/imgui_node_editor.cpp +++ b/imgui_node_editor.cpp @@ -454,6 +454,79 @@ static void ImDrawList_PolyFillScanFlood(ImDrawList *draw, std::vector<ImVec2>* } */ +namespace ax { +namespace NodeEditor { +namespace Detail { + +std::string Serialize(NodeId n) +{ +#ifdef IMGUI_NODE_EDITOR_CUSTOM_NODEID + return n.AsString(); +#else + return std::to_string(n.Get()); +#endif +} + +std::string Serialize(PinId n) +{ +#ifdef IMGUI_NODE_EDITOR_CUSTOM_PINID + return n.AsString(); +#else + return std::to_string(n.Get()); +#endif +} + +std::string Serialize(LinkId n) +{ +#ifdef IMGUI_NODE_EDITOR_CUSTOM_LINKID + return n.AsString(); +#else + return std::to_string(n.Get()); +#endif +} + +std::string ObjectId::AsString() const +{ + switch (m_Type) { + case ObjectType::Pin: return Serialize(AsPinId()); + case ObjectType::Node: return Serialize(AsNodeId()); + case ObjectType::Link: return Serialize(AsLinkId()); + case ObjectType::None: break; + } + return {}; +} + +NodeId DeserializeNodeId(const char *str, const char *end) +{ +#ifdef IMGUI_NODE_EDITOR_CUSTOM_NODEID + return NodeId::FromString(str, end); +#else + return NodeId(reinterpret_cast<void*>(strtoull(str, nullptr, 10))); +#endif +} + +PinId DeserializePinId(const char *str, const char *end) +{ +#ifdef IMGUI_NODE_EDITOR_CUSTOM_PINID + return PinId::FromString(str, end); +#else + return PinId(reinterpret_cast<void*>(strtoull(str, nullptr, 10))); +#endif +} + +LinkId DeserializeLinkId(const char *str, const char *end) +{ +#ifdef IMGUI_NODE_EDITOR_CUSTOM_LINKID + return LinkId::FromString(str, end); +#else + return LinkId(reinterpret_cast<void*>(strtoull(str, nullptr, 10))); +#endif +} + +} +} +} + static void ImDrawList_AddBezierWithArrows(ImDrawList* drawList, const ImCubicBezierPoints& curve, float thickness, float startArrowSize, float startArrowWidth, float endArrowSize, float endArrowWidth, bool fill, ImU32 color, float strokeThickness, const ImVec2* startDirHint = nullptr, const ImVec2* endDirHint = nullptr) @@ -1082,12 +1155,12 @@ ed::EditorContext::EditorContext(const ax::NodeEditor::Config* config) , m_DeleteItemsAction(this) , m_AnimationControllers{ &m_FlowAnimationController } , m_FlowAnimationController(this) - , m_HoveredNode(0) - , m_HoveredPin(0) - , m_HoveredLink(0) - , m_DoubleClickedNode(0) - , m_DoubleClickedPin(0) - , m_DoubleClickedLink(0) + , m_HoveredNode(NodeId::Invalid) + , m_HoveredPin(PinId::Invalid) + , m_HoveredLink(LinkId::Invalid) + , m_DoubleClickedNode(NodeId::Invalid) + , m_DoubleClickedPin(PinId::Invalid) + , m_DoubleClickedLink(LinkId::Invalid) , m_BackgroundClickButtonIndex(-1) , m_BackgroundDoubleClickButtonIndex(-1) , m_IsInitialized(false) @@ -1242,12 +1315,12 @@ void ed::EditorContext::End() auto control = BuildControl(m_CurrentAction && m_CurrentAction->IsDragging()); // NavigateAction.IsMovingOverEdge() //auto& editorStyle = GetStyle(); - m_HoveredNode = control.HotNode && m_CurrentAction == nullptr ? control.HotNode->m_ID : 0; - m_HoveredPin = control.HotPin && m_CurrentAction == nullptr ? control.HotPin->m_ID : 0; - m_HoveredLink = control.HotLink && m_CurrentAction == nullptr ? control.HotLink->m_ID : 0; - m_DoubleClickedNode = control.DoubleClickedNode ? control.DoubleClickedNode->m_ID : 0; - m_DoubleClickedPin = control.DoubleClickedPin ? control.DoubleClickedPin->m_ID : 0; - m_DoubleClickedLink = control.DoubleClickedLink ? control.DoubleClickedLink->m_ID : 0; + m_HoveredNode = control.HotNode && m_CurrentAction == nullptr ? control.HotNode->m_ID : NodeId::Invalid; + m_HoveredPin = control.HotPin && m_CurrentAction == nullptr ? control.HotPin->m_ID : PinId::Invalid; + m_HoveredLink = control.HotLink && m_CurrentAction == nullptr ? control.HotLink->m_ID : LinkId::Invalid; + m_DoubleClickedNode = control.DoubleClickedNode ? control.DoubleClickedNode->m_ID : NodeId::Invalid; + m_DoubleClickedPin = control.DoubleClickedPin ? control.DoubleClickedPin->m_ID : PinId::Invalid; + m_DoubleClickedLink = control.DoubleClickedLink ? control.DoubleClickedLink->m_ID : LinkId::Invalid; m_BackgroundClickButtonIndex = control.BackgroundClickButtonIndex; m_BackgroundDoubleClickButtonIndex = control.BackgroundDoubleClickButtonIndex; @@ -1581,7 +1654,7 @@ void ed::EditorContext::End() if (!m_CurrentAction && m_IsFirstFrame && !m_Settings.m_Selection.empty()) { ClearSelection(); - for (auto id : m_Settings.m_Selection) + for (auto &id : m_Settings.m_Selection) if (auto object = FindObject(id)) SelectObject(object); } @@ -2105,7 +2178,7 @@ ed::Link* ed::EditorContext::FindLink(LinkId id) return FindItemIn(m_Links, id); } -ed::Object* ed::EditorContext::FindObject(ObjectId id) +ed::Object* ed::EditorContext::FindObject(const ObjectId &id) { if (id.IsNodeId()) return FindNode(id.AsNodeId()); @@ -2367,16 +2440,14 @@ ed::Control ed::EditorContext::BuildControl(bool allowOffscreen) }; // Emits invisible button and returns true if it is clicked. - auto emitInteractiveAreaEx = [&activeId](ObjectId id, const ImRect& rect, ImGuiButtonFlags extraFlags) -> int + auto emitInteractiveAreaEx = [&activeId](const std::string &idString, const ImRect& rect, ImGuiButtonFlags extraFlags) -> int { - char idString[33] = { 0 }; // itoa can output 33 bytes maximum - snprintf(idString, 32, "%p", id.AsPointer()); ImGui::SetCursorScreenPos(rect.Min); // debug //if (id < 0) return ImGui::Button(idString, to_imvec(rect.size)); - auto buttonIndex = invisibleButtonEx(idString, rect.GetSize(), extraFlags); + auto buttonIndex = invisibleButtonEx(idString.c_str(), rect.GetSize(), extraFlags); // #debug //ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(0, 255, 0, 64)); @@ -2387,15 +2458,15 @@ ed::Control ed::EditorContext::BuildControl(bool allowOffscreen) return buttonIndex; }; - auto emitInteractiveArea = [&emitInteractiveAreaEx, extraFlags](ObjectId id, const ImRect& rect) + auto emitInteractiveArea = [&emitInteractiveAreaEx, extraFlags](const std::string &idString, const ImRect& rect) { - return emitInteractiveAreaEx(id, rect, extraFlags); + return emitInteractiveAreaEx(idString, rect, extraFlags); }; // Check input interactions over area. - auto checkInteractionsInArea = [this, &emitInteractiveArea, &hotObject, &activeObject, &clickedObject, &doubleClickedObject](ObjectId id, const ImRect& rect, Object* object) + auto checkInteractionsInArea = [this, &emitInteractiveArea, &hotObject, &activeObject, &clickedObject, &doubleClickedObject](const std::string &idString, const ImRect& rect, Object* object) { - if (emitInteractiveArea(id, rect) >= 0) + if (emitInteractiveArea(idString, rect) >= 0) clickedObject = object; if (!doubleClickedObject && ImGui::IsMouseDoubleClicked(m_Config.DragButtonIndex) && ImGui::IsItemHovered()) doubleClickedObject = object; @@ -2421,14 +2492,14 @@ ed::Control ed::EditorContext::BuildControl(bool allowOffscreen) { if (!pin->m_IsLive) continue; - checkInteractionsInArea(pin->m_ID, pin->m_Bounds, pin); + checkInteractionsInArea(Serialize(pin->m_ID), pin->m_Bounds, pin); } // Check for interactions with node. if (node->m_Type == NodeType::Group) { // Node with a hole - ImGui::PushID(node->m_ID.AsPointer()); + ImGui::PushID(Serialize(node->m_ID).c_str()); static const NodeRegion c_Regions[] = { @@ -2448,13 +2519,13 @@ ed::Control ed::EditorContext::BuildControl(bool allowOffscreen) auto bounds = node->GetRegionBounds(region); if (ImRect_IsEmpty(bounds)) continue; - checkInteractionsInArea(NodeId(static_cast<int>(region)), bounds, node); + checkInteractionsInArea(std::to_string(static_cast<int>(region)), bounds, node); } ImGui::PopID(); } else - checkInteractionsInArea(node->m_ID, node->m_Bounds, node); + checkInteractionsInArea(Serialize(node->m_ID), node->m_Bounds, node); } // Links are not regular widgets and must be done manually since @@ -2494,7 +2565,7 @@ ed::Control ed::EditorContext::BuildControl(bool allowOffscreen) }; // Check for interaction with background. - auto backgroundClickButonIndex = emitInteractiveAreaEx(NodeId(0), editorRect, backgroundExtraFlags); + auto backgroundClickButonIndex = emitInteractiveAreaEx("background", editorRect, backgroundExtraFlags); auto backgroundDoubleClickButtonIndex = isMouseDoubleClickOverBackground(); auto isBackgroundActive = ImGui::IsItemActive(); auto isBackgroundHot = !hotObject; @@ -2610,13 +2681,13 @@ void ed::EditorContext::ShowMetrics(const Control& control) ImGui::Text("Live Nodes: %d", liveNodeCount); ImGui::Text("Live Pins: %d", livePinCount); ImGui::Text("Live Links: %d", liveLinkCount); - ImGui::Text("Hot Object: %s (%p)", getHotObjectName(), control.HotObject ? control.HotObject->ID().AsPointer() : nullptr); + ImGui::Text("Hot Object: %s (%s)", getHotObjectName(), control.HotObject ? control.HotObject->ID().AsString().c_str() : nullptr); if (auto node = control.HotObject ? control.HotObject->AsNode() : nullptr) { ImGui::SameLine(); ImGui::Text("{ x=%g y=%g w=%g h=%g }", node->m_Bounds.Min.x, node->m_Bounds.Min.y, node->m_Bounds.GetWidth(), node->m_Bounds.GetHeight()); } - ImGui::Text("Active Object: %s (%p)", getActiveObjectName(), control.ActiveObject ? control.ActiveObject->ID().AsPointer() : nullptr); + ImGui::Text("Active Object: %s (%s)", getActiveObjectName(), control.ActiveObject ? control.ActiveObject->ID().AsString().c_str() : nullptr); if (auto node = control.ActiveObject ? control.ActiveObject->AsNode() : nullptr) { ImGui::SameLine(); @@ -2779,9 +2850,9 @@ std::string ed::Settings::Serialize() { json::value result; - auto serializeObjectId = [](ObjectId id) + auto serializeObjectId = [](const ObjectId &id) { - auto value = std::to_string(reinterpret_cast<uintptr_t>(id.AsPointer())); + auto value = id.AsString(); switch (id.Type()) { default: @@ -2849,16 +2920,16 @@ bool ed::Settings::Parse(const std::string& string, Settings& settings) { 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)); + auto *end = str.c_str() + str.size(); if (str.compare(0, separator, "node") == 0) - return ObjectId(NodeId(id)); + return ObjectId(DeserializeNodeId(idStart, end)); else if (str.compare(0, separator, "link") == 0) - return ObjectId(LinkId(id)); + return ObjectId(DeserializeLinkId(idStart, end)); else if (str.compare(0, separator, "pin") == 0) - return ObjectId(PinId(id)); + return ObjectId(DeserializePinId(idStart, end)); else // fallback to old format - return ObjectId(NodeId(id)); //return ObjectId(); + return ObjectId(DeserializeNodeId(idStart, end)); //return ObjectId(); }; //auto& settingsObject = settingsValue.get<json::object>(); @@ -3791,7 +3862,7 @@ void ed::SizeAction::ShowMetrics() ImGui::Text("%s:", GetName()); ImGui::Text(" Active: %s", m_IsActive ? "yes" : "no"); - ImGui::Text(" Node: %s (%p)", getObjectName(m_SizedNode), m_SizedNode ? m_SizedNode->m_ID.AsPointer() : nullptr); + ImGui::Text(" Node: %s (%s)", getObjectName(m_SizedNode), m_SizedNode ? Serialize(m_SizedNode->m_ID).c_str() : nullptr); if (m_SizedNode && m_IsActive) { ImGui::Text(" Bounds: { x=%g y=%g w=%g h=%g }", m_SizedNode->m_Bounds.Min.x, m_SizedNode->m_Bounds.Min.y, m_SizedNode->m_Bounds.GetWidth(), m_SizedNode->m_Bounds.GetHeight()); @@ -3997,7 +4068,7 @@ void ed::DragAction::ShowMetrics() ImGui::Text("%s:", GetName()); ImGui::Text(" Active: %s", m_IsActive ? "yes" : "no"); - ImGui::Text(" Node: %s (%p)", getObjectName(m_DraggedObject), m_DraggedObject ? m_DraggedObject->ID().AsPointer() : nullptr); + ImGui::Text(" Node: %s (%s)", getObjectName(m_DraggedObject), m_DraggedObject ? m_DraggedObject->ID().AsString().c_str() : nullptr); } @@ -4594,12 +4665,12 @@ bool ed::CreateItemAction::Process(const Control& control) { const auto draggingFromSource = (m_DraggedPin->m_Kind == PinKind::Output); - ed::Pin cursorPin(Editor, 0, draggingFromSource ? PinKind::Input : PinKind::Output); + ed::Pin cursorPin(Editor, PinId::Invalid, draggingFromSource ? PinKind::Input : PinKind::Output); cursorPin.m_Pivot = ImRect(ImGui::GetMousePos(), ImGui::GetMousePos()); cursorPin.m_Dir = -m_DraggedPin->m_Dir; cursorPin.m_Strength = m_DraggedPin->m_Strength; - ed::Link candidate(Editor, 0); + ed::Link candidate(Editor, LinkId::Invalid); candidate.m_Color = m_LinkColor; candidate.m_StartPin = draggingFromSource ? m_DraggedPin : &cursorPin; candidate.m_EndPin = draggingFromSource ? &cursorPin : m_DraggedPin; @@ -4842,7 +4913,7 @@ ed::CreateItemAction::Result ed::CreateItemAction::QueryNode(PinId* pinId) if (!m_InActive || m_CurrentStage == None || m_ItemType != Node) return Indeterminate; - *pinId = m_LinkStart ? m_LinkStart->m_ID : 0; + *pinId = m_LinkStart ? m_LinkStart->m_ID : PinId::Invalid; Editor->SetUserContext(true); @@ -5006,7 +5077,8 @@ bool ed::DeleteItemsAction::QueryLink(LinkId* linkId, PinId* startId, PinId* end if (!QueryItem(&objectId, Link)) return false; - if (auto id = objectId.AsLinkId()) + auto id = objectId.AsLinkId(); + if (id.IsValid()) *linkId = id; else return false; @@ -5029,7 +5101,8 @@ bool ed::DeleteItemsAction::QueryNode(NodeId* nodeId) if (!QueryItem(&objectId, Node)) return false; - if (auto id = objectId.AsNodeId()) + auto id = objectId.AsNodeId(); + if (id.IsValid()) *nodeId = id; else return false; diff --git a/imgui_node_editor.h b/imgui_node_editor.h index a173cde30..a895a66bd 100644 --- a/imgui_node_editor.h +++ b/imgui_node_editor.h @@ -24,6 +24,9 @@ # define IMGUI_NODE_EDITOR_VERSION "0.9.2" # define IMGUI_NODE_EDITOR_VERSION_NUM 000902 +#ifdef IMGUI_NODE_EDITOR_USER_CONFIG +#include IMGUI_NODE_EDITOR_USER_CONFIG +#endif //------------------------------------------------------------------------------ namespace ax { @@ -31,9 +34,23 @@ namespace NodeEditor { //------------------------------------------------------------------------------ +#ifdef IMGUI_NODE_EDITOR_CUSTOM_NODEID +using NodeId = IMGUI_NODE_EDITOR_CUSTOM_NODEID; +#else struct NodeId; +#endif + +#ifdef IMGUI_NODE_EDITOR_CUSTOM_LINKID +using LinkId = IMGUI_NODE_EDITOR_CUSTOM_LINKID; +#else struct LinkId; +#endif + +#ifdef IMGUI_NODE_EDITOR_CUSTOM_PINID +using PinId = IMGUI_NODE_EDITOR_CUSTOM_PINID; +#else struct PinId; +#endif //------------------------------------------------------------------------------ @@ -465,7 +482,11 @@ struct SafePointerType template <typename T = void> explicit SafePointerType(T* ptr): SafePointerType(reinterpret_cast<uintptr_t>(ptr)) {} template <typename T = void> T* AsPointer() const { return reinterpret_cast<T*>(this->Get()); } + inline bool IsValid() const { return *this != Invalid; } + explicit operator bool() const { return *this != Invalid; } + + inline bool operator<(Tag o) const { return AsPointer() < o.AsPointer(); } }; template <typename Tag> @@ -485,20 +506,26 @@ inline bool operator!=(const SafePointerType<Tag>& lhs, const SafePointerType<Ta } // namespace Details +#ifndef IMGUI_NODE_EDITOR_CUSTOM_NODEID struct NodeId final: Details::SafePointerType<NodeId> { using SafePointerType::SafePointerType; }; +#endif +#ifndef IMGUI_NODE_EDITOR_CUSTOM_LINKID struct LinkId final: Details::SafePointerType<LinkId> { using SafePointerType::SafePointerType; }; +#endif +#ifndef IMGUI_NODE_EDITOR_CUSTOM_PINID struct PinId final: Details::SafePointerType<PinId> { using SafePointerType::SafePointerType; }; +#endif //------------------------------------------------------------------------------ @@ -507,4 +534,4 @@ struct PinId final: Details::SafePointerType<PinId> //------------------------------------------------------------------------------ -# endif // __IMGUI_NODE_EDITOR_H__ \ No newline at end of file +# endif // __IMGUI_NODE_EDITOR_H__ diff --git a/imgui_node_editor_api.cpp b/imgui_node_editor_api.cpp index c8c7c3ff7..2208970f7 100644 --- a/imgui_node_editor_api.cpp +++ b/imgui_node_editor_api.cpp @@ -31,7 +31,7 @@ static int BuildIdList(C& container, I* list, int listSize, F&& accept) if (accept(object)) { - list[count] = I(object->ID().AsPointer()); + list[count] = I(object->ID()); ++count; --listSize;} } diff --git a/imgui_node_editor_internal.h b/imgui_node_editor_internal.h index 4c57725f0..ed7519c83 100644 --- a/imgui_node_editor_internal.h +++ b/imgui_node_editor_internal.h @@ -30,7 +30,7 @@ # include <vector> # include <string> - +# include <variant> //------------------------------------------------------------------------------ namespace ax { @@ -147,23 +147,55 @@ using ax::NodeEditor::NodeId; using ax::NodeEditor::PinId; using ax::NodeEditor::LinkId; -struct ObjectId final: Details::SafePointerType<ObjectId> +struct ObjectId final { - using Super = Details::SafePointerType<ObjectId>; - using Super::Super; - - ObjectId(): Super(Invalid), m_Type(ObjectType::None) {} - ObjectId(PinId pinId): Super(pinId.AsPointer()), m_Type(ObjectType::Pin) {} - ObjectId(NodeId nodeId): Super(nodeId.AsPointer()), m_Type(ObjectType::Node) {} - ObjectId(LinkId linkId): Super(linkId.AsPointer()), m_Type(ObjectType::Link) {} + ObjectId(): m_Type(ObjectType::None) {} + ObjectId(PinId pinId): m_Type(ObjectType::Pin) { new (&m_Id.Pin) PinId(std::move(pinId)); } + ObjectId(NodeId nodeId): m_Type(ObjectType::Node) { new (&m_Id.Node) NodeId(std::move(nodeId)); } + ObjectId(LinkId linkId): m_Type(ObjectType::Link) { new (&m_Id.Link) LinkId(std::move(linkId)); } + ObjectId(ObjectId &&o): m_Type(o.m_Type) { + switch (m_Type) { + case ObjectType::Pin: + new (&m_Id.Pin) PinId(std::move(o.m_Id.Pin)); + break; + case ObjectType::Node: + new (&m_Id.Node) NodeId(std::move(o.m_Id.Node)); + break; + case ObjectType::Link: + new (&m_Id.Link) LinkId(std::move(o.m_Id.Link)); + break; + case ObjectType::None: + break; + } + } + ObjectId(const ObjectId &o): m_Type(o.m_Type) { + switch (m_Type) { + case ObjectType::Pin: + new (&m_Id.Pin) PinId(o.m_Id.Pin); + break; + case ObjectType::Node: + new (&m_Id.Node) NodeId(o.m_Id.Node); + break; + case ObjectType::Link: + new (&m_Id.Link) LinkId(o.m_Id.Link); + break; + case ObjectType::None: + break; + } + } + ~ObjectId() { + clearId(); + } explicit operator PinId() const { return AsPinId(); } explicit operator NodeId() const { return AsNodeId(); } explicit operator LinkId() const { return AsLinkId(); } - PinId AsPinId() const { IM_ASSERT(IsPinId()); return PinId(AsPointer()); } - NodeId AsNodeId() const { IM_ASSERT(IsNodeId()); return NodeId(AsPointer()); } - LinkId AsLinkId() const { IM_ASSERT(IsLinkId()); return LinkId(AsPointer()); } + PinId AsPinId() const { IM_ASSERT(IsPinId()); return m_Id.Pin; } + NodeId AsNodeId() const { IM_ASSERT(IsNodeId()); return m_Id.Node; } + LinkId AsLinkId() const { IM_ASSERT(IsLinkId()); return m_Id.Link; } + + std::string AsString() const; bool IsPinId() const { return m_Type == ObjectType::Pin; } bool IsNodeId() const { return m_Type == ObjectType::Node; } @@ -171,8 +203,86 @@ struct ObjectId final: Details::SafePointerType<ObjectId> ObjectType Type() const { return m_Type; } + bool operator==(const ObjectId &o) const { + if (m_Type != o.m_Type) { + return false; + } + + switch (m_Type) { + case ObjectType::Pin: return m_Id.Pin == o.m_Id.Pin; + case ObjectType::Node: return m_Id.Node == o.m_Id.Node; + case ObjectType::Link: return m_Id.Link == o.m_Id.Link; + case ObjectType::None: break; + } + return true; + } + + ObjectId &operator=(ObjectId &&o) { + clearId(); + + m_Type = o.m_Type; + switch (m_Type) { + case ObjectType::Pin: + new (&m_Id.Pin) PinId(std::move(o.m_Id.Pin)); + break; + case ObjectType::Node: + new (&m_Id.Node) NodeId(std::move(o.m_Id.Node)); + break; + case ObjectType::Link: + new (&m_Id.Link) LinkId(std::move(o.m_Id.Link)); + break; + case ObjectType::None: + break; + } + return *this; + } + ObjectId &operator=(const ObjectId &o) { + clearId(); + + m_Type = o.m_Type; + switch (m_Type) { + case ObjectType::Pin: + new (&m_Id.Pin) PinId(o.m_Id.Pin); + break; + case ObjectType::Node: + new (&m_Id.Node) NodeId(o.m_Id.Node); + break; + case ObjectType::Link: + new (&m_Id.Link) LinkId(o.m_Id.Link); + break; + case ObjectType::None: + break; + } + return *this; + } + private: + void clearId() + { + switch (m_Type) { + case ObjectType::Pin: + m_Id.Pin.~PinId(); + break; + case ObjectType::Node: + m_Id.Node.~NodeId(); + break; + case ObjectType::Link: + m_Id.Link.~LinkId(); + break; + case ObjectType::None: + break; + } + m_Type = ObjectType::None; + } + ObjectType m_Type; + union Id { + Id() {} + ~Id() {} + NodeId Node; + PinId Pin; + LinkId Link; + } m_Id; }; struct EditorContext; @@ -195,7 +305,7 @@ struct ObjectWrapper bool operator<(const ObjectWrapper& rhs) const { - return m_ID.AsPointer() < rhs.m_ID.AsPointer(); + return m_ID < rhs.m_ID; } }; @@ -1381,7 +1491,7 @@ struct EditorContext Node* FindNode(NodeId id); Pin* FindPin(PinId id); Link* FindLink(LinkId id); - Object* FindObject(ObjectId id); + Object* FindObject(const ObjectId &id); Node* GetNode(NodeId id); Pin* GetPin(PinId id, PinKind kind); diff --git a/misc/cmake-modules/Findimgui_node_editor.cmake b/misc/cmake-modules/Findimgui_node_editor.cmake index a8c295f7c..b70c37bb7 100644 --- a/misc/cmake-modules/Findimgui_node_editor.cmake +++ b/misc/cmake-modules/Findimgui_node_editor.cmake @@ -36,6 +36,8 @@ target_include_directories(imgui_node_editor PUBLIC ${IMGUI_NODE_EDITOR_ROOT_DIR} ) +set(IMGUI_NODE_EDITOR_SOURCES ${_imgui_node_editor_Sources} PARENT_SCOPE) + target_link_libraries(imgui_node_editor PUBLIC imgui) source_group(TREE ${IMGUI_NODE_EDITOR_ROOT_DIR} FILES ${_imgui_node_editor_Sources})