diff --git a/IMNODES_NAMESPACE.h b/IMNODES_NAMESPACE.h new file mode 100644 index 0000000..b16a571 --- /dev/null +++ b/IMNODES_NAMESPACE.h @@ -0,0 +1,5 @@ +#pragma once + +#ifndef IMNODES_NAMESPACE +#define IMNODES_NAMESPACE ImNodes +#endif \ No newline at end of file diff --git a/ImGuiStorage.h b/ImGuiStorage.h new file mode 100644 index 0000000..9fa6c1d --- /dev/null +++ b/ImGuiStorage.h @@ -0,0 +1,91 @@ +#pragma once + +#include "IMNODES_NAMESPACE.h" +#include "imnodes_config_or_default.h" + +namespace IMNODES_NAMESPACE +{ +namespace Internal +{ + +// Copy-pasted from ImGui because we needed to change the ID type + +// Helper: Key->Value storage +// Typically you don't have to worry about this since a storage is held within each Window. +// We use it to e.g. store collapse state for a tree (Int 0/1) +// This is optimized for efficient lookup (dichotomy into a contiguous buffer) and rare insertion +// (typically tied to user interactions aka max once a frame) You can use it as custom user storage +// for temporary values. Declare your own storage if, for example: +// - You want to manipulate the open/close state of a particular sub-tree in your interface (tree +// node uses Int 0/1 to store their state). +// - You want to store custom debug data easily without adding or editing structures in your code +// (probably not efficient, but convenient) Types are NOT stored, so it is up to you to make sure +// your Key don't collide with different types. +struct Storage +{ + // [Internal] + struct ImGuiStoragePair + { + ID key; + union + { + int val_i; + float val_f; + void* val_p; + }; + ImGuiStoragePair(ID _key, int _val_i) + { + key = _key; + val_i = _val_i; + } + ImGuiStoragePair(ID _key, float _val_f) + { + key = _key; + val_f = _val_f; + } + ImGuiStoragePair(ID _key, void* _val_p) + { + key = _key; + val_p = _val_p; + } + }; + + ImVector Data; + + // - Get***() functions find pair, never add/allocate. Pairs are sorted so a query is O(log N) + // - Set***() functions find pair, insertion on demand if missing. + // - Sorted insertion is costly, paid once. A typical frame shouldn't need to insert any new + // pair. + void Clear() { Data.clear(); } + IMGUI_API int GetInt(ID key, int default_val = 0) const; + IMGUI_API void SetInt(ID key, int val); + IMGUI_API bool GetBool(ID key, bool default_val = false) const; + IMGUI_API void SetBool(ID key, bool val); + IMGUI_API float GetFloat(ID key, float default_val = 0.0f) const; + IMGUI_API void SetFloat(ID key, float val); + IMGUI_API void* GetVoidPtr(ID key) const; // default_val is NULL + IMGUI_API void SetVoidPtr(ID key, void* val); + + // - Get***Ref() functions finds pair, insert on demand if missing, return pointer. Useful if + // you intend to do Get+Set. + // - References are only valid until a new value is added to the storage. Calling a Set***() + // function or a Get***Ref() function invalidates the pointer. + // - A typical use case where this is convenient for quick hacking (e.g. add storage during a + // live Edit&Continue session if you can't modify existing struct) + // float* pvar = ImGui::GetFloatRef(key); ImGui::SliderFloat("var", pvar, 0, 100.0f); + // some_var += *pvar; + IMGUI_API int* GetIntRef(ID key, int default_val = 0); + IMGUI_API bool* GetBoolRef(ID key, bool default_val = false); + IMGUI_API float* GetFloatRef(ID key, float default_val = 0.0f); + IMGUI_API void** GetVoidPtrRef(ID key, void* default_val = NULL); + + // Use on your own storage if you know only integer are being stored (open/close all tree nodes) + IMGUI_API void SetAllInt(int val); + + // For quicker full rebuild of a storage (instead of an incremental one), you may add all your + // contents and then sort once. + IMGUI_API void BuildSortByKey(); +}; + +} // namespace Internal +} // namespace IMNODES_NAMESPACE \ No newline at end of file diff --git a/README.md b/README.md index 1fa2a7d..4a99a3a 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ ImGui::End(); Now you should have a workspace with a grid visible in the window. An empty node can now be instantiated: ```cpp -const int hardcoded_node_id = 1; +const ImNodes::ID hardcoded_node_id = 1; ImNodes::BeginNodeEditor(); @@ -72,14 +72,14 @@ ImNodes::EndNode(); ImNodes::EndNodeEditor(); ``` -Nodes, like windows in `dear imgui` must be uniquely identified. But we can't use the node titles for identification, because it should be possible to have many nodes of the same name in the workspace. Instead, you just use integers for identification. +Nodes, like windows in `dear imgui` must be uniquely identified. But we can't use the node titles for identification, because it should be possible to have many nodes of the same name in the workspace. Instead, you just use integers for identification. (`ImNodes::ID` type is `int` by default but you can override that in "imnodes_config.h". See [ID type](#id-type)). Attributes are the UI content of the node. An attribute will have a pin (the little circle) on either side of the node. There are two types of attributes: input, and output attributes. Input attribute pins are on the left side of the node, and output attribute pins are on the right. Like nodes, pins must be uniquely identified. ```cpp ImNodes::BeginNode(hardcoded_node_id); -const int output_attr_id = 2; +const ImNodes::ID output_attr_id = 2; ImNodes::BeginOutputAttribute(output_attr_id); // in between Begin|EndAttribute calls, you can call ImGui // UI functions @@ -108,11 +108,11 @@ ImNodes::EndNode(); The user has to render their own links between nodes as well. A link is a curve which connects two attributes. A link is just a pair of attribute ids. And like nodes and attributes, links too have to be identified by unique integer values: ```cpp -std::vector> links; +std::vector> links; // elsewhere in the code... for (int i = 0; i < links.size(); ++i) { - const std::pair p = links[i]; + const std::pair p = links[i]; // in this case, we just use the array index of the link // as the unique identifier ImNodes::Link(i, p.first, p.second); @@ -122,7 +122,7 @@ for (int i = 0; i < links.size(); ++i) After `EndNodeEditor` has been called, you can check if a link was created during the frame with the function call `IsLinkCreated`: ```cpp -int start_attr, end_attr; +ImNodes::ID start_attr, end_attr; if (ImNodes::IsLinkCreated(&start_attr, &end_attr)) { links.push_back(std::make_pair(start_attr, end_attr)); @@ -132,7 +132,7 @@ if (ImNodes::IsLinkCreated(&start_attr, &end_attr)) In addition to checking for new links, you can also check whether UI elements are being hovered over by the mouse cursor: ```cpp -int node_id; +ImNodes::ID node_id; if (ImNodes::IsNodeHovered(&node_id)) { node_hovered = node_id; @@ -147,7 +147,7 @@ You can also check to see if any node has been selected. Nodes can be clicked on const int num_selected_nodes = ImNodes::NumSelectedNodes(); if (num_selected_nodes > 0) { - std::vector selected_nodes; + std::vector selected_nodes; selected_nodes.resize(num_selected_nodes); ImNodes::GetSelectedNodes(selected_nodes.data()); } @@ -208,7 +208,7 @@ ImNodes::MiniMap(0.2f, ImNodesMiniMapLocation_TopRight); The mini-map also supports limited node hovering customization through a user-defined callback. ```cpp // User callback -void mini_map_node_hovering_callback(int node_id, void* user_data) +void mini_map_node_hovering_callback(ImNodes::ID node_id, void* user_data) { ImGui::SetTooltip("This is node %d", node_id); } @@ -223,6 +223,8 @@ ImNodes::MiniMap(0.2f, ImNodesMiniMapLocation_TopRight, mini_map_node_hovering_c ImNodes can be customized by providing an `imnodes_config.h` header and specifying defining `IMNODES_USER_CONFIG=imnodes_config.h` when compiling. +### Minimap hovering callback + It is currently possible to override the type of the minimap hovering callback function. This is useful when generating bindings for another language. Here's an example imnodes_config.h, which generates a pybind wrapper for the callback. @@ -251,6 +253,30 @@ namespace py = pybind11; #define ImNodesMiniMapNodeHoveringCallbackUserData py::wrapper ``` +### ID type + +By default `ImNodes::ID` is `int` but you can define it to be whatever interger type you like: + +```cpp +// imnodes_config.h +#pragma once +#include +#include + +namespace IMNODES_NAMESPACE +{ +using ID = int; +static constexpr ID INVALID_ID = INT_MIN; + +inline void PushID(ID id) { ImGui::PushID(id); } + +inline std::string IDToString(ID id) { return std::to_string(id); } + +inline ID IDFromString(const std::string& str) { return std::stoi(str); } + +} // namespace IMNODES_NAMESPACE +``` + ## Known issues * `ImGui::Separator()` spans the current window span. As a result, using a separator inside a node will result in the separator spilling out of the node into the node editor grid. diff --git a/imnodes.cpp b/imnodes.cpp index 7689cb1..1022414 100644 --- a/imnodes.cpp +++ b/imnodes.cpp @@ -25,6 +25,7 @@ #include // for fwrite, ssprintf, sscanf #include #include // strlen, strncmp +#include // Use secure CRT function variants to avoid MSVC compiler errors #ifdef _MSC_VER @@ -418,8 +419,7 @@ void DrawListSet(ImDrawList* window_draw_list) void DrawListAddNode(const int node_idx) { - GImNodes->NodeIdxToSubmissionIdx.SetInt( - static_cast(node_idx), GImNodes->NodeIdxSubmissionOrder.Size); + GImNodes->NodeIdxToSubmissionIdx.SetInt(node_idx, GImNodes->NodeIdxSubmissionOrder.Size); GImNodes->NodeIdxSubmissionOrder.push_back(node_idx); ImDrawListGrowChannels(GImNodes->CanvasDrawList, 2); } @@ -458,8 +458,7 @@ void DrawListActivateCurrentNodeForeground() void DrawListActivateNodeBackground(const int node_idx) { - const int submission_idx = - GImNodes->NodeIdxToSubmissionIdx.GetInt(static_cast(node_idx), -1); + const int submission_idx = GImNodes->NodeIdxToSubmissionIdx.GetInt(node_idx, -1); // There is a discrepancy in the submitted node count and the rendered node count! Did you call // one of the following functions // * EditorContextMoveToNode @@ -883,7 +882,7 @@ ImOptionalIndex FindDuplicateLink( const int start_pin_idx, const int end_pin_idx) { - ImLinkData test_link(0); + ImLinkData test_link{{}}; test_link.StartPinIdx = start_pin_idx; test_link.EndPinIdx = end_pin_idx; for (int link_idx = 0; link_idx < editor.Links.Pool.size(); ++link_idx) @@ -1632,7 +1631,7 @@ void DrawLink(ImNodesEditorContext& editor, const int link_idx) } void BeginPinAttribute( - const int id, + const ID id, const ImNodesAttributeType type, const ImNodesPinShape shape, const int node_idx) @@ -1643,7 +1642,7 @@ void BeginPinAttribute( GImNodes->CurrentScope = ImNodesScope_Attribute; ImGui::BeginGroup(); - ImGui::PushID(id); + PushID(id); ImNodesEditorContext& editor = EditorContextGet(); @@ -1735,7 +1734,7 @@ static inline void CalcMiniMapLayout() const ImVec2 grid_content_size = editor.GridContentBounds.IsInverted() ? max_size : ImFloor(editor.GridContentBounds.GetSize()); - const float grid_content_aspect_ratio = grid_content_size.x / grid_content_size.y; + const float grid_content_aspect_ratio = grid_content_size.x / grid_content_size.y; mini_map_size = ImFloor( grid_content_aspect_ratio > max_size_aspect_ratio ? ImVec2(max_size.x, max_size.x / grid_content_aspect_ratio) @@ -1947,7 +1946,7 @@ static void MiniMapUpdate() // [SECTION] selection helpers template -void SelectObject(const ImObjectPool& objects, ImVector& selected_indices, const int id) +void SelectObject(const ImObjectPool& objects, ImVector& selected_indices, const ID id) { const int idx = ObjectPoolFind(objects, id); IM_ASSERT(idx >= 0); @@ -1959,7 +1958,7 @@ template void ClearObjectSelection( const ImObjectPool& objects, ImVector& selected_indices, - const int id) + const ID id) { const int idx = ObjectPoolFind(objects, id); IM_ASSERT(idx >= 0); @@ -1968,7 +1967,7 @@ void ClearObjectSelection( } template -bool IsObjectSelected(const ImObjectPool& objects, ImVector& selected_indices, const int id) +bool IsObjectSelected(const ImObjectPool& objects, ImVector& selected_indices, const ID id) { const int idx = ObjectPoolFind(objects, id); return selected_indices.find(idx) != selected_indices.end(); @@ -2053,7 +2052,7 @@ void EditorContextResetPanning(const ImVec2& pos) editor.Panning = pos; } -void EditorContextMoveToNode(const int node_id) +void EditorContextMoveToNode(const ID node_id) { ImNodesEditorContext& editor = EditorContextGet(); ImNodeData& node = ObjectPoolFindOrCreateObject(editor.Nodes, node_id); @@ -2453,7 +2452,7 @@ void MiniMap( // of the state for the mini map in GImNodes for the actual drawing/updating } -void BeginNode(const int node_id) +void BeginNode(const ID node_id) { // Remember to call BeginNodeEditor before calling BeginNode IM_ASSERT(GImNodes->CurrentScope == ImNodesScope_Editor); @@ -2484,7 +2483,7 @@ void BeginNode(const int node_id) DrawListAddNode(node_idx); DrawListActivateCurrentNodeForeground(); - ImGui::PushID(node.Id); + PushID(node.Id); ImGui::BeginGroup(); } @@ -2512,7 +2511,7 @@ void EndNode() } } -ImVec2 GetNodeDimensions(int node_id) +ImVec2 GetNodeDimensions(ID node_id) { ImNodesEditorContext& editor = EditorContextGet(); const int node_idx = ObjectPoolFind(editor.Nodes, node_id); @@ -2541,21 +2540,21 @@ void EndNodeTitleBar() ImGui::SetCursorPos(GridSpaceToEditorSpace(editor, GetNodeContentOrigin(node))); } -void BeginInputAttribute(const int id, const ImNodesPinShape shape) +void BeginInputAttribute(const ID id, const ImNodesPinShape shape) { BeginPinAttribute(id, ImNodesAttributeType_Input, shape, GImNodes->CurrentNodeIdx); } void EndInputAttribute() { EndPinAttribute(); } -void BeginOutputAttribute(const int id, const ImNodesPinShape shape) +void BeginOutputAttribute(const ID id, const ImNodesPinShape shape) { BeginPinAttribute(id, ImNodesAttributeType_Output, shape, GImNodes->CurrentNodeIdx); } void EndOutputAttribute() { EndPinAttribute(); } -void BeginStaticAttribute(const int id) +void BeginStaticAttribute(const ID id) { // Make sure to call BeginNode() before calling BeginAttribute() IM_ASSERT(GImNodes->CurrentScope == ImNodesScope_Node); @@ -2564,7 +2563,7 @@ void BeginStaticAttribute(const int id) GImNodes->CurrentAttributeId = id; ImGui::BeginGroup(); - ImGui::PushID(id); + PushID(id); } void EndStaticAttribute() @@ -2599,7 +2598,7 @@ void PopAttributeFlag() GImNodes->CurrentAttributeFlags = GImNodes->AttributeFlagStack.back(); } -void Link(const int id, const int start_attr_id, const int end_attr_id) +void Link(const ID id, const ID start_attr_id, const ID end_attr_id) { IM_ASSERT(GImNodes->CurrentScope == ImNodesScope_Editor); @@ -2734,35 +2733,35 @@ void PopStyleVar(int count) } } -void SetNodeScreenSpacePos(const int node_id, const ImVec2& screen_space_pos) +void SetNodeScreenSpacePos(const ID node_id, const ImVec2& screen_space_pos) { ImNodesEditorContext& editor = EditorContextGet(); ImNodeData& node = ObjectPoolFindOrCreateObject(editor.Nodes, node_id); node.Origin = ScreenSpaceToGridSpace(editor, screen_space_pos); } -void SetNodeEditorSpacePos(const int node_id, const ImVec2& editor_space_pos) +void SetNodeEditorSpacePos(const ID node_id, const ImVec2& editor_space_pos) { ImNodesEditorContext& editor = EditorContextGet(); ImNodeData& node = ObjectPoolFindOrCreateObject(editor.Nodes, node_id); node.Origin = EditorSpaceToGridSpace(editor, editor_space_pos); } -void SetNodeGridSpacePos(const int node_id, const ImVec2& grid_pos) +void SetNodeGridSpacePos(const ID node_id, const ImVec2& grid_pos) { ImNodesEditorContext& editor = EditorContextGet(); ImNodeData& node = ObjectPoolFindOrCreateObject(editor.Nodes, node_id); node.Origin = grid_pos; } -void SetNodeDraggable(const int node_id, const bool draggable) +void SetNodeDraggable(const ID node_id, const bool draggable) { ImNodesEditorContext& editor = EditorContextGet(); ImNodeData& node = ObjectPoolFindOrCreateObject(editor.Nodes, node_id); node.Draggable = draggable; } -ImVec2 GetNodeScreenSpacePos(const int node_id) +ImVec2 GetNodeScreenSpacePos(const ID node_id) { ImNodesEditorContext& editor = EditorContextGet(); const int node_idx = ObjectPoolFind(editor.Nodes, node_id); @@ -2771,7 +2770,7 @@ ImVec2 GetNodeScreenSpacePos(const int node_id) return GridSpaceToScreenSpace(editor, node.Origin); } -ImVec2 GetNodeEditorSpacePos(const int node_id) +ImVec2 GetNodeEditorSpacePos(const ID node_id) { ImNodesEditorContext& editor = EditorContextGet(); const int node_idx = ObjectPoolFind(editor.Nodes, node_id); @@ -2780,7 +2779,7 @@ ImVec2 GetNodeEditorSpacePos(const int node_id) return GridSpaceToEditorSpace(editor, node.Origin); } -ImVec2 GetNodeGridSpacePos(const int node_id) +ImVec2 GetNodeGridSpacePos(const ID node_id) { ImNodesEditorContext& editor = EditorContextGet(); const int node_idx = ObjectPoolFind(editor.Nodes, node_id); @@ -2789,7 +2788,7 @@ ImVec2 GetNodeGridSpacePos(const int node_id) return node.Origin; } -void SnapNodeToGrid(int node_id) +void SnapNodeToGrid(ID node_id) { ImNodesEditorContext& editor = EditorContextGet(); ImNodeData& node = ObjectPoolFindOrCreateObject(editor.Nodes, node_id); @@ -2798,7 +2797,7 @@ void SnapNodeToGrid(int node_id) bool IsEditorHovered() { return MouseInCanvas(); } -bool IsNodeHovered(int* const node_id) +bool IsNodeHovered(ID* const node_id) { IM_ASSERT(GImNodes->CurrentScope == ImNodesScope_None); IM_ASSERT(node_id != NULL); @@ -2812,7 +2811,7 @@ bool IsNodeHovered(int* const node_id) return is_hovered; } -bool IsLinkHovered(int* const link_id) +bool IsLinkHovered(ID* const link_id) { IM_ASSERT(GImNodes->CurrentScope == ImNodesScope_None); IM_ASSERT(link_id != NULL); @@ -2826,7 +2825,7 @@ bool IsLinkHovered(int* const link_id) return is_hovered; } -bool IsPinHovered(int* const attr) +bool IsPinHovered(ID* const attr) { IM_ASSERT(GImNodes->CurrentScope == ImNodesScope_None); IM_ASSERT(attr != NULL); @@ -2854,7 +2853,7 @@ int NumSelectedLinks() return editor.SelectedLinkIndices.size(); } -void GetSelectedNodes(int* node_ids) +void GetSelectedNodes(ID* node_ids) { IM_ASSERT(node_ids != NULL); @@ -2866,7 +2865,7 @@ void GetSelectedNodes(int* node_ids) } } -void GetSelectedLinks(int* link_ids) +void GetSelectedLinks(ID* link_ids) { IM_ASSERT(link_ids != NULL); @@ -2884,7 +2883,7 @@ void ClearNodeSelection() editor.SelectedNodeIndices.clear(); } -void ClearNodeSelection(int node_id) +void ClearNodeSelection(ID node_id) { ImNodesEditorContext& editor = EditorContextGet(); ClearObjectSelection(editor.Nodes, editor.SelectedNodeIndices, node_id); @@ -2896,31 +2895,31 @@ void ClearLinkSelection() editor.SelectedLinkIndices.clear(); } -void ClearLinkSelection(int link_id) +void ClearLinkSelection(ID link_id) { ImNodesEditorContext& editor = EditorContextGet(); ClearObjectSelection(editor.Links, editor.SelectedLinkIndices, link_id); } -void SelectNode(int node_id) +void SelectNode(ID node_id) { ImNodesEditorContext& editor = EditorContextGet(); SelectObject(editor.Nodes, editor.SelectedNodeIndices, node_id); } -void SelectLink(int link_id) +void SelectLink(ID link_id) { ImNodesEditorContext& editor = EditorContextGet(); SelectObject(editor.Links, editor.SelectedLinkIndices, link_id); } -bool IsNodeSelected(int node_id) +bool IsNodeSelected(ID node_id) { ImNodesEditorContext& editor = EditorContextGet(); return IsObjectSelected(editor.Nodes, editor.SelectedNodeIndices, node_id); } -bool IsLinkSelected(int link_id) +bool IsLinkSelected(ID link_id) { ImNodesEditorContext& editor = EditorContextGet(); return IsObjectSelected(editor.Links, editor.SelectedLinkIndices, link_id); @@ -2938,7 +2937,7 @@ bool IsAttributeActive() return GImNodes->ActiveAttributeId == GImNodes->CurrentAttributeId; } -bool IsAnyAttributeActive(int* const attribute_id) +bool IsAnyAttributeActive(ID* const attribute_id) { IM_ASSERT((GImNodes->CurrentScope & (ImNodesScope_Node | ImNodesScope_Attribute)) == 0); @@ -2955,7 +2954,7 @@ bool IsAnyAttributeActive(int* const attribute_id) return true; } -bool IsLinkStarted(int* const started_at_id) +bool IsLinkStarted(ID* const started_at_id) { // Call this function after EndNodeEditor()! IM_ASSERT(GImNodes->CurrentScope == ImNodesScope_None); @@ -2972,7 +2971,7 @@ bool IsLinkStarted(int* const started_at_id) return is_started; } -bool IsLinkDropped(int* const started_at_id, const bool including_detached_links) +bool IsLinkDropped(ID* const started_at_id, const bool including_detached_links) { // Call this function after EndNodeEditor()! IM_ASSERT(GImNodes->CurrentScope == ImNodesScope_None); @@ -2994,8 +2993,8 @@ bool IsLinkDropped(int* const started_at_id, const bool including_detached_links } bool IsLinkCreated( - int* const started_at_pin_id, - int* const ended_at_pin_id, + ID* const started_at_pin_id, + ID* const ended_at_pin_id, bool* const created_from_snap) { IM_ASSERT(GImNodes->CurrentScope == ImNodesScope_None); @@ -3034,10 +3033,10 @@ bool IsLinkCreated( } bool IsLinkCreated( - int* started_at_node_id, - int* started_at_pin_id, - int* ended_at_node_id, - int* ended_at_pin_id, + ID* started_at_node_id, + ID* started_at_pin_id, + ID* ended_at_node_id, + ID* ended_at_pin_id, bool* created_from_snap) { IM_ASSERT(GImNodes->CurrentScope == ImNodesScope_None); @@ -3083,7 +3082,7 @@ bool IsLinkCreated( return is_created; } -bool IsLinkDestroyed(int* const link_id) +bool IsLinkDestroyed(ID* const link_id) { IM_ASSERT(GImNodes->CurrentScope == ImNodesScope_None); @@ -3100,12 +3099,24 @@ bool IsLinkDestroyed(int* const link_id) namespace { + +std::string SubstringAfter(const std::string& begin, const std::string& s) +{ + auto from = s.find(begin); + if (from == std::string::npos) + return ""; + + from += begin.length(); + return s.substr(from, s.length() - from); +} + void NodeLineHandler(ImNodesEditorContext& editor, const char* const line) { - int id; - int x, y; - if (sscanf(line, "[node.%i", &id) == 1) + std::string id_as_string = SubstringAfter("[node.", line); + int x, y; + if (!id_as_string.empty()) { + ID id = IDFromString(id_as_string); const int node_idx = ObjectPoolFindOrCreateIndex(editor.Nodes, id); GImNodes->CurrentNodeIdx = node_idx; ImNodeData& node = editor.Nodes.Pool[node_idx]; @@ -3148,7 +3159,7 @@ const char* SaveEditorStateToIniString( if (editor.Nodes.InUse[i]) { const ImNodeData& node = editor.Nodes.Pool[i]; - GImNodes->TextBuffer.appendf("\n[node.%d]\n", node.Id); + GImNodes->TextBuffer.appendf("\n[node.%s]\n", IDToString(node.Id).c_str()); GImNodes->TextBuffer.appendf("origin=%i,%i\n", (int)node.Origin.x, (int)node.Origin.y); } } @@ -3263,3 +3274,162 @@ void LoadEditorStateFromIniFile(ImNodesEditorContext* const editor, const char* ImGui::MemFree(file_data); } } // namespace IMNODES_NAMESPACE + +//----------------------------------------------------------------------------- +// [SECTION] ImGuiStorage +// Helper: Key->value storage +//----------------------------------------------------------------------------- + +namespace IMNODES_NAMESPACE +{ +namespace Internal +{ + +// std::lower_bound but without the bullshit +static Storage::ImGuiStoragePair* LowerBound(ImVector& data, ID key) +{ + Storage::ImGuiStoragePair* first = data.Data; + Storage::ImGuiStoragePair* last = data.Data + data.Size; + size_t count = (size_t)(last - first); + while (count > 0) + { + size_t count2 = count >> 1; + Storage::ImGuiStoragePair* mid = first + count2; + if (mid->key < key) + { + first = ++mid; + count -= count2 + 1; + } + else + { + count = count2; + } + } + return first; +} + +// For quicker full rebuild of a storage (instead of an incremental one), you may add all your +// contents and then sort once. +void Storage::BuildSortByKey() +{ + struct StaticFunc + { + static int IMGUI_CDECL PairComparerByID(const void* lhs, const void* rhs) + { + // We can't just do a subtraction because qsort uses signed integers and subtracting our + // ID doesn't play well with that. + if (((const ImGuiStoragePair*)lhs)->key > ((const ImGuiStoragePair*)rhs)->key) + return +1; + if (((const ImGuiStoragePair*)lhs)->key < ((const ImGuiStoragePair*)rhs)->key) + return -1; + return 0; + } + }; + ImQsort(Data.Data, (size_t)Data.Size, sizeof(ImGuiStoragePair), StaticFunc::PairComparerByID); +} + +int Storage::GetInt(ID key, int default_val) const +{ + ImGuiStoragePair* it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return default_val; + return it->val_i; +} + +bool Storage::GetBool(ID key, bool default_val) const +{ + return GetInt(key, default_val ? 1 : 0) != 0; +} + +float Storage::GetFloat(ID key, float default_val) const +{ + ImGuiStoragePair* it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return default_val; + return it->val_f; +} + +void* Storage::GetVoidPtr(ID key) const +{ + ImGuiStoragePair* it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return NULL; + return it->val_p; +} + +// References are only valid until a new value is added to the storage. Calling a Set***() function +// or a Get***Ref() function invalidates the pointer. +int* Storage::GetIntRef(ID key, int default_val) +{ + ImGuiStoragePair* it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, ImGuiStoragePair(key, default_val)); + return &it->val_i; +} + +bool* Storage::GetBoolRef(ID key, bool default_val) +{ + return (bool*)GetIntRef(key, default_val ? 1 : 0); +} + +float* Storage::GetFloatRef(ID key, float default_val) +{ + ImGuiStoragePair* it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, ImGuiStoragePair(key, default_val)); + return &it->val_f; +} + +void** Storage::GetVoidPtrRef(ID key, void* default_val) +{ + ImGuiStoragePair* it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, ImGuiStoragePair(key, default_val)); + return &it->val_p; +} + +// FIXME-OPT: Need a way to reuse the result of lower_bound when doing GetInt()/SetInt() - not too +// bad because it only happens on explicit interaction (maximum one a frame) +void Storage::SetInt(ID key, int val) +{ + ImGuiStoragePair* it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, ImGuiStoragePair(key, val)); + return; + } + it->val_i = val; +} + +void Storage::SetBool(ID key, bool val) { SetInt(key, val ? 1 : 0); } + +void Storage::SetFloat(ID key, float val) +{ + ImGuiStoragePair* it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, ImGuiStoragePair(key, val)); + return; + } + it->val_f = val; +} + +void Storage::SetVoidPtr(ID key, void* val) +{ + ImGuiStoragePair* it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, ImGuiStoragePair(key, val)); + return; + } + it->val_p = val; +} + +void Storage::SetAllInt(int v) +{ + for (int i = 0; i < Data.Size; i++) + Data[i].val_i = v; +} + +} // namespace Internal +} // namespace IMNODES_NAMESPACE \ No newline at end of file diff --git a/imnodes.h b/imnodes.h index 15ad5ed..7aece5d 100644 --- a/imnodes.h +++ b/imnodes.h @@ -3,13 +3,9 @@ #include #include -#ifdef IMNODES_USER_CONFIG -#include IMNODES_USER_CONFIG -#endif +#include "imnodes_config_or_default.h" -#ifndef IMNODES_NAMESPACE -#define IMNODES_NAMESPACE ImNodes -#endif +#include "IMNODES_NAMESPACE.h" typedef int ImNodesCol; // -> enum ImNodesCol_ typedef int ImNodesStyleVar; // -> enum ImNodesStyleVar_ @@ -230,7 +226,7 @@ struct ImNodesEditorContext; // Callback type used to specify special behavior when hovering a node in the minimap #ifndef ImNodesMiniMapNodeHoveringCallback -typedef void (*ImNodesMiniMapNodeHoveringCallback)(int, void*); +typedef void (*ImNodesMiniMapNodeHoveringCallback)(IMNODES_NAMESPACE::ID, void*); #endif #ifndef ImNodesMiniMapNodeHoveringCallbackUserData @@ -253,7 +249,7 @@ void EditorContextFree(ImNodesEditorContext*); void EditorContextSet(ImNodesEditorContext*); ImVec2 EditorContextGetPanning(); void EditorContextResetPanning(const ImVec2& pos); -void EditorContextMoveToNode(const int node_id); +void EditorContextMoveToNode(const ID node_id); ImNodesIO& GetIO(); @@ -285,11 +281,10 @@ void PushStyleVar(ImNodesStyleVar style_item, float value); void PushStyleVar(ImNodesStyleVar style_item, const ImVec2& value); void PopStyleVar(int count = 1); -// id can be any positive or negative integer, but INT_MIN is currently reserved for internal use. -void BeginNode(int id); +void BeginNode(ID id); void EndNode(); -ImVec2 GetNodeDimensions(int id); +ImVec2 GetNodeDimensions(ID id); // Place your node title bar content (such as the node title, using ImGui::Text) between the // following function calls. These functions have to be called before adding any attributes, or the @@ -307,15 +302,15 @@ void EndNodeTitleBar(); // Each attribute id must be unique. // Create an input attribute block. The pin is rendered on left side. -void BeginInputAttribute(int id, ImNodesPinShape shape = ImNodesPinShape_CircleFilled); +void BeginInputAttribute(ID id, ImNodesPinShape shape = ImNodesPinShape_CircleFilled); void EndInputAttribute(); // Create an output attribute block. The pin is rendered on the right side. -void BeginOutputAttribute(int id, ImNodesPinShape shape = ImNodesPinShape_CircleFilled); +void BeginOutputAttribute(ID id, ImNodesPinShape shape = ImNodesPinShape_CircleFilled); void EndOutputAttribute(); // Create a static attribute block. A static attribute has no pin, and therefore can't be linked to // anything. However, you can still use IsAttributeActive() and IsAnyAttributeActive() to check for // attribute activity. -void BeginStaticAttribute(int id); +void BeginStaticAttribute(ID id); void EndStaticAttribute(); // Push a single AttributeFlags value. By default, only AttributeFlags_None is set. @@ -325,10 +320,10 @@ void PopAttributeFlag(); // Render a link between attributes. // The attributes ids used here must match the ids used in Begin(Input|Output)Attribute function // calls. The order of start_attr and end_attr doesn't make a difference for rendering the link. -void Link(int id, int start_attribute_id, int end_attribute_id); +void Link(ID id, ID start_attribute_id, ID end_attribute_id); // Enable or disable the ability to click and drag a specific node. -void SetNodeDraggable(int node_id, const bool draggable); +void SetNodeDraggable(ID node_id, const bool draggable); // The node's position can be expressed in three coordinate systems: // * screen space coordinates, -- the origin is the upper left corner of the window. @@ -339,16 +334,16 @@ void SetNodeDraggable(int node_id, const bool draggable); // Use the following functions to get and set the node's coordinates in these coordinate systems. -void SetNodeScreenSpacePos(int node_id, const ImVec2& screen_space_pos); -void SetNodeEditorSpacePos(int node_id, const ImVec2& editor_space_pos); -void SetNodeGridSpacePos(int node_id, const ImVec2& grid_pos); +void SetNodeScreenSpacePos(ID node_id, const ImVec2& screen_space_pos); +void SetNodeEditorSpacePos(ID node_id, const ImVec2& editor_space_pos); +void SetNodeGridSpacePos(ID node_id, const ImVec2& grid_pos); -ImVec2 GetNodeScreenSpacePos(const int node_id); -ImVec2 GetNodeEditorSpacePos(const int node_id); -ImVec2 GetNodeGridSpacePos(const int node_id); +ImVec2 GetNodeScreenSpacePos(const ID node_id); +ImVec2 GetNodeEditorSpacePos(const ID node_id); +ImVec2 GetNodeGridSpacePos(const ID node_id); // If ImNodesStyleFlags_GridSnapping is enabled, snap the specified node's origin to the grid. -void SnapNodeToGrid(int node_id); +void SnapNodeToGrid(ID node_id); // Returns true if the current node editor canvas is being hovered over by the mouse, and is not // blocked by any other windows. @@ -356,9 +351,9 @@ bool IsEditorHovered(); // The following functions return true if a UI element is being hovered over by the mouse cursor. // Assigns the id of the UI element being hovered over to the function argument. Use these functions // after EndNodeEditor() has been called. -bool IsNodeHovered(int* node_id); -bool IsLinkHovered(int* link_id); -bool IsPinHovered(int* attribute_id); +bool IsNodeHovered(ID* node_id); +bool IsLinkHovered(ID* link_id); +bool IsPinHovered(ID* attribute_id); // Use The following two functions to query the number of selected nodes or links in the current // editor. Use after calling EndNodeEditor(). @@ -367,8 +362,8 @@ int NumSelectedLinks(); // Get the selected node/link ids. The pointer argument should point to an integer array with at // least as many elements as the respective NumSelectedNodes/NumSelectedLinks function call // returned. -void GetSelectedNodes(int* node_ids); -void GetSelectedLinks(int* link_ids); +void GetSelectedNodes(ID* node_ids); +void GetSelectedLinks(ID* link_ids); // Clears the list of selected nodes/links. Useful if you want to delete a selected node or link. void ClearNodeSelection(); void ClearLinkSelection(); @@ -378,46 +373,46 @@ void ClearLinkSelection(); // Clear-functions has the precondition that the object is currently considered selected. // Preconditions listed above can be checked via IsNodeSelected/IsLinkSelected if not already // known. -void SelectNode(int node_id); -void ClearNodeSelection(int node_id); -bool IsNodeSelected(int node_id); -void SelectLink(int link_id); -void ClearLinkSelection(int link_id); -bool IsLinkSelected(int link_id); +void SelectNode(ID node_id); +void ClearNodeSelection(ID node_id); +bool IsNodeSelected(ID node_id); +void SelectLink(ID link_id); +void ClearLinkSelection(ID link_id); +bool IsLinkSelected(ID link_id); // Was the previous attribute active? This will continuously return true while the left mouse button // is being pressed over the UI content of the attribute. bool IsAttributeActive(); // Was any attribute active? If so, sets the active attribute id to the output function argument. -bool IsAnyAttributeActive(int* attribute_id = NULL); +bool IsAnyAttributeActive(ID* attribute_id = NULL); // Use the following functions to query a change of state for an existing link, or new link. Call // these after EndNodeEditor(). // Did the user start dragging a new link from a pin? -bool IsLinkStarted(int* started_at_attribute_id); +bool IsLinkStarted(ID* started_at_attribute_id); // Did the user drop the dragged link before attaching it to a pin? // There are two different kinds of situations to consider when handling this event: // 1) a link which is created at a pin and then dropped // 2) an existing link which is detached from a pin and then dropped // Use the including_detached_links flag to control whether this function triggers when the user // detaches a link and drops it. -bool IsLinkDropped(int* started_at_attribute_id = NULL, bool including_detached_links = true); +bool IsLinkDropped(ID* started_at_attribute_id = NULL, bool including_detached_links = true); // Did the user finish creating a new link? bool IsLinkCreated( - int* started_at_attribute_id, - int* ended_at_attribute_id, + ID* started_at_attribute_id, + ID* ended_at_attribute_id, bool* created_from_snap = NULL); bool IsLinkCreated( - int* started_at_node_id, - int* started_at_attribute_id, - int* ended_at_node_id, - int* ended_at_attribute_id, + ID* started_at_node_id, + ID* started_at_attribute_id, + ID* ended_at_node_id, + ID* ended_at_attribute_id, bool* created_from_snap = NULL); // Was an existing link detached from a pin by the user? The detached link's id is assigned to the // output argument link_id. -bool IsLinkDestroyed(int* link_id); +bool IsLinkDestroyed(ID* link_id); // Use the following functions to write the editor context's state to a string, or directly to a // file. The editor context is serialized in the INI file format. diff --git a/imnodes_config_or_default.h b/imnodes_config_or_default.h new file mode 100644 index 0000000..f3fd0a5 --- /dev/null +++ b/imnodes_config_or_default.h @@ -0,0 +1,26 @@ +#pragma once + +#include "IMNODES_NAMESPACE.h" + +#ifdef IMNODES_USER_CONFIG +#include "imnodes_config.h" +#else + +#include +#include + +namespace IMNODES_NAMESPACE +{ +// id can be any positive or negative integer, but INT_MIN is currently reserved for internal use. +using ID = int; +static constexpr ID INVALID_ID = INT_MIN; + +inline void PushID(ID id) { ImGui::PushID(id); } + +inline std::string IDToString(ID id) { return std::to_string(id); } + +inline ID IDFromString(const std::string& str) { return std::stoi(str); } + +} // namespace IMNODES_NAMESPACE + +#endif \ No newline at end of file diff --git a/imnodes_internal.h b/imnodes_internal.h index 593ab49..f078a69 100644 --- a/imnodes_internal.h +++ b/imnodes_internal.h @@ -1,6 +1,8 @@ #pragma once #include "imnodes.h" +#include "imnodes_config_or_default.h" +#include "ImGuiStorage.h" #include #define IMGUI_DEFINE_MATH_OPERATORS @@ -75,15 +77,15 @@ enum ImNodesLinkCreationType_ // { // T(); // -// int id; +// ID id; // }; template struct ImObjectPool { - ImVector Pool; - ImVector InUse; - ImVector FreeList; - ImGuiStorage IdMap; + ImVector Pool; + ImVector InUse; + ImVector FreeList; + IMNODES_NAMESPACE::Internal::Storage IdMap; ImObjectPool() : Pool(), InUse(), FreeList(), IdMap() {} }; @@ -130,10 +132,10 @@ struct ImOptionalIndex struct ImNodeData { - int Id; - ImVec2 Origin; // The node origin is in editor space - ImRect TitleBarContentRect; - ImRect Rect; + IMNODES_NAMESPACE::ID Id; + ImVec2 Origin; // The node origin is in editor space + ImRect TitleBarContentRect; + ImRect Rect; struct { @@ -151,32 +153,32 @@ struct ImNodeData ImVector PinIndices; bool Draggable; - ImNodeData(const int node_id) + ImNodeData(const IMNODES_NAMESPACE::ID node_id) : Id(node_id), Origin(0.0f, 0.0f), TitleBarContentRect(), Rect(ImVec2(0.0f, 0.0f), ImVec2(0.0f, 0.0f)), ColorStyle(), LayoutStyle(), PinIndices(), Draggable(true) { } - ~ImNodeData() { Id = INT_MIN; } + ~ImNodeData() { Id = IMNODES_NAMESPACE::INVALID_ID; } }; struct ImPinData { - int Id; - int ParentNodeIdx; - ImRect AttributeRect; - ImNodesAttributeType Type; - ImNodesPinShape Shape; - ImVec2 Pos; // screen-space coordinates - int Flags; + IMNODES_NAMESPACE::ID Id; + int ParentNodeIdx; + ImRect AttributeRect; + ImNodesAttributeType Type; + ImNodesPinShape Shape; + ImVec2 Pos; // screen-space coordinates + int Flags; struct { ImU32 Background, Hovered; } ColorStyle; - ImPinData(const int pin_id) + ImPinData(const IMNODES_NAMESPACE::ID pin_id) : Id(pin_id), ParentNodeIdx(), AttributeRect(), Type(ImNodesAttributeType_None), Shape(ImNodesPinShape_CircleFilled), Pos(), Flags(ImNodesAttributeFlags_None), ColorStyle() @@ -186,15 +188,18 @@ struct ImPinData struct ImLinkData { - int Id; - int StartPinIdx, EndPinIdx; + IMNODES_NAMESPACE::ID Id; + int StartPinIdx, EndPinIdx; struct { ImU32 Base, Hovered, Selected; } ColorStyle; - ImLinkData(const int link_id) : Id(link_id), StartPinIdx(), EndPinIdx(), ColorStyle() {} + ImLinkData(const IMNODES_NAMESPACE::ID link_id) + : Id(link_id), StartPinIdx(), EndPinIdx(), ColorStyle() + { + } }; struct ImClickInteractionState @@ -264,7 +269,7 @@ struct ImNodesEditorContext // Relative origins of selected nodes for snapping of dragged nodes ImVector SelectedNodeOffsets; // Offset of the primary node origin relative to the mouse cursor. - ImVec2 PrimaryNodeOffset; + ImVec2 PrimaryNodeOffset; ImClickInteractionState ClickInteraction; @@ -285,9 +290,8 @@ struct ImNodesEditorContext ImNodesEditorContext() : Nodes(), Pins(), Links(), Panning(0.f, 0.f), SelectedNodeIndices(), SelectedLinkIndices(), SelectedNodeOffsets(), PrimaryNodeOffset(0.f, 0.f), ClickInteraction(), - MiniMapEnabled(false), MiniMapSizeFraction(0.0f), - MiniMapNodeHoveringCallback(NULL), MiniMapNodeHoveringCallbackUserData(NULL), - MiniMapScaling(0.0f) + MiniMapEnabled(false), MiniMapSizeFraction(0.0f), MiniMapNodeHoveringCallback(NULL), + MiniMapNodeHoveringCallbackUserData(NULL), MiniMapScaling(0.0f) { } }; @@ -322,9 +326,9 @@ struct ImNodesContext ImVector AttributeFlagStack; // UI element state - int CurrentNodeIdx; - int CurrentPinIdx; - int CurrentAttributeId; + int CurrentNodeIdx; + int CurrentPinIdx; + IMNODES_NAMESPACE::ID CurrentAttributeId; ImOptionalIndex HoveredNodeIdx; ImOptionalIndex HoveredLinkIdx; @@ -338,8 +342,8 @@ struct ImNodesContext // Unclear what parts of the code this relates to. int ImNodesUIState; - int ActiveAttributeId; - bool ActiveAttribute; + IMNODES_NAMESPACE::ID ActiveAttributeId; + bool ActiveAttribute; // ImGui::IO cache @@ -366,9 +370,9 @@ static inline ImNodesEditorContext& EditorContextGet() // [SECTION] ObjectPool implementation template -static inline int ObjectPoolFind(const ImObjectPool& objects, const int id) +static inline int ObjectPoolFind(const ImObjectPool& objects, const IMNODES_NAMESPACE::ID id) { - const int index = objects.IdMap.GetInt(static_cast(id), -1); + const int index = objects.IdMap.GetInt(id, -1); return index; } @@ -377,7 +381,7 @@ static inline void ObjectPoolUpdate(ImObjectPool& objects) { for (int i = 0; i < objects.InUse.size(); ++i) { - const int id = objects.Pool[i].Id; + const ID id = objects.Pool[i].Id; if (!objects.InUse[i] && objects.IdMap.GetInt(id, -1) == i) { @@ -399,7 +403,7 @@ inline void ObjectPoolUpdate(ImObjectPool& nodes) } else { - const int id = nodes.Pool[i].Id; + const ID id = nodes.Pool[i].Id; if (nodes.IdMap.GetInt(id, -1) == i) { @@ -428,9 +432,9 @@ static inline void ObjectPoolReset(ImObjectPool& objects) } template -static inline int ObjectPoolFindOrCreateIndex(ImObjectPool& objects, const int id) +static inline int ObjectPoolFindOrCreateIndex(ImObjectPool& objects, const ID id) { - int index = objects.IdMap.GetInt(static_cast(id), -1); + int index = objects.IdMap.GetInt(id, -1); // Construct new object if (index == -1) @@ -449,7 +453,7 @@ static inline int ObjectPoolFindOrCreateIndex(ImObjectPool& objects, const in objects.FreeList.pop_back(); } IM_PLACEMENT_NEW(objects.Pool.Data + index) T(id); - objects.IdMap.SetInt(static_cast(id), index); + objects.IdMap.SetInt(id, index); } // Flag it as used @@ -459,9 +463,9 @@ static inline int ObjectPoolFindOrCreateIndex(ImObjectPool& objects, const in } template<> -inline int ObjectPoolFindOrCreateIndex(ImObjectPool& nodes, const int node_id) +inline int ObjectPoolFindOrCreateIndex(ImObjectPool& nodes, const ID node_id) { - int node_idx = nodes.IdMap.GetInt(static_cast(node_id), -1); + int node_idx = nodes.IdMap.GetInt(node_id, -1); // Construct new node if (node_idx == -1) @@ -480,7 +484,7 @@ inline int ObjectPoolFindOrCreateIndex(ImObjectPool& nodes, const in nodes.FreeList.pop_back(); } IM_PLACEMENT_NEW(nodes.Pool.Data + node_idx) ImNodeData(node_id); - nodes.IdMap.SetInt(static_cast(node_id), node_idx); + nodes.IdMap.SetInt(node_id, node_idx); ImNodesEditorContext& editor = EditorContextGet(); editor.NodeDepthOrder.push_back(node_idx); @@ -493,7 +497,7 @@ inline int ObjectPoolFindOrCreateIndex(ImObjectPool& nodes, const in } template -static inline T& ObjectPoolFindOrCreateObject(ImObjectPool& objects, const int id) +static inline T& ObjectPoolFindOrCreateObject(ImObjectPool& objects, const ID id) { const int index = ObjectPoolFindOrCreateIndex(objects, id); return objects.Pool[index];