Commit 60d65a1f authored by Gauthier Quesnel's avatar Gauthier Quesnel
Browse files

imnodes: bump to v0.4

parent 5dec4126
......@@ -16,7 +16,7 @@
#include <imgui_internal.h>
// Check minimum ImGui version
#define MINIMUM_COMPATIBLE_IMGUI_VERSION 16401
#define MINIMUM_COMPATIBLE_IMGUI_VERSION 17400
#if IMGUI_VERSION_NUM < MINIMUM_COMPATIBLE_IMGUI_VERSION
#error \
"Minimum ImGui version requirement not met -- please use a newer version!"
......@@ -128,6 +128,16 @@ struct OptionalIndex
return m_index == rhs;
}
inline bool operator!=(const OptionalIndex& rhs) const
{
return m_index != rhs.m_index;
}
inline bool operator!=(const int rhs) const
{
return m_index != rhs;
}
static const int invalid_index = -1;
private:
......@@ -151,6 +161,7 @@ struct NodeData
{
float corner_rounding;
ImVec2 padding;
float border_thickness;
} layout_style;
ImVector<int> pin_indices;
......@@ -166,6 +177,11 @@ struct NodeData
, pin_indices()
, draggable(true)
{}
~NodeData()
{
id = INT_MIN;
}
};
struct PinData
......@@ -304,10 +320,10 @@ struct StyleElement
, value(value)
{}
};
} // namespace
// [SECTION] global struct
// this stores data which only lives for one frame
struct
struct Context
{
EditorContext* default_editor_ctx;
EditorContext* editor_ctx;
......@@ -317,6 +333,7 @@ struct
ImGuiStorage node_idx_to_submission_idx;
ImVector<int> node_idx_submission_order;
ImVector<int> node_indices_overlapping_with_mouse;
ImVector<int> occluded_pin_indices;
// Canvas extents
ImVec2 canvas_origin_screen_space;
......@@ -361,24 +378,21 @@ struct
bool left_mouse_clicked;
bool left_mouse_released;
bool middle_mouse_clicked;
bool alt_mouse_clicked;
bool left_mouse_dragging;
bool middle_mouse_dragging;
} g;
bool alt_mouse_dragging;
};
Context* g = NULL;
namespace {
EditorContext&
editor_context_get()
{
// No editor context was set! Did you forget to call imnodes::Initialize?
assert(g.editor_ctx != NULL);
return *g.editor_ctx;
}
inline bool
is_mouse_hovering_near_point(const ImVec2& point, float radius)
{
ImVec2 delta = g.mouse_pos - point;
return (delta.x * delta.x + delta.y * delta.y) < (radius * radius);
assert(g->editor_ctx != NULL);
return *g->editor_ctx;
}
inline ImVec2
......@@ -438,7 +452,7 @@ get_containing_rect_for_bezier_curve(const BezierCurve& bezier)
const ImVec2 max =
ImVec2(ImMax(bezier.p0.x, bezier.p3.x), ImMax(bezier.p0.y, bezier.p3.y));
const float hover_distance = g.style.link_hover_distance;
const float hover_distance = g->style.link_hover_distance;
ImRect rect(min, max);
rect.Add(bezier.p1);
......@@ -472,26 +486,6 @@ get_link_renderable(ImVec2 start,
return link_data;
}
inline bool
is_mouse_hovering_near_link(const BezierCurve& bezier, const int num_segments)
{
const ImVec2 mouse_pos = g.mouse_pos;
// First, do a simple bounding box test against the box containing the link
// to see whether calculating the distance to the link is worth doing.
const ImRect link_rect = get_containing_rect_for_bezier_curve(bezier);
if (link_rect.Contains(mouse_pos)) {
const float distance =
get_distance_to_cubic_bezier(mouse_pos, bezier, num_segments);
if (distance < g.style.link_hover_distance) {
return true;
}
}
return false;
}
inline float
eval_implicit_line_eq(const ImVec2& p1, const ImVec2& p2, const ImVec2& p)
{
......@@ -601,7 +595,7 @@ rectangle_overlaps_link(const ImRect& rectangle,
// link
const LinkBezierData link_data = get_link_renderable(
start, end, start_type, g.style.link_line_segments_per_length);
start, end, start_type, g->style.link_line_segments_per_length);
return rectangle_overlaps_bezier(rectangle, link_data);
}
......@@ -722,9 +716,9 @@ ImDrawListSplitter_swap_channels(ImDrawListSplitter& splitter,
void
draw_list_set(ImDrawList* window_draw_list)
{
g.canvas_draw_list = window_draw_list;
g.node_idx_to_submission_idx.Clear();
g.node_idx_submission_order.clear();
g->canvas_draw_list = window_draw_list;
g->node_idx_to_submission_idx.Clear();
g->node_idx_submission_order.clear();
}
// The draw list channels are structured as follows. First we have our base
......@@ -751,10 +745,10 @@ draw_list_set(ImDrawList* window_draw_list)
void
draw_list_add_node(const int node_idx)
{
g.node_idx_to_submission_idx.SetInt(static_cast<ImGuiID>(node_idx),
g.node_idx_submission_order.Size);
g.node_idx_submission_order.push_back(node_idx);
ImDrawList_grow_channels(g.canvas_draw_list, 2);
g->node_idx_to_submission_idx.SetInt(static_cast<ImGuiID>(node_idx),
g->node_idx_submission_order.Size);
g->node_idx_submission_order.push_back(node_idx);
ImDrawList_grow_channels(g->canvas_draw_list, 2);
}
void
......@@ -762,7 +756,7 @@ draw_list_append_click_interaction_channel()
{
// NOTE: don't use this function outside of EndNodeEditor. Using this before
// all nodes have been added will screw up the node draw order.
ImDrawList_grow_channels(g.canvas_draw_list, 1);
ImDrawList_grow_channels(g->canvas_draw_list, 1);
}
int
......@@ -782,8 +776,8 @@ draw_list_submission_idx_to_foreground_channel_idx(const int submission_idx)
void
draw_list_activate_click_interaction_channel()
{
g.canvas_draw_list->_Splitter.SetCurrentChannel(
g.canvas_draw_list, g.canvas_draw_list->_Splitter._Count - 1);
g->canvas_draw_list->_Splitter.SetCurrentChannel(
g->canvas_draw_list, g->canvas_draw_list->_Splitter._Count - 1);
}
void
......@@ -791,16 +785,16 @@ draw_list_activate_current_node_foreground()
{
const int foreground_channel_idx =
draw_list_submission_idx_to_foreground_channel_idx(
g.node_idx_submission_order.Size - 1);
g.canvas_draw_list->_Splitter.SetCurrentChannel(g.canvas_draw_list,
foreground_channel_idx);
g->node_idx_submission_order.Size - 1);
g->canvas_draw_list->_Splitter.SetCurrentChannel(g->canvas_draw_list,
foreground_channel_idx);
}
void
draw_list_activate_node_background(const int node_idx)
{
const int submission_idx =
g.node_idx_to_submission_idx.GetInt(static_cast<ImGuiID>(node_idx), -1);
g->node_idx_to_submission_idx.GetInt(static_cast<ImGuiID>(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
......@@ -811,8 +805,8 @@ draw_list_activate_node_background(const int node_idx)
assert(submission_idx != -1);
const int background_channel_idx =
draw_list_submission_idx_to_background_channel_idx(submission_idx);
g.canvas_draw_list->_Splitter.SetCurrentChannel(g.canvas_draw_list,
background_channel_idx);
g->canvas_draw_list->_Splitter.SetCurrentChannel(g->canvas_draw_list,
background_channel_idx);
}
void
......@@ -829,10 +823,10 @@ draw_list_swap_submission_indices(const int lhs_idx, const int rhs_idx)
const int rhs_background_channel_idx =
draw_list_submission_idx_to_background_channel_idx(rhs_idx);
ImDrawListSplitter_swap_channels(g.canvas_draw_list->_Splitter,
ImDrawListSplitter_swap_channels(g->canvas_draw_list->_Splitter,
lhs_background_channel_idx,
rhs_background_channel_idx);
ImDrawListSplitter_swap_channels(g.canvas_draw_list->_Splitter,
ImDrawListSplitter_swap_channels(g->canvas_draw_list->_Splitter,
lhs_foreground_channel_idx,
rhs_foreground_channel_idx);
}
......@@ -840,16 +834,16 @@ draw_list_swap_submission_indices(const int lhs_idx, const int rhs_idx)
void
draw_list_sort_channels_by_depth(const ImVector<int>& node_idx_depth_order)
{
if (g.node_idx_to_submission_idx.Data.Size < 2) {
if (g->node_idx_to_submission_idx.Data.Size < 2) {
return;
}
assert(node_idx_depth_order.Size == g.node_idx_submission_order.Size);
assert(node_idx_depth_order.Size == g->node_idx_submission_order.Size);
int start_idx = node_idx_depth_order.Size - 1;
while (node_idx_depth_order[start_idx] ==
g.node_idx_submission_order[start_idx]) {
g->node_idx_submission_order[start_idx]) {
if (--start_idx == 0) {
// early out if submission order and depth order are the same
return;
......@@ -864,8 +858,8 @@ draw_list_sort_channels_by_depth(const ImVector<int>& node_idx_depth_order)
// Find the current index of the node_idx in the submission order array
int submission_idx = -1;
for (int i = 0; i < g.node_idx_submission_order.Size; ++i) {
if (g.node_idx_submission_order[i] == node_idx) {
for (int i = 0; i < g->node_idx_submission_order.Size; ++i) {
if (g->node_idx_submission_order[i] == node_idx) {
submission_idx = i;
break;
}
......@@ -878,8 +872,8 @@ draw_list_sort_channels_by_depth(const ImVector<int>& node_idx_depth_order)
for (int j = submission_idx; j < depth_idx; ++j) {
draw_list_swap_submission_indices(j, j + 1);
ImSwap(g.node_idx_submission_order[j],
g.node_idx_submission_order[j + 1]);
ImSwap(g->node_idx_submission_order[j],
g->node_idx_submission_order[j + 1]);
}
}
}
......@@ -903,6 +897,7 @@ object_pool_update(ObjectPool<T>& objects)
if (!objects.in_use[i]) {
objects.id_map.SetInt(objects.pool[i].id, -1);
objects.free_list.push_back(i);
(objects.pool.Data + i)->~T();
}
}
}
......@@ -913,10 +908,14 @@ object_pool_update(ObjectPool<NodeData>& nodes)
{
nodes.free_list.clear();
for (int i = 0; i < nodes.in_use.size(); ++i) {
if (!nodes.in_use[i]) {
if (nodes.in_use[i]) {
nodes.pool[i].pin_indices.clear();
} else {
const int previous_id = nodes.pool[i].id;
const int previous_idx = nodes.id_map.GetInt(previous_id, -1);
if (previous_idx != -1) {
assert(previous_idx == i);
// Remove node idx form depth stack the first time we detect
// that this idx slot is unused
ImVector<int>& depth_stack =
......@@ -928,6 +927,7 @@ object_pool_update(ObjectPool<NodeData>& nodes)
nodes.id_map.SetInt(previous_id, -1);
nodes.free_list.push_back(i);
(nodes.pool.Data + i)->~NodeData();
}
}
}
......@@ -946,18 +946,26 @@ int
object_pool_find_or_create_index(ObjectPool<T>& objects, const int id)
{
int index = objects.id_map.GetInt(static_cast<ImGuiID>(id), -1);
// Construct new object
if (index == -1) {
if (objects.free_list.empty()) {
index = objects.pool.size();
objects.pool.push_back(T(id));
objects.in_use.push_back(true);
IM_ASSERT(objects.pool.size() == objects.in_use.size());
const int new_size = objects.pool.size() + 1;
objects.pool.resize(new_size);
objects.in_use.resize(new_size);
} else {
index = objects.free_list.back();
objects.free_list.pop_back();
}
IM_PLACEMENT_NEW(objects.pool.Data + index) T(id);
objects.id_map.SetInt(static_cast<ImGuiID>(id), index);
}
// Flag it as used
objects.in_use[index] = true;
return index;
}
......@@ -966,20 +974,27 @@ int
object_pool_find_or_create_index(ObjectPool<NodeData>& nodes, const int node_id)
{
int node_idx = nodes.id_map.GetInt(static_cast<ImGuiID>(node_id), -1);
// Construct new node
if (node_idx == -1) {
if (nodes.free_list.empty()) {
node_idx = nodes.pool.size();
nodes.pool.push_back(NodeData(node_id));
nodes.in_use.push_back(true);
IM_ASSERT(nodes.pool.size() == nodes.in_use.size());
const int new_size = nodes.pool.size() + 1;
nodes.pool.resize(new_size);
nodes.in_use.resize(new_size);
} else {
node_idx = nodes.free_list.back();
nodes.free_list.pop_back();
}
IM_PLACEMENT_NEW(nodes.pool.Data + node_idx) NodeData(node_id);
nodes.id_map.SetInt(static_cast<ImGuiID>(node_id), node_idx);
EditorContext& editor = editor_context_get();
editor.node_depth_order.push_back(node_idx);
}
// Flag node as used
nodes.in_use[node_idx] = true;
return node_idx;
......@@ -1002,8 +1017,8 @@ get_screen_space_pin_coordinates(const ImRect& node_rect,
{
assert(type == AttributeType_Input || type == AttributeType_Output);
const float x = type == AttributeType_Input
? (node_rect.Min.x - g.style.pin_offset)
: (node_rect.Max.x + g.style.pin_offset);
? (node_rect.Min.x - g->style.pin_offset)
: (node_rect.Max.x + g->style.pin_offset);
return ImVec2(x, 0.5f * (attribute_rect.Min.y + attribute_rect.Max.y));
}
......@@ -1017,11 +1032,17 @@ get_screen_space_pin_coordinates(const EditorContext& editor,
parent_node_rect, pin.attribute_rect, pin.type);
}
// These functions are here, and not members of the BoxSelector struct, because
// implementing a C API in C++ is frustrating. EditorContext has a BoxSelector
// field, but the state changes depend on the editor. So, these are implemented
// as C-style free functions so that the code is not too much of a mish-mash of
// C functions and C++ method definitions.
bool
mouse_in_canvas()
{
// This flag should be true either when hovering or clicking something in
// the canvas.
const bool is_window_hovered_or_focused =
ImGui::IsWindowHovered() || ImGui::IsWindowFocused();
return is_window_hovered_or_focused &&
g->canvas_rect_screen_space.Contains(ImGui::GetMousePos());
}
void
begin_node_selection(EditorContext& editor, const int node_idx)
......@@ -1075,7 +1096,7 @@ begin_link_detach(EditorContext& editor,
state.link_creation.start_pin_idx = detach_pin_idx == link.start_pin_idx
? link.end_pin_idx
: link.start_pin_idx;
g.deleted_link_idx = link_idx;
g->deleted_link_idx = link_idx;
}
void
......@@ -1084,9 +1105,9 @@ begin_link_interaction(EditorContext& editor, const int link_idx)
// First check if we are clicking a link in the vicinity of a pin.
// This may result in a link detach via click and drag.
if (editor.click_interaction_type == ClickInteractionType_LinkCreation) {
if ((g.hovered_pin_flags &
if ((g->hovered_pin_flags &
AttributeFlags_EnableLinkDetachWithDragClick) != 0) {
begin_link_detach(editor, link_idx, g.hovered_pin_idx.value());
begin_link_detach(editor, link_idx, g->hovered_pin_idx.value());
editor.click_interaction_state.link_creation.link_creation_type =
LinkCreationType_FromDetach;
}
......@@ -1095,15 +1116,15 @@ begin_link_interaction(EditorContext& editor, const int link_idx)
// modifier pressed. This may also result in a link detach via clicking.
else {
const bool modifier_pressed =
g.io.link_detach_with_modifier_click.modifier == NULL
g->io.link_detach_with_modifier_click.modifier == NULL
? false
: *g.io.link_detach_with_modifier_click.modifier;
: *g->io.link_detach_with_modifier_click.modifier;
if (modifier_pressed) {
const LinkData& link = editor.links.pool[link_idx];
const PinData& start_pin = editor.pins.pool[link.start_pin_idx];
const PinData& end_pin = editor.pins.pool[link.end_pin_idx];
const ImVec2& mouse_pos = g.mouse_pos;
const ImVec2& mouse_pos = g->mouse_pos;
const float dist_to_start = ImLengthSqr(start_pin.pos - mouse_pos);
const float dist_to_end = ImLengthSqr(end_pin.pos - mouse_pos);
const int closest_pin_idx = dist_to_start < dist_to_end
......@@ -1112,6 +1133,8 @@ begin_link_interaction(EditorContext& editor, const int link_idx)
editor.click_interaction_type = ClickInteractionType_LinkCreation;
begin_link_detach(editor, link_idx, closest_pin_idx);
editor.click_interaction_state.link_creation.link_creation_type =
LinkCreationType_FromDetach;
} else {
begin_link_selection(editor, link_idx);
}
......@@ -1127,35 +1150,30 @@ begin_link_creation(EditorContext& editor, const int hovered_pin_idx)
editor.click_interaction_state.link_creation.end_pin_idx.reset();
editor.click_interaction_state.link_creation.link_creation_type =
LinkCreationType_Standard;
g.element_state_change |= ElementStateChange_LinkStarted;
g->element_state_change |= ElementStateChange_LinkStarted;
}
void
begin_canvas_interaction(EditorContext& editor)
{
const bool any_ui_element_hovered =
g.hovered_node_idx.has_value() || g.hovered_link_idx.has_value() ||
g.hovered_pin_idx.has_value() || ImGui::IsAnyItemHovered();
g->hovered_node_idx.has_value() || g->hovered_link_idx.has_value() ||
g->hovered_pin_idx.has_value() || ImGui::IsAnyItemHovered();
const bool mouse_not_in_canvas =
!(g.canvas_rect_screen_space.Contains(ImGui::GetMousePos()) &&
ImGui::IsWindowHovered());
const bool mouse_not_in_canvas = !mouse_in_canvas();
if (any_ui_element_hovered || mouse_not_in_canvas) {
if (editor.click_interaction_type != ClickInteractionType_None ||
any_ui_element_hovered || mouse_not_in_canvas) {
return;
}
const bool started_panning =
g.io.emulate_three_button_mouse.enabled
? (g.left_mouse_clicked && *g.io.emulate_three_button_mouse.modifier)
: g.middle_mouse_clicked;
const bool started_panning = g->alt_mouse_clicked;
editor.click_interaction_type = started_panning
? ClickInteractionType_Panning
: ClickInteractionType_BoxSelection;
if (editor.click_interaction_type == ClickInteractionType_BoxSelection) {
editor.click_interaction_state.box_selector.rect.Min = g.mouse_pos;
if (started_panning) {
editor.click_interaction_type = ClickInteractionType_Panning;
} else if (g->left_mouse_clicked) {
editor.click_interaction_type = ClickInteractionType_BoxSelection;
editor.click_interaction_state.box_selector.rect.Min = g->mouse_pos;
}
}
......@@ -1220,7 +1238,7 @@ box_selector_update_selection(EditorContext& editor, ImRect box_rect)
void
translate_selected_nodes(EditorContext& editor)
{
if (g.left_mouse_dragging) {
if (g->left_mouse_dragging) {
const ImGuiIO& io = ImGui::GetIO();
for (int i = 0; i < editor.selected_node_indices.size(); ++i) {
const int node_idx = editor.selected_node_indices[i];
......@@ -1271,7 +1289,7 @@ should_link_snap_to_pin(const EditorContext& editor,
// The link to be created must not be a duplicate, unless it is the link
// which was created on snap. In that case we want to snap, since we want it
// to appear visually as if the created link remains snapped to the pin.
if (duplicate_link.has_value() && !(duplicate_link == g.snap_link_idx)) {
if (duplicate_link.has_value() && !(duplicate_link == g->snap_link_idx)) {
return false;
}
......@@ -1284,19 +1302,20 @@ click_interaction_update(EditorContext& editor)
switch (editor.click_interaction_type) {
case ClickInteractionType_BoxSelection: {
ImRect& box_rect = editor.click_interaction_state.box_selector.rect;
box_rect.Max = g.mouse_pos;
box_rect.Max = g->mouse_pos;
box_selector_update_selection(editor, box_rect);
const ImU32 box_selector_color = g.style.colors[ColorStyle_BoxSelector];
const ImU32 box_selector_color =
g->style.colors[ColorStyle_BoxSelector];
const ImU32 box_selector_outline =
g.style.colors[ColorStyle_BoxSelectorOutline];
g.canvas_draw_list->AddRectFilled(
g->style.colors[ColorStyle_BoxSelectorOutline];
g->canvas_draw_list->AddRectFilled(
box_rect.Min, box_rect.Max, box_selector_color);
g.canvas_draw_list->AddRect(
g->canvas_draw_list->AddRect(
box_rect.Min, box_rect.Max, box_selector_outline);
if (g.left_mouse_released) {
if (g->left_mouse_released) {
ImVector<int>& depth_stack = editor.node_depth_order;
const ImVector<int>& selected_idxs = editor.selected_node_indices;
......@@ -1332,12 +1351,12 @@ click_interaction_update(EditorContext& editor)
case ClickInteractionType_Node: {
translate_selected_nodes(editor);
if (g.left_mouse_released) {
if (g->left_mouse_released) {
editor.click_interaction_type = ClickInteractionType_None;
}
} break;
case ClickInteractionType_Link: {
if (g.left_mouse_released) {
if (g->left_mouse_released) {
editor.click_interaction_type = ClickInteractionType_None;
}
} break;
......@@ -1347,18 +1366,18 @@ click_interaction_update(EditorContext& editor)
.pool[editor.click_interaction_state.link_creation.start_pin_idx];
const OptionalIndex maybe_duplicate_link_idx =
g.hovered_pin_idx.has_value()
g->hovered_pin_idx.has_value()
? find_duplicate_link(
editor,
editor.click_interaction_state.link_creation.start_pin_idx,
g.hovered_pin_idx.value())
g->hovered_pin_idx.value())
: OptionalIndex();
const bool should_snap =
g.hovered_pin_idx.has_value() &&
g->hovered_pin_idx.has_value() &&
should_link_snap_to_pin(editor,
start_pin,
g.hovered_pin_idx.value(),
g->hovered_pin_idx.value(),
maybe_duplicate_link_idx);
// If we created on snap and the hovered pin is empty or changed, then
......@@ -1366,15 +1385,15 @@ click_interaction_update(EditorContext& editor)
const bool snapping_pin_changed =
editor.click_interaction_state.link_creation.end_pin_idx
.has_value() &&
!(g.hovered_pin_idx ==
!(g->hovered_pin_idx ==
editor.click_interaction_state.link_creation.end_pin_idx);
// Detach the link that was created by this link event if it's no longer
// in snap range
if (snapping_pin_changed && g.snap_link_idx.has_value()) {
if (snapping_pin_changed && g->snap_link_idx.has_value()) {
begin_link_detach(
editor,
g.snap_link_idx.value(),
g->snap_link_idx.value(),
editor.click_interaction_state.link_creation.end_pin_idx.value());
}
......@@ -1384,25 +1403,30 @@ click_interaction_update(EditorContext& editor)
// endpoint to it
const ImVec2 end_pos =
should_snap ? get_screen_space_pin_coordinates(
editor, editor.pins.pool[g.hovered_pin_idx.value()])
: g.mouse_pos;
editor, editor.pins.pool[g->hovered_pin_idx.value()])
: g->mouse_pos;
const LinkBezierData link_data =
get_link_renderable(start_pos,
end_pos,
start_pin.type,
g.style.link_line_segments_per_length);
g.canvas_draw_list->AddBezierCurve(link_data.bezier.p0,
link_data.bezier.p1,
link_data.bezier.p2,
link_data.bezier.p3,
g.style.colors[ColorStyle_Link],
g.style.link_thickness,
link_data.num_segments);
g->style.link_line_segments_per_length);
#if IMGUI_VERSION_NUM < 18000
g->canvas_draw_list->AddBezierCurve(
#else
g->canvas_draw_list->AddBezierCubic(
#endif
link_data.bezier.p0,
link_data.bezier.p1,
link_data.bezier.p2,
link_data.bezier.p3,
g->style.colors[ColorStyle_Link],
g->style.link_thickness,
link_data.num_segments);