From 67d1dcec4ef83a6dba2c62cb68f11f477885e148 Mon Sep 17 00:00:00 2001 From: Giulio Camuffo Date: Thu, 8 Jun 2023 10:30:41 +0200 Subject: [PATCH 1/2] Add missing include file --- examples/application/source/renderer_ogl3.cpp | 1 + 1 file changed, 1 insertion(+) 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 +# include # if PLATFORM(WINDOWS) # define NOMINMAX From 5880b799f90e195d0f42debac0af5f709ec8a141 Mon Sep 17 00:00:00 2001 From: Giulio Camuffo Date: Tue, 6 Jun 2023 16:14:12 +0200 Subject: [PATCH 2/2] Add a way to specify custom id types NodeId, LinkId and PinId are just glorified pointers. In some cases 32/64 bits of data may not be enough to identify an object, or the objects may lack a stable pointer/id value. This commit adds a way to specify custom data types for them: similarly to ImGui, a IMGUI_NODE_EDITOR_USER_CONFIG macro can be set before including imgui_node_editor.h. If defined, its value will be used as a filename to be included at the top of imgui_node_editor.h. In it, the macros IMGUI_NODE_EDITOR_CUSTOM_NODEID, IMGUI_NODE_EDITOR_CUSTOM_PINID and IMGUI_NODE_EDITOR_CUSTOM_LINKID may be defined as the names of the types to be used for them. If one or more of them are not defined the old types will be used, old code does not need to be changed. The types used must be copy contructible, assignable and must provide a few more functions, as in this example: struct CustomId { CustomId(const CustomId &id); CustomId &operator=(const CustomId &id);; bool operator<(const CustomId &id) const; bool operator==(const CustomId &id) const;; std::string AsString() const; static CustomId FromString(std::string_view str); bool IsValid() const; static const CustomId Invalid; }; A new example showcasing the functionality is added. --- examples/CMakeLists.txt | 14 +- .../basic-interaction-example/CMakeLists.txt | 4 +- examples/blueprints-example/CMakeLists.txt | 2 +- examples/canvas-example/CMakeLists.txt | 4 +- examples/idtypes-example/CMakeLists.txt | 19 ++ examples/idtypes-example/edconfig.h | 67 ++++++ examples/idtypes-example/idtypes-example.cpp | 212 ++++++++++++++++++ examples/simple-example/CMakeLists.txt | 4 +- examples/widgets-example/CMakeLists.txt | 4 +- imgui_node_editor.cpp | 159 +++++++++---- imgui_node_editor.h | 29 ++- imgui_node_editor_api.cpp | 2 +- imgui_node_editor_internal.h | 138 ++++++++++-- .../cmake-modules/Findimgui_node_editor.cmake | 2 + 14 files changed, 590 insertions(+), 70 deletions(-) create mode 100644 examples/idtypes-example/CMakeLists.txt create mode 100644 examples/idtypes-example/edconfig.h create mode 100644 examples/idtypes-example/idtypes-example.cpp 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/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 + +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 +# include +# include +# include +# include + +#include "edconfig.h" + +namespace ed = ax::NodeEditor; + +struct Pin +{ +}; + +struct Node +{ + std::vector InPins; + std::vector 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 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* } */ +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(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(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(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(region)), bounds, node); + checkInteractionsInArea(std::to_string(static_cast(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(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(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(); @@ -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 explicit SafePointerType(T* ptr): SafePointerType(reinterpret_cast(ptr)) {} template T* AsPointer() const { return reinterpret_cast(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 @@ -485,20 +506,26 @@ inline bool operator!=(const SafePointerType& lhs, const SafePointerType { using SafePointerType::SafePointerType; }; +#endif +#ifndef IMGUI_NODE_EDITOR_CUSTOM_LINKID struct LinkId final: Details::SafePointerType { using SafePointerType::SafePointerType; }; +#endif +#ifndef IMGUI_NODE_EDITOR_CUSTOM_PINID struct PinId final: Details::SafePointerType { using SafePointerType::SafePointerType; }; +#endif //------------------------------------------------------------------------------ @@ -507,4 +534,4 @@ struct PinId final: Details::SafePointerType //------------------------------------------------------------------------------ -# 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 # include - +# include //------------------------------------------------------------------------------ namespace ax { @@ -147,23 +147,55 @@ using ax::NodeEditor::NodeId; using ax::NodeEditor::PinId; using ax::NodeEditor::LinkId; -struct ObjectId final: Details::SafePointerType +struct ObjectId final { - using Super = Details::SafePointerType; - 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 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})