Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customizable ID type #169

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions IMNODES_NAMESPACE.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#pragma once

#ifndef IMNODES_NAMESPACE
#define IMNODES_NAMESPACE ImNodes
#endif
91 changes: 91 additions & 0 deletions ImGuiStorage.h
Original file line number Diff line number Diff line change
@@ -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<ImGuiStoragePair> 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
44 changes: 35 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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
Expand Down Expand Up @@ -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<std::pair<int, int>> links;
std::vector<std::pair<ImNodes::ID, ImNodes::ID>> links;
// elsewhere in the code...
for (int i = 0; i < links.size(); ++i)
{
const std::pair<int, int> p = links[i];
const std::pair<ImNodes::ID, ImNodes::ID> 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);
Expand All @@ -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));
Expand All @@ -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;
Expand All @@ -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<int> selected_nodes;
std::vector<ImNodes::ID> selected_nodes;
selected_nodes.resize(num_selected_nodes);
ImNodes::GetSelectedNodes(selected_nodes.data());
}
Expand Down Expand Up @@ -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);
}
Expand All @@ -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.
Expand Down Expand Up @@ -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 <limits.h>
#include <string>

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.
Expand Down
Loading