diff --git a/external/ImGui/include/imnodes.h b/external/ImGui/include/imnodes.h index 80febad1e..edabdbf2d 100644 --- a/external/ImGui/include/imnodes.h +++ b/external/ImGui/include/imnodes.h @@ -2,174 +2,219 @@ #include -struct ImVec2; +typedef int ImNodesCol; // -> enum ImNodesCol_ +typedef int ImNodesStyleVar; // -> enum ImNodesStyleVar_ +typedef int ImNodesStyleFlags; // -> enum ImNodesStyleFlags_ +typedef int ImNodesPinShape; // -> enum ImNodesPinShape_ +typedef int ImNodesAttributeFlags; // -> enum ImNodesAttributeFlags_ +typedef int ImNodesMiniMapLocation; // -> enum ImNodesMiniMapLocation_ -namespace imnodes +enum ImNodesCol_ { -enum ColorStyle -{ - ColorStyle_NodeBackground = 0, - ColorStyle_NodeBackgroundHovered, - ColorStyle_NodeBackgroundSelected, - ColorStyle_NodeOutline, - ColorStyle_TitleBar, - ColorStyle_TitleBarHovered, - ColorStyle_TitleBarSelected, - ColorStyle_Link, - ColorStyle_LinkHovered, - ColorStyle_LinkSelected, - ColorStyle_Pin, - ColorStyle_PinHovered, - ColorStyle_BoxSelector, - ColorStyle_BoxSelectorOutline, - ColorStyle_GridBackground, - ColorStyle_GridLine, - ColorStyle_Count + ImNodesCol_NodeBackground = 0, + ImNodesCol_NodeBackgroundHovered, + ImNodesCol_NodeBackgroundSelected, + ImNodesCol_NodeOutline, + ImNodesCol_TitleBar, + ImNodesCol_TitleBarHovered, + ImNodesCol_TitleBarSelected, + ImNodesCol_Link, + ImNodesCol_LinkHovered, + ImNodesCol_LinkSelected, + ImNodesCol_Pin, + ImNodesCol_PinHovered, + ImNodesCol_BoxSelector, + ImNodesCol_BoxSelectorOutline, + ImNodesCol_GridBackground, + ImNodesCol_GridLine, + ImNodesCol_MiniMapBackground, + ImNodesCol_MiniMapBackgroundHovered, + ImNodesCol_MiniMapOutline, + ImNodesCol_MiniMapOutlineHovered, + ImNodesCol_MiniMapNodeBackground, + ImNodesCol_MiniMapNodeBackgroundHovered, + ImNodesCol_MiniMapNodeBackgroundSelected, + ImNodesCol_MiniMapNodeOutline, + ImNodesCol_MiniMapLink, + ImNodesCol_MiniMapLinkSelected, + ImNodesCol_COUNT }; -enum StyleVar +enum ImNodesStyleVar_ { - StyleVar_GridSpacing = 0, - StyleVar_NodeCornerRounding, - StyleVar_NodePaddingHorizontal, - StyleVar_NodePaddingVertical, - StyleVar_NodeBorderThickness, - StyleVar_LinkThickness, - StyleVar_LinkLineSegmentsPerLength, - StyleVar_LinkHoverDistance, - StyleVar_PinCircleRadius, - StyleVar_PinQuadSideLength, - StyleVar_PinTriangleSideLength, - StyleVar_PinLineThickness, - StyleVar_PinHoverRadius, - StyleVar_PinOffset + ImNodesStyleVar_GridSpacing = 0, + ImNodesStyleVar_NodeCornerRounding, + ImNodesStyleVar_NodePaddingHorizontal, + ImNodesStyleVar_NodePaddingVertical, + ImNodesStyleVar_NodeBorderThickness, + ImNodesStyleVar_LinkThickness, + ImNodesStyleVar_LinkLineSegmentsPerLength, + ImNodesStyleVar_LinkHoverDistance, + ImNodesStyleVar_PinCircleRadius, + ImNodesStyleVar_PinQuadSideLength, + ImNodesStyleVar_PinTriangleSideLength, + ImNodesStyleVar_PinLineThickness, + ImNodesStyleVar_PinHoverRadius, + ImNodesStyleVar_PinOffset }; -enum StyleFlags +enum ImNodesStyleFlags_ { - StyleFlags_None = 0, - StyleFlags_NodeOutline = 1 << 0, - StyleFlags_GridLines = 1 << 2 + ImNodesStyleFlags_None = 0, + ImNodesStyleFlags_NodeOutline = 1 << 0, + ImNodesStyleFlags_GridLines = 1 << 2 }; -// This enum controls the way attribute pins look. -enum PinShape +enum ImNodesPinShape_ { - PinShape_Circle, - PinShape_CircleFilled, - PinShape_Triangle, - PinShape_TriangleFilled, - PinShape_Quad, - PinShape_QuadFilled + ImNodesPinShape_Circle, + ImNodesPinShape_CircleFilled, + ImNodesPinShape_Triangle, + ImNodesPinShape_TriangleFilled, + ImNodesPinShape_Quad, + ImNodesPinShape_QuadFilled }; // This enum controls the way the attribute pins behave. -enum AttributeFlags +enum ImNodesAttributeFlags_ { - AttributeFlags_None = 0, + ImNodesAttributeFlags_None = 0, // Allow detaching a link by left-clicking and dragging the link at a pin it is connected to. // NOTE: the user has to actually delete the link for this to work. A deleted link can be // detected by calling IsLinkDestroyed() after EndNodeEditor(). - AttributeFlags_EnableLinkDetachWithDragClick = 1 << 0, + ImNodesAttributeFlags_EnableLinkDetachWithDragClick = 1 << 0, // Visual snapping of an in progress link will trigger IsLink Created/Destroyed events. Allows // for previewing the creation of a link while dragging it across attributes. See here for demo: // https://github.com/Nelarius/imnodes/issues/41#issuecomment-647132113 NOTE: the user has to // actually delete the link for this to work. A deleted link can be detected by calling // IsLinkDestroyed() after EndNodeEditor(). - AttributeFlags_EnableLinkCreationOnSnap = 1 << 1 + ImNodesAttributeFlags_EnableLinkCreationOnSnap = 1 << 1 }; -struct IO +struct ImNodesIO { struct EmulateThreeButtonMouse { EmulateThreeButtonMouse(); - // Controls whether this feature is enabled or not. - bool enabled; - const bool* modifier; // The keyboard modifier to use with the mouse left click. Set to - // &ImGuiIO::KeyAlt by default. - } emulate_three_button_mouse; + // The keyboard modifier to use in combination with mouse left click to pan the editor view. + // Set to NULL by default. To enable this feature, set the modifier to point to a boolean + // indicating the state of a modifier. For example, + // + // ImNodes::GetIO().EmulateThreeButtonMouse.Modifier = &ImGui::GetIO().KeyAlt; + const bool* Modifier; + } EmulateThreeButtonMouse; struct LinkDetachWithModifierClick { LinkDetachWithModifierClick(); // Pointer to a boolean value indicating when the desired modifier is pressed. Set to NULL - // by default (i.e. this feature is disabled). To enable the feature, set the link to point - // to, for example, &ImGuiIO::KeyCtrl. + // by default. To enable the feature, set the modifier to point to a boolean indicating the + // state of a modifier. For example, + // + // ImNodes::GetIO().LinkDetachWithModifierClick.Modifier = &ImGui::GetIO().KeyCtrl; // // Left-clicking a link with this modifier pressed will detach that link. NOTE: the user has // to actually delete the link for this to work. A deleted link can be detected by calling // IsLinkDestroyed() after EndNodeEditor(). - const bool* modifier; - } link_detach_with_modifier_click; + const bool* Modifier; + } LinkDetachWithModifierClick; - IO(); + // Holding alt mouse button pans the node area, by default middle mouse button will be used + // Set based on ImGuiMouseButton values + int AltMouseButton; + + ImNodesIO(); }; -struct Style +struct ImNodesStyle { - float grid_spacing; + float GridSpacing; - float node_corner_rounding; - float node_padding_horizontal; - float node_padding_vertical; - float node_border_thickness; + float NodeCornerRounding; + float NodePaddingHorizontal; + float NodePaddingVertical; + float NodeBorderThickness; - float link_thickness; - float link_line_segments_per_length; - float link_hover_distance; + float LinkThickness; + float LinkLineSegmentsPerLength; + float LinkHoverDistance; // The following variables control the look and behavior of the pins. The default size of each // pin shape is balanced to occupy approximately the same surface area on the screen. - // The circle radius used when the pin shape is either PinShape_Circle or PinShape_CircleFilled. - float pin_circle_radius; - // The quad side length used when the shape is either PinShape_Quad or PinShape_QuadFilled. - float pin_quad_side_length; - // The equilateral triangle side length used when the pin shape is either PinShape_Triangle or - // PinShape_TriangleFilled. - float pin_triangle_side_length; + // The circle radius used when the pin shape is either ImNodesPinShape_Circle or + // ImNodesPinShape_CircleFilled. + float PinCircleRadius; + // The quad side length used when the shape is either ImNodesPinShape_Quad or + // ImNodesPinShape_QuadFilled. + float PinQuadSideLength; + // The equilateral triangle side length used when the pin shape is either + // ImNodesPinShape_Triangle or ImNodesPinShape_TriangleFilled. + float PinTriangleSideLength; // The thickness of the line used when the pin shape is not filled. - float pin_line_thickness; + float PinLineThickness; // The radius from the pin's center position inside of which it is detected as being hovered // over. - float pin_hover_radius; + float PinHoverRadius; // Offsets the pins' positions from the edge of the node to the outside of the node. - float pin_offset; + float PinOffset; - // By default, StyleFlags_NodeOutline and StyleFlags_Gridlines are enabled. - StyleFlags flags; + // By default, ImNodesStyleFlags_NodeOutline and ImNodesStyleFlags_Gridlines are enabled. + ImNodesStyleFlags Flags; // Set these mid-frame using Push/PopColorStyle. You can index this color array with with a - // ColorStyle enum value. - unsigned int colors[ColorStyle_Count]; + // ImNodesCol value. + unsigned int Colors[ImNodesCol_COUNT]; - Style(); + ImNodesStyle(); }; +enum ImNodesMiniMapLocation_ +{ + ImNodesMiniMapLocation_BottomLeft, + ImNodesMiniMapLocation_BottomRight, + ImNodesMiniMapLocation_TopLeft, + ImNodesMiniMapLocation_TopRight, +}; + +struct ImGuiContext; +struct ImVec2; + +struct ImNodesContext; + // An editor context corresponds to a set of nodes in a single workspace (created with a single // Begin/EndNodeEditor pair) // // By default, the library creates an editor context behind the scenes, so using any of the imnodes // functions doesn't require you to explicitly create a context. -struct EditorContext; +struct ImNodesEditorContext; -EditorContext* EditorContextCreate(); -void EditorContextFree(EditorContext*); -void EditorContextSet(EditorContext*); -ImVec2 EditorContextGetPanning(); -void EditorContextResetPanning(const ImVec2& pos); -void EditorContextMoveToNode(const int node_id); +// Callback type used to specify special behavior when hovering a node in the minimap +typedef void (*ImNodesMiniMapNodeHoveringCallback)(int, void*); -// Initialize the node editor system. -void Initialize(); -void Shutdown(); +namespace ImNodes +{ +// Call this function if you are compiling imnodes in to a dll, separate from ImGui. Calling this +// function sets the GImGui global variable, which is not shared across dll boundaries. +void SetImGuiContext(ImGuiContext* ctx); -IO& GetIO(); +ImNodesContext* CreateContext(); +void DestroyContext(ImNodesContext* ctx = NULL); // NULL = destroy current context +ImNodesContext* GetCurrentContext(); +void SetCurrentContext(ImNodesContext* ctx); + +ImNodesEditorContext* EditorContextCreate(); +void EditorContextFree(ImNodesEditorContext*); +void EditorContextSet(ImNodesEditorContext*); +ImVec2 EditorContextGetPanning(); +void EditorContextResetPanning(const ImVec2& pos); +void EditorContextMoveToNode(const int node_id); + +ImNodesIO& GetIO(); // Returns the global style struct. See the struct declaration for default values. -Style& GetStyle(); +ImNodesStyle& GetStyle(); // Style presets matching the dear imgui styles of the same name. void StyleColorsDark(); // on by default void StyleColorsClassic(); @@ -180,10 +225,18 @@ void StyleColorsLight(); void BeginNodeEditor(); void EndNodeEditor(); -// Use PushColorStyle and PopColorStyle to modify Style::colors mid-frame. -void PushColorStyle(ColorStyle item, unsigned int color); +// Add a navigable minimap to the editor; call before EndNodeEditor after all +// nodes and links have been specified +void MiniMap( + const float minimap_size_fraction = 0.2f, + const ImNodesMiniMapLocation location = ImNodesMiniMapLocation_TopLeft, + const ImNodesMiniMapNodeHoveringCallback node_hovering_callback = NULL, + void* node_hovering_callback_data = NULL); + +// Use PushColorStyle and PopColorStyle to modify ImNodesStyle::Colors mid-frame. +void PushColorStyle(ImNodesCol item, unsigned int color); void PopColorStyle(); -void PushStyleVar(StyleVar style_item, float value); +void PushStyleVar(ImNodesStyleVar style_item, float value); void PopStyleVar(); // id can be any positive or negative integer, but INT_MIN is currently reserved for internal use. @@ -208,10 +261,10 @@ void EndNodeTitleBar(); // Each attribute id must be unique. // Create an input attribute block. The pin is rendered on left side. -void BeginInputAttribute(int id, PinShape shape = PinShape_CircleFilled); +void BeginInputAttribute(int id, ImNodesPinShape shape = ImNodesPinShape_CircleFilled); void EndInputAttribute(); // Create an output attribute block. The pin is rendered on the right side. -void BeginOutputAttribute(int id, PinShape shape = PinShape_CircleFilled); +void BeginOutputAttribute(int 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 @@ -220,7 +273,7 @@ void BeginStaticAttribute(int id); void EndStaticAttribute(); // Push a single AttributeFlags value. By default, only AttributeFlags_None is set. -void PushAttributeFlag(AttributeFlags flag); +void PushAttributeFlag(ImNodesAttributeFlags flag); void PopAttributeFlag(); // Render a link between attributes. @@ -267,10 +320,21 @@ int NumSelectedLinks(); // returned. void GetSelectedNodes(int* node_ids); void GetSelectedLinks(int* link_ids); - // Clears the list of selected nodes/links. Useful if you want to delete a selected node or link. void ClearNodeSelection(); void ClearLinkSelection(); +// Use the following functions to add or remove individual nodes or links from the current editors +// selection. Note that all functions require the id to be an existing valid id for this editor. +// Select-functions has the precondition that the object is currently considered unselected. +// 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); // 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. @@ -292,14 +356,14 @@ bool IsLinkStarted(int* started_at_attribute_id); bool IsLinkDropped(int* 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, + int* started_at_attribute_id, + int* 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, + int* started_at_node_id, + int* started_at_attribute_id, + int* ended_at_node_id, + int* 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 @@ -310,14 +374,16 @@ bool IsLinkDestroyed(int* link_id); // file. The editor context is serialized in the INI file format. const char* SaveCurrentEditorStateToIniString(size_t* data_size = NULL); -const char* SaveEditorStateToIniString(const EditorContext* editor, size_t* data_size = NULL); +const char* SaveEditorStateToIniString( + const ImNodesEditorContext* editor, + size_t* data_size = NULL); void LoadCurrentEditorStateFromIniString(const char* data, size_t data_size); -void LoadEditorStateFromIniString(EditorContext* editor, const char* data, size_t data_size); +void LoadEditorStateFromIniString(ImNodesEditorContext* editor, const char* data, size_t data_size); void SaveCurrentEditorStateToIniFile(const char* file_name); -void SaveEditorStateToIniFile(const EditorContext* editor, const char* file_name); +void SaveEditorStateToIniFile(const ImNodesEditorContext* editor, const char* file_name); void LoadCurrentEditorStateFromIniFile(const char* file_name); -void LoadEditorStateFromIniFile(EditorContext* editor, const char* file_name); -} // namespace imnodes +void LoadEditorStateFromIniFile(ImNodesEditorContext* editor, const char* file_name); +} // namespace ImNodes diff --git a/external/ImGui/include/imnodes_internal.h b/external/ImGui/include/imnodes_internal.h new file mode 100644 index 000000000..6a2162820 --- /dev/null +++ b/external/ImGui/include/imnodes_internal.h @@ -0,0 +1,482 @@ +#pragma once + +#include +#define IMGUI_DEFINE_MATH_OPERATORS +#include + +#include +#include + +// the structure of this file: +// +// [SECTION] internal enums +// [SECTION] internal data structures +// [SECTION] global and editor context structs +// [SECTION] object pool implementation + +struct ImNodesContext; + +extern ImNodesContext* GImNodes; + +// [SECTION] internal enums + +typedef int ImNodesScope; +typedef int ImNodesAttributeType; +typedef int ImNodesUIState; +typedef int ImNodesClickInteractionType; +typedef int ImNodesLinkCreationType; + +enum ImNodesScope_ +{ + ImNodesScope_None = 1, + ImNodesScope_Editor = 1 << 1, + ImNodesScope_Node = 1 << 2, + ImNodesScope_Attribute = 1 << 3 +}; + +enum ImNodesAttributeType_ +{ + ImNodesAttributeType_None, + ImNodesAttributeType_Input, + ImNodesAttributeType_Output +}; + +enum ImNodesUIState_ +{ + ImNodesUIState_None = 0, + ImNodesUIState_LinkStarted = 1 << 0, + ImNodesUIState_LinkDropped = 1 << 1, + ImNodesUIState_LinkCreated = 1 << 2 +}; + +enum ImNodesClickInteractionType_ +{ + ImNodesClickInteractionType_Node, + ImNodesClickInteractionType_Link, + ImNodesClickInteractionType_LinkCreation, + ImNodesClickInteractionType_Panning, + ImNodesClickInteractionType_BoxSelection, + ImNodesClickInteractionType_MiniMapPanning, + ImNodesClickInteractionType_MiniMapZooming, + ImNodesClickInteractionType_MiniMapSnapping, + ImNodesClickInteractionType_ImGuiItem, + ImNodesClickInteractionType_None +}; + +enum ImNodesLinkCreationType_ +{ + ImNodesLinkCreationType_Standard, + ImNodesLinkCreationType_FromDetach +}; + +// Callback type used to specify special behavior when hovering a node in the minimap +typedef void (*ImNodesMiniMapNodeHoveringCallback)(int, void*); + +// [SECTION] internal data structures + +// The object T must have the following interface: +// +// struct T +// { +// T(); +// +// int id; +// }; +template +struct ImObjectPool +{ + ImVector Pool; + ImVector InUse; + ImVector FreeList; + ImGuiStorage IdMap; + + ImObjectPool() : Pool(), InUse(), FreeList(), IdMap() {} +}; + +// Emulates std::optional using the sentinel value `INVALID_INDEX`. +struct ImOptionalIndex +{ + ImOptionalIndex() : _Index(INVALID_INDEX) {} + ImOptionalIndex(const int value) : _Index(value) {} + + // Observers + + inline bool HasValue() const { return _Index != INVALID_INDEX; } + + inline int Value() const + { + assert(HasValue()); + return _Index; + } + + // Modifiers + + inline ImOptionalIndex& operator=(const int value) + { + _Index = value; + return *this; + } + + inline void Reset() { _Index = INVALID_INDEX; } + + inline bool operator==(const ImOptionalIndex& rhs) const { return _Index == rhs._Index; } + + inline bool operator==(const int rhs) const { return _Index == rhs; } + + inline bool operator!=(const ImOptionalIndex& rhs) const { return _Index != rhs._Index; } + + inline bool operator!=(const int rhs) const { return _Index != rhs; } + + static const int INVALID_INDEX = -1; + +private: + int _Index; +}; + +struct ImNodeData +{ + int Id; + ImVec2 Origin; // The node origin is in editor space + ImRect TitleBarContentRect; + ImRect Rect; + + struct + { + ImU32 Background, BackgroundHovered, BackgroundSelected, Outline, Titlebar, TitlebarHovered, + TitlebarSelected; + } ColorStyle; + + struct + { + float CornerRounding; + ImVec2 Padding; + float BorderThickness; + } LayoutStyle; + + ImVector PinIndices; + bool Draggable; + + ImNodeData(const int node_id) + : Id(node_id), Origin(100.0f, 100.0f), TitleBarContentRect(), + Rect(ImVec2(0.0f, 0.0f), ImVec2(0.0f, 0.0f)), ColorStyle(), LayoutStyle(), PinIndices(), + Draggable(true) + { + } + + ~ImNodeData() { Id = INT_MIN; } +}; + +struct ImPinData +{ + int 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) + : Id(pin_id), ParentNodeIdx(), AttributeRect(), Type(ImNodesAttributeType_None), + Shape(ImNodesPinShape_CircleFilled), Pos(), Flags(ImNodesAttributeFlags_None), + ColorStyle() + { + } +}; + +struct ImLinkData +{ + int Id; + int StartPinIdx, EndPinIdx; + + struct + { + ImU32 Base, Hovered, Selected; + } ColorStyle; + + ImLinkData(const int link_id) : Id(link_id), StartPinIdx(), EndPinIdx(), ColorStyle() {} +}; + +struct ImClickInteractionState +{ + ImNodesClickInteractionType Type; + + struct + { + int StartPinIdx; + ImOptionalIndex EndPinIdx; + ImNodesLinkCreationType Type; + } LinkCreation; + + struct + { + ImRect Rect; + } BoxSelector; + + ImClickInteractionState() : Type(ImNodesClickInteractionType_None) {} +}; + +struct ImNodesColElement +{ + ImU32 Color; + ImNodesCol Item; + + ImNodesColElement(const ImU32 c, const ImNodesCol s) : Color(c), Item(s) {} +}; + +struct ImNodesStyleVarElement +{ + ImNodesStyleVar Item; + float Value; + + ImNodesStyleVarElement(const float value, const ImNodesStyleVar variable) + : Item(variable), Value(value) + { + } +}; + +// [SECTION] global and editor context structs + +struct ImNodesEditorContext +{ + ImObjectPool Nodes; + ImObjectPool Pins; + ImObjectPool Links; + + ImVector NodeDepthOrder; + + // ui related fields + ImVec2 Panning; + + ImVector SelectedNodeIndices; + ImVector SelectedLinkIndices; + + ImClickInteractionState ClickInteraction; + + ImNodesEditorContext() + : Nodes(), Pins(), Links(), Panning(0.f, 0.f), SelectedNodeIndices(), SelectedLinkIndices(), + ClickInteraction() + { + } +}; + +struct ImNodesContext +{ + ImNodesEditorContext* DefaultEditorCtx; + ImNodesEditorContext* EditorCtx; + + // Canvas draw list and helper state + ImDrawList* CanvasDrawList; + ImGuiStorage NodeIdxToSubmissionIdx; + ImVector NodeIdxSubmissionOrder; + ImVector NodeIndicesOverlappingWithMouse; + ImVector OccludedPinIndices; + + // Canvas extents + ImVec2 CanvasOriginScreenSpace; + ImRect CanvasRectScreenSpace; + + // MiniMap state + ImRect MiniMapRectScreenSpace; + ImVec2 MiniMapRectSnappingOffset; + float MiniMapZoom; + ImNodesMiniMapNodeHoveringCallback MiniMapNodeHoveringCallback; + void* MiniMapNodeHoveringCallbackUserData; + + // Debug helpers + ImNodesScope CurrentScope; + + // Configuration state + ImNodesIO Io; + ImNodesStyle Style; + ImVector ColorModifierStack; + ImVector StyleModifierStack; + ImGuiTextBuffer TextBuffer; + + int CurrentAttributeFlags; + ImVector AttributeFlagStack; + + // UI element state + int CurrentNodeIdx; + int CurrentPinIdx; + int CurrentAttributeId; + + ImOptionalIndex HoveredNodeIdx; + ImOptionalIndex HoveredLinkIdx; + ImOptionalIndex HoveredPinIdx; + + ImOptionalIndex DeletedLinkIdx; + ImOptionalIndex SnapLinkIdx; + + // Event helper state + // TODO: this should be a part of a state machine, and not a member of the global struct. + // Unclear what parts of the code this relates to. + int ImNodesUIState; + + int ActiveAttributeId; + bool ActiveAttribute; + + // ImGui::IO cache + + ImVec2 MousePos; + + bool LeftMouseClicked; + bool LeftMouseReleased; + bool AltMouseClicked; + bool LeftMouseDragging; + bool AltMouseDragging; + float AltMouseScrollDelta; +}; + +namespace ImNodes +{ +static inline ImNodesEditorContext& EditorContextGet() +{ + // No editor context was set! Did you forget to call ImNodes::CreateContext()? + assert(GImNodes->EditorCtx != NULL); + return *GImNodes->EditorCtx; +} + +// [SECTION] ObjectPool implementation + +template +static inline int ObjectPoolFind(const ImObjectPool& objects, const int id) +{ + const int index = objects.IdMap.GetInt(static_cast(id), -1); + return index; +} + +template +static inline void ObjectPoolUpdate(ImObjectPool& objects) +{ + objects.FreeList.clear(); + for (int i = 0; i < objects.InUse.size(); ++i) + { + if (!objects.InUse[i]) + { + objects.IdMap.SetInt(objects.Pool[i].Id, -1); + objects.FreeList.push_back(i); + (objects.Pool.Data + i)->~T(); + } + } +} + +template<> +inline void ObjectPoolUpdate(ImObjectPool& nodes) +{ + nodes.FreeList.clear(); + for (int i = 0; i < nodes.InUse.size(); ++i) + { + if (nodes.InUse[i]) + { + nodes.Pool[i].PinIndices.clear(); + } + else + { + const int previous_id = nodes.Pool[i].Id; + const int previous_idx = nodes.IdMap.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& depth_stack = EditorContextGet().NodeDepthOrder; + const int* const elem = depth_stack.find(i); + assert(elem != depth_stack.end()); + depth_stack.erase(elem); + } + + nodes.IdMap.SetInt(previous_id, -1); + nodes.FreeList.push_back(i); + (nodes.Pool.Data + i)->~ImNodeData(); + } + } +} + +template +static inline void ObjectPoolReset(ImObjectPool& objects) +{ + if (!objects.InUse.empty()) + { + memset(objects.InUse.Data, 0, objects.InUse.size_in_bytes()); + } +} + +template +static inline int ObjectPoolFindOrCreateIndex(ImObjectPool& objects, const int id) +{ + int index = objects.IdMap.GetInt(static_cast(id), -1); + + // Construct new object + if (index == -1) + { + if (objects.FreeList.empty()) + { + index = objects.Pool.size(); + IM_ASSERT(objects.Pool.size() == objects.InUse.size()); + const int new_size = objects.Pool.size() + 1; + objects.Pool.resize(new_size); + objects.InUse.resize(new_size); + } + else + { + index = objects.FreeList.back(); + objects.FreeList.pop_back(); + } + IM_PLACEMENT_NEW(objects.Pool.Data + index) T(id); + objects.IdMap.SetInt(static_cast(id), index); + } + + // Flag it as used + objects.InUse[index] = true; + + return index; +} + +template<> +inline int ObjectPoolFindOrCreateIndex(ImObjectPool& nodes, const int node_id) +{ + int node_idx = nodes.IdMap.GetInt(static_cast(node_id), -1); + + // Construct new node + if (node_idx == -1) + { + if (nodes.FreeList.empty()) + { + node_idx = nodes.Pool.size(); + IM_ASSERT(nodes.Pool.size() == nodes.InUse.size()); + const int new_size = nodes.Pool.size() + 1; + nodes.Pool.resize(new_size); + nodes.InUse.resize(new_size); + } + else + { + node_idx = nodes.FreeList.back(); + nodes.FreeList.pop_back(); + } + IM_PLACEMENT_NEW(nodes.Pool.Data + node_idx) ImNodeData(node_id); + nodes.IdMap.SetInt(static_cast(node_id), node_idx); + + ImNodesEditorContext& editor = EditorContextGet(); + editor.NodeDepthOrder.push_back(node_idx); + } + + // Flag node as used + nodes.InUse[node_idx] = true; + + return node_idx; +} + +template +static inline T& ObjectPoolFindOrCreateObject(ImObjectPool& objects, const int id) +{ + const int index = ObjectPoolFindOrCreateIndex(objects, id); + return objects.Pool[index]; +} +} // namespace ImNodes diff --git a/external/ImGui/source/imnodes.cpp b/external/ImGui/source/imnodes.cpp index b9c8e32bd..9692de088 100644 --- a/external/ImGui/source/imnodes.cpp +++ b/external/ImGui/source/imnodes.cpp @@ -1,15 +1,13 @@ // the structure of this file: // -// [SECTION] internal data structures -// [SECTION] global struct -// [SECTION] editor context definition +// [SECTION] bezier curve helpers // [SECTION] draw list helper -// [SECTION] ObjectPool implementation // [SECTION] ui state logic // [SECTION] render helpers // [SECTION] API implementation #include "imnodes.h" +#include "imnodes_internal.h" #include #define IMGUI_DEFINE_MATH_OPERATORS @@ -26,350 +24,56 @@ #include #include #include -#include // strlen, strncmp -#include // for fwrite, ssprintf, sscanf +#include // for fwrite, ssprintf, sscanf #include +#include // strlen, strncmp -namespace imnodes +ImNodesContext* GImNodes = NULL; + +namespace ImNodes { namespace { -enum ScopeFlags +// [SECTION] bezier curve helpers + +struct CubicBezier { - Scope_None = 1, - Scope_Editor = 1 << 1, - Scope_Node = 1 << 2, - Scope_Attribute = 1 << 3 + ImVec2 P0, P1, P2, P3; + int NumSegments; }; -enum AttributeType -{ - AttributeType_None, - AttributeType_Input, - AttributeType_Output -}; - -enum ElementStateChange -{ - ElementStateChange_None = 0, - ElementStateChange_LinkStarted = 1 << 0, - ElementStateChange_LinkDropped = 1 << 1, - ElementStateChange_LinkCreated = 1 << 2 -}; - -// [SECTION] internal data structures - -// The object T must have the following interface: -// -// struct T -// { -// T(); -// -// int id; -// }; -template -struct ObjectPool -{ - ImVector pool; - ImVector in_use; - ImVector free_list; - ImGuiStorage id_map; - - ObjectPool() : pool(), in_use(), free_list(), id_map() {} -}; - -// Emulates std::optional using the sentinel value `invalid_index`. -struct OptionalIndex -{ - OptionalIndex() : m_index(invalid_index) {} - OptionalIndex(const int value) : m_index(value) {} - - // Observers - - inline bool has_value() const { return m_index != invalid_index; } - - inline int value() const - { - assert(has_value()); - return m_index; - } - - // Modifiers - - inline OptionalIndex& operator=(const int value) - { - m_index = value; - return *this; - } - - inline void reset() { m_index = invalid_index; } - - 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: - int m_index; -}; - -struct NodeData -{ - int id; - ImVec2 origin; // The node origin is in editor space - ImRect title_bar_content_rect; - ImRect rect; - - struct - { - ImU32 background, background_hovered, background_selected, outline, titlebar, - titlebar_hovered, titlebar_selected; - } color_style; - - struct - { - float corner_rounding; - ImVec2 padding; - float border_thickness; - } layout_style; - - ImVector pin_indices; - bool draggable; - - NodeData(const int node_id) - : id(node_id), origin(100.0f, 100.0f), title_bar_content_rect(), - rect(ImVec2(0.0f, 0.0f), ImVec2(0.0f, 0.0f)), color_style(), layout_style(), - pin_indices(), draggable(true) - { - } - - ~NodeData() { id = INT_MIN; } -}; - -struct PinData -{ - int id; - int parent_node_idx; - ImRect attribute_rect; - AttributeType type; - PinShape shape; - ImVec2 pos; // screen-space coordinates - int flags; - - struct - { - ImU32 background, hovered; - } color_style; - - PinData(const int pin_id) - : id(pin_id), parent_node_idx(), attribute_rect(), type(AttributeType_None), - shape(PinShape_CircleFilled), pos(), flags(AttributeFlags_None), color_style() - { - } -}; - -struct LinkData -{ - int id; - int start_pin_idx, end_pin_idx; - - struct - { - ImU32 base, hovered, selected; - } color_style; - - LinkData(const int link_id) : id(link_id), start_pin_idx(), end_pin_idx(), color_style() {} -}; - -struct LinkPredicate -{ - bool operator()(const LinkData& lhs, const LinkData& rhs) const - { - // Do a unique compare by sorting the pins' addresses. - // This catches duplicate links, whether they are in the - // same direction or not. - // Sorting by pin index should have the uniqueness guarantees as sorting - // by id -- each unique id will get one slot in the link pool array. - - int lhs_start = lhs.start_pin_idx; - int lhs_end = lhs.end_pin_idx; - int rhs_start = rhs.start_pin_idx; - int rhs_end = rhs.end_pin_idx; - - if (lhs_start > lhs_end) - { - ImSwap(lhs_start, lhs_end); - } - - if (rhs_start > rhs_end) - { - ImSwap(rhs_start, rhs_end); - } - - return lhs_start == rhs_start && lhs_end == rhs_end; - } -}; - -struct BezierCurve -{ - // the curve control points - ImVec2 p0, p1, p2, p3; -}; - -struct LinkBezierData -{ - BezierCurve bezier; - int num_segments; -}; - -enum ClickInteractionType -{ - ClickInteractionType_Node, - ClickInteractionType_Link, - ClickInteractionType_LinkCreation, - ClickInteractionType_Panning, - ClickInteractionType_BoxSelection, - ClickInteractionType_None -}; - -enum LinkCreationType -{ - LinkCreationType_Standard, - LinkCreationType_FromDetach -}; - -struct ClickInteractionState -{ - struct - { - int start_pin_idx; - OptionalIndex end_pin_idx; - LinkCreationType link_creation_type; - } link_creation; - - struct - { - ImRect rect; - } box_selector; -}; - -struct ColorStyleElement -{ - ImU32 color; - ColorStyle item; - - ColorStyleElement(const ImU32 c, const ColorStyle s) : color(c), item(s) {} -}; - -struct StyleElement -{ - StyleVar item; - float value; - - StyleElement(const float value, const StyleVar variable) : item(variable), value(value) {} -}; - -// [SECTION] global struct -// this stores data which only lives for one frame -struct -{ - EditorContext* default_editor_ctx; - EditorContext* editor_ctx; - - // Canvas draw list and helper state - ImDrawList* canvas_draw_list; - ImGuiStorage node_idx_to_submission_idx; - ImVector node_idx_submission_order; - ImVector node_indices_overlapping_with_mouse; - - // Canvas extents - ImVec2 canvas_origin_screen_space; - ImRect canvas_rect_screen_space; - - // Debug helpers - ScopeFlags current_scope; - - // Configuration state - IO io; - Style style; - ImVector color_modifier_stack; - ImVector style_modifier_stack; - ImGuiTextBuffer text_buffer; - - int current_attribute_flags; - ImVector attribute_flag_stack; - - // UI element state - int current_node_idx; - int current_pin_idx; - int current_attribute_id; - - OptionalIndex hovered_node_idx; - OptionalIndex interactive_node_idx; - OptionalIndex hovered_link_idx; - OptionalIndex hovered_pin_idx; - int hovered_pin_flags; - - OptionalIndex deleted_link_idx; - OptionalIndex snap_link_idx; - - // Event helper state - int element_state_change; - - int active_attribute_id; - bool active_attribute; - - // ImGui::IO cache - - ImVec2 mouse_pos; - - bool left_mouse_clicked; - bool left_mouse_released; - bool middle_mouse_clicked; - bool left_mouse_dragging; - bool middle_mouse_dragging; -} g; - -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); -} - -inline ImVec2 eval_bezier(float t, const BezierCurve& bezier) +inline ImVec2 EvalCubicBezier( + const float t, + const ImVec2& P0, + const ImVec2& P1, + const ImVec2& P2, + const ImVec2& P3) { // B(t) = (1-t)**3 p0 + 3(1 - t)**2 t P1 + 3(1-t)t**2 P2 + t**3 P3 + + const float u = 1.0f - t; + const float b0 = u * u * u; + const float b1 = 3 * u * u * t; + const float b2 = 3 * u * t * t; + const float b3 = t * t * t; return ImVec2( - (1 - t) * (1 - t) * (1 - t) * bezier.p0.x + 3 * (1 - t) * (1 - t) * t * bezier.p1.x + - 3 * (1 - t) * t * t * bezier.p2.x + t * t * t * bezier.p3.x, - (1 - t) * (1 - t) * (1 - t) * bezier.p0.y + 3 * (1 - t) * (1 - t) * t * bezier.p1.y + - 3 * (1 - t) * t * t * bezier.p2.y + t * t * t * bezier.p3.y); + b0 * P0.x + b1 * P1.x + b2 * P2.x + b3 * P3.x, + b0 * P0.y + b1 * P1.y + b2 * P2.y + b3 * P3.y); } // Calculates the closest point along each bezier curve segment. -ImVec2 get_closest_point_on_cubic_bezier( - const int num_segments, - const ImVec2& p, - const BezierCurve& bezier) +ImVec2 GetClosestPointOnCubicBezier(const int num_segments, const ImVec2& p, const CubicBezier& cb) { IM_ASSERT(num_segments > 0); - ImVec2 p_last = bezier.p0; + ImVec2 p_last = cb.P0; ImVec2 p_closest; - float p_closest_dist = FLT_MAX; - float t_step = 1.0f / (float)num_segments; + float p_closest_dist = FLT_MAX; + float t_step = 1.0f / (float)num_segments; for (int i = 1; i <= num_segments; ++i) { - ImVec2 p_current = eval_bezier(t_step * i, bezier); + ImVec2 p_current = EvalCubicBezier(t_step * i, cb.P0, cb.P1, cb.P2, cb.P3); ImVec2 p_line = ImLineClosestPoint(p_last, p_current, p); - float dist = ImLengthSqr(p - p_line); + float dist = ImLengthSqr(p - p_line); if (dist < p_closest_dist) { p_closest = p_line; @@ -380,83 +84,64 @@ ImVec2 get_closest_point_on_cubic_bezier( return p_closest; } -inline float get_distance_to_cubic_bezier( - const ImVec2& pos, - const BezierCurve& bezier, - const int num_segments) +inline float GetDistanceToCubicBezier( + const ImVec2& pos, + const CubicBezier& cubic_bezier, + const int num_segments) { - const ImVec2 point_on_curve = get_closest_point_on_cubic_bezier(num_segments, pos, bezier); + const ImVec2 point_on_curve = GetClosestPointOnCubicBezier(num_segments, pos, cubic_bezier); const ImVec2 to_curve = point_on_curve - pos; return ImSqrt(ImLengthSqr(to_curve)); } -inline ImRect get_containing_rect_for_bezier_curve(const BezierCurve& bezier) +inline ImRect GetContainingRectForCubicBezier(const CubicBezier& cb) { - const ImVec2 min = ImVec2(ImMin(bezier.p0.x, bezier.p3.x), ImMin(bezier.p0.y, bezier.p3.y)); - const ImVec2 max = ImVec2(ImMax(bezier.p0.x, bezier.p3.x), ImMax(bezier.p0.y, bezier.p3.y)); + const ImVec2 min = ImVec2(ImMin(cb.P0.x, cb.P3.x), ImMin(cb.P0.y, cb.P3.y)); + const ImVec2 max = ImVec2(ImMax(cb.P0.x, cb.P3.x), ImMax(cb.P0.y, cb.P3.y)); - const float hover_distance = g.style.link_hover_distance; + const float hover_distance = GImNodes->Style.LinkHoverDistance; ImRect rect(min, max); - rect.Add(bezier.p1); - rect.Add(bezier.p2); + rect.Add(cb.P1); + rect.Add(cb.P2); rect.Expand(ImVec2(hover_distance, hover_distance)); return rect; } -inline LinkBezierData get_link_renderable( - ImVec2 start, - ImVec2 end, - const AttributeType start_type, - const float line_segments_per_length) +inline CubicBezier GetCubicBezier( + ImVec2 start, + ImVec2 end, + const ImNodesAttributeType start_type, + const float line_segments_per_length) { - assert((start_type == AttributeType_Input) || (start_type == AttributeType_Output)); - if (start_type == AttributeType_Input) + assert( + (start_type == ImNodesAttributeType_Input) || (start_type == ImNodesAttributeType_Output)); + if (start_type == ImNodesAttributeType_Input) { ImSwap(start, end); } - const float link_length = ImSqrt(ImLengthSqr(end - start)); + const float link_length = ImSqrt(ImLengthSqr(end - start)); const ImVec2 offset = ImVec2(0.25f * link_length, 0.f); - LinkBezierData link_data; - link_data.bezier.p0 = start; - link_data.bezier.p1 = start + offset; - link_data.bezier.p2 = end - offset; - link_data.bezier.p3 = end; - link_data.num_segments = ImMax(static_cast(link_length * line_segments_per_length), 1); - return link_data; + CubicBezier cubic_bezier; + cubic_bezier.P0 = start; + cubic_bezier.P1 = start + offset; + cubic_bezier.P2 = end - offset; + cubic_bezier.P3 = end; + cubic_bezier.NumSegments = ImMax(static_cast(link_length * line_segments_per_length), 1); + return cubic_bezier; } -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) +inline float EvalImplicitLineEq(const ImVec2& p1, const ImVec2& p2, const ImVec2& p) { return (p2.y - p1.y) * p.x + (p1.x - p2.x) * p.y + (p2.x * p1.y - p1.x * p2.y); } -inline int sign(float val) { return int(val > 0.0f) - int(val < 0.0f); } +inline int Sign(float val) { return int(val > 0.0f) - int(val < 0.0f); } -inline bool rectangle_overlaps_line_segment(const ImRect& rect, const ImVec2& p1, const ImVec2& p2) +inline bool RectangleOverlapsLineSegment(const ImRect& rect, const ImVec2& p1, const ImVec2& p2) { // Trivial case: rectangle contains an endpoint if (rect.Contains(p1) || rect.Contains(p2)) @@ -487,10 +172,10 @@ inline bool rectangle_overlaps_line_segment(const ImRect& rect, const ImVec2& p1 } const int corner_signs[4] = { - sign(eval_implicit_line_eq(p1, p2, flip_rect.Min)), - sign(eval_implicit_line_eq(p1, p2, ImVec2(flip_rect.Max.x, flip_rect.Min.y))), - sign(eval_implicit_line_eq(p1, p2, ImVec2(flip_rect.Min.x, flip_rect.Max.y))), - sign(eval_implicit_line_eq(p1, p2, flip_rect.Max))}; + Sign(EvalImplicitLineEq(p1, p2, flip_rect.Min)), + Sign(EvalImplicitLineEq(p1, p2, ImVec2(flip_rect.Max.x, flip_rect.Min.y))), + Sign(EvalImplicitLineEq(p1, p2, ImVec2(flip_rect.Min.x, flip_rect.Max.y))), + Sign(EvalImplicitLineEq(p1, p2, flip_rect.Max))}; int sum = 0; int sum_abs = 0; @@ -505,14 +190,20 @@ inline bool rectangle_overlaps_line_segment(const ImRect& rect, const ImVec2& p1 return abs(sum) != sum_abs; } -inline bool rectangle_overlaps_bezier(const ImRect& rectangle, const LinkBezierData& link_data) +inline bool RectangleOverlapsBezier(const ImRect& rectangle, const CubicBezier& cubic_bezier) { - ImVec2 current = eval_bezier(0.f, link_data.bezier); - const float dt = 1.0f / link_data.num_segments; - for (int s = 0; s < link_data.num_segments; ++s) + ImVec2 current = + EvalCubicBezier(0.f, cubic_bezier.P0, cubic_bezier.P1, cubic_bezier.P2, cubic_bezier.P3); + const float dt = 1.0f / cubic_bezier.NumSegments; + for (int s = 0; s < cubic_bezier.NumSegments; ++s) { - ImVec2 next = eval_bezier(static_cast((s + 1) * dt), link_data.bezier); - if (rectangle_overlaps_line_segment(rectangle, current, next)) + ImVec2 next = EvalCubicBezier( + static_cast((s + 1) * dt), + cubic_bezier.P0, + cubic_bezier.P1, + cubic_bezier.P2, + cubic_bezier.P3); + if (RectangleOverlapsLineSegment(rectangle, current, next)) { return true; } @@ -521,11 +212,11 @@ inline bool rectangle_overlaps_bezier(const ImRect& rectangle, const LinkBezierD return false; } -inline bool rectangle_overlaps_link( - const ImRect& rectangle, - const ImVec2& start, - const ImVec2& end, - const AttributeType start_type) +inline bool RectangleOverlapsLink( + const ImRect& rectangle, + const ImVec2& start, + const ImVec2& end, + const ImNodesAttributeType start_type) { // First level: simple rejection test via rectangle overlap: @@ -553,47 +244,17 @@ inline bool rectangle_overlaps_link( // Second level of refinement: do a more expensive test against the // link - const LinkBezierData link_data = - get_link_renderable(start, end, start_type, g.style.link_line_segments_per_length); - return rectangle_overlaps_bezier(rectangle, link_data); + const CubicBezier cubic_bezier = + GetCubicBezier(start, end, start_type, GImNodes->Style.LinkLineSegmentsPerLength); + return RectangleOverlapsBezier(rectangle, cubic_bezier); } return false; } -} // namespace -// [SECTION] editor context definition - -struct EditorContext -{ - ObjectPool nodes; - ObjectPool pins; - ObjectPool links; - - ImVector node_depth_order; - - // ui related fields - ImVec2 panning; - - ImVector selected_node_indices; - ImVector selected_link_indices; - - ClickInteractionType click_interaction_type; - ClickInteractionState click_interaction_state; - - EditorContext() - : nodes(), pins(), links(), panning(0.f, 0.f), selected_node_indices(), - selected_link_indices(), click_interaction_type(ClickInteractionType_None), - click_interaction_state() - { - } -}; - -namespace -{ // [SECTION] draw list helper -void ImDrawList_grow_channels(ImDrawList* draw_list, const int num_channels) +void ImDrawListGrowChannels(ImDrawList* draw_list, const int num_channels) { ImDrawListSplitter& splitter = draw_list->_Splitter; @@ -645,10 +306,10 @@ void ImDrawList_grow_channels(ImDrawList* draw_list, const int num_channels) } } -void ImDrawListSplitter_swap_channels( +void ImDrawListSplitterSwapChannels( ImDrawListSplitter& splitter, - const int lhs_idx, - const int rhs_idx) + const int lhs_idx, + const int rhs_idx) { if (lhs_idx == rhs_idx) { @@ -675,11 +336,11 @@ void ImDrawListSplitter_swap_channels( } } -void draw_list_set(ImDrawList* window_draw_list) +void DrawListSet(ImDrawList* window_draw_list) { - g.canvas_draw_list = window_draw_list; - g.node_idx_to_submission_idx.Clear(); - g.node_idx_submission_order.clear(); + GImNodes->CanvasDrawList = window_draw_list; + GImNodes->NodeIdxToSubmissionIdx.Clear(); + GImNodes->NodeIdxSubmissionOrder.clear(); } // The draw list channels are structured as follows. First we have our base channel, the canvas grid @@ -701,49 +362,50 @@ void draw_list_set(ImDrawList* window_draw_list) // | | // ----------------------- -void draw_list_add_node(const int node_idx) +void DrawListAddNode(const int node_idx) { - g.node_idx_to_submission_idx.SetInt( - static_cast(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); + GImNodes->NodeIdxToSubmissionIdx.SetInt( + static_cast(node_idx), GImNodes->NodeIdxSubmissionOrder.Size); + GImNodes->NodeIdxSubmissionOrder.push_back(node_idx); + ImDrawListGrowChannels(GImNodes->CanvasDrawList, 2); } -void draw_list_append_click_interaction_channel() +void DrawListAppendClickInteractionChannel() { // 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); + ImDrawListGrowChannels(GImNodes->CanvasDrawList, 1); } -int draw_list_submission_idx_to_background_channel_idx(const int submission_idx) +int DrawListSubmissionIdxToBackgroundChannelIdx(const int submission_idx) { // NOTE: the first channel is the canvas background, i.e. the grid return 1 + 2 * submission_idx; } -int draw_list_submission_idx_to_foreground_channel_idx(const int submission_idx) +int DrawListSubmissionIdxToForegroundChannelIdx(const int submission_idx) { - return draw_list_submission_idx_to_background_channel_idx(submission_idx) + 1; + return DrawListSubmissionIdxToBackgroundChannelIdx(submission_idx) + 1; } -void draw_list_activate_click_interaction_channel() +void DrawListActivateClickInteractionChannel() { - g.canvas_draw_list->_Splitter.SetCurrentChannel( - g.canvas_draw_list, g.canvas_draw_list->_Splitter._Count - 1); + GImNodes->CanvasDrawList->_Splitter.SetCurrentChannel( + GImNodes->CanvasDrawList, GImNodes->CanvasDrawList->_Splitter._Count - 1); } -void draw_list_activate_current_node_foreground() +void DrawListActivateCurrentNodeForeground() { 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); + DrawListSubmissionIdxToForegroundChannelIdx(GImNodes->NodeIdxSubmissionOrder.Size - 1); + GImNodes->CanvasDrawList->_Splitter.SetCurrentChannel( + GImNodes->CanvasDrawList, foreground_channel_idx); } -void draw_list_activate_node_background(const int node_idx) +void DrawListActivateNodeBackground(const int node_idx) { const int submission_idx = - g.node_idx_to_submission_idx.GetInt(static_cast(node_idx), -1); + GImNodes->NodeIdxToSubmissionIdx.GetInt(static_cast(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 @@ -752,42 +414,42 @@ void draw_list_activate_node_background(const int node_idx) // * SetNodeDraggable // after the BeginNode/EndNode function calls? 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); + const int background_channel_idx = DrawListSubmissionIdxToBackgroundChannelIdx(submission_idx); + GImNodes->CanvasDrawList->_Splitter.SetCurrentChannel( + GImNodes->CanvasDrawList, background_channel_idx); } -void draw_list_swap_submission_indices(const int lhs_idx, const int rhs_idx) +void DrawListSwapSubmissionIndices(const int lhs_idx, const int rhs_idx) { assert(lhs_idx != rhs_idx); - const int lhs_foreground_channel_idx = - draw_list_submission_idx_to_foreground_channel_idx(lhs_idx); - const int lhs_background_channel_idx = - draw_list_submission_idx_to_background_channel_idx(lhs_idx); - const int rhs_foreground_channel_idx = - draw_list_submission_idx_to_foreground_channel_idx(rhs_idx); - const int rhs_background_channel_idx = - draw_list_submission_idx_to_background_channel_idx(rhs_idx); + const int lhs_foreground_channel_idx = DrawListSubmissionIdxToForegroundChannelIdx(lhs_idx); + const int lhs_background_channel_idx = DrawListSubmissionIdxToBackgroundChannelIdx(lhs_idx); + const int rhs_foreground_channel_idx = DrawListSubmissionIdxToForegroundChannelIdx(rhs_idx); + const int rhs_background_channel_idx = DrawListSubmissionIdxToBackgroundChannelIdx(rhs_idx); - ImDrawListSplitter_swap_channels( - g.canvas_draw_list->_Splitter, lhs_background_channel_idx, rhs_background_channel_idx); - ImDrawListSplitter_swap_channels( - g.canvas_draw_list->_Splitter, lhs_foreground_channel_idx, rhs_foreground_channel_idx); + ImDrawListSplitterSwapChannels( + GImNodes->CanvasDrawList->_Splitter, + lhs_background_channel_idx, + rhs_background_channel_idx); + ImDrawListSplitterSwapChannels( + GImNodes->CanvasDrawList->_Splitter, + lhs_foreground_channel_idx, + rhs_foreground_channel_idx); } -void draw_list_sort_channels_by_depth(const ImVector& node_idx_depth_order) +void DrawListSortChannelsByDepth(const ImVector& node_idx_depth_order) { - if (g.node_idx_to_submission_idx.Data.Size < 2) + if (GImNodes->NodeIdxToSubmissionIdx.Data.Size < 2) { return; } - assert(node_idx_depth_order.Size == g.node_idx_submission_order.Size); + assert(node_idx_depth_order.Size == GImNodes->NodeIdxSubmissionOrder.Size); int start_idx = node_idx_depth_order.Size - 1; - while (node_idx_depth_order[start_idx] == g.node_idx_submission_order[start_idx]) + while (node_idx_depth_order[start_idx] == GImNodes->NodeIdxSubmissionOrder[start_idx]) { if (--start_idx == 0) { @@ -805,9 +467,9 @@ void draw_list_sort_channels_by_depth(const ImVector& 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) + for (int i = 0; i < GImNodes->NodeIdxSubmissionOrder.Size; ++i) { - if (g.node_idx_submission_order[i] == node_idx) + if (GImNodes->NodeIdxSubmissionOrder[i] == node_idx) { submission_idx = i; break; @@ -822,204 +484,65 @@ void draw_list_sort_channels_by_depth(const ImVector& 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]); + DrawListSwapSubmissionIndices(j, j + 1); + ImSwap(GImNodes->NodeIdxSubmissionOrder[j], GImNodes->NodeIdxSubmissionOrder[j + 1]); } } } -// [SECTION] ObjectPool implementation - -template -int object_pool_find(ObjectPool& objects, const int id) -{ - const int index = objects.id_map.GetInt(static_cast(id), -1); - return index; -} - -template -void object_pool_update(ObjectPool& objects) -{ - objects.free_list.clear(); - for (int i = 0; i < objects.in_use.size(); ++i) - { - 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(); - } - } -} - -template<> -void object_pool_update(ObjectPool& nodes) -{ - nodes.free_list.clear(); - for (int i = 0; i < nodes.in_use.size(); ++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& depth_stack = editor_context_get().node_depth_order; - const int* const elem = depth_stack.find(i); - assert(elem != depth_stack.end()); - depth_stack.erase(elem); - } - - nodes.id_map.SetInt(previous_id, -1); - nodes.free_list.push_back(i); - (nodes.pool.Data + i)->~NodeData(); - } - } -} - -template -void object_pool_reset(ObjectPool& objects) -{ - if (!objects.in_use.empty()) - { - memset(objects.in_use.Data, 0, objects.in_use.size_in_bytes()); - } -} - -template -int object_pool_find_or_create_index(ObjectPool& objects, const int id) -{ - int index = objects.id_map.GetInt(static_cast(id), -1); - - // Construct new object - if (index == -1) - { - if (objects.free_list.empty()) - { - index = objects.pool.size(); - 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(id), index); - } - - // Flag it as used - objects.in_use[index] = true; - - return index; -} - -template<> -int object_pool_find_or_create_index(ObjectPool& nodes, const int node_id) -{ - int node_idx = nodes.id_map.GetInt(static_cast(node_id), -1); - - // Construct new node - if (node_idx == -1) - { - if (nodes.free_list.empty()) - { - node_idx = nodes.pool.size(); - 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(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; -} - -template -T& object_pool_find_or_create_object(ObjectPool& objects, const int id) -{ - const int index = object_pool_find_or_create_index(objects, id); - return objects.pool[index]; -} - // [SECTION] ui state logic -ImVec2 get_screen_space_pin_coordinates( - const ImRect& node_rect, - const ImRect& attribute_rect, - const AttributeType type) +ImVec2 GetScreenSpacePinCoordinates( + const ImRect& node_rect, + const ImRect& attribute_rect, + const ImNodesAttributeType type) { - 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); + assert(type == ImNodesAttributeType_Input || type == ImNodesAttributeType_Output); + const float x = type == ImNodesAttributeType_Input + ? (node_rect.Min.x - GImNodes->Style.PinOffset) + : (node_rect.Max.x + GImNodes->Style.PinOffset); return ImVec2(x, 0.5f * (attribute_rect.Min.y + attribute_rect.Max.y)); } -ImVec2 get_screen_space_pin_coordinates(const EditorContext& editor, const PinData& pin) +ImVec2 GetScreenSpacePinCoordinates(const ImNodesEditorContext& editor, const ImPinData& pin) { - const ImRect& parent_node_rect = editor.nodes.pool[pin.parent_node_idx].rect; - return get_screen_space_pin_coordinates(parent_node_rect, pin.attribute_rect, pin.type); + const ImRect& parent_node_rect = editor.Nodes.Pool[pin.ParentNodeIdx].Rect; + return GetScreenSpacePinCoordinates(parent_node_rect, pin.AttributeRect, 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() +bool MouseInCanvas() { - return g.canvas_rect_screen_space.Contains(ImGui::GetMousePos()) && ImGui::IsWindowHovered(); + // 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 && + GImNodes->CanvasRectScreenSpace.Contains(ImGui::GetMousePos()); } -void begin_node_selection(EditorContext& editor, const int node_idx) +void BeginNodeSelection(ImNodesEditorContext& editor, const int node_idx) { // Don't start selecting a node if we are e.g. already creating and dragging // a new link! New link creation can happen when the mouse is clicked over // a node, but within the hover radius of a pin. - if (editor.click_interaction_type != ClickInteractionType_None) + if (editor.ClickInteraction.Type != ImNodesClickInteractionType_None) { return; } - editor.click_interaction_type = ClickInteractionType_Node; + editor.ClickInteraction.Type = ImNodesClickInteractionType_Node; // If the node is not already contained in the selection, then we want only // the interaction node to be selected, effective immediately. // // Otherwise, we want to allow for the possibility of multiple nodes to be // moved at once. - if (!editor.selected_node_indices.contains(node_idx)) + if (!editor.SelectedNodeIndices.contains(node_idx)) { - editor.selected_node_indices.clear(); - editor.selected_link_indices.clear(); - editor.selected_node_indices.push_back(node_idx); + editor.SelectedNodeIndices.clear(); + editor.SelectedLinkIndices.clear(); + editor.SelectedNodeIndices.push_back(node_idx); // Ensure that individually selected nodes get rendered on top - ImVector& depth_stack = editor.node_depth_order; + ImVector& depth_stack = editor.NodeDepthOrder; const int* const elem = depth_stack.find(node_idx); assert(elem != depth_stack.end()); depth_stack.erase(elem); @@ -1027,108 +550,126 @@ void begin_node_selection(EditorContext& editor, const int node_idx) } } -void begin_link_selection(EditorContext& editor, const int link_idx) +void BeginLinkSelection(ImNodesEditorContext& editor, const int link_idx) { - editor.click_interaction_type = ClickInteractionType_Link; + editor.ClickInteraction.Type = ImNodesClickInteractionType_Link; // When a link is selected, clear all other selections, and insert the link // as the sole selection. - editor.selected_node_indices.clear(); - editor.selected_link_indices.clear(); - editor.selected_link_indices.push_back(link_idx); + editor.SelectedNodeIndices.clear(); + editor.SelectedLinkIndices.clear(); + editor.SelectedLinkIndices.push_back(link_idx); } -void begin_link_detach(EditorContext& editor, const int link_idx, const int detach_pin_idx) +void BeginLinkDetach(ImNodesEditorContext& editor, const int link_idx, const int detach_pin_idx) { - const LinkData& link = editor.links.pool[link_idx]; - ClickInteractionState& state = editor.click_interaction_state; - state.link_creation.end_pin_idx.reset(); - 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; + const ImLinkData& link = editor.Links.Pool[link_idx]; + ImClickInteractionState& state = editor.ClickInteraction; + state.Type = ImNodesClickInteractionType_LinkCreation; + state.LinkCreation.EndPinIdx.Reset(); + state.LinkCreation.StartPinIdx = + detach_pin_idx == link.StartPinIdx ? link.EndPinIdx : link.StartPinIdx; + GImNodes->DeletedLinkIdx = link_idx; } -void begin_link_interaction(EditorContext& editor, const int link_idx) +void BeginLinkInteraction(ImNodesEditorContext& 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) + // Check the 'click and drag to detach' case. + if (GImNodes->HoveredPinIdx.HasValue() && + (editor.Pins.Pool[GImNodes->HoveredPinIdx.Value()].Flags & + ImNodesAttributeFlags_EnableLinkDetachWithDragClick) != 0) { - if ((g.hovered_pin_flags & AttributeFlags_EnableLinkDetachWithDragClick) != 0) - { - begin_link_detach(editor, link_idx, g.hovered_pin_idx.value()); - editor.click_interaction_state.link_creation.link_creation_type = - LinkCreationType_FromDetach; - } + BeginLinkDetach(editor, link_idx, GImNodes->HoveredPinIdx.Value()); + editor.ClickInteraction.LinkCreation.Type = ImNodesLinkCreationType_FromDetach; } // If we aren't near a pin, check if we are clicking the link with the // 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 + const bool modifier_pressed = GImNodes->Io.LinkDetachWithModifierClick.Modifier == NULL ? false - : *g.io.link_detach_with_modifier_click.modifier; + : *GImNodes->Io.LinkDetachWithModifierClick.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 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 ? link.start_pin_idx : link.end_pin_idx; + const ImLinkData& link = editor.Links.Pool[link_idx]; + const ImPinData& start_pin = editor.Pins.Pool[link.StartPinIdx]; + const ImPinData& end_pin = editor.Pins.Pool[link.EndPinIdx]; + const ImVec2& mouse_pos = GImNodes->MousePos; + 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 ? link.StartPinIdx : link.EndPinIdx; - editor.click_interaction_type = ClickInteractionType_LinkCreation; - begin_link_detach(editor, link_idx, closest_pin_idx); + editor.ClickInteraction.Type = ImNodesClickInteractionType_LinkCreation; + BeginLinkDetach(editor, link_idx, closest_pin_idx); + editor.ClickInteraction.LinkCreation.Type = ImNodesLinkCreationType_FromDetach; } else { - begin_link_selection(editor, link_idx); + BeginLinkSelection(editor, link_idx); } } } -void begin_link_creation(EditorContext& editor, const int hovered_pin_idx) +void BeginLinkCreation(ImNodesEditorContext& editor, const int hovered_pin_idx) { - editor.click_interaction_type = ClickInteractionType_LinkCreation; - editor.click_interaction_state.link_creation.start_pin_idx = 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; + editor.ClickInteraction.Type = ImNodesClickInteractionType_LinkCreation; + editor.ClickInteraction.LinkCreation.StartPinIdx = hovered_pin_idx; + editor.ClickInteraction.LinkCreation.EndPinIdx.Reset(); + editor.ClickInteraction.LinkCreation.Type = ImNodesLinkCreationType_Standard; + GImNodes->ImNodesUIState |= ImNodesUIState_LinkStarted; } -void begin_canvas_interaction(EditorContext& editor) +static inline bool IsMiniMapHovered(); + +void BeginCanvasInteraction(ImNodesEditorContext& 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(); + const bool any_ui_element_hovered = + GImNodes->HoveredNodeIdx.HasValue() || GImNodes->HoveredLinkIdx.HasValue() || + GImNodes->HoveredPinIdx.HasValue() || ImGui::IsAnyItemHovered(); - const bool mouse_not_in_canvas = !mouse_in_canvas(); + const bool mouse_not_in_canvas = !MouseInCanvas(); - if (editor.click_interaction_type != ClickInteractionType_None || any_ui_element_hovered || - mouse_not_in_canvas) + if (editor.ClickInteraction.Type != ImNodesClickInteractionType_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 = GImNodes->AltMouseClicked; - if (started_panning) + // Handle mini-map interactions + if (IsMiniMapHovered()) { - editor.click_interaction_type = ClickInteractionType_Panning; + if (started_panning) + { + editor.ClickInteraction.Type = ImNodesClickInteractionType_MiniMapPanning; + } + else if (GImNodes->LeftMouseReleased) + { + editor.ClickInteraction.Type = ImNodesClickInteractionType_MiniMapSnapping; + } + else if (GImNodes->AltMouseScrollDelta != 0.f) + { + editor.ClickInteraction.Type = ImNodesClickInteractionType_MiniMapZooming; + } } - else if (g.left_mouse_clicked) + // Handle normal editor interactions + else { - editor.click_interaction_type = ClickInteractionType_BoxSelection; - editor.click_interaction_state.box_selector.rect.Min = g.mouse_pos; + if (started_panning) + { + editor.ClickInteraction.Type = ImNodesClickInteractionType_Panning; + } + else if (GImNodes->LeftMouseClicked) + { + editor.ClickInteraction.Type = ImNodesClickInteractionType_BoxSelection; + editor.ClickInteraction.BoxSelector.Rect.Min = GImNodes->MousePos; + } } } -void box_selector_update_selection(EditorContext& editor, ImRect box_rect) +void BoxSelectorUpdateSelection(ImNodesEditorContext& editor, ImRect box_rect) { // Invert box selector coordinates as needed @@ -1144,106 +685,135 @@ void box_selector_update_selection(EditorContext& editor, ImRect box_rect) // Update node selection - editor.selected_node_indices.clear(); + editor.SelectedNodeIndices.clear(); // Test for overlap against node rectangles - for (int node_idx = 0; node_idx < editor.nodes.pool.size(); ++node_idx) + for (int node_idx = 0; node_idx < editor.Nodes.Pool.size(); ++node_idx) { - if (editor.nodes.in_use[node_idx]) + if (editor.Nodes.InUse[node_idx]) { - NodeData& node = editor.nodes.pool[node_idx]; - if (box_rect.Overlaps(node.rect)) + ImNodeData& node = editor.Nodes.Pool[node_idx]; + if (box_rect.Overlaps(node.Rect)) { - editor.selected_node_indices.push_back(node_idx); + editor.SelectedNodeIndices.push_back(node_idx); } } } // Update link selection - editor.selected_link_indices.clear(); + editor.SelectedLinkIndices.clear(); // Test for overlap against links - for (int link_idx = 0; link_idx < editor.links.pool.size(); ++link_idx) + for (int link_idx = 0; link_idx < editor.Links.Pool.size(); ++link_idx) { - if (editor.links.in_use[link_idx]) + if (editor.Links.InUse[link_idx]) { - const LinkData& link = editor.links.pool[link_idx]; + const ImLinkData& link = editor.Links.Pool[link_idx]; - const PinData& pin_start = editor.pins.pool[link.start_pin_idx]; - const PinData& pin_end = editor.pins.pool[link.end_pin_idx]; - const ImRect& node_start_rect = editor.nodes.pool[pin_start.parent_node_idx].rect; - const ImRect& node_end_rect = editor.nodes.pool[pin_end.parent_node_idx].rect; + const ImPinData& pin_start = editor.Pins.Pool[link.StartPinIdx]; + const ImPinData& pin_end = editor.Pins.Pool[link.EndPinIdx]; + const ImRect& node_start_rect = editor.Nodes.Pool[pin_start.ParentNodeIdx].Rect; + const ImRect& node_end_rect = editor.Nodes.Pool[pin_end.ParentNodeIdx].Rect; - const ImVec2 start = get_screen_space_pin_coordinates( - node_start_rect, pin_start.attribute_rect, pin_start.type); - const ImVec2 end = get_screen_space_pin_coordinates( - node_end_rect, pin_end.attribute_rect, pin_end.type); + const ImVec2 start = GetScreenSpacePinCoordinates( + node_start_rect, pin_start.AttributeRect, pin_start.Type); + const ImVec2 end = + GetScreenSpacePinCoordinates(node_end_rect, pin_end.AttributeRect, pin_end.Type); // Test - if (rectangle_overlaps_link(box_rect, start, end, pin_start.type)) + if (RectangleOverlapsLink(box_rect, start, end, pin_start.Type)) { - editor.selected_link_indices.push_back(link_idx); + editor.SelectedLinkIndices.push_back(link_idx); } } } } -void translate_selected_nodes(EditorContext& editor) +void TranslateSelectedNodes(ImNodesEditorContext& editor) { - if (g.left_mouse_dragging) + if (GImNodes->LeftMouseDragging) { const ImGuiIO& io = ImGui::GetIO(); - for (int i = 0; i < editor.selected_node_indices.size(); ++i) + for (int i = 0; i < editor.SelectedNodeIndices.size(); ++i) { - const int node_idx = editor.selected_node_indices[i]; - NodeData& node = editor.nodes.pool[node_idx]; - if (node.draggable) + const int node_idx = editor.SelectedNodeIndices[i]; + ImNodeData& node = editor.Nodes.Pool[node_idx]; + if (node.Draggable) { - node.origin += io.MouseDelta; + node.Origin += io.MouseDelta; } } } } -OptionalIndex find_duplicate_link( - const EditorContext& editor, - const int start_pin_idx, - const int end_pin_idx) +struct LinkPredicate { - LinkData test_link(0); - test_link.start_pin_idx = start_pin_idx; - test_link.end_pin_idx = end_pin_idx; - for (int link_idx = 0; link_idx < editor.links.pool.size(); ++link_idx) + bool operator()(const ImLinkData& lhs, const ImLinkData& rhs) const { - const LinkData& link = editor.links.pool[link_idx]; - if (LinkPredicate()(test_link, link) && editor.links.in_use[link_idx]) + // Do a unique compare by sorting the pins' addresses. + // This catches duplicate links, whether they are in the + // same direction or not. + // Sorting by pin index should have the uniqueness guarantees as sorting + // by id -- each unique id will get one slot in the link pool array. + + int lhs_start = lhs.StartPinIdx; + int lhs_end = lhs.EndPinIdx; + int rhs_start = rhs.StartPinIdx; + int rhs_end = rhs.EndPinIdx; + + if (lhs_start > lhs_end) { - return OptionalIndex(link_idx); + ImSwap(lhs_start, lhs_end); + } + + if (rhs_start > rhs_end) + { + ImSwap(rhs_start, rhs_end); + } + + return lhs_start == rhs_start && lhs_end == rhs_end; + } +}; + +ImOptionalIndex FindDuplicateLink( + const ImNodesEditorContext& editor, + const int start_pin_idx, + const int end_pin_idx) +{ + ImLinkData test_link(0); + 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) + { + const ImLinkData& link = editor.Links.Pool[link_idx]; + if (LinkPredicate()(test_link, link) && editor.Links.InUse[link_idx]) + { + return ImOptionalIndex(link_idx); } } - return OptionalIndex(); + return ImOptionalIndex(); } -bool should_link_snap_to_pin( - const EditorContext& editor, - const PinData& start_pin, - const int hovered_pin_idx, - const OptionalIndex duplicate_link) +bool ShouldLinkSnapToPin( + const ImNodesEditorContext& editor, + const ImPinData& start_pin, + const int hovered_pin_idx, + const ImOptionalIndex duplicate_link) { - const PinData& end_pin = editor.pins.pool[hovered_pin_idx]; + const ImPinData& end_pin = editor.Pins.Pool[hovered_pin_idx]; // The end pin must be in a different node - if (start_pin.parent_node_idx == end_pin.parent_node_idx) + if (start_pin.ParentNodeIdx == end_pin.ParentNodeIdx) { return false; } // The end pin must be of a different type - if (start_pin.type == end_pin.type) + if (start_pin.Type == end_pin.Type) { return false; } @@ -1251,7 +821,7 @@ bool should_link_snap_to_pin( // 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.HasValue() && !(duplicate_link == GImNodes->SnapLinkIdx)) { return false; } @@ -1259,26 +829,26 @@ bool should_link_snap_to_pin( return true; } -void click_interaction_update(EditorContext& editor) +void ClickInteractionUpdate(ImNodesEditorContext& editor) { - switch (editor.click_interaction_type) + switch (editor.ClickInteraction.Type) { - case ClickInteractionType_BoxSelection: + case ImNodesClickInteractionType_BoxSelection: { - ImRect& box_rect = editor.click_interaction_state.box_selector.rect; - box_rect.Max = g.mouse_pos; + ImRect& box_rect = editor.ClickInteraction.BoxSelector.Rect; + box_rect.Max = GImNodes->MousePos; - box_selector_update_selection(editor, box_rect); + BoxSelectorUpdateSelection(editor, box_rect); - 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(box_rect.Min, box_rect.Max, box_selector_color); - g.canvas_draw_list->AddRect(box_rect.Min, box_rect.Max, box_selector_outline); + const ImU32 box_selector_color = GImNodes->Style.Colors[ImNodesCol_BoxSelector]; + const ImU32 box_selector_outline = GImNodes->Style.Colors[ImNodesCol_BoxSelectorOutline]; + GImNodes->CanvasDrawList->AddRectFilled(box_rect.Min, box_rect.Max, box_selector_color); + GImNodes->CanvasDrawList->AddRect(box_rect.Min, box_rect.Max, box_selector_outline); - if (g.left_mouse_released) + if (GImNodes->LeftMouseReleased) { - ImVector& depth_stack = editor.node_depth_order; - const ImVector& selected_idxs = editor.selected_node_indices; + ImVector& depth_stack = editor.NodeDepthOrder; + const ImVector& selected_idxs = editor.SelectedNodeIndices; // Bump the selected node indices, in order, to the top of the depth stack. // NOTE: this algorithm has worst case time complexity of O(N^2), if the node selection @@ -1304,137 +874,176 @@ void click_interaction_update(EditorContext& editor) } } - editor.click_interaction_type = ClickInteractionType_None; + editor.ClickInteraction.Type = ImNodesClickInteractionType_None; } } break; - case ClickInteractionType_Node: + case ImNodesClickInteractionType_Node: { - translate_selected_nodes(editor); + TranslateSelectedNodes(editor); - if (g.left_mouse_released) + if (GImNodes->LeftMouseReleased) { - editor.click_interaction_type = ClickInteractionType_None; + editor.ClickInteraction.Type = ImNodesClickInteractionType_None; } } break; - case ClickInteractionType_Link: + case ImNodesClickInteractionType_Link: { - if (g.left_mouse_released) + if (GImNodes->LeftMouseReleased) { - editor.click_interaction_type = ClickInteractionType_None; + editor.ClickInteraction.Type = ImNodesClickInteractionType_None; } } break; - case ClickInteractionType_LinkCreation: + case ImNodesClickInteractionType_LinkCreation: { - const PinData& start_pin = - editor.pins.pool[editor.click_interaction_state.link_creation.start_pin_idx]; + const ImPinData& start_pin = + editor.Pins.Pool[editor.ClickInteraction.LinkCreation.StartPinIdx]; - const OptionalIndex maybe_duplicate_link_idx = - g.hovered_pin_idx.has_value() - ? find_duplicate_link( + const ImOptionalIndex maybe_duplicate_link_idx = + GImNodes->HoveredPinIdx.HasValue() + ? FindDuplicateLink( editor, - editor.click_interaction_state.link_creation.start_pin_idx, - g.hovered_pin_idx.value()) - : OptionalIndex(); + editor.ClickInteraction.LinkCreation.StartPinIdx, + GImNodes->HoveredPinIdx.Value()) + : ImOptionalIndex(); const bool should_snap = - g.hovered_pin_idx.has_value() && - should_link_snap_to_pin( - editor, start_pin, g.hovered_pin_idx.value(), maybe_duplicate_link_idx); + GImNodes->HoveredPinIdx.HasValue() && + ShouldLinkSnapToPin( + editor, start_pin, GImNodes->HoveredPinIdx.Value(), maybe_duplicate_link_idx); // If we created on snap and the hovered pin is empty or changed, then we need signal that // the link's state has changed. const bool snapping_pin_changed = - editor.click_interaction_state.link_creation.end_pin_idx.has_value() && - !(g.hovered_pin_idx == editor.click_interaction_state.link_creation.end_pin_idx); + editor.ClickInteraction.LinkCreation.EndPinIdx.HasValue() && + !(GImNodes->HoveredPinIdx == editor.ClickInteraction.LinkCreation.EndPinIdx); // 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 && GImNodes->SnapLinkIdx.HasValue()) { - begin_link_detach( + BeginLinkDetach( editor, - g.snap_link_idx.value(), - editor.click_interaction_state.link_creation.end_pin_idx.value()); + GImNodes->SnapLinkIdx.Value(), + editor.ClickInteraction.LinkCreation.EndPinIdx.Value()); } - const ImVec2 start_pos = get_screen_space_pin_coordinates(editor, start_pin); + const ImVec2 start_pos = GetScreenSpacePinCoordinates(editor, start_pin); // If we are within the hover radius of a receiving pin, snap the link // 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; + ? GetScreenSpacePinCoordinates( + editor, editor.Pins.Pool[GImNodes->HoveredPinIdx.Value()]) + : GImNodes->MousePos; - const LinkBezierData link_data = get_link_renderable( - start_pos, end_pos, start_pin.type, g.style.link_line_segments_per_length); + const CubicBezier cubic_bezier = GetCubicBezier( + start_pos, end_pos, start_pin.Type, GImNodes->Style.LinkLineSegmentsPerLength); #if IMGUI_VERSION_NUM < 18000 - g.canvas_draw_list->AddBezierCurve( + GImNodes->CanvasDrawList->AddBezierCurve( #else - g.canvas_draw_list->AddBezierCubic( + GImNodes->CanvasDrawList->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); + cubic_bezier.P0, + cubic_bezier.P1, + cubic_bezier.P2, + cubic_bezier.P3, + GImNodes->Style.Colors[ImNodesCol_Link], + GImNodes->Style.LinkThickness, + cubic_bezier.NumSegments); const bool link_creation_on_snap = - g.hovered_pin_idx.has_value() && (editor.pins.pool[g.hovered_pin_idx.value()].flags & - AttributeFlags_EnableLinkCreationOnSnap); + GImNodes->HoveredPinIdx.HasValue() && + (editor.Pins.Pool[GImNodes->HoveredPinIdx.Value()].Flags & + ImNodesAttributeFlags_EnableLinkCreationOnSnap); if (!should_snap) { - editor.click_interaction_state.link_creation.end_pin_idx.reset(); + editor.ClickInteraction.LinkCreation.EndPinIdx.Reset(); } - const bool create_link = should_snap && (g.left_mouse_released || link_creation_on_snap); + const bool create_link = + should_snap && (GImNodes->LeftMouseReleased || link_creation_on_snap); - if (create_link && !maybe_duplicate_link_idx.has_value()) + if (create_link && !maybe_duplicate_link_idx.HasValue()) { // Avoid send OnLinkCreated() events every frame if the snap link is not saved // (only applies for EnableLinkCreationOnSnap) - if (!g.left_mouse_released && - editor.click_interaction_state.link_creation.end_pin_idx == g.hovered_pin_idx) + if (!GImNodes->LeftMouseReleased && + editor.ClickInteraction.LinkCreation.EndPinIdx == GImNodes->HoveredPinIdx) { break; } - g.element_state_change |= ElementStateChange_LinkCreated; - editor.click_interaction_state.link_creation.end_pin_idx = g.hovered_pin_idx.value(); + GImNodes->ImNodesUIState |= ImNodesUIState_LinkCreated; + editor.ClickInteraction.LinkCreation.EndPinIdx = GImNodes->HoveredPinIdx.Value(); } - if (g.left_mouse_released) + if (GImNodes->LeftMouseReleased) { - editor.click_interaction_type = ClickInteractionType_None; + editor.ClickInteraction.Type = ImNodesClickInteractionType_None; if (!create_link) { - g.element_state_change |= ElementStateChange_LinkDropped; + GImNodes->ImNodesUIState |= ImNodesUIState_LinkDropped; } } } break; - case ClickInteractionType_Panning: + case ImNodesClickInteractionType_Panning: { - const bool dragging = - g.io.emulate_three_button_mouse.enabled - ? (g.left_mouse_dragging && (*g.io.emulate_three_button_mouse.modifier)) - : g.middle_mouse_dragging; + const bool dragging = GImNodes->AltMouseDragging; if (dragging) { - editor.panning += ImGui::GetIO().MouseDelta; + editor.Panning += ImGui::GetIO().MouseDelta; } else { - editor.click_interaction_type = ClickInteractionType_None; + editor.ClickInteraction.Type = ImNodesClickInteractionType_None; } } break; - case ClickInteractionType_None: + case ImNodesClickInteractionType_MiniMapPanning: + { + const bool dragging = GImNodes->AltMouseDragging; + + if (dragging) + { + editor.Panning += ImGui::GetIO().MouseDelta / GImNodes->MiniMapZoom; + } + else + { + editor.ClickInteraction.Type = ImNodesClickInteractionType_None; + } + } + break; + case ImNodesClickInteractionType_MiniMapZooming: + { + GImNodes->MiniMapZoom = fmaxf( + 0.05f, + fminf( + GImNodes->MiniMapZoom + + 0.1f * GImNodes->MiniMapZoom * GImNodes->AltMouseScrollDelta, + 1.f)); + editor.ClickInteraction.Type = ImNodesClickInteractionType_None; + } + break; + case ImNodesClickInteractionType_MiniMapSnapping: + { + editor.Panning += GImNodes->MiniMapRectSnappingOffset; + GImNodes->MiniMapRectSnappingOffset = ImVec2(0.f, 0.f); + + editor.ClickInteraction.Type = ImNodesClickInteractionType_None; + } + break; + case ImNodesClickInteractionType_ImGuiItem: + { + if (GImNodes->LeftMouseReleased) + { + editor.ClickInteraction.Type = ImNodesClickInteractionType_None; + } + } + case ImNodesClickInteractionType_None: break; default: assert(!"Unreachable code!"); @@ -1442,21 +1051,100 @@ void click_interaction_update(EditorContext& editor) } } -OptionalIndex resolve_hovered_node(const EditorContext& editor) +void ResolveOccludedPins(const ImNodesEditorContext& editor, ImVector& occluded_pin_indices) { - if (g.node_indices_overlapping_with_mouse.Size == 0) + const ImVector& depth_stack = editor.NodeDepthOrder; + + occluded_pin_indices.resize(0); + + if (depth_stack.Size < 2) { - return OptionalIndex(); + return; + } + + // For each node in the depth stack + for (int depth_idx = 0; depth_idx < (depth_stack.Size - 1); ++depth_idx) + { + const ImNodeData& node_below = editor.Nodes.Pool[depth_stack[depth_idx]]; + + // Iterate over the rest of the depth stack to find nodes overlapping the pins + for (int next_depth_idx = depth_idx + 1; next_depth_idx < depth_stack.Size; + ++next_depth_idx) + { + const ImRect& rect_above = editor.Nodes.Pool[depth_stack[next_depth_idx]].Rect; + + // Iterate over each pin + for (int idx = 0; idx < node_below.PinIndices.Size; ++idx) + { + const int pin_idx = node_below.PinIndices[idx]; + const ImVec2& pin_pos = editor.Pins.Pool[pin_idx].Pos; + + if (rect_above.Contains(pin_pos)) + { + occluded_pin_indices.push_back(pin_idx); + } + } + } + } +} + +ImOptionalIndex ResolveHoveredPin( + const ImObjectPool& pins, + const ImVector& occluded_pin_indices) +{ + float smallest_distance = FLT_MAX; + ImOptionalIndex pin_idx_with_smallest_distance; + + const float hover_radius_sqr = GImNodes->Style.PinHoverRadius * GImNodes->Style.PinHoverRadius; + + for (int idx = 0; idx < pins.Pool.Size; ++idx) + { + if (!pins.InUse[idx]) + { + continue; + } + + if (occluded_pin_indices.contains(idx)) + { + continue; + } + + const ImVec2& pin_pos = pins.Pool[idx].Pos; + const float distance_sqr = ImLengthSqr(pin_pos - GImNodes->MousePos); + + // TODO: GImNodes->Style.PinHoverRadius needs to be copied into pin data and the pin-local + // value used here. This is no longer called in BeginAttribute/EndAttribute scope and the + // detected pin might have a different hover radius than what the user had when calling + // BeginAttribute/EndAttribute. + if (distance_sqr < hover_radius_sqr && distance_sqr < smallest_distance) + { + smallest_distance = distance_sqr; + pin_idx_with_smallest_distance = idx; + } + } + + return pin_idx_with_smallest_distance; +} + +ImOptionalIndex ResolveHoveredNode(const ImVector& depth_stack) +{ + if (GImNodes->NodeIndicesOverlappingWithMouse.size() == 0) + { + return ImOptionalIndex(); + } + + if (GImNodes->NodeIndicesOverlappingWithMouse.size() == 1) + { + return ImOptionalIndex(GImNodes->NodeIndicesOverlappingWithMouse[0]); } int largest_depth_idx = -1; int node_idx_on_top = -1; - const ImVector& depth_stack = editor.node_depth_order; - for (int i = 0; i < g.node_indices_overlapping_with_mouse.Size; ++i) + for (int i = 0; i < GImNodes->NodeIndicesOverlappingWithMouse.size(); ++i) { - const int node_idx = g.node_indices_overlapping_with_mouse[i]; - for (int depth_idx = 0; depth_idx < depth_stack.Size; ++depth_idx) + const int node_idx = GImNodes->NodeIndicesOverlappingWithMouse[i]; + for (int depth_idx = 0; depth_idx < depth_stack.size(); ++depth_idx) { if (depth_stack[depth_idx] == node_idx && (depth_idx > largest_depth_idx)) { @@ -1467,109 +1155,174 @@ OptionalIndex resolve_hovered_node(const EditorContext& editor) } assert(node_idx_on_top != -1); - return OptionalIndex(node_idx_on_top); + return ImOptionalIndex(node_idx_on_top); +} + +ImOptionalIndex ResolveHoveredLink( + const ImObjectPool& links, + const ImObjectPool& pins) +{ + float smallest_distance = FLT_MAX; + ImOptionalIndex link_idx_with_smallest_distance; + + // There are two ways a link can be detected as "hovered". + // 1. The link is within hover distance to the mouse. The closest such link is selected as being + // hovered over. + // 2. If the link is connected to the currently hovered pin. + // + // The latter is a requirement for link detaching with drag click to work, as both a link and + // pin are required to be hovered over for the feature to work. + + for (int idx = 0; idx < links.Pool.Size; ++idx) + { + if (!links.InUse[idx]) + { + continue; + } + + const ImLinkData& link = links.Pool[idx]; + const ImPinData& start_pin = pins.Pool[link.StartPinIdx]; + const ImPinData& end_pin = pins.Pool[link.EndPinIdx]; + + if (GImNodes->HoveredPinIdx == link.StartPinIdx || + GImNodes->HoveredPinIdx == link.EndPinIdx) + { + return idx; + } + + // TODO: the calculated CubicBeziers could be cached since we generate them again when + // rendering the links + + const CubicBezier cubic_bezier = GetCubicBezier( + start_pin.Pos, end_pin.Pos, start_pin.Type, GImNodes->Style.LinkLineSegmentsPerLength); + + // The distance test + { + const ImRect link_rect = GetContainingRectForCubicBezier(cubic_bezier); + + // 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. + if (link_rect.Contains(GImNodes->MousePos)) + { + const float distance = GetDistanceToCubicBezier( + GImNodes->MousePos, cubic_bezier, cubic_bezier.NumSegments); + + // TODO: GImNodes->Style.LinkHoverDistance could be also copied into ImLinkData, + // since we're not calling this function in the same scope as ImNodes::Link(). The + // rendered/detected link might have a different hover distance than what the user + // had specified when calling Link() + if (distance < GImNodes->Style.LinkHoverDistance && distance < smallest_distance) + { + smallest_distance = distance; + link_idx_with_smallest_distance = idx; + } + } + } + } + + return link_idx_with_smallest_distance; } // [SECTION] render helpers -inline ImVec2 screen_space_to_grid_space(const EditorContext& editor, const ImVec2& v) +inline ImVec2 ScreenSpaceToGridSpace(const ImNodesEditorContext& editor, const ImVec2& v) { - return v - g.canvas_origin_screen_space - editor.panning; + return v - GImNodes->CanvasOriginScreenSpace - editor.Panning; } -inline ImVec2 grid_space_to_screen_space(const EditorContext& editor, const ImVec2& v) +inline ImVec2 GridSpaceToScreenSpace(const ImNodesEditorContext& editor, const ImVec2& v) { - return v + g.canvas_origin_screen_space + editor.panning; + return v + GImNodes->CanvasOriginScreenSpace + editor.Panning; } -inline ImVec2 grid_space_to_editor_space(const EditorContext& editor, const ImVec2& v) +inline ImVec2 GridSpaceToEditorSpace(const ImNodesEditorContext& editor, const ImVec2& v) { - return v + editor.panning; + return v + editor.Panning; } -inline ImVec2 editor_space_to_grid_space(const EditorContext& editor, const ImVec2& v) +inline ImVec2 EditorSpaceToGridSpace(const ImNodesEditorContext& editor, const ImVec2& v) { - return v - editor.panning; + return v - editor.Panning; } -inline ImVec2 editor_space_to_screen_space(const ImVec2& v) +inline ImVec2 EditorSpaceToScreenSpace(const ImVec2& v) { - return g.canvas_origin_screen_space + v; + return GImNodes->CanvasOriginScreenSpace + v; } -inline ImRect get_item_rect() { return ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); } +inline ImRect GetItemRect() { return ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); } -inline ImVec2 get_node_title_bar_origin(const NodeData& node) +inline ImVec2 GetNodeTitleBarOrigin(const ImNodeData& node) { - return node.origin + node.layout_style.padding; + return node.Origin + node.LayoutStyle.Padding; } -inline ImVec2 get_node_content_origin(const NodeData& node) +inline ImVec2 GetNodeContentOrigin(const ImNodeData& node) { const ImVec2 title_bar_height = - ImVec2(0.f, node.title_bar_content_rect.GetHeight() + 2.0f * node.layout_style.padding.y); - return node.origin + title_bar_height + node.layout_style.padding; + ImVec2(0.f, node.TitleBarContentRect.GetHeight() + 2.0f * node.LayoutStyle.Padding.y); + return node.Origin + title_bar_height + node.LayoutStyle.Padding; } -inline ImRect get_node_title_rect(const NodeData& node) +inline ImRect GetNodeTitleRect(const ImNodeData& node) { - ImRect expanded_title_rect = node.title_bar_content_rect; - expanded_title_rect.Expand(node.layout_style.padding); + ImRect expanded_title_rect = node.TitleBarContentRect; + expanded_title_rect.Expand(node.LayoutStyle.Padding); return ImRect( expanded_title_rect.Min, - expanded_title_rect.Min + ImVec2(node.rect.GetWidth(), 0.f) + + expanded_title_rect.Min + ImVec2(node.Rect.GetWidth(), 0.f) + ImVec2(0.f, expanded_title_rect.GetHeight())); } -void draw_grid(EditorContext& editor, const ImVec2& canvas_size) +void DrawGrid(ImNodesEditorContext& editor, const ImVec2& canvas_size) { - const ImVec2 offset = editor.panning; + const ImVec2 offset = editor.Panning; - for (float x = fmodf(offset.x, g.style.grid_spacing); x < canvas_size.x; - x += g.style.grid_spacing) + for (float x = fmodf(offset.x, GImNodes->Style.GridSpacing); x < canvas_size.x; + x += GImNodes->Style.GridSpacing) { - g.canvas_draw_list->AddLine( - editor_space_to_screen_space(ImVec2(x, 0.0f)), - editor_space_to_screen_space(ImVec2(x, canvas_size.y)), - g.style.colors[ColorStyle_GridLine]); + GImNodes->CanvasDrawList->AddLine( + EditorSpaceToScreenSpace(ImVec2(x, 0.0f)), + EditorSpaceToScreenSpace(ImVec2(x, canvas_size.y)), + GImNodes->Style.Colors[ImNodesCol_GridLine]); } - for (float y = fmodf(offset.y, g.style.grid_spacing); y < canvas_size.y; - y += g.style.grid_spacing) + for (float y = fmodf(offset.y, GImNodes->Style.GridSpacing); y < canvas_size.y; + y += GImNodes->Style.GridSpacing) { - g.canvas_draw_list->AddLine( - editor_space_to_screen_space(ImVec2(0.0f, y)), - editor_space_to_screen_space(ImVec2(canvas_size.x, y)), - g.style.colors[ColorStyle_GridLine]); + GImNodes->CanvasDrawList->AddLine( + EditorSpaceToScreenSpace(ImVec2(0.0f, y)), + EditorSpaceToScreenSpace(ImVec2(canvas_size.x, y)), + GImNodes->Style.Colors[ImNodesCol_GridLine]); } } struct QuadOffsets { - ImVec2 top_left, bottom_left, bottom_right, top_right; + ImVec2 TopLeft, BottomLeft, BottomRight, TopRight; }; -QuadOffsets calculate_quad_offsets(const float side_length) +QuadOffsets CalculateQuadOffsets(const float side_length) { const float half_side = 0.5f * side_length; QuadOffsets offset; - offset.top_left = ImVec2(-half_side, half_side); - offset.bottom_left = ImVec2(-half_side, -half_side); - offset.bottom_right = ImVec2(half_side, -half_side); - offset.top_right = ImVec2(half_side, half_side); + offset.TopLeft = ImVec2(-half_side, half_side); + offset.BottomLeft = ImVec2(-half_side, -half_side); + offset.BottomRight = ImVec2(half_side, -half_side); + offset.TopRight = ImVec2(half_side, half_side); return offset; } struct TriangleOffsets { - ImVec2 top_left, bottom_left, right; + ImVec2 TopLeft, BottomLeft, Right; }; -TriangleOffsets calculate_triangle_offsets(const float side_length) +TriangleOffsets CalculateTriangleOffsets(const float side_length) { // Calculates the Vec2 offsets from an equilateral triangle's midpoint to // its vertices. Here is how the left_offset and right_offset are @@ -1586,80 +1339,82 @@ TriangleOffsets calculate_triangle_offsets(const float side_length) const float vertical_offset = 0.5f * side_length; TriangleOffsets offset; - offset.top_left = ImVec2(left_offset, vertical_offset); - offset.bottom_left = ImVec2(left_offset, -vertical_offset); - offset.right = ImVec2(right_offset, 0.f); + offset.TopLeft = ImVec2(left_offset, vertical_offset); + offset.BottomLeft = ImVec2(left_offset, -vertical_offset); + offset.Right = ImVec2(right_offset, 0.f); return offset; } -void draw_pin_shape(const ImVec2& pin_pos, const PinData& pin, const ImU32 pin_color) +void DrawPinShape(const ImVec2& pin_pos, const ImPinData& pin, const ImU32 pin_color) { - static const int circle_num_segments = 8; + static const int CIRCLE_NUM_SEGMENTS = 8; - switch (pin.shape) + switch (pin.Shape) { - case PinShape_Circle: + case ImNodesPinShape_Circle: { - g.canvas_draw_list->AddCircle( + GImNodes->CanvasDrawList->AddCircle( pin_pos, - g.style.pin_circle_radius, + GImNodes->Style.PinCircleRadius, pin_color, - circle_num_segments, - g.style.pin_line_thickness); + CIRCLE_NUM_SEGMENTS, + GImNodes->Style.PinLineThickness); } break; - case PinShape_CircleFilled: + case ImNodesPinShape_CircleFilled: { - g.canvas_draw_list->AddCircleFilled( - pin_pos, g.style.pin_circle_radius, pin_color, circle_num_segments); + GImNodes->CanvasDrawList->AddCircleFilled( + pin_pos, GImNodes->Style.PinCircleRadius, pin_color, CIRCLE_NUM_SEGMENTS); } break; - case PinShape_Quad: + case ImNodesPinShape_Quad: { - const QuadOffsets offset = calculate_quad_offsets(g.style.pin_quad_side_length); - g.canvas_draw_list->AddQuad( - pin_pos + offset.top_left, - pin_pos + offset.bottom_left, - pin_pos + offset.bottom_right, - pin_pos + offset.top_right, + const QuadOffsets offset = CalculateQuadOffsets(GImNodes->Style.PinQuadSideLength); + GImNodes->CanvasDrawList->AddQuad( + pin_pos + offset.TopLeft, + pin_pos + offset.BottomLeft, + pin_pos + offset.BottomRight, + pin_pos + offset.TopRight, pin_color, - g.style.pin_line_thickness); + GImNodes->Style.PinLineThickness); } break; - case PinShape_QuadFilled: + case ImNodesPinShape_QuadFilled: { - const QuadOffsets offset = calculate_quad_offsets(g.style.pin_quad_side_length); - g.canvas_draw_list->AddQuadFilled( - pin_pos + offset.top_left, - pin_pos + offset.bottom_left, - pin_pos + offset.bottom_right, - pin_pos + offset.top_right, + const QuadOffsets offset = CalculateQuadOffsets(GImNodes->Style.PinQuadSideLength); + GImNodes->CanvasDrawList->AddQuadFilled( + pin_pos + offset.TopLeft, + pin_pos + offset.BottomLeft, + pin_pos + offset.BottomRight, + pin_pos + offset.TopRight, pin_color); } break; - case PinShape_Triangle: + case ImNodesPinShape_Triangle: { - const TriangleOffsets offset = calculate_triangle_offsets(g.style.pin_triangle_side_length); - g.canvas_draw_list->AddTriangle( - pin_pos + offset.top_left, - pin_pos + offset.bottom_left, - pin_pos + offset.right, + const TriangleOffsets offset = + CalculateTriangleOffsets(GImNodes->Style.PinTriangleSideLength); + GImNodes->CanvasDrawList->AddTriangle( + pin_pos + offset.TopLeft, + pin_pos + offset.BottomLeft, + pin_pos + offset.Right, pin_color, // NOTE: for some weird reason, the line drawn by AddTriangle is // much thinner than the lines drawn by AddCircle or AddQuad. // Multiplying the line thickness by two seemed to solve the // problem at a few different thickness values. - 2.f * g.style.pin_line_thickness); + 2.f * GImNodes->Style.PinLineThickness); } break; - case PinShape_TriangleFilled: + case ImNodesPinShape_TriangleFilled: { - const TriangleOffsets offset = calculate_triangle_offsets(g.style.pin_triangle_side_length); - g.canvas_draw_list->AddTriangleFilled( - pin_pos + offset.top_left, - pin_pos + offset.bottom_left, - pin_pos + offset.right, + const TriangleOffsets offset = + CalculateTriangleOffsets(GImNodes->Style.PinTriangleSideLength); + GImNodes->CanvasDrawList->AddTriangleFilled( + pin_pos + offset.TopLeft, + pin_pos + offset.BottomLeft, + pin_pos + offset.Right, pin_color); } break; @@ -1669,158 +1424,123 @@ void draw_pin_shape(const ImVec2& pin_pos, const PinData& pin, const ImU32 pin_c } } -bool is_pin_hovered(const PinData& pin) +void DrawPin(ImNodesEditorContext& editor, const int pin_idx) { - return is_mouse_hovering_near_point(pin.pos, g.style.pin_hover_radius); -} + ImPinData& pin = editor.Pins.Pool[pin_idx]; + const ImRect& parent_node_rect = editor.Nodes.Pool[pin.ParentNodeIdx].Rect; -void draw_pin(EditorContext& editor, const int pin_idx, const bool left_mouse_clicked) -{ - PinData& pin = editor.pins.pool[pin_idx]; - const ImRect& parent_node_rect = editor.nodes.pool[pin.parent_node_idx].rect; + pin.Pos = GetScreenSpacePinCoordinates(parent_node_rect, pin.AttributeRect, pin.Type); - pin.pos = get_screen_space_pin_coordinates(parent_node_rect, pin.attribute_rect, pin.type); + ImU32 pin_color = pin.ColorStyle.Background; - ImU32 pin_color = pin.color_style.background; - - const bool pin_hovered = is_pin_hovered(pin) && mouse_in_canvas() && - editor.click_interaction_type != ClickInteractionType_BoxSelection; - - if (pin_hovered) + if (GImNodes->HoveredPinIdx == pin_idx) { - g.hovered_pin_idx = pin_idx; - g.hovered_pin_flags = pin.flags; - pin_color = pin.color_style.hovered; - - if (left_mouse_clicked) - { - begin_link_creation(editor, pin_idx); - } + pin_color = pin.ColorStyle.Hovered; } - draw_pin_shape(pin.pos, pin, pin_color); + DrawPinShape(pin.Pos, pin, pin_color); } -// TODO: Separate hover code from drawing code to avoid this unpleasant divergent function -// signature. -bool is_node_hovered(const NodeData& node, const int node_idx, const ObjectPool pins) +void DrawNode(ImNodesEditorContext& editor, const int node_idx) { - // We render pins on top of nodes. In order to prevent node interaction when a pin is on top of - // a node, we just early out here if a pin is hovered. - for (int i = 0; i < node.pin_indices.size(); ++i) + const ImNodeData& node = editor.Nodes.Pool[node_idx]; + ImGui::SetCursorPos(node.Origin + editor.Panning); + + const bool node_hovered = + GImNodes->HoveredNodeIdx == node_idx && + editor.ClickInteraction.Type != ImNodesClickInteractionType_BoxSelection; + + ImU32 node_background = node.ColorStyle.Background; + ImU32 titlebar_background = node.ColorStyle.Titlebar; + + if (editor.SelectedNodeIndices.contains(node_idx)) { - const PinData& pin = pins.pool[node.pin_indices[i]]; - if (is_pin_hovered(pin)) - { - return false; - } - } - - return g.hovered_node_idx.has_value() && node_idx == g.hovered_node_idx.value(); -} - -// TODO: It may be useful to make this an EditorContext method, since this uses -// a lot of editor state. Currently that is just not clear, since we don't pass -// the editor as a part of the function signature. -void draw_node(EditorContext& editor, const int node_idx) -{ - const NodeData& node = editor.nodes.pool[node_idx]; - ImGui::SetCursorPos(node.origin + editor.panning); - - const bool node_hovered = is_node_hovered(node, node_idx, editor.pins) && mouse_in_canvas() && - editor.click_interaction_type != ClickInteractionType_BoxSelection; - - ImU32 node_background = node.color_style.background; - ImU32 titlebar_background = node.color_style.titlebar; - - if (editor.selected_node_indices.contains(node_idx)) - { - node_background = node.color_style.background_selected; - titlebar_background = node.color_style.titlebar_selected; + node_background = node.ColorStyle.BackgroundSelected; + titlebar_background = node.ColorStyle.TitlebarSelected; } else if (node_hovered) { - node_background = node.color_style.background_hovered; - titlebar_background = node.color_style.titlebar_hovered; + node_background = node.ColorStyle.BackgroundHovered; + titlebar_background = node.ColorStyle.TitlebarHovered; } { // node base - g.canvas_draw_list->AddRectFilled( - node.rect.Min, node.rect.Max, node_background, node.layout_style.corner_rounding); + GImNodes->CanvasDrawList->AddRectFilled( + node.Rect.Min, node.Rect.Max, node_background, node.LayoutStyle.CornerRounding); // title bar: - if (node.title_bar_content_rect.GetHeight() > 0.f) + if (node.TitleBarContentRect.GetHeight() > 0.f) { - ImRect title_bar_rect = get_node_title_rect(node); + ImRect title_bar_rect = GetNodeTitleRect(node); - g.canvas_draw_list->AddRectFilled( +#if IMGUI_VERSION_NUM < 18200 + GImNodes->CanvasDrawList->AddRectFilled( title_bar_rect.Min, title_bar_rect.Max, titlebar_background, - node.layout_style.corner_rounding, + node.LayoutStyle.CornerRounding, ImDrawCornerFlags_Top); +#else + GImNodes->CanvasDrawList->AddRectFilled( + title_bar_rect.Min, + title_bar_rect.Max, + titlebar_background, + node.LayoutStyle.CornerRounding, + ImDrawFlags_RoundCornersTop); + +#endif } - if ((g.style.flags & StyleFlags_NodeOutline) != 0) + if ((GImNodes->Style.Flags & ImNodesStyleFlags_NodeOutline) != 0) { - g.canvas_draw_list->AddRect( - node.rect.Min, - node.rect.Max, - node.color_style.outline, - node.layout_style.corner_rounding, +#if IMGUI_VERSION_NUM < 18200 + GImNodes->CanvasDrawList->AddRect( + node.Rect.Min, + node.Rect.Max, + node.ColorStyle.Outline, + node.LayoutStyle.CornerRounding, ImDrawCornerFlags_All, - node.layout_style.border_thickness); + node.LayoutStyle.BorderThickness); +#else + GImNodes->CanvasDrawList->AddRect( + node.Rect.Min, + node.Rect.Max, + node.ColorStyle.Outline, + node.LayoutStyle.CornerRounding, + ImDrawFlags_RoundCornersAll, + node.LayoutStyle.BorderThickness); +#endif } } - for (int i = 0; i < node.pin_indices.size(); ++i) + for (int i = 0; i < node.PinIndices.size(); ++i) { - draw_pin(editor, node.pin_indices[i], g.left_mouse_clicked); + DrawPin(editor, node.PinIndices[i]); } if (node_hovered) { - g.hovered_node_idx = node_idx; - const bool node_ui_interaction = g.interactive_node_idx == node_idx; - if (g.left_mouse_clicked && !node_ui_interaction) - { - begin_node_selection(editor, node_idx); - } + GImNodes->HoveredNodeIdx = node_idx; } } -bool is_link_hovered(const LinkBezierData& link_data) +void DrawLink(ImNodesEditorContext& editor, const int link_idx) { - // We render pins and nodes on top of links. In order to prevent link interaction when a pin or - // node is on top of a link, we just early out here if a pin or node is hovered. - if (g.hovered_pin_idx.has_value() || g.hovered_node_idx.has_value()) - { - return false; - } + const ImLinkData& link = editor.Links.Pool[link_idx]; + const ImPinData& start_pin = editor.Pins.Pool[link.StartPinIdx]; + const ImPinData& end_pin = editor.Pins.Pool[link.EndPinIdx]; - return is_mouse_hovering_near_link(link_data.bezier, link_data.num_segments); -} + const CubicBezier cubic_bezier = GetCubicBezier( + start_pin.Pos, end_pin.Pos, start_pin.Type, GImNodes->Style.LinkLineSegmentsPerLength); -void draw_link(EditorContext& editor, const int link_idx) -{ - 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 LinkBezierData link_data = get_link_renderable( - start_pin.pos, end_pin.pos, start_pin.type, g.style.link_line_segments_per_length); - - const bool link_hovered = is_link_hovered(link_data) && mouse_in_canvas() && - editor.click_interaction_type != ClickInteractionType_BoxSelection; + const bool link_hovered = + GImNodes->HoveredLinkIdx == link_idx && + editor.ClickInteraction.Type != ImNodesClickInteractionType_BoxSelection; if (link_hovered) { - g.hovered_link_idx = link_idx; - if (g.left_mouse_clicked) - { - begin_link_interaction(editor, link_idx); - } + GImNodes->HoveredLinkIdx = link_idx; } // It's possible for a link to be deleted in begin_link_interaction. A user @@ -1828,303 +1548,639 @@ void draw_link(EditorContext& editor, const int link_idx) // position. // // In other words, skip rendering the link if it was deleted. - if (g.deleted_link_idx == link_idx) + if (GImNodes->DeletedLinkIdx == link_idx) { return; } - ImU32 link_color = link.color_style.base; - if (editor.selected_link_indices.contains(link_idx)) + ImU32 link_color = link.ColorStyle.Base; + if (editor.SelectedLinkIndices.contains(link_idx)) { - link_color = link.color_style.selected; + link_color = link.ColorStyle.Selected; } else if (link_hovered) { - link_color = link.color_style.hovered; + link_color = link.ColorStyle.Hovered; } #if IMGUI_VERSION_NUM < 18000 - g.canvas_draw_list->AddBezierCurve( + GImNodes->CanvasDrawList->AddBezierCurve( #else - g.canvas_draw_list->AddBezierCubic( + GImNodes->CanvasDrawList->AddBezierCubic( #endif - link_data.bezier.p0, - link_data.bezier.p1, - link_data.bezier.p2, - link_data.bezier.p3, + cubic_bezier.P0, + cubic_bezier.P1, + cubic_bezier.P2, + cubic_bezier.P3, link_color, - g.style.link_thickness, - link_data.num_segments); + GImNodes->Style.LinkThickness, + cubic_bezier.NumSegments); } -void begin_pin_attribute( - const int id, - const AttributeType type, - const PinShape shape, - const int node_idx) +void BeginPinAttribute( + const int id, + const ImNodesAttributeType type, + const ImNodesPinShape shape, + const int node_idx) { // Make sure to call BeginNode() before calling // BeginAttribute() - assert(g.current_scope == Scope_Node); - g.current_scope = Scope_Attribute; + assert(GImNodes->CurrentScope == ImNodesScope_Node); + GImNodes->CurrentScope = ImNodesScope_Attribute; ImGui::BeginGroup(); ImGui::PushID(id); - EditorContext& editor = editor_context_get(); + ImNodesEditorContext& editor = EditorContextGet(); - g.current_attribute_id = id; + GImNodes->CurrentAttributeId = id; - const int pin_idx = object_pool_find_or_create_index(editor.pins, id); - g.current_pin_idx = pin_idx; - PinData& pin = editor.pins.pool[pin_idx]; - pin.id = id; - pin.parent_node_idx = node_idx; - pin.type = type; - pin.shape = shape; - pin.flags = g.current_attribute_flags; - pin.color_style.background = g.style.colors[ColorStyle_Pin]; - pin.color_style.hovered = g.style.colors[ColorStyle_PinHovered]; + const int pin_idx = ObjectPoolFindOrCreateIndex(editor.Pins, id); + GImNodes->CurrentPinIdx = pin_idx; + ImPinData& pin = editor.Pins.Pool[pin_idx]; + pin.Id = id; + pin.ParentNodeIdx = node_idx; + pin.Type = type; + pin.Shape = shape; + pin.Flags = GImNodes->CurrentAttributeFlags; + pin.ColorStyle.Background = GImNodes->Style.Colors[ImNodesCol_Pin]; + pin.ColorStyle.Hovered = GImNodes->Style.Colors[ImNodesCol_PinHovered]; } -void end_pin_attribute() +void EndPinAttribute() { - assert(g.current_scope == Scope_Attribute); - g.current_scope = Scope_Node; + assert(GImNodes->CurrentScope == ImNodesScope_Attribute); + GImNodes->CurrentScope = ImNodesScope_Node; ImGui::PopID(); ImGui::EndGroup(); if (ImGui::IsItemActive()) { - g.active_attribute = true; - g.active_attribute_id = g.current_attribute_id; - g.interactive_node_idx = g.current_node_idx; + GImNodes->ActiveAttribute = true; + GImNodes->ActiveAttributeId = GImNodes->CurrentAttributeId; } - EditorContext& editor = editor_context_get(); - PinData& pin = editor.pins.pool[g.current_pin_idx]; - NodeData& node = editor.nodes.pool[g.current_node_idx]; - pin.attribute_rect = get_item_rect(); - node.pin_indices.push_back(g.current_pin_idx); -} -} // namespace - -// [SECTION] API implementation - -IO::EmulateThreeButtonMouse::EmulateThreeButtonMouse() : enabled(false), modifier(NULL) {} - -IO::LinkDetachWithModifierClick::LinkDetachWithModifierClick() : modifier(NULL) {} - -IO::IO() : emulate_three_button_mouse(), link_detach_with_modifier_click() {} - -Style::Style() - : grid_spacing(32.f), node_corner_rounding(4.f), node_padding_horizontal(8.f), - node_padding_vertical(8.f), node_border_thickness(1.f), link_thickness(3.f), - link_line_segments_per_length(0.1f), link_hover_distance(10.f), pin_circle_radius(4.f), - pin_quad_side_length(7.f), pin_triangle_side_length(9.5), pin_line_thickness(1.f), - pin_hover_radius(10.f), pin_offset(0.f), - flags(StyleFlags(StyleFlags_NodeOutline | StyleFlags_GridLines)), colors() -{ + ImNodesEditorContext& editor = EditorContextGet(); + ImPinData& pin = editor.Pins.Pool[GImNodes->CurrentPinIdx]; + ImNodeData& node = editor.Nodes.Pool[GImNodes->CurrentNodeIdx]; + pin.AttributeRect = GetItemRect(); + node.PinIndices.push_back(GImNodes->CurrentPinIdx); } -EditorContext* EditorContextCreate() +void Initialize(ImNodesContext* context) { - void* mem = ImGui::MemAlloc(sizeof(EditorContext)); - new (mem) EditorContext(); - return (EditorContext*)mem; -} + context->CanvasOriginScreenSpace = ImVec2(0.0f, 0.0f); + context->CanvasRectScreenSpace = ImRect(ImVec2(0.f, 0.f), ImVec2(0.f, 0.f)); + context->CurrentScope = ImNodesScope_None; -void EditorContextFree(EditorContext* ctx) -{ - ctx->~EditorContext(); - ImGui::MemFree(ctx); -} + context->MiniMapRectScreenSpace = ImRect(ImVec2(0.f, 0.f), ImVec2(0.f, 0.f)); + context->MiniMapRectSnappingOffset = ImVec2(0.f, 0.f); + context->MiniMapZoom = 0.1f; + context->MiniMapNodeHoveringCallback = NULL; + context->MiniMapNodeHoveringCallbackUserData = NULL; -void EditorContextSet(EditorContext* ctx) { g.editor_ctx = ctx; } + context->CurrentPinIdx = INT_MAX; + context->CurrentNodeIdx = INT_MAX; -ImVec2 EditorContextGetPanning() -{ - const EditorContext& editor = editor_context_get(); - return editor.panning; -} + context->DefaultEditorCtx = EditorContextCreate(); + EditorContextSet(GImNodes->DefaultEditorCtx); -void EditorContextResetPanning(const ImVec2& pos) -{ - EditorContext& editor = editor_context_get(); - editor.panning = pos; -} - -void EditorContextMoveToNode(const int node_id) -{ - EditorContext& editor = editor_context_get(); - NodeData& node = object_pool_find_or_create_object(editor.nodes, node_id); - - editor.panning.x = -node.origin.x; - editor.panning.y = -node.origin.y; -} - -void Initialize() -{ - g.canvas_origin_screen_space = ImVec2(0.0f, 0.0f); - g.canvas_rect_screen_space = ImRect(ImVec2(0.f, 0.f), ImVec2(0.f, 0.f)); - g.current_scope = Scope_None; - - g.current_pin_idx = INT_MAX; - g.current_node_idx = INT_MAX; - - g.default_editor_ctx = EditorContextCreate(); - EditorContextSet(g.default_editor_ctx); - - const ImGuiIO& io = ImGui::GetIO(); - g.io.emulate_three_button_mouse.modifier = &io.KeyAlt; - - g.current_attribute_flags = AttributeFlags_None; - g.attribute_flag_stack.push_back(g.current_attribute_flags); + context->CurrentAttributeFlags = ImNodesAttributeFlags_None; + context->AttributeFlagStack.push_back(GImNodes->CurrentAttributeFlags); StyleColorsDark(); } -void Shutdown() +void Shutdown(ImNodesContext* ctx) { EditorContextFree(ctx->DefaultEditorCtx); } + +// [SECTION] minimap + +static inline bool IsMiniMapActive() { return GImNodes->MiniMapRectScreenSpace.GetWidth() > 0.f; } + +static inline bool IsMiniMapHovered() { - EditorContextFree(g.default_editor_ctx); - g.editor_ctx = NULL; - g.default_editor_ctx = NULL; + return IsMiniMapActive() && + ImGui::IsMouseHoveringRect( + GImNodes->MiniMapRectScreenSpace.Min, GImNodes->MiniMapRectScreenSpace.Max); } -IO& GetIO() { return g.io; } +static inline ImRect ToMiniMapRect( + const float minimap_size_fraction, + const ImRect& editor_rect, + const ImNodesMiniMapLocation location) +{ + const ImVec2 editor_size(editor_rect.Max - editor_rect.Min); + const float max_editor_coord = fmaxf(editor_size.x, editor_size.y); + const float mini_map_coord = minimap_size_fraction * max_editor_coord; + const float corner_offset_alpha = fminf(1.f - minimap_size_fraction, 0.1f); + const float corner_offset_coord = corner_offset_alpha * mini_map_coord; -Style& GetStyle() { return g.style; } + // Compute the size of the mini-map area; lower bound with some reasonable size values + const ImVec2 mini_map_size(mini_map_coord, mini_map_coord); + + // Corner offset from editor context + const ImVec2 corner_offset(corner_offset_coord, corner_offset_coord); + + switch (location) + { + case ImNodesMiniMapLocation_BottomRight: + return ImRect( + editor_rect.Max - corner_offset - mini_map_size, editor_rect.Max - corner_offset); + case ImNodesMiniMapLocation_BottomLeft: + return ImRect( + ImVec2( + editor_rect.Min.x + corner_offset.x, + editor_rect.Max.y - corner_offset.y - mini_map_size.y), + ImVec2( + editor_rect.Min.x + corner_offset.x + mini_map_size.x, + editor_rect.Max.y - corner_offset.y)); + case ImNodesMiniMapLocation_TopRight: + return ImRect( + ImVec2( + editor_rect.Max.x - corner_offset.x - mini_map_size.x, + editor_rect.Min.y + corner_offset.y), + ImVec2( + editor_rect.Max.x - corner_offset.x, + editor_rect.Min.y + corner_offset.y + mini_map_size.y)); + case ImNodesMiniMapLocation_TopLeft: + // [[fallthrough]] + default: + // [[fallthrough]] + break; + } + return ImRect(editor_rect.Min + corner_offset, editor_rect.Min + corner_offset + mini_map_size); +} + +static void MiniMapDrawNode( + ImNodesEditorContext& editor, + const int node_idx, + const ImVec2& editor_center, + const ImVec2& mini_map_center, + const float scaling) +{ + const ImNodeData& node = editor.Nodes.Pool[node_idx]; + + const ImVec2 editor_node_offset(node.Rect.Min - editor_center); + + const ImVec2 mini_map_node_size((node.Rect.Max - node.Rect.Min) * scaling); + + const ImVec2 mini_map_node_min(editor_node_offset * scaling + mini_map_center); + + const ImVec2 mini_map_node_max(mini_map_node_min + mini_map_node_size); + + // Round to near whole pixel value for corner-rounding to prevent visual glitches + const float mini_map_node_rounding = floorf(node.LayoutStyle.CornerRounding * scaling); + + ImU32 mini_map_node_background; + + if (editor.ClickInteraction.Type == ImNodesClickInteractionType_None && + ImGui::IsMouseHoveringRect(mini_map_node_min, mini_map_node_max)) + { + mini_map_node_background = GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundHovered]; + + // Run user callback when hovering a mini-map node + if (GImNodes->MiniMapNodeHoveringCallback) + { + GImNodes->MiniMapNodeHoveringCallback( + node.Id, GImNodes->MiniMapNodeHoveringCallbackUserData); + } + + // Compute the amount to pan editor to center node selected in the minimap + GImNodes->MiniMapRectSnappingOffset = + editor_center - (node.Rect.Min + node.Rect.Max) * 0.5f; + } + else if (editor.SelectedNodeIndices.contains(node_idx)) + { + mini_map_node_background = GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundSelected]; + } + else + { + mini_map_node_background = GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackground]; + } + + const ImU32 mini_map_node_outline = GImNodes->Style.Colors[ImNodesCol_MiniMapNodeOutline]; + + GImNodes->CanvasDrawList->AddRectFilled( + mini_map_node_min, mini_map_node_max, mini_map_node_background, mini_map_node_rounding); + + GImNodes->CanvasDrawList->AddRect( + mini_map_node_min, mini_map_node_max, mini_map_node_outline, mini_map_node_rounding); +} + +static void MiniMapDrawLink( + ImNodesEditorContext& editor, + const int link_idx, + const ImVec2& editor_center, + const ImVec2& mini_map_center, + const float scaling) +{ + const ImLinkData& link = editor.Links.Pool[link_idx]; + const ImPinData& start_pin = editor.Pins.Pool[link.StartPinIdx]; + const ImPinData& end_pin = editor.Pins.Pool[link.EndPinIdx]; + + const CubicBezier cubic_bezier = GetCubicBezier( + (start_pin.Pos - editor_center) * scaling + mini_map_center, + (end_pin.Pos - editor_center) * scaling + mini_map_center, + start_pin.Type, + GImNodes->Style.LinkLineSegmentsPerLength / scaling); + + // It's possible for a link to be deleted in begin_link_interaction. A user + // may detach a link, resulting in the link wire snapping to the mouse + // position. + // + // In other words, skip rendering the link if it was deleted. + if (GImNodes->DeletedLinkIdx == link_idx) + { + return; + } + + const ImU32 link_color = + GImNodes->Style.Colors + [editor.SelectedLinkIndices.contains(link_idx) ? ImNodesCol_MiniMapLinkSelected + : ImNodesCol_MiniMapLink]; + +#if IMGUI_VERSION_NUM < 18000 + GImNodes->CanvasDrawList->AddBezierCurve( +#else + GImNodes->CanvasDrawList->AddBezierCubic( +#endif + cubic_bezier.P0, + cubic_bezier.P1, + cubic_bezier.P2, + cubic_bezier.P3, + link_color, + GImNodes->Style.LinkThickness * scaling, + cubic_bezier.NumSegments); +} + +static void MiniMapUpdate() +{ + ImNodesEditorContext& editor = EditorContextGet(); + + ImU32 mini_map_background; + + // NOTE: use normal background when panning (typically opaque) + if (editor.ClickInteraction.Type != ImNodesClickInteractionType_MiniMapPanning && + IsMiniMapHovered()) + { + mini_map_background = GImNodes->Style.Colors[ImNodesCol_MiniMapBackgroundHovered]; + } + else + { + mini_map_background = GImNodes->Style.Colors[ImNodesCol_MiniMapBackground]; + } + + const ImRect& editor_rect = GImNodes->CanvasRectScreenSpace; + + const ImVec2 editor_center( + 0.5f * (editor_rect.Min.x + editor_rect.Max.x), + 0.5f * (editor_rect.Min.y + editor_rect.Max.y)); + + const ImRect& mini_map_rect = GImNodes->MiniMapRectScreenSpace; + + const ImVec2 mini_map_center( + 0.5f * (mini_map_rect.Min.x + mini_map_rect.Max.x), + 0.5f * (mini_map_rect.Min.y + mini_map_rect.Max.y)); + + // Draw minimap background and border + GImNodes->CanvasDrawList->AddRectFilled( + mini_map_rect.Min, mini_map_rect.Max, mini_map_background); + + GImNodes->CanvasDrawList->AddRect( + mini_map_rect.Min, mini_map_rect.Max, GImNodes->Style.Colors[ImNodesCol_MiniMapOutline]); + + // Clip draw list items to mini-map rect (after drawing background/outline) + GImNodes->CanvasDrawList->PushClipRect( + mini_map_rect.Min, mini_map_rect.Max, true /* intersect with editor clip-rect */); + + // Get zoom scaling (0, 1] + const float scaling = GImNodes->MiniMapZoom; + + // Draw links first so they appear under nodes, and we can use the same draw channel + for (int link_idx = 0; link_idx < editor.Links.Pool.size(); ++link_idx) + { + if (editor.Links.InUse[link_idx]) + { + MiniMapDrawLink(editor, link_idx, editor_center, mini_map_center, scaling); + } + } + + for (int node_idx = 0; node_idx < editor.Nodes.Pool.size(); ++node_idx) + { + if (editor.Nodes.InUse[node_idx]) + { + MiniMapDrawNode(editor, node_idx, editor_center, mini_map_center, scaling); + } + } + + // Have to pop mini-map clip rect + GImNodes->CanvasDrawList->PopClipRect(); + + // Reset callback info after use + GImNodes->MiniMapNodeHoveringCallback = NULL; + GImNodes->MiniMapNodeHoveringCallbackUserData = NULL; + + // Reset mini-map area so that it will disappear if MiniMap(...) is not called on the next frame + GImNodes->MiniMapRectScreenSpace = ImRect(ImVec2(0.f, 0.f), ImVec2(0.f, 0.f)); +} + +// [SECTION] selection helpers + +template +void SelectObject(const ImObjectPool& objects, ImVector& selected_indices, const int id) +{ + const int idx = ObjectPoolFind(objects, id); + assert(idx >= 0); + assert(selected_indices.find(idx) == selected_indices.end()); + selected_indices.push_back(idx); +} + +template +void ClearObjectSelection( + const ImObjectPool& objects, + ImVector& selected_indices, + const int id) +{ + const int idx = ObjectPoolFind(objects, id); + assert(idx >= 0); + assert(selected_indices.find(idx) != selected_indices.end()); + selected_indices.find_erase_unsorted(idx); +} + +template +bool IsObjectSelected(const ImObjectPool& objects, ImVector& selected_indices, const int id) +{ + const int idx = ObjectPoolFind(objects, id); + return selected_indices.find(idx) != selected_indices.end(); +} + +} // namespace +} // namespace ImNodes + +// [SECTION] API implementation + +ImNodesIO::EmulateThreeButtonMouse::EmulateThreeButtonMouse() : Modifier(NULL) {} + +ImNodesIO::LinkDetachWithModifierClick::LinkDetachWithModifierClick() : Modifier(NULL) {} + +ImNodesIO::ImNodesIO() + : EmulateThreeButtonMouse(), LinkDetachWithModifierClick(), + AltMouseButton(ImGuiMouseButton_Middle) +{ +} + +ImNodesStyle::ImNodesStyle() + : GridSpacing(32.f), NodeCornerRounding(4.f), NodePaddingHorizontal(8.f), + NodePaddingVertical(8.f), NodeBorderThickness(1.f), LinkThickness(3.f), + LinkLineSegmentsPerLength(0.1f), LinkHoverDistance(10.f), PinCircleRadius(4.f), + PinQuadSideLength(7.f), PinTriangleSideLength(9.5), PinLineThickness(1.f), + PinHoverRadius(10.f), PinOffset(0.f), + Flags(ImNodesStyleFlags_NodeOutline | ImNodesStyleFlags_GridLines), Colors() +{ +} + +namespace ImNodes +{ +ImNodesContext* CreateContext() +{ + ImNodesContext* ctx = IM_NEW(ImNodesContext)(); + if (GImNodes == NULL) + SetCurrentContext(ctx); + Initialize(ctx); + return ctx; +} + +void DestroyContext(ImNodesContext* ctx) +{ + if (ctx == NULL) + ctx = GImNodes; + Shutdown(ctx); + if (GImNodes == ctx) + SetCurrentContext(NULL); + IM_DELETE(ctx); +} + +ImNodesContext* GetCurrentContext() { return GImNodes; } + +void SetCurrentContext(ImNodesContext* ctx) { GImNodes = ctx; } + +ImNodesEditorContext* EditorContextCreate() +{ + void* mem = ImGui::MemAlloc(sizeof(ImNodesEditorContext)); + new (mem) ImNodesEditorContext(); + return (ImNodesEditorContext*)mem; +} + +void EditorContextFree(ImNodesEditorContext* ctx) +{ + ctx->~ImNodesEditorContext(); + ImGui::MemFree(ctx); +} + +void EditorContextSet(ImNodesEditorContext* ctx) { GImNodes->EditorCtx = ctx; } + +ImVec2 EditorContextGetPanning() +{ + const ImNodesEditorContext& editor = EditorContextGet(); + return editor.Panning; +} + +void EditorContextResetPanning(const ImVec2& pos) +{ + ImNodesEditorContext& editor = EditorContextGet(); + editor.Panning = pos; +} + +void EditorContextMoveToNode(const int node_id) +{ + ImNodesEditorContext& editor = EditorContextGet(); + ImNodeData& node = ObjectPoolFindOrCreateObject(editor.Nodes, node_id); + + editor.Panning.x = -node.Origin.x; + editor.Panning.y = -node.Origin.y; +} + +void SetImGuiContext(ImGuiContext* ctx) { ImGui::SetCurrentContext(ctx); } + +ImNodesIO& GetIO() { return GImNodes->Io; } + +ImNodesStyle& GetStyle() { return GImNodes->Style; } void StyleColorsDark() { - g.style.colors[ColorStyle_NodeBackground] = IM_COL32(50, 50, 50, 255); - g.style.colors[ColorStyle_NodeBackgroundHovered] = IM_COL32(75, 75, 75, 255); - g.style.colors[ColorStyle_NodeBackgroundSelected] = IM_COL32(75, 75, 75, 255); - g.style.colors[ColorStyle_NodeOutline] = IM_COL32(100, 100, 100, 255); + GImNodes->Style.Colors[ImNodesCol_NodeBackground] = IM_COL32(50, 50, 50, 255); + GImNodes->Style.Colors[ImNodesCol_NodeBackgroundHovered] = IM_COL32(75, 75, 75, 255); + GImNodes->Style.Colors[ImNodesCol_NodeBackgroundSelected] = IM_COL32(75, 75, 75, 255); + GImNodes->Style.Colors[ImNodesCol_NodeOutline] = IM_COL32(100, 100, 100, 255); // title bar colors match ImGui's titlebg colors - g.style.colors[ColorStyle_TitleBar] = IM_COL32(41, 74, 122, 255); - g.style.colors[ColorStyle_TitleBarHovered] = IM_COL32(66, 150, 250, 255); - g.style.colors[ColorStyle_TitleBarSelected] = IM_COL32(66, 150, 250, 255); + GImNodes->Style.Colors[ImNodesCol_TitleBar] = IM_COL32(41, 74, 122, 255); + GImNodes->Style.Colors[ImNodesCol_TitleBarHovered] = IM_COL32(66, 150, 250, 255); + GImNodes->Style.Colors[ImNodesCol_TitleBarSelected] = IM_COL32(66, 150, 250, 255); // link colors match ImGui's slider grab colors - g.style.colors[ColorStyle_Link] = IM_COL32(61, 133, 224, 200); - g.style.colors[ColorStyle_LinkHovered] = IM_COL32(66, 150, 250, 255); - g.style.colors[ColorStyle_LinkSelected] = IM_COL32(66, 150, 250, 255); + GImNodes->Style.Colors[ImNodesCol_Link] = IM_COL32(61, 133, 224, 200); + GImNodes->Style.Colors[ImNodesCol_LinkHovered] = IM_COL32(66, 150, 250, 255); + GImNodes->Style.Colors[ImNodesCol_LinkSelected] = IM_COL32(66, 150, 250, 255); // pin colors match ImGui's button colors - g.style.colors[ColorStyle_Pin] = IM_COL32(53, 150, 250, 180); - g.style.colors[ColorStyle_PinHovered] = IM_COL32(53, 150, 250, 255); + GImNodes->Style.Colors[ImNodesCol_Pin] = IM_COL32(53, 150, 250, 180); + GImNodes->Style.Colors[ImNodesCol_PinHovered] = IM_COL32(53, 150, 250, 255); - g.style.colors[ColorStyle_BoxSelector] = IM_COL32(61, 133, 224, 30); - g.style.colors[ColorStyle_BoxSelectorOutline] = IM_COL32(61, 133, 224, 150); + GImNodes->Style.Colors[ImNodesCol_BoxSelector] = IM_COL32(61, 133, 224, 30); + GImNodes->Style.Colors[ImNodesCol_BoxSelectorOutline] = IM_COL32(61, 133, 224, 150); - g.style.colors[ColorStyle_GridBackground] = IM_COL32(40, 40, 50, 200); - g.style.colors[ColorStyle_GridLine] = IM_COL32(200, 200, 200, 40); + GImNodes->Style.Colors[ImNodesCol_GridBackground] = IM_COL32(40, 40, 50, 200); + GImNodes->Style.Colors[ImNodesCol_GridLine] = IM_COL32(200, 200, 200, 40); + + // minimap colors + GImNodes->Style.Colors[ImNodesCol_MiniMapBackground] = IM_COL32(25, 25, 25, 100); + GImNodes->Style.Colors[ImNodesCol_MiniMapBackgroundHovered] = IM_COL32(25, 25, 25, 200); + GImNodes->Style.Colors[ImNodesCol_MiniMapOutline] = IM_COL32(150, 150, 150, 100); + GImNodes->Style.Colors[ImNodesCol_MiniMapOutlineHovered] = IM_COL32(150, 150, 150, 200); + GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackground] = IM_COL32(200, 200, 200, 100); + GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundHovered] = IM_COL32(200, 200, 200, 255); + GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundSelected] = + GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundHovered]; + GImNodes->Style.Colors[ImNodesCol_MiniMapNodeOutline] = IM_COL32(200, 200, 200, 100); + GImNodes->Style.Colors[ImNodesCol_MiniMapLink] = GImNodes->Style.Colors[ImNodesCol_Link]; + GImNodes->Style.Colors[ImNodesCol_MiniMapLinkSelected] = + GImNodes->Style.Colors[ImNodesCol_LinkSelected]; } void StyleColorsClassic() { - g.style.colors[ColorStyle_NodeBackground] = IM_COL32(50, 50, 50, 255); - g.style.colors[ColorStyle_NodeBackgroundHovered] = IM_COL32(75, 75, 75, 255); - g.style.colors[ColorStyle_NodeBackgroundSelected] = IM_COL32(75, 75, 75, 255); - g.style.colors[ColorStyle_NodeOutline] = IM_COL32(100, 100, 100, 255); - g.style.colors[ColorStyle_TitleBar] = IM_COL32(69, 69, 138, 255); - g.style.colors[ColorStyle_TitleBarHovered] = IM_COL32(82, 82, 161, 255); - g.style.colors[ColorStyle_TitleBarSelected] = IM_COL32(82, 82, 161, 255); - g.style.colors[ColorStyle_Link] = IM_COL32(255, 255, 255, 100); - g.style.colors[ColorStyle_LinkHovered] = IM_COL32(105, 99, 204, 153); - g.style.colors[ColorStyle_LinkSelected] = IM_COL32(105, 99, 204, 153); - g.style.colors[ColorStyle_Pin] = IM_COL32(89, 102, 156, 170); - g.style.colors[ColorStyle_PinHovered] = IM_COL32(102, 122, 179, 200); - g.style.colors[ColorStyle_BoxSelector] = IM_COL32(82, 82, 161, 100); - g.style.colors[ColorStyle_BoxSelectorOutline] = IM_COL32(82, 82, 161, 255); - g.style.colors[ColorStyle_GridBackground] = IM_COL32(40, 40, 50, 200); - g.style.colors[ColorStyle_GridLine] = IM_COL32(200, 200, 200, 40); + GImNodes->Style.Colors[ImNodesCol_NodeBackground] = IM_COL32(50, 50, 50, 255); + GImNodes->Style.Colors[ImNodesCol_NodeBackgroundHovered] = IM_COL32(75, 75, 75, 255); + GImNodes->Style.Colors[ImNodesCol_NodeBackgroundSelected] = IM_COL32(75, 75, 75, 255); + GImNodes->Style.Colors[ImNodesCol_NodeOutline] = IM_COL32(100, 100, 100, 255); + GImNodes->Style.Colors[ImNodesCol_TitleBar] = IM_COL32(69, 69, 138, 255); + GImNodes->Style.Colors[ImNodesCol_TitleBarHovered] = IM_COL32(82, 82, 161, 255); + GImNodes->Style.Colors[ImNodesCol_TitleBarSelected] = IM_COL32(82, 82, 161, 255); + GImNodes->Style.Colors[ImNodesCol_Link] = IM_COL32(255, 255, 255, 100); + GImNodes->Style.Colors[ImNodesCol_LinkHovered] = IM_COL32(105, 99, 204, 153); + GImNodes->Style.Colors[ImNodesCol_LinkSelected] = IM_COL32(105, 99, 204, 153); + GImNodes->Style.Colors[ImNodesCol_Pin] = IM_COL32(89, 102, 156, 170); + GImNodes->Style.Colors[ImNodesCol_PinHovered] = IM_COL32(102, 122, 179, 200); + GImNodes->Style.Colors[ImNodesCol_BoxSelector] = IM_COL32(82, 82, 161, 100); + GImNodes->Style.Colors[ImNodesCol_BoxSelectorOutline] = IM_COL32(82, 82, 161, 255); + GImNodes->Style.Colors[ImNodesCol_GridBackground] = IM_COL32(40, 40, 50, 200); + GImNodes->Style.Colors[ImNodesCol_GridLine] = IM_COL32(200, 200, 200, 40); + + // minimap colors + GImNodes->Style.Colors[ImNodesCol_MiniMapBackground] = IM_COL32(25, 25, 25, 100); + GImNodes->Style.Colors[ImNodesCol_MiniMapBackgroundHovered] = IM_COL32(25, 25, 25, 200); + GImNodes->Style.Colors[ImNodesCol_MiniMapOutline] = IM_COL32(150, 150, 150, 100); + GImNodes->Style.Colors[ImNodesCol_MiniMapOutlineHovered] = IM_COL32(150, 150, 150, 200); + GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackground] = IM_COL32(200, 200, 200, 100); + GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundSelected] = + GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundHovered]; + GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundSelected] = IM_COL32(200, 200, 240, 255); + GImNodes->Style.Colors[ImNodesCol_MiniMapNodeOutline] = IM_COL32(200, 200, 200, 100); + GImNodes->Style.Colors[ImNodesCol_MiniMapLink] = GImNodes->Style.Colors[ImNodesCol_Link]; + GImNodes->Style.Colors[ImNodesCol_MiniMapLinkSelected] = + GImNodes->Style.Colors[ImNodesCol_LinkSelected]; } void StyleColorsLight() { - g.style.colors[ColorStyle_NodeBackground] = IM_COL32(240, 240, 240, 255); - g.style.colors[ColorStyle_NodeBackgroundHovered] = IM_COL32(240, 240, 240, 255); - g.style.colors[ColorStyle_NodeBackgroundSelected] = IM_COL32(240, 240, 240, 255); - g.style.colors[ColorStyle_NodeOutline] = IM_COL32(100, 100, 100, 255); - g.style.colors[ColorStyle_TitleBar] = IM_COL32(248, 248, 248, 255); - g.style.colors[ColorStyle_TitleBarHovered] = IM_COL32(209, 209, 209, 255); - g.style.colors[ColorStyle_TitleBarSelected] = IM_COL32(209, 209, 209, 255); + GImNodes->Style.Colors[ImNodesCol_NodeBackground] = IM_COL32(240, 240, 240, 255); + GImNodes->Style.Colors[ImNodesCol_NodeBackgroundHovered] = IM_COL32(240, 240, 240, 255); + GImNodes->Style.Colors[ImNodesCol_NodeBackgroundSelected] = IM_COL32(240, 240, 240, 255); + GImNodes->Style.Colors[ImNodesCol_NodeOutline] = IM_COL32(100, 100, 100, 255); + GImNodes->Style.Colors[ImNodesCol_TitleBar] = IM_COL32(248, 248, 248, 255); + GImNodes->Style.Colors[ImNodesCol_TitleBarHovered] = IM_COL32(209, 209, 209, 255); + GImNodes->Style.Colors[ImNodesCol_TitleBarSelected] = IM_COL32(209, 209, 209, 255); // original imgui values: 66, 150, 250 - g.style.colors[ColorStyle_Link] = IM_COL32(66, 150, 250, 100); + GImNodes->Style.Colors[ImNodesCol_Link] = IM_COL32(66, 150, 250, 100); // original imgui values: 117, 138, 204 - g.style.colors[ColorStyle_LinkHovered] = IM_COL32(66, 150, 250, 242); - g.style.colors[ColorStyle_LinkSelected] = IM_COL32(66, 150, 250, 242); + GImNodes->Style.Colors[ImNodesCol_LinkHovered] = IM_COL32(66, 150, 250, 242); + GImNodes->Style.Colors[ImNodesCol_LinkSelected] = IM_COL32(66, 150, 250, 242); // original imgui values: 66, 150, 250 - g.style.colors[ColorStyle_Pin] = IM_COL32(66, 150, 250, 160); - g.style.colors[ColorStyle_PinHovered] = IM_COL32(66, 150, 250, 255); - g.style.colors[ColorStyle_BoxSelector] = IM_COL32(90, 170, 250, 30); - g.style.colors[ColorStyle_BoxSelectorOutline] = IM_COL32(90, 170, 250, 150); - g.style.colors[ColorStyle_GridBackground] = IM_COL32(225, 225, 225, 255); - g.style.colors[ColorStyle_GridLine] = IM_COL32(180, 180, 180, 100); - g.style.flags = StyleFlags(StyleFlags_None); + GImNodes->Style.Colors[ImNodesCol_Pin] = IM_COL32(66, 150, 250, 160); + GImNodes->Style.Colors[ImNodesCol_PinHovered] = IM_COL32(66, 150, 250, 255); + GImNodes->Style.Colors[ImNodesCol_BoxSelector] = IM_COL32(90, 170, 250, 30); + GImNodes->Style.Colors[ImNodesCol_BoxSelectorOutline] = IM_COL32(90, 170, 250, 150); + GImNodes->Style.Colors[ImNodesCol_GridBackground] = IM_COL32(225, 225, 225, 255); + GImNodes->Style.Colors[ImNodesCol_GridLine] = IM_COL32(180, 180, 180, 100); + + // minimap colors + GImNodes->Style.Colors[ImNodesCol_MiniMapBackground] = IM_COL32(25, 25, 25, 100); + GImNodes->Style.Colors[ImNodesCol_MiniMapBackgroundHovered] = IM_COL32(25, 25, 25, 200); + GImNodes->Style.Colors[ImNodesCol_MiniMapOutline] = IM_COL32(150, 150, 150, 100); + GImNodes->Style.Colors[ImNodesCol_MiniMapOutlineHovered] = IM_COL32(150, 150, 150, 200); + GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackground] = IM_COL32(200, 200, 200, 100); + GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundSelected] = + GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundHovered]; + GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundSelected] = IM_COL32(200, 200, 240, 255); + GImNodes->Style.Colors[ImNodesCol_MiniMapNodeOutline] = IM_COL32(200, 200, 200, 100); + GImNodes->Style.Colors[ImNodesCol_MiniMapLink] = GImNodes->Style.Colors[ImNodesCol_Link]; + GImNodes->Style.Colors[ImNodesCol_MiniMapLinkSelected] = + GImNodes->Style.Colors[ImNodesCol_LinkSelected]; } void BeginNodeEditor() { - assert(g.current_scope == Scope_None); - g.current_scope = Scope_Editor; + assert(GImNodes->CurrentScope == ImNodesScope_None); + GImNodes->CurrentScope = ImNodesScope_Editor; // Reset state from previous pass - EditorContext& editor = editor_context_get(); - object_pool_reset(editor.nodes); - object_pool_reset(editor.pins); - object_pool_reset(editor.links); + ImNodesEditorContext& editor = EditorContextGet(); + ObjectPoolReset(editor.Nodes); + ObjectPoolReset(editor.Pins); + ObjectPoolReset(editor.Links); - g.hovered_node_idx.reset(); - g.interactive_node_idx.reset(); - g.hovered_link_idx.reset(); - g.hovered_pin_idx.reset(); - g.hovered_pin_flags = AttributeFlags_None; - g.deleted_link_idx.reset(); - g.snap_link_idx.reset(); + GImNodes->HoveredNodeIdx.Reset(); + GImNodes->HoveredLinkIdx.Reset(); + GImNodes->HoveredPinIdx.Reset(); + GImNodes->DeletedLinkIdx.Reset(); + GImNodes->SnapLinkIdx.Reset(); - g.node_indices_overlapping_with_mouse.clear(); + GImNodes->NodeIndicesOverlappingWithMouse.clear(); - g.element_state_change = ElementStateChange_None; + GImNodes->ImNodesUIState = ImNodesUIState_None; - g.mouse_pos = ImGui::GetIO().MousePos; - g.left_mouse_clicked = ImGui::IsMouseClicked(0); - g.left_mouse_released = ImGui::IsMouseReleased(0); - g.middle_mouse_clicked = ImGui::IsMouseClicked(2); - g.left_mouse_dragging = ImGui::IsMouseDragging(0, 0.0f); - g.middle_mouse_dragging = ImGui::IsMouseDragging(2, 0.0f); + GImNodes->MousePos = ImGui::GetIO().MousePos; + GImNodes->LeftMouseClicked = ImGui::IsMouseClicked(0); + GImNodes->LeftMouseReleased = ImGui::IsMouseReleased(0); + GImNodes->AltMouseClicked = + (GImNodes->Io.EmulateThreeButtonMouse.Modifier != NULL && + *GImNodes->Io.EmulateThreeButtonMouse.Modifier && GImNodes->LeftMouseClicked) || + ImGui::IsMouseClicked(GImNodes->Io.AltMouseButton); + GImNodes->LeftMouseDragging = ImGui::IsMouseDragging(0, 0.0f); + GImNodes->AltMouseDragging = + (GImNodes->Io.EmulateThreeButtonMouse.Modifier != NULL && GImNodes->LeftMouseDragging && + (*GImNodes->Io.EmulateThreeButtonMouse.Modifier)) || + ImGui::IsMouseDragging(GImNodes->Io.AltMouseButton, 0.0f); + GImNodes->AltMouseScrollDelta = ImGui::GetIO().MouseWheel; - g.active_attribute = false; + GImNodes->ActiveAttribute = false; ImGui::BeginGroup(); { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1.f, 1.f)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.f, 0.f)); - ImGui::PushStyleColor(ImGuiCol_ChildBg, g.style.colors[ColorStyle_GridBackground]); + ImGui::PushStyleColor(ImGuiCol_ChildBg, GImNodes->Style.Colors[ImNodesCol_GridBackground]); ImGui::BeginChild( "scrolling_region", ImVec2(0.f, 0.f), true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollWithMouse); - g.canvas_origin_screen_space = ImGui::GetCursorScreenPos(); + GImNodes->CanvasOriginScreenSpace = ImGui::GetCursorScreenPos(); // NOTE: we have to fetch the canvas draw list *after* we call // BeginChild(), otherwise the ImGui UI elements are going to be // rendered into the parent window draw list. - draw_list_set(ImGui::GetWindowDrawList()); + DrawListSet(ImGui::GetWindowDrawList()); { const ImVec2 canvas_size = ImGui::GetWindowSize(); - g.canvas_rect_screen_space = ImRect( - editor_space_to_screen_space(ImVec2(0.f, 0.f)), - editor_space_to_screen_space(canvas_size)); + GImNodes->CanvasRectScreenSpace = ImRect( + EditorSpaceToScreenSpace(ImVec2(0.f, 0.f)), EditorSpaceToScreenSpace(canvas_size)); - if (g.style.flags & StyleFlags_GridLines) + if (GImNodes->Style.Flags & ImNodesStyleFlags_GridLines) { - draw_grid(editor, canvas_size); + DrawGrid(editor, canvas_size); } } } @@ -2132,66 +2188,123 @@ void BeginNodeEditor() void EndNodeEditor() { - assert(g.current_scope == Scope_Editor); - g.current_scope = Scope_None; + assert(GImNodes->CurrentScope == ImNodesScope_Editor); + GImNodes->CurrentScope = ImNodesScope_None; - EditorContext& editor = editor_context_get(); + ImNodesEditorContext& editor = EditorContextGet(); - // Resolve which node is actually on top and being hovered. This needs to be done before any of - // the nodes can be rendered. + // Detect ImGui interaction first, because it blocks interaction with the rest of the UI - g.hovered_node_idx = resolve_hovered_node(editor); - - // Render the nodes and resolve which pin the mouse is hovering over. The hovered pin is needed - // for handling click interactions. - - for (int node_idx = 0; node_idx < editor.nodes.pool.size(); ++node_idx) + if (GImNodes->LeftMouseClicked && ImGui::IsAnyItemActive()) { - if (editor.nodes.in_use[node_idx]) + editor.ClickInteraction.Type = ImNodesClickInteractionType_ImGuiItem; + } + + // Detect which UI element is being hovered over. Detection is done in a hierarchical fashion, + // because a UI element being hovered excludes any other as being hovered over. + + // Don't do hovering detection for nodes/links/pins when interacting with the mini-map, since + // its an *overlay* with its own interaction behavior and must have precedence during mouse + // interaction. + + if ((editor.ClickInteraction.Type == ImNodesClickInteractionType_None || + editor.ClickInteraction.Type == ImNodesClickInteractionType_LinkCreation) && + MouseInCanvas() && !IsMiniMapHovered()) + { + // Pins needs some special care. We need to check the depth stack to see which pins are + // being occluded by other nodes. + ResolveOccludedPins(editor, GImNodes->OccludedPinIndices); + + GImNodes->HoveredPinIdx = ResolveHoveredPin(editor.Pins, GImNodes->OccludedPinIndices); + + if (!GImNodes->HoveredPinIdx.HasValue()) { - draw_list_activate_node_background(node_idx); - draw_node(editor, node_idx); + // Resolve which node is actually on top and being hovered using the depth stack. + GImNodes->HoveredNodeIdx = ResolveHoveredNode(editor.NodeDepthOrder); + } + + // We don't check for hovered pins here, because if we want to detach a link by clicking and + // dragging, we need to have both a link and pin hovered. + if (!GImNodes->HoveredNodeIdx.HasValue()) + { + GImNodes->HoveredLinkIdx = ResolveHoveredLink(editor.Links, editor.Pins); + } + } + + for (int node_idx = 0; node_idx < editor.Nodes.Pool.size(); ++node_idx) + { + if (editor.Nodes.InUse[node_idx]) + { + DrawListActivateNodeBackground(node_idx); + DrawNode(editor, node_idx); } } // In order to render the links underneath the nodes, we want to first select the bottom draw // channel. - g.canvas_draw_list->ChannelsSetCurrent(0); + GImNodes->CanvasDrawList->ChannelsSetCurrent(0); - for (int link_idx = 0; link_idx < editor.links.pool.size(); ++link_idx) + for (int link_idx = 0; link_idx < editor.Links.Pool.size(); ++link_idx) { - if (editor.links.in_use[link_idx]) + if (editor.Links.InUse[link_idx]) { - draw_link(editor, link_idx); + DrawLink(editor, link_idx); } } // Render the click interaction UI elements (partial links, box selector) on top of everything // else. - draw_list_append_click_interaction_channel(); - draw_list_activate_click_interaction_channel(); + DrawListAppendClickInteractionChannel(); + DrawListActivateClickInteractionChannel(); + + // Handle node graph interaction - if (g.left_mouse_clicked || g.middle_mouse_clicked) { - begin_canvas_interaction(editor); + if (GImNodes->LeftMouseClicked && GImNodes->HoveredLinkIdx.HasValue()) + { + BeginLinkInteraction(editor, GImNodes->HoveredLinkIdx.Value()); + } + + else if (GImNodes->LeftMouseClicked && GImNodes->HoveredPinIdx.HasValue()) + { + BeginLinkCreation(editor, GImNodes->HoveredPinIdx.Value()); + } + + else if (GImNodes->LeftMouseClicked && GImNodes->HoveredNodeIdx.HasValue()) + { + BeginNodeSelection(editor, GImNodes->HoveredNodeIdx.Value()); + } + + else if ( + GImNodes->LeftMouseClicked || GImNodes->LeftMouseReleased || + GImNodes->AltMouseClicked || GImNodes->AltMouseScrollDelta != 0.f) + { + BeginCanvasInteraction(editor); + } + + ClickInteractionUpdate(editor); } - click_interaction_update(editor); + // Mini-map rect will be set with non-zero width if MiniMap(...) was called + if (IsMiniMapActive()) + { + MiniMapUpdate(); + } // At this point, draw commands have been issued for all nodes (and pins). Update the node pool // to detect unused node slots and remove those indices from the depth stack before sorting the // node draw commands by depth. - object_pool_update(editor.nodes); - object_pool_update(editor.pins); + ObjectPoolUpdate(editor.Nodes); + ObjectPoolUpdate(editor.Pins); - draw_list_sort_channels_by_depth(editor.node_depth_order); + DrawListSortChannelsByDepth(editor.NodeDepthOrder); // After the links have been rendered, the link pool can be updated as well. - object_pool_update(editor.links); + ObjectPoolUpdate(editor.Links); // Finally, merge the draw channels - g.canvas_draw_list->ChannelsMerge(); + GImNodes->CanvasDrawList->ChannelsMerge(); // pop style ImGui::EndChild(); // end scrolling region @@ -2201,113 +2314,142 @@ void EndNodeEditor() ImGui::EndGroup(); } +void MiniMap( + const float minimap_size_fraction, + const ImNodesMiniMapLocation location, + const ImNodesMiniMapNodeHoveringCallback node_hovering_callback, + void* node_hovering_callback_data) +{ + // Check that editor size fraction is sane; must be in the range (0, 1] + assert(minimap_size_fraction > 0.f && minimap_size_fraction <= 1.f); + + // Remember to call before EndNodeEditor + assert(GImNodes->CurrentScope == ImNodesScope_Editor); + + // Set the size of the mini map to the global state + GImNodes->MiniMapRectScreenSpace = + ToMiniMapRect(minimap_size_fraction, GImNodes->CanvasRectScreenSpace, location); + + // We'll know that the mini map is active if GImNodes->MiniMapRectScreenSpace specifies + // a non-zero area (actually, just the width is checked for non-zero size) + + // Set node hovering callback information + GImNodes->MiniMapNodeHoveringCallback = node_hovering_callback; + GImNodes->MiniMapNodeHoveringCallbackUserData = node_hovering_callback_data; + + // Actual drawing/updating of the MiniMap is done in EndNodeEditor so that + // mini map is draw over everything and all pin/link positions are updated + // correctly relative to their respective nodes. Hence, we must store some of + // of the state for the mini map in GImNodes for the actual drawing/updating +} + void BeginNode(const int node_id) { // Remember to call BeginNodeEditor before calling BeginNode - assert(g.current_scope == Scope_Editor); - g.current_scope = Scope_Node; + assert(GImNodes->CurrentScope == ImNodesScope_Editor); + GImNodes->CurrentScope = ImNodesScope_Node; - EditorContext& editor = editor_context_get(); + ImNodesEditorContext& editor = EditorContextGet(); - const int node_idx = object_pool_find_or_create_index(editor.nodes, node_id); - g.current_node_idx = node_idx; + const int node_idx = ObjectPoolFindOrCreateIndex(editor.Nodes, node_id); + GImNodes->CurrentNodeIdx = node_idx; - NodeData& node = editor.nodes.pool[node_idx]; - node.color_style.background = g.style.colors[ColorStyle_NodeBackground]; - node.color_style.background_hovered = g.style.colors[ColorStyle_NodeBackgroundHovered]; - node.color_style.background_selected = g.style.colors[ColorStyle_NodeBackgroundSelected]; - node.color_style.outline = g.style.colors[ColorStyle_NodeOutline]; - node.color_style.titlebar = g.style.colors[ColorStyle_TitleBar]; - node.color_style.titlebar_hovered = g.style.colors[ColorStyle_TitleBarHovered]; - node.color_style.titlebar_selected = g.style.colors[ColorStyle_TitleBarSelected]; - node.layout_style.corner_rounding = g.style.node_corner_rounding; - node.layout_style.padding = - ImVec2(g.style.node_padding_horizontal, g.style.node_padding_vertical); - node.layout_style.border_thickness = g.style.node_border_thickness; + ImNodeData& node = editor.Nodes.Pool[node_idx]; + node.ColorStyle.Background = GImNodes->Style.Colors[ImNodesCol_NodeBackground]; + node.ColorStyle.BackgroundHovered = GImNodes->Style.Colors[ImNodesCol_NodeBackgroundHovered]; + node.ColorStyle.BackgroundSelected = GImNodes->Style.Colors[ImNodesCol_NodeBackgroundSelected]; + node.ColorStyle.Outline = GImNodes->Style.Colors[ImNodesCol_NodeOutline]; + node.ColorStyle.Titlebar = GImNodes->Style.Colors[ImNodesCol_TitleBar]; + node.ColorStyle.TitlebarHovered = GImNodes->Style.Colors[ImNodesCol_TitleBarHovered]; + node.ColorStyle.TitlebarSelected = GImNodes->Style.Colors[ImNodesCol_TitleBarSelected]; + node.LayoutStyle.CornerRounding = GImNodes->Style.NodeCornerRounding; + node.LayoutStyle.Padding = + ImVec2(GImNodes->Style.NodePaddingHorizontal, GImNodes->Style.NodePaddingVertical); + node.LayoutStyle.BorderThickness = GImNodes->Style.NodeBorderThickness; // ImGui::SetCursorPos sets the cursor position, local to the current widget // (in this case, the child object started in BeginNodeEditor). Use // ImGui::SetCursorScreenPos to set the screen space coordinates directly. - ImGui::SetCursorPos(grid_space_to_editor_space(editor, get_node_title_bar_origin(node))); + ImGui::SetCursorPos(GridSpaceToEditorSpace(editor, GetNodeTitleBarOrigin(node))); - draw_list_add_node(node_idx); - draw_list_activate_current_node_foreground(); + DrawListAddNode(node_idx); + DrawListActivateCurrentNodeForeground(); - ImGui::PushID(node.id); + ImGui::PushID(node.Id); ImGui::BeginGroup(); } void EndNode() { - assert(g.current_scope == Scope_Node); - g.current_scope = Scope_Editor; + assert(GImNodes->CurrentScope == ImNodesScope_Node); + GImNodes->CurrentScope = ImNodesScope_Editor; - EditorContext& editor = editor_context_get(); + ImNodesEditorContext& editor = EditorContextGet(); // The node's rectangle depends on the ImGui UI group size. ImGui::EndGroup(); ImGui::PopID(); - NodeData& node = editor.nodes.pool[g.current_node_idx]; - node.rect = get_item_rect(); - node.rect.Expand(node.layout_style.padding); + ImNodeData& node = editor.Nodes.Pool[GImNodes->CurrentNodeIdx]; + node.Rect = GetItemRect(); + node.Rect.Expand(node.LayoutStyle.Padding); - if (node.rect.Contains(g.mouse_pos)) + if (node.Rect.Contains(GImNodes->MousePos)) { - g.node_indices_overlapping_with_mouse.push_back(g.current_node_idx); + GImNodes->NodeIndicesOverlappingWithMouse.push_back(GImNodes->CurrentNodeIdx); } } ImVec2 GetNodeDimensions(int node_id) { - EditorContext& editor = editor_context_get(); - const int node_idx = object_pool_find(editor.nodes, node_id); + ImNodesEditorContext& editor = EditorContextGet(); + const int node_idx = ObjectPoolFind(editor.Nodes, node_id); assert(node_idx != -1); // invalid node_id - const NodeData& node = editor.nodes.pool[node_idx]; - return node.rect.GetSize(); + const ImNodeData& node = editor.Nodes.Pool[node_idx]; + return node.Rect.GetSize(); } void BeginNodeTitleBar() { - assert(g.current_scope == Scope_Node); + assert(GImNodes->CurrentScope == ImNodesScope_Node); ImGui::BeginGroup(); } void EndNodeTitleBar() { - assert(g.current_scope == Scope_Node); + assert(GImNodes->CurrentScope == ImNodesScope_Node); ImGui::EndGroup(); - EditorContext& editor = editor_context_get(); - NodeData& node = editor.nodes.pool[g.current_node_idx]; - node.title_bar_content_rect = get_item_rect(); + ImNodesEditorContext& editor = EditorContextGet(); + ImNodeData& node = editor.Nodes.Pool[GImNodes->CurrentNodeIdx]; + node.TitleBarContentRect = GetItemRect(); - ImGui::ItemAdd(get_node_title_rect(node), ImGui::GetID("title_bar")); + ImGui::ItemAdd(GetNodeTitleRect(node), ImGui::GetID("title_bar")); - ImGui::SetCursorPos(grid_space_to_editor_space(editor, get_node_content_origin(node))); + ImGui::SetCursorPos(GridSpaceToEditorSpace(editor, GetNodeContentOrigin(node))); } -void BeginInputAttribute(const int id, const PinShape shape) +void BeginInputAttribute(const int id, const ImNodesPinShape shape) { - begin_pin_attribute(id, AttributeType_Input, shape, g.current_node_idx); + BeginPinAttribute(id, ImNodesAttributeType_Input, shape, GImNodes->CurrentNodeIdx); } -void EndInputAttribute() { end_pin_attribute(); } +void EndInputAttribute() { EndPinAttribute(); } -void BeginOutputAttribute(const int id, const PinShape shape) +void BeginOutputAttribute(const int id, const ImNodesPinShape shape) { - begin_pin_attribute(id, AttributeType_Output, shape, g.current_node_idx); + BeginPinAttribute(id, ImNodesAttributeType_Output, shape, GImNodes->CurrentNodeIdx); } -void EndOutputAttribute() { end_pin_attribute(); } +void EndOutputAttribute() { EndPinAttribute(); } void BeginStaticAttribute(const int id) { // Make sure to call BeginNode() before calling BeginAttribute() - assert(g.current_scope == Scope_Node); - g.current_scope = Scope_Attribute; + assert(GImNodes->CurrentScope == ImNodesScope_Node); + GImNodes->CurrentScope = ImNodesScope_Attribute; - g.current_attribute_id = id; + GImNodes->CurrentAttributeId = id; ImGui::BeginGroup(); ImGui::PushID(id); @@ -2316,76 +2458,75 @@ void BeginStaticAttribute(const int id) void EndStaticAttribute() { // Make sure to call BeginNode() before calling BeginAttribute() - assert(g.current_scope == Scope_Attribute); - g.current_scope = Scope_Node; + assert(GImNodes->CurrentScope == ImNodesScope_Attribute); + GImNodes->CurrentScope = ImNodesScope_Node; ImGui::PopID(); ImGui::EndGroup(); if (ImGui::IsItemActive()) { - g.active_attribute = true; - g.active_attribute_id = g.current_attribute_id; - g.interactive_node_idx = g.current_node_idx; + GImNodes->ActiveAttribute = true; + GImNodes->ActiveAttributeId = GImNodes->CurrentAttributeId; } } -void PushAttributeFlag(AttributeFlags flag) +void PushAttributeFlag(const ImNodesAttributeFlags flag) { - g.current_attribute_flags |= static_cast(flag); - g.attribute_flag_stack.push_back(g.current_attribute_flags); + GImNodes->CurrentAttributeFlags |= flag; + GImNodes->AttributeFlagStack.push_back(GImNodes->CurrentAttributeFlags); } void PopAttributeFlag() { // PopAttributeFlag called without a matching PushAttributeFlag! // The bottom value is always the default value, pushed in Initialize(). - assert(g.attribute_flag_stack.size() > 1); + assert(GImNodes->AttributeFlagStack.size() > 1); - g.attribute_flag_stack.pop_back(); - g.current_attribute_flags = g.attribute_flag_stack.back(); + GImNodes->AttributeFlagStack.pop_back(); + GImNodes->CurrentAttributeFlags = GImNodes->AttributeFlagStack.back(); } -void Link(int id, const int start_attr_id, const int end_attr_id) +void Link(const int id, const int start_attr_id, const int end_attr_id) { - assert(g.current_scope == Scope_Editor); + assert(GImNodes->CurrentScope == ImNodesScope_Editor); - EditorContext& editor = editor_context_get(); - LinkData& link = object_pool_find_or_create_object(editor.links, id); - link.id = id; - link.start_pin_idx = object_pool_find_or_create_index(editor.pins, start_attr_id); - link.end_pin_idx = object_pool_find_or_create_index(editor.pins, end_attr_id); - link.color_style.base = g.style.colors[ColorStyle_Link]; - link.color_style.hovered = g.style.colors[ColorStyle_LinkHovered]; - link.color_style.selected = g.style.colors[ColorStyle_LinkSelected]; + ImNodesEditorContext& editor = EditorContextGet(); + ImLinkData& link = ObjectPoolFindOrCreateObject(editor.Links, id); + link.Id = id; + link.StartPinIdx = ObjectPoolFindOrCreateIndex(editor.Pins, start_attr_id); + link.EndPinIdx = ObjectPoolFindOrCreateIndex(editor.Pins, end_attr_id); + link.ColorStyle.Base = GImNodes->Style.Colors[ImNodesCol_Link]; + link.ColorStyle.Hovered = GImNodes->Style.Colors[ImNodesCol_LinkHovered]; + link.ColorStyle.Selected = GImNodes->Style.Colors[ImNodesCol_LinkSelected]; // Check if this link was created by the current link event - if ((editor.click_interaction_type == ClickInteractionType_LinkCreation && - editor.pins.pool[link.end_pin_idx].flags & AttributeFlags_EnableLinkCreationOnSnap && - editor.click_interaction_state.link_creation.start_pin_idx == link.start_pin_idx && - editor.click_interaction_state.link_creation.end_pin_idx == link.end_pin_idx) || - (editor.click_interaction_state.link_creation.start_pin_idx == link.end_pin_idx && - editor.click_interaction_state.link_creation.end_pin_idx == link.start_pin_idx)) + if ((editor.ClickInteraction.Type == ImNodesClickInteractionType_LinkCreation && + editor.Pins.Pool[link.EndPinIdx].Flags & ImNodesAttributeFlags_EnableLinkCreationOnSnap && + editor.ClickInteraction.LinkCreation.StartPinIdx == link.StartPinIdx && + editor.ClickInteraction.LinkCreation.EndPinIdx == link.EndPinIdx) || + (editor.ClickInteraction.LinkCreation.StartPinIdx == link.EndPinIdx && + editor.ClickInteraction.LinkCreation.EndPinIdx == link.StartPinIdx)) { - g.snap_link_idx = object_pool_find_or_create_index(editor.links, id); + GImNodes->SnapLinkIdx = ObjectPoolFindOrCreateIndex(editor.Links, id); } } -void PushColorStyle(ColorStyle item, unsigned int color) +void PushColorStyle(const ImNodesCol item, unsigned int color) { - g.color_modifier_stack.push_back(ColorStyleElement(g.style.colors[item], item)); - g.style.colors[item] = color; + GImNodes->ColorModifierStack.push_back(ImNodesColElement(GImNodes->Style.Colors[item], item)); + GImNodes->Style.Colors[item] = color; } void PopColorStyle() { - assert(g.color_modifier_stack.size() > 0); - const ColorStyleElement elem = g.color_modifier_stack.back(); - g.style.colors[elem.item] = elem.color; - g.color_modifier_stack.pop_back(); + assert(GImNodes->ColorModifierStack.size() > 0); + const ImNodesColElement elem = GImNodes->ColorModifierStack.back(); + GImNodes->Style.Colors[elem.Item] = elem.Color; + GImNodes->ColorModifierStack.pop_back(); } -float& lookup_style_var(const StyleVar item) +float& LookupStyleVar(const ImNodesStyleVar item) { // TODO: once the switch gets too big and unwieldy to work with, we could do // a byte-offset lookup into the Style struct, using the StyleVar as an @@ -2393,47 +2534,47 @@ float& lookup_style_var(const StyleVar item) float* style_var = 0; switch (item) { - case StyleVar_GridSpacing: - style_var = &g.style.grid_spacing; + case ImNodesStyleVar_GridSpacing: + style_var = &GImNodes->Style.GridSpacing; break; - case StyleVar_NodeCornerRounding: - style_var = &g.style.node_corner_rounding; + case ImNodesStyleVar_NodeCornerRounding: + style_var = &GImNodes->Style.NodeCornerRounding; break; - case StyleVar_NodePaddingHorizontal: - style_var = &g.style.node_padding_horizontal; + case ImNodesStyleVar_NodePaddingHorizontal: + style_var = &GImNodes->Style.NodePaddingHorizontal; break; - case StyleVar_NodePaddingVertical: - style_var = &g.style.node_padding_vertical; + case ImNodesStyleVar_NodePaddingVertical: + style_var = &GImNodes->Style.NodePaddingVertical; break; - case StyleVar_NodeBorderThickness: - style_var = &g.style.node_border_thickness; + case ImNodesStyleVar_NodeBorderThickness: + style_var = &GImNodes->Style.NodeBorderThickness; break; - case StyleVar_LinkThickness: - style_var = &g.style.link_thickness; + case ImNodesStyleVar_LinkThickness: + style_var = &GImNodes->Style.LinkThickness; break; - case StyleVar_LinkLineSegmentsPerLength: - style_var = &g.style.link_line_segments_per_length; + case ImNodesStyleVar_LinkLineSegmentsPerLength: + style_var = &GImNodes->Style.LinkLineSegmentsPerLength; break; - case StyleVar_LinkHoverDistance: - style_var = &g.style.link_hover_distance; + case ImNodesStyleVar_LinkHoverDistance: + style_var = &GImNodes->Style.LinkHoverDistance; break; - case StyleVar_PinCircleRadius: - style_var = &g.style.pin_circle_radius; + case ImNodesStyleVar_PinCircleRadius: + style_var = &GImNodes->Style.PinCircleRadius; break; - case StyleVar_PinQuadSideLength: - style_var = &g.style.pin_quad_side_length; + case ImNodesStyleVar_PinQuadSideLength: + style_var = &GImNodes->Style.PinQuadSideLength; break; - case StyleVar_PinTriangleSideLength: - style_var = &g.style.pin_triangle_side_length; + case ImNodesStyleVar_PinTriangleSideLength: + style_var = &GImNodes->Style.PinTriangleSideLength; break; - case StyleVar_PinLineThickness: - style_var = &g.style.pin_line_thickness; + case ImNodesStyleVar_PinLineThickness: + style_var = &GImNodes->Style.PinLineThickness; break; - case StyleVar_PinHoverRadius: - style_var = &g.style.pin_hover_radius; + case ImNodesStyleVar_PinHoverRadius: + style_var = &GImNodes->Style.PinHoverRadius; break; - case StyleVar_PinOffset: - style_var = &g.style.pin_offset; + case ImNodesStyleVar_PinOffset: + style_var = &GImNodes->Style.PinOffset; break; default: assert(!"Invalid StyleVar value!"); @@ -2442,144 +2583,144 @@ float& lookup_style_var(const StyleVar item) return *style_var; } -void PushStyleVar(const StyleVar item, const float value) +void PushStyleVar(const ImNodesStyleVar item, const float value) { - float& style_var = lookup_style_var(item); - g.style_modifier_stack.push_back(StyleElement(style_var, item)); + float& style_var = LookupStyleVar(item); + GImNodes->StyleModifierStack.push_back(ImNodesStyleVarElement(style_var, item)); style_var = value; } void PopStyleVar() { - assert(g.style_modifier_stack.size() > 0); - const StyleElement style_elem = g.style_modifier_stack.back(); - g.style_modifier_stack.pop_back(); - float& style_var = lookup_style_var(style_elem.item); - style_var = style_elem.value; + assert(GImNodes->StyleModifierStack.size() > 0); + const ImNodesStyleVarElement style_elem = GImNodes->StyleModifierStack.back(); + GImNodes->StyleModifierStack.pop_back(); + float& style_var = LookupStyleVar(style_elem.Item); + style_var = style_elem.Value; } -void SetNodeScreenSpacePos(int node_id, const ImVec2& screen_space_pos) +void SetNodeScreenSpacePos(const int node_id, const ImVec2& screen_space_pos) { - EditorContext& editor = editor_context_get(); - NodeData& node = object_pool_find_or_create_object(editor.nodes, node_id); - node.origin = screen_space_to_grid_space(editor, screen_space_pos); + ImNodesEditorContext& editor = EditorContextGet(); + ImNodeData& node = ObjectPoolFindOrCreateObject(editor.Nodes, node_id); + node.Origin = ScreenSpaceToGridSpace(editor, screen_space_pos); } -void SetNodeEditorSpacePos(int node_id, const ImVec2& editor_space_pos) +void SetNodeEditorSpacePos(const int node_id, const ImVec2& editor_space_pos) { - EditorContext& editor = editor_context_get(); - NodeData& node = object_pool_find_or_create_object(editor.nodes, node_id); - node.origin = editor_space_to_grid_space(editor, editor_space_pos); + ImNodesEditorContext& editor = EditorContextGet(); + ImNodeData& node = ObjectPoolFindOrCreateObject(editor.Nodes, node_id); + node.Origin = EditorSpaceToGridSpace(editor, editor_space_pos); } -void SetNodeGridSpacePos(int node_id, const ImVec2& grid_pos) +void SetNodeGridSpacePos(const int node_id, const ImVec2& grid_pos) { - EditorContext& editor = editor_context_get(); - NodeData& node = object_pool_find_or_create_object(editor.nodes, node_id); - node.origin = grid_pos; + ImNodesEditorContext& editor = EditorContextGet(); + ImNodeData& node = ObjectPoolFindOrCreateObject(editor.Nodes, node_id); + node.Origin = grid_pos; } -void SetNodeDraggable(int node_id, const bool draggable) +void SetNodeDraggable(const int node_id, const bool draggable) { - EditorContext& editor = editor_context_get(); - NodeData& node = object_pool_find_or_create_object(editor.nodes, node_id); - node.draggable = draggable; + ImNodesEditorContext& editor = EditorContextGet(); + ImNodeData& node = ObjectPoolFindOrCreateObject(editor.Nodes, node_id); + node.Draggable = draggable; } ImVec2 GetNodeScreenSpacePos(const int node_id) { - EditorContext& editor = editor_context_get(); - const int node_idx = object_pool_find(editor.nodes, node_id); + ImNodesEditorContext& editor = EditorContextGet(); + const int node_idx = ObjectPoolFind(editor.Nodes, node_id); assert(node_idx != -1); - NodeData& node = editor.nodes.pool[node_idx]; - return grid_space_to_screen_space(editor, node.origin); + ImNodeData& node = editor.Nodes.Pool[node_idx]; + return GridSpaceToScreenSpace(editor, node.Origin); } ImVec2 GetNodeEditorSpacePos(const int node_id) { - EditorContext& editor = editor_context_get(); - const int node_idx = object_pool_find(editor.nodes, node_id); + ImNodesEditorContext& editor = EditorContextGet(); + const int node_idx = ObjectPoolFind(editor.Nodes, node_id); assert(node_idx != -1); - NodeData& node = editor.nodes.pool[node_idx]; - return grid_space_to_editor_space(editor, node.origin); + ImNodeData& node = editor.Nodes.Pool[node_idx]; + return GridSpaceToEditorSpace(editor, node.Origin); } -ImVec2 GetNodeGridSpacePos(int node_id) +ImVec2 GetNodeGridSpacePos(const int node_id) { - EditorContext& editor = editor_context_get(); - const int node_idx = object_pool_find(editor.nodes, node_id); + ImNodesEditorContext& editor = EditorContextGet(); + const int node_idx = ObjectPoolFind(editor.Nodes, node_id); assert(node_idx != -1); - NodeData& node = editor.nodes.pool[node_idx]; - return node.origin; + ImNodeData& node = editor.Nodes.Pool[node_idx]; + return node.Origin; } -bool IsEditorHovered() { return mouse_in_canvas(); } +bool IsEditorHovered() { return MouseInCanvas(); } bool IsNodeHovered(int* const node_id) { - assert(g.current_scope == Scope_None); + assert(GImNodes->CurrentScope == ImNodesScope_None); assert(node_id != NULL); - const bool is_hovered = g.hovered_node_idx.has_value(); + const bool is_hovered = GImNodes->HoveredNodeIdx.HasValue(); if (is_hovered) { - const EditorContext& editor = editor_context_get(); - *node_id = editor.nodes.pool[g.hovered_node_idx.value()].id; + const ImNodesEditorContext& editor = EditorContextGet(); + *node_id = editor.Nodes.Pool[GImNodes->HoveredNodeIdx.Value()].Id; } return is_hovered; } bool IsLinkHovered(int* const link_id) { - assert(g.current_scope == Scope_None); + assert(GImNodes->CurrentScope == ImNodesScope_None); assert(link_id != NULL); - const bool is_hovered = g.hovered_link_idx.has_value(); + const bool is_hovered = GImNodes->HoveredLinkIdx.HasValue(); if (is_hovered) { - const EditorContext& editor = editor_context_get(); - *link_id = editor.links.pool[g.hovered_link_idx.value()].id; + const ImNodesEditorContext& editor = EditorContextGet(); + *link_id = editor.Links.Pool[GImNodes->HoveredLinkIdx.Value()].Id; } return is_hovered; } bool IsPinHovered(int* const attr) { - assert(g.current_scope == Scope_None); + assert(GImNodes->CurrentScope == ImNodesScope_None); assert(attr != NULL); - const bool is_hovered = g.hovered_pin_idx.has_value(); + const bool is_hovered = GImNodes->HoveredPinIdx.HasValue(); if (is_hovered) { - const EditorContext& editor = editor_context_get(); - *attr = editor.pins.pool[g.hovered_pin_idx.value()].id; + const ImNodesEditorContext& editor = EditorContextGet(); + *attr = editor.Pins.Pool[GImNodes->HoveredPinIdx.Value()].Id; } return is_hovered; } int NumSelectedNodes() { - assert(g.current_scope == Scope_None); - const EditorContext& editor = editor_context_get(); - return editor.selected_node_indices.size(); + assert(GImNodes->CurrentScope == ImNodesScope_None); + const ImNodesEditorContext& editor = EditorContextGet(); + return editor.SelectedNodeIndices.size(); } int NumSelectedLinks() { - assert(g.current_scope == Scope_None); - const EditorContext& editor = editor_context_get(); - return editor.selected_link_indices.size(); + assert(GImNodes->CurrentScope == ImNodesScope_None); + const ImNodesEditorContext& editor = EditorContextGet(); + return editor.SelectedLinkIndices.size(); } void GetSelectedNodes(int* node_ids) { assert(node_ids != NULL); - const EditorContext& editor = editor_context_get(); - for (int i = 0; i < editor.selected_node_indices.size(); ++i) + const ImNodesEditorContext& editor = EditorContextGet(); + for (int i = 0; i < editor.SelectedNodeIndices.size(); ++i) { - const int node_idx = editor.selected_node_indices[i]; - node_ids[i] = editor.nodes.pool[node_idx].id; + const int node_idx = editor.SelectedNodeIndices[i]; + node_ids[i] = editor.Nodes.Pool[node_idx].Id; } } @@ -2587,50 +2728,86 @@ void GetSelectedLinks(int* link_ids) { assert(link_ids != NULL); - const EditorContext& editor = editor_context_get(); - for (int i = 0; i < editor.selected_link_indices.size(); ++i) + const ImNodesEditorContext& editor = EditorContextGet(); + for (int i = 0; i < editor.SelectedLinkIndices.size(); ++i) { - const int link_idx = editor.selected_link_indices[i]; - link_ids[i] = editor.links.pool[link_idx].id; + const int link_idx = editor.SelectedLinkIndices[i]; + link_ids[i] = editor.Links.Pool[link_idx].Id; } } void ClearNodeSelection() { - EditorContext& editor = editor_context_get(); - editor.selected_node_indices.clear(); + ImNodesEditorContext& editor = EditorContextGet(); + editor.SelectedNodeIndices.clear(); +} + +void ClearNodeSelection(int node_id) +{ + ImNodesEditorContext& editor = EditorContextGet(); + ClearObjectSelection(editor.Nodes, editor.SelectedNodeIndices, node_id); } void ClearLinkSelection() { - EditorContext& editor = editor_context_get(); - editor.selected_link_indices.clear(); + ImNodesEditorContext& editor = EditorContextGet(); + editor.SelectedLinkIndices.clear(); +} + +void ClearLinkSelection(int link_id) +{ + ImNodesEditorContext& editor = EditorContextGet(); + ClearObjectSelection(editor.Links, editor.SelectedLinkIndices, link_id); +} + +void SelectNode(int node_id) +{ + ImNodesEditorContext& editor = EditorContextGet(); + SelectObject(editor.Nodes, editor.SelectedNodeIndices, node_id); +} + +void SelectLink(int link_id) +{ + ImNodesEditorContext& editor = EditorContextGet(); + SelectObject(editor.Links, editor.SelectedLinkIndices, link_id); +} + +bool IsNodeSelected(int node_id) +{ + ImNodesEditorContext& editor = EditorContextGet(); + return IsObjectSelected(editor.Nodes, editor.SelectedNodeIndices, node_id); +} + +bool IsLinkSelected(int link_id) +{ + ImNodesEditorContext& editor = EditorContextGet(); + return IsObjectSelected(editor.Links, editor.SelectedLinkIndices, link_id); } bool IsAttributeActive() { - assert((g.current_scope & Scope_Node) != 0); + assert((GImNodes->CurrentScope & ImNodesScope_Node) != 0); - if (!g.active_attribute) + if (!GImNodes->ActiveAttribute) { return false; } - return g.active_attribute_id == g.current_attribute_id; + return GImNodes->ActiveAttributeId == GImNodes->CurrentAttributeId; } bool IsAnyAttributeActive(int* const attribute_id) { - assert((g.current_scope & (Scope_Node | Scope_Attribute)) == 0); + assert((GImNodes->CurrentScope & (ImNodesScope_Node | ImNodesScope_Attribute)) == 0); - if (!g.active_attribute) + if (!GImNodes->ActiveAttribute) { return false; } if (attribute_id != NULL) { - *attribute_id = g.active_attribute_id; + *attribute_id = GImNodes->ActiveAttributeId; } return true; @@ -2639,15 +2816,15 @@ bool IsAnyAttributeActive(int* const attribute_id) bool IsLinkStarted(int* const started_at_id) { // Call this function after EndNodeEditor()! - assert(g.current_scope == Scope_None); + assert(GImNodes->CurrentScope == ImNodesScope_None); assert(started_at_id != NULL); - const bool is_started = (g.element_state_change & ElementStateChange_LinkStarted) != 0; + const bool is_started = (GImNodes->ImNodesUIState & ImNodesUIState_LinkStarted) != 0; if (is_started) { - const EditorContext& editor = editor_context_get(); - const int pin_idx = editor.click_interaction_state.link_creation.start_pin_idx; - *started_at_id = editor.pins.pool[pin_idx].id; + const ImNodesEditorContext& editor = EditorContextGet(); + const int pin_idx = editor.ClickInteraction.LinkCreation.StartPinIdx; + *started_at_id = editor.Pins.Pool[pin_idx].Id; } return is_started; @@ -2656,57 +2833,58 @@ bool IsLinkStarted(int* const started_at_id) bool IsLinkDropped(int* const started_at_id, const bool including_detached_links) { // Call this function after EndNodeEditor()! - assert(g.current_scope == Scope_None); + assert(GImNodes->CurrentScope == ImNodesScope_None); - const EditorContext& editor = editor_context_get(); + const ImNodesEditorContext& editor = EditorContextGet(); - const bool link_dropped = (g.element_state_change & ElementStateChange_LinkDropped) != 0 && - (including_detached_links || - editor.click_interaction_state.link_creation.link_creation_type != - LinkCreationType_FromDetach); + const bool link_dropped = + (GImNodes->ImNodesUIState & ImNodesUIState_LinkDropped) != 0 && + (including_detached_links || + editor.ClickInteraction.LinkCreation.Type != ImNodesLinkCreationType_FromDetach); if (link_dropped && started_at_id) { - const int pin_idx = editor.click_interaction_state.link_creation.start_pin_idx; - *started_at_id = editor.pins.pool[pin_idx].id; + const int pin_idx = editor.ClickInteraction.LinkCreation.StartPinIdx; + *started_at_id = editor.Pins.Pool[pin_idx].Id; } return link_dropped; } bool IsLinkCreated( - int* const started_at_pin_id, - int* const ended_at_pin_id, + int* const started_at_pin_id, + int* const ended_at_pin_id, bool* const created_from_snap) { - assert(g.current_scope == Scope_None); + assert(GImNodes->CurrentScope == ImNodesScope_None); assert(started_at_pin_id != NULL); assert(ended_at_pin_id != NULL); - const bool is_created = (g.element_state_change & ElementStateChange_LinkCreated) != 0; + const bool is_created = (GImNodes->ImNodesUIState & ImNodesUIState_LinkCreated) != 0; if (is_created) { - const EditorContext& editor = editor_context_get(); - const int start_idx = editor.click_interaction_state.link_creation.start_pin_idx; - const int end_idx = editor.click_interaction_state.link_creation.end_pin_idx.value(); - const PinData& start_pin = editor.pins.pool[start_idx]; - const PinData& end_pin = editor.pins.pool[end_idx]; + const ImNodesEditorContext& editor = EditorContextGet(); + const int start_idx = editor.ClickInteraction.LinkCreation.StartPinIdx; + const int end_idx = editor.ClickInteraction.LinkCreation.EndPinIdx.Value(); + const ImPinData& start_pin = editor.Pins.Pool[start_idx]; + const ImPinData& end_pin = editor.Pins.Pool[end_idx]; - if (start_pin.type == AttributeType_Output) + if (start_pin.Type == ImNodesAttributeType_Output) { - *started_at_pin_id = start_pin.id; - *ended_at_pin_id = end_pin.id; + *started_at_pin_id = start_pin.Id; + *ended_at_pin_id = end_pin.Id; } else { - *started_at_pin_id = end_pin.id; - *ended_at_pin_id = start_pin.id; + *started_at_pin_id = end_pin.Id; + *ended_at_pin_id = start_pin.Id; } if (created_from_snap) { - *created_from_snap = editor.click_interaction_type == ClickInteractionType_LinkCreation; + *created_from_snap = + editor.ClickInteraction.Type == ImNodesClickInteractionType_LinkCreation; } } @@ -2714,48 +2892,49 @@ bool IsLinkCreated( } bool IsLinkCreated( - int* started_at_node_id, - int* started_at_pin_id, - int* ended_at_node_id, - int* ended_at_pin_id, + int* started_at_node_id, + int* started_at_pin_id, + int* ended_at_node_id, + int* ended_at_pin_id, bool* created_from_snap) { - assert(g.current_scope == Scope_None); + assert(GImNodes->CurrentScope == ImNodesScope_None); assert(started_at_node_id != NULL); assert(started_at_pin_id != NULL); assert(ended_at_node_id != NULL); assert(ended_at_pin_id != NULL); - const bool is_created = (g.element_state_change & ElementStateChange_LinkCreated) != 0; + const bool is_created = (GImNodes->ImNodesUIState & ImNodesUIState_LinkCreated) != 0; if (is_created) { - const EditorContext& editor = editor_context_get(); - const int start_idx = editor.click_interaction_state.link_creation.start_pin_idx; - const int end_idx = editor.click_interaction_state.link_creation.end_pin_idx.value(); - const PinData& start_pin = editor.pins.pool[start_idx]; - const NodeData& start_node = editor.nodes.pool[start_pin.parent_node_idx]; - const PinData& end_pin = editor.pins.pool[end_idx]; - const NodeData& end_node = editor.nodes.pool[end_pin.parent_node_idx]; + const ImNodesEditorContext& editor = EditorContextGet(); + const int start_idx = editor.ClickInteraction.LinkCreation.StartPinIdx; + const int end_idx = editor.ClickInteraction.LinkCreation.EndPinIdx.Value(); + const ImPinData& start_pin = editor.Pins.Pool[start_idx]; + const ImNodeData& start_node = editor.Nodes.Pool[start_pin.ParentNodeIdx]; + const ImPinData& end_pin = editor.Pins.Pool[end_idx]; + const ImNodeData& end_node = editor.Nodes.Pool[end_pin.ParentNodeIdx]; - if (start_pin.type == AttributeType_Output) + if (start_pin.Type == ImNodesAttributeType_Output) { - *started_at_pin_id = start_pin.id; - *started_at_node_id = start_node.id; - *ended_at_pin_id = end_pin.id; - *ended_at_node_id = end_node.id; + *started_at_pin_id = start_pin.Id; + *started_at_node_id = start_node.Id; + *ended_at_pin_id = end_pin.Id; + *ended_at_node_id = end_node.Id; } else { - *started_at_pin_id = end_pin.id; - *started_at_node_id = end_node.id; - *ended_at_pin_id = start_pin.id; - *ended_at_node_id = start_node.id; + *started_at_pin_id = end_pin.Id; + *started_at_node_id = end_node.Id; + *ended_at_pin_id = start_pin.Id; + *ended_at_node_id = start_node.Id; } if (created_from_snap) { - *created_from_snap = editor.click_interaction_type == ClickInteractionType_LinkCreation; + *created_from_snap = + editor.ClickInteraction.Type == ImNodesClickInteractionType_LinkCreation; } } @@ -2764,14 +2943,14 @@ bool IsLinkCreated( bool IsLinkDestroyed(int* const link_id) { - assert(g.current_scope == Scope_None); + assert(GImNodes->CurrentScope == ImNodesScope_None); - const bool link_destroyed = g.deleted_link_idx.has_value(); + const bool link_destroyed = GImNodes->DeletedLinkIdx.HasValue(); if (link_destroyed) { - const EditorContext& editor = editor_context_get(); - const int link_idx = g.deleted_link_idx.value(); - *link_id = editor.links.pool[link_idx].id; + const ImNodesEditorContext& editor = EditorContextGet(); + const int link_idx = GImNodes->DeletedLinkIdx.Value(); + *link_id = editor.Links.Pool[link_idx].Id; } return link_destroyed; @@ -2779,90 +2958,90 @@ bool IsLinkDestroyed(int* const link_id) namespace { -void node_line_handler(EditorContext& editor, const char* line) +void NodeLineHandler(ImNodesEditorContext& editor, const char* const line) { int id; int x, y; if (sscanf(line, "[node.%i", &id) == 1) { - const int node_idx = object_pool_find_or_create_index(editor.nodes, id); - g.current_node_idx = node_idx; - NodeData& node = editor.nodes.pool[node_idx]; - node.id = id; + const int node_idx = ObjectPoolFindOrCreateIndex(editor.Nodes, id); + GImNodes->CurrentNodeIdx = node_idx; + ImNodeData& node = editor.Nodes.Pool[node_idx]; + node.Id = id; } else if (sscanf(line, "origin=%i,%i", &x, &y) == 2) { - NodeData& node = editor.nodes.pool[g.current_node_idx]; - node.origin = ImVec2((float)x, (float)y); + ImNodeData& node = editor.Nodes.Pool[GImNodes->CurrentNodeIdx]; + node.Origin = ImVec2((float)x, (float)y); } } -void editor_line_handler(EditorContext& editor, const char* line) +void EditorLineHandler(ImNodesEditorContext& editor, const char* const line) { - sscanf(line, "panning=%f,%f", &editor.panning.x, &editor.panning.y); + (void)sscanf(line, "panning=%f,%f", &editor.Panning.x, &editor.Panning.y); } } // namespace const char* SaveCurrentEditorStateToIniString(size_t* const data_size) { - return SaveEditorStateToIniString(&editor_context_get(), data_size); + return SaveEditorStateToIniString(&EditorContextGet(), data_size); } const char* SaveEditorStateToIniString( - const EditorContext* const editor_ptr, - size_t* const data_size) + const ImNodesEditorContext* const editor_ptr, + size_t* const data_size) { assert(editor_ptr != NULL); - const EditorContext& editor = *editor_ptr; + const ImNodesEditorContext& editor = *editor_ptr; - g.text_buffer.clear(); + GImNodes->TextBuffer.clear(); // TODO: check to make sure that the estimate is the upper bound of element - g.text_buffer.reserve(64 * editor.nodes.pool.size()); + GImNodes->TextBuffer.reserve(64 * editor.Nodes.Pool.size()); - g.text_buffer.appendf( - "[editor]\npanning=%i,%i\n", (int)editor.panning.x, (int)editor.panning.y); + GImNodes->TextBuffer.appendf( + "[editor]\npanning=%i,%i\n", (int)editor.Panning.x, (int)editor.Panning.y); - for (int i = 0; i < editor.nodes.pool.size(); i++) + for (int i = 0; i < editor.Nodes.Pool.size(); i++) { - if (editor.nodes.in_use[i]) + if (editor.Nodes.InUse[i]) { - const NodeData& node = editor.nodes.pool[i]; - g.text_buffer.appendf("\n[node.%d]\n", node.id); - g.text_buffer.appendf("origin=%i,%i\n", (int)node.origin.x, (int)node.origin.y); + const ImNodeData& node = editor.Nodes.Pool[i]; + GImNodes->TextBuffer.appendf("\n[node.%d]\n", node.Id); + GImNodes->TextBuffer.appendf("origin=%i,%i\n", (int)node.Origin.x, (int)node.Origin.y); } } if (data_size != NULL) { - *data_size = g.text_buffer.size(); + *data_size = GImNodes->TextBuffer.size(); } - return g.text_buffer.c_str(); + return GImNodes->TextBuffer.c_str(); } void LoadCurrentEditorStateFromIniString(const char* const data, const size_t data_size) { - LoadEditorStateFromIniString(&editor_context_get(), data, data_size); + LoadEditorStateFromIniString(&EditorContextGet(), data, data_size); } void LoadEditorStateFromIniString( - EditorContext* const editor_ptr, - const char* const data, - const size_t data_size) + ImNodesEditorContext* const editor_ptr, + const char* const data, + const size_t data_size) { if (data_size == 0u) { return; } - EditorContext& editor = editor_ptr == NULL ? editor_context_get() : *editor_ptr; + ImNodesEditorContext& editor = editor_ptr == NULL ? EditorContextGet() : *editor_ptr; - char* buf = (char*)ImGui::MemAlloc(data_size + 1); + char* buf = (char*)ImGui::MemAlloc(data_size + 1); const char* buf_end = buf + data_size; memcpy(buf, data, data_size); buf[data_size] = 0; - void (*line_handler)(EditorContext&, const char*); + void (*line_handler)(ImNodesEditorContext&, const char*); line_handler = NULL; char* line_end = NULL; for (char* line = buf; line < buf_end; line = line_end + 1) @@ -2888,11 +3067,11 @@ void LoadEditorStateFromIniString( line_end[-1] = 0; if (strncmp(line + 1, "node", 4) == 0) { - line_handler = node_line_handler; + line_handler = NodeLineHandler; } else if (strcmp(line + 1, "editor") == 0) { - line_handler = editor_line_handler; + line_handler = EditorLineHandler; } } @@ -2906,14 +3085,14 @@ void LoadEditorStateFromIniString( void SaveCurrentEditorStateToIniFile(const char* const file_name) { - SaveEditorStateToIniFile(&editor_context_get(), file_name); + SaveEditorStateToIniFile(&EditorContextGet(), file_name); } -void SaveEditorStateToIniFile(const EditorContext* const editor, const char* const file_name) +void SaveEditorStateToIniFile(const ImNodesEditorContext* const editor, const char* const file_name) { - size_t data_size = 0u; + size_t data_size = 0u; const char* data = SaveEditorStateToIniString(editor, &data_size); - FILE* file = ImFileOpen(file_name, "wt"); + FILE* file = ImFileOpen(file_name, "wt"); if (!file) { return; @@ -2925,13 +3104,13 @@ void SaveEditorStateToIniFile(const EditorContext* const editor, const char* con void LoadCurrentEditorStateFromIniFile(const char* const file_name) { - LoadEditorStateFromIniFile(&editor_context_get(), file_name); + LoadEditorStateFromIniFile(&EditorContextGet(), file_name); } -void LoadEditorStateFromIniFile(EditorContext* const editor, const char* const file_name) +void LoadEditorStateFromIniFile(ImNodesEditorContext* const editor, const char* const file_name) { size_t data_size = 0u; - char* file_data = (char*)ImFileLoadToMemory(file_name, "rb", &data_size); + char* file_data = (char*)ImFileLoadToMemory(file_name, "rb", &data_size); if (!file_data) { @@ -2941,4 +3120,4 @@ void LoadEditorStateFromIniFile(EditorContext* const editor, const char* const f LoadEditorStateFromIniString(editor, file_data, data_size); ImGui::MemFree(file_data); } -} // namespace imnodes +} // namespace ImNodes diff --git a/source/views/view_data_processor.cpp b/source/views/view_data_processor.cpp index 4d1aa98cf..0817faec5 100644 --- a/source/views/view_data_processor.cpp +++ b/source/views/view_data_processor.cpp @@ -9,16 +9,6 @@ namespace hex { ViewDataProcessor::ViewDataProcessor() : View("hex.view.data_processor.name") { - imnodes::Initialize(); - imnodes::PushAttributeFlag(imnodes::AttributeFlags_EnableLinkDetachWithDragClick); - imnodes::PushAttributeFlag(imnodes::AttributeFlags_EnableLinkCreationOnSnap); - - { - static bool always = true; - imnodes::IO& io = imnodes::GetIO(); - io.link_detach_with_modifier_click.modifier = &always; - } - EventManager::subscribe(this, [] { auto theme = ContentRegistry::Settings::getSetting("hex.builtin.setting.interface", "hex.builtin.setting.interface.color"); @@ -26,17 +16,17 @@ namespace hex { switch (static_cast(theme)) { default: case 0: /* Dark theme */ - imnodes::StyleColorsDark(); + ImNodes::StyleColorsDark(); break; case 1: /* Light theme */ - imnodes::StyleColorsLight(); + ImNodes::StyleColorsLight(); break; case 2: /* Classic theme */ - imnodes::StyleColorsClassic(); + ImNodes::StyleColorsClassic(); break; } - imnodes::GetStyle().flags = imnodes::StyleFlags(imnodes::StyleFlags_NodeOutline | imnodes::StyleFlags_GridLines); + ImNodes::GetStyle().Flags = ImNodesStyleFlags_NodeOutline | ImNodesStyleFlags_GridLines; } }); @@ -64,10 +54,6 @@ namespace hex { EventManager::unsubscribe(this); EventManager::unsubscribe(this); EventManager::unsubscribe(this); - - imnodes::PopAttributeFlag(); - imnodes::PopAttributeFlag(); - imnodes::Shutdown(); } @@ -155,14 +141,14 @@ namespace hex { if (ImGui::Begin(View::toWindowName("hex.view.data_processor.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse)) { if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) { - imnodes::ClearNodeSelection(); - imnodes::ClearLinkSelection(); + ImNodes::ClearNodeSelection(); + ImNodes::ClearLinkSelection(); this->m_rightClickedCoords = ImGui::GetMousePos(); - if (imnodes::IsNodeHovered(&this->m_rightClickedId)) + if (ImNodes::IsNodeHovered(&this->m_rightClickedId)) ImGui::OpenPopup("Node Menu"); - else if (imnodes::IsLinkHovered(&this->m_rightClickedId)) + else if (ImNodes::IsLinkHovered(&this->m_rightClickedId)) ImGui::OpenPopup("Link Menu"); else ImGui::OpenPopup("Context Menu"); @@ -171,21 +157,21 @@ namespace hex { if (ImGui::BeginPopup("Context Menu")) { dp::Node *node = nullptr; - if (imnodes::NumSelectedNodes() > 0 || imnodes::NumSelectedLinks() > 0) { + if (ImNodes::NumSelectedNodes() > 0 || ImNodes::NumSelectedLinks() > 0) { if (ImGui::MenuItem("hex.view.data_processor.name"_lang)) { std::vector ids; - ids.resize(imnodes::NumSelectedNodes()); - imnodes::GetSelectedNodes(ids.data()); + ids.resize(ImNodes::NumSelectedNodes()); + ImNodes::GetSelectedNodes(ids.data()); this->eraseNodes(ids); - imnodes::ClearNodeSelection(); + ImNodes::ClearNodeSelection(); - ids.resize(imnodes::NumSelectedLinks()); - imnodes::GetSelectedLinks(ids.data()); + ids.resize(ImNodes::NumSelectedLinks()); + ImNodes::GetSelectedLinks(ids.data()); for (auto id : ids) this->eraseLink(id); - imnodes::ClearLinkSelection(); + ImNodes::ClearLinkSelection(); } } @@ -222,7 +208,7 @@ namespace hex { if (hasInput && !hasOutput) this->m_endNodes.push_back(node); - imnodes::SetNodeScreenSpacePos(node->getID(), this->m_rightClickedCoords); + ImNodes::SetNodeScreenSpacePos(node->getID(), this->m_rightClickedCoords); } ImGui::EndPopup(); @@ -244,7 +230,7 @@ namespace hex { { int nodeId; - if (imnodes::IsNodeHovered(&nodeId) && this->m_currNodeError.has_value() && this->m_currNodeError->first->getID() == nodeId) { + if (ImNodes::IsNodeHovered(&nodeId) && this->m_currNodeError.has_value() && this->m_currNodeError->first->getID() == nodeId) { ImGui::BeginTooltip(); ImGui::TextUnformatted("hex.common.error"_lang); ImGui::Separator(); @@ -253,63 +239,65 @@ namespace hex { } } - imnodes::BeginNodeEditor(); + ImNodes::BeginNodeEditor(); for (auto& node : this->m_nodes) { const bool hasError = this->m_currNodeError.has_value() && this->m_currNodeError->first == node; if (hasError) - imnodes::PushColorStyle(imnodes::ColorStyle_NodeOutline, 0xFF0000FF); + ImNodes::PushColorStyle(ImNodesCol_NodeOutline, 0xFF0000FF); - imnodes::BeginNode(node->getID()); + ImNodes::BeginNode(node->getID()); - imnodes::BeginNodeTitleBar(); + ImNodes::BeginNodeTitleBar(); ImGui::TextUnformatted(LangEntry(node->getUnlocalizedTitle())); - imnodes::EndNodeTitleBar(); + ImNodes::EndNodeTitleBar(); node->drawNode(); for (auto& attribute : node->getAttributes()) { - imnodes::PinShape pinShape; + ImNodesPinShape pinShape; switch (attribute.getType()) { - case dp::Attribute::Type::Integer: pinShape = imnodes::PinShape_Circle; break; - case dp::Attribute::Type::Float: pinShape = imnodes::PinShape_Triangle; break; - case dp::Attribute::Type::Buffer: pinShape = imnodes::PinShape_Quad; break; + case dp::Attribute::Type::Integer: pinShape = ImNodesPinShape_Circle; break; + case dp::Attribute::Type::Float: pinShape = ImNodesPinShape_Triangle; break; + case dp::Attribute::Type::Buffer: pinShape = ImNodesPinShape_Quad; break; } if (attribute.getIOType() == dp::Attribute::IOType::In) { - imnodes::BeginInputAttribute(attribute.getID(), pinShape); + ImNodes::BeginInputAttribute(attribute.getID(), pinShape); ImGui::TextUnformatted(LangEntry(attribute.getUnlocalizedName())); - imnodes::EndInputAttribute(); + ImNodes::EndInputAttribute(); } else if (attribute.getIOType() == dp::Attribute::IOType::Out) { - imnodes::BeginOutputAttribute(attribute.getID(), imnodes::PinShape(pinShape + 1)); + ImNodes::BeginOutputAttribute(attribute.getID(), ImNodesPinShape(pinShape + 1)); ImGui::TextUnformatted(LangEntry(attribute.getUnlocalizedName())); - imnodes::EndOutputAttribute(); + ImNodes::EndOutputAttribute(); } } - imnodes::EndNode(); + ImNodes::EndNode(); if (hasError) - imnodes::PopColorStyle(); + ImNodes::PopColorStyle(); } for (const auto &link : this->m_links) - imnodes::Link(link.getID(), link.getFromID(), link.getToID()); + ImNodes::Link(link.getID(), link.getFromID(), link.getToID()); - imnodes::EndNodeEditor(); + ImNodes::MiniMap(0.2F, ImNodesMiniMapLocation_BottomRight); + + ImNodes::EndNodeEditor(); { int linkId; - if (imnodes::IsLinkDestroyed(&linkId)) { + if (ImNodes::IsLinkDestroyed(&linkId)) { this->eraseLink(linkId); } } { int from, to; - if (imnodes::IsLinkCreated(&from, &to)) { + if (ImNodes::IsLinkCreated(&from, &to)) { do { dp::Attribute *fromAttr, *toAttr; @@ -344,11 +332,11 @@ namespace hex { } { - const int selectedLinkCount = imnodes::NumSelectedLinks(); + const int selectedLinkCount = ImNodes::NumSelectedLinks(); if (selectedLinkCount > 0 && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) { static std::vector selectedLinks; selectedLinks.resize(static_cast(selectedLinkCount)); - imnodes::GetSelectedLinks(selectedLinks.data()); + ImNodes::GetSelectedLinks(selectedLinks.data()); for (const int id : selectedLinks) { eraseLink(id); @@ -358,11 +346,11 @@ namespace hex { } { - const int selectedNodeCount = imnodes::NumSelectedNodes(); + const int selectedNodeCount = ImNodes::NumSelectedNodes(); if (selectedNodeCount > 0 && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) { static std::vector selectedNodes; selectedNodes.resize(static_cast(selectedNodeCount)); - imnodes::GetSelectedNodes(selectedNodes.data()); + ImNodes::GetSelectedNodes(selectedNodes.data()); this->eraseNodes(selectedNodes); @@ -387,7 +375,7 @@ namespace hex { for (auto &node : this->m_nodes) { auto id = node->getID(); auto &currNodeOutput = output["nodes"][std::to_string(id)]; - auto pos = imnodes::GetNodeGridSpacePos(id); + auto pos = ImNodes::GetNodeGridSpacePos(id); currNodeOutput["type"] = node->getUnlocalizedName(); currNodeOutput["pos"] = { { "x", pos.x }, { "y", pos.y } }; @@ -472,7 +460,7 @@ namespace hex { this->m_endNodes.push_back(newNode); this->m_nodes.push_back(newNode); - imnodes::SetNodeGridSpacePos(nodeId, ImVec2(node["pos"]["x"], node["pos"]["y"])); + ImNodes::SetNodeGridSpacePos(nodeId, ImVec2(node["pos"]["x"], node["pos"]["y"])); } for (auto &link : input["links"]) {