zgui/libs/node_editor/imgui_node_editor_internal.h

1561 lines
44 KiB
C
Raw Normal View History

//------------------------------------------------------------------------------
// VERSION 0.9.1
//
// LICENSE
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
//
// CREDITS
// Written by Michal Cichon
//------------------------------------------------------------------------------
# ifndef __IMGUI_NODE_EDITOR_INTERNAL_H__
# define __IMGUI_NODE_EDITOR_INTERNAL_H__
# pragma once
//------------------------------------------------------------------------------
# ifndef IMGUI_DEFINE_MATH_OPERATORS
# define IMGUI_DEFINE_MATH_OPERATORS
# endif
# include "imgui_node_editor.h"
//------------------------------------------------------------------------------
# include <imgui.h>
# include <imgui_internal.h>
# include "imgui_extra_math.h"
# include "imgui_bezier_math.h"
# include "imgui_canvas.h"
# include "crude_json.h"
# include <vector>
# include <string>
//------------------------------------------------------------------------------
namespace ax {
namespace NodeEditor {
namespace Detail {
//------------------------------------------------------------------------------
namespace ed = ax::NodeEditor::Detail;
namespace json = crude_json;
//------------------------------------------------------------------------------
using std::vector;
using std::string;
//------------------------------------------------------------------------------
void Log(const char* fmt, ...);
//------------------------------------------------------------------------------
//inline ImRect ToRect(const ax::rectf& rect);
//inline ImRect ToRect(const ax::rect& rect);
inline ImRect ImGui_GetItemRect();
inline ImVec2 ImGui_GetMouseClickPos(ImGuiMouseButton buttonIndex);
//------------------------------------------------------------------------------
// https://stackoverflow.com/a/36079786
# define DECLARE_HAS_MEMBER(__trait_name__, __member_name__) \
\
template <typename __boost_has_member_T__> \
class __trait_name__ \
{ \
using check_type = ::std::remove_const_t<__boost_has_member_T__>; \
struct no_type {char x[2];}; \
using yes_type = char; \
\
struct base { void __member_name__() {}}; \
struct mixin : public base, public check_type {}; \
\
template <void (base::*)()> struct aux {}; \
\
template <typename U> static no_type test(aux<&U::__member_name__>*); \
template <typename U> static yes_type test(...); \
\
public: \
\
static constexpr bool value = (sizeof(yes_type) == sizeof(test<mixin>(0))); \
}
DECLARE_HAS_MEMBER(HasFringeScale, _FringeScale);
# undef DECLARE_HAS_MEMBER
struct FringeScaleRef
{
// Overload is present when ImDrawList does have _FringeScale member variable.
template <typename T>
static float& Get(typename std::enable_if<HasFringeScale<T>::value, T>::type* drawList)
{
return drawList->_FringeScale;
}
// Overload is present when ImDrawList does not have _FringeScale member variable.
template <typename T>
static float& Get(typename std::enable_if<!HasFringeScale<T>::value, T>::type*)
{
static float placeholder = 1.0f;
return placeholder;
}
};
static inline float& ImFringeScaleRef(ImDrawList* drawList)
{
return FringeScaleRef::Get<ImDrawList>(drawList);
}
struct FringeScaleScope
{
FringeScaleScope(float scale)
: m_LastFringeScale(ImFringeScaleRef(ImGui::GetWindowDrawList()))
{
ImFringeScaleRef(ImGui::GetWindowDrawList()) = scale;
}
~FringeScaleScope()
{
ImFringeScaleRef(ImGui::GetWindowDrawList()) = m_LastFringeScale;
}
private:
float m_LastFringeScale;
};
//------------------------------------------------------------------------------
enum class ObjectType
{
None,
Node,
Link,
Pin
};
using ax::NodeEditor::PinKind;
using ax::NodeEditor::StyleColor;
using ax::NodeEditor::StyleVar;
using ax::NodeEditor::SaveReasonFlags;
using ax::NodeEditor::NodeId;
using ax::NodeEditor::PinId;
using ax::NodeEditor::LinkId;
struct ObjectId final: Details::SafePointerType<ObjectId>
{
using Super = Details::SafePointerType<ObjectId>;
using Super::Super;
ObjectId(): Super(Invalid), m_Type(ObjectType::None) {}
ObjectId(PinId pinId): Super(pinId.AsPointer()), m_Type(ObjectType::Pin) {}
ObjectId(NodeId nodeId): Super(nodeId.AsPointer()), m_Type(ObjectType::Node) {}
ObjectId(LinkId linkId): Super(linkId.AsPointer()), m_Type(ObjectType::Link) {}
explicit operator PinId() const { return AsPinId(); }
explicit operator NodeId() const { return AsNodeId(); }
explicit operator LinkId() const { return AsLinkId(); }
PinId AsPinId() const { IM_ASSERT(IsPinId()); return PinId(AsPointer()); }
NodeId AsNodeId() const { IM_ASSERT(IsNodeId()); return NodeId(AsPointer()); }
LinkId AsLinkId() const { IM_ASSERT(IsLinkId()); return LinkId(AsPointer()); }
bool IsPinId() const { return m_Type == ObjectType::Pin; }
bool IsNodeId() const { return m_Type == ObjectType::Node; }
bool IsLinkId() const { return m_Type == ObjectType::Link; }
ObjectType Type() const { return m_Type; }
private:
ObjectType m_Type;
};
struct EditorContext;
struct Node;
struct Pin;
struct Link;
template <typename T, typename Id = typename T::IdType>
struct ObjectWrapper
{
Id m_ID;
T* m_Object;
T* operator->() { return m_Object; }
const T* operator->() const { return m_Object; }
operator T*() { return m_Object; }
operator const T*() const { return m_Object; }
bool operator<(const ObjectWrapper& rhs) const
{
return m_ID.AsPointer() < rhs.m_ID.AsPointer();
}
};
struct Object
{
enum DrawFlags
{
None = 0,
Hovered = 1,
Selected = 2,
Highlighted = 4,
};
inline friend DrawFlags operator|(DrawFlags lhs, DrawFlags rhs) { return static_cast<DrawFlags>(static_cast<int>(lhs) | static_cast<int>(rhs)); }
inline friend DrawFlags operator&(DrawFlags lhs, DrawFlags rhs) { return static_cast<DrawFlags>(static_cast<int>(lhs) & static_cast<int>(rhs)); }
inline friend DrawFlags& operator|=(DrawFlags& lhs, DrawFlags rhs) { lhs = lhs | rhs; return lhs; }
inline friend DrawFlags& operator&=(DrawFlags& lhs, DrawFlags rhs) { lhs = lhs & rhs; return lhs; }
EditorContext* const Editor;
bool m_IsLive;
bool m_IsSelected;
bool m_DeleteOnNewFrame;
Object(EditorContext* editor)
: Editor(editor)
, m_IsLive(true)
, m_IsSelected(false)
, m_DeleteOnNewFrame(false)
{
}
virtual ~Object() = default;
virtual ObjectId ID() = 0;
bool IsVisible() const
{
if (!m_IsLive)
return false;
const auto bounds = GetBounds();
return ImGui::IsRectVisible(bounds.Min, bounds.Max);
}
virtual void Reset() { m_IsLive = false; }
virtual void Draw(ImDrawList* drawList, DrawFlags flags = None) = 0;
virtual bool AcceptDrag() { return false; }
virtual void UpdateDrag(const ImVec2& offset) { IM_UNUSED(offset); }
virtual bool EndDrag() { return false; }
virtual ImVec2 DragStartLocation() { return GetBounds().Min; }
virtual bool IsDraggable() { bool result = AcceptDrag(); EndDrag(); return result; }
virtual bool IsSelectable() { return false; }
virtual bool TestHit(const ImVec2& point, float extraThickness = 0.0f) const
{
if (!m_IsLive)
return false;
auto bounds = GetBounds();
if (extraThickness > 0)
bounds.Expand(extraThickness);
return bounds.Contains(point);
}
virtual bool TestHit(const ImRect& rect, bool allowIntersect = true) const
{
if (!m_IsLive)
return false;
const auto bounds = GetBounds();
return !ImRect_IsEmpty(bounds) && (allowIntersect ? bounds.Overlaps(rect) : rect.Contains(bounds));
}
virtual ImRect GetBounds() const = 0;
virtual Node* AsNode() { return nullptr; }
virtual Pin* AsPin() { return nullptr; }
virtual Link* AsLink() { return nullptr; }
};
struct Pin final: Object
{
using IdType = PinId;
PinId m_ID;
PinKind m_Kind;
Node* m_Node;
ImRect m_Bounds;
ImRect m_Pivot;
Pin* m_PreviousPin;
ImU32 m_Color;
ImU32 m_BorderColor;
float m_BorderWidth;
float m_Rounding;
int m_Corners;
ImVec2 m_Dir;
float m_Strength;
float m_Radius;
float m_ArrowSize;
float m_ArrowWidth;
bool m_SnapLinkToDir;
bool m_HasConnection;
bool m_HadConnection;
Pin(EditorContext* editor, PinId id, PinKind kind)
: Object(editor)
, m_ID(id)
, m_Kind(kind)
, m_Node(nullptr)
, m_Bounds()
, m_PreviousPin(nullptr)
, m_Color(IM_COL32_WHITE)
, m_BorderColor(IM_COL32_BLACK)
, m_BorderWidth(0)
, m_Rounding(0)
, m_Corners(0)
, m_Dir(0, 0)
, m_Strength(0)
, m_Radius(0)
, m_ArrowSize(0)
, m_ArrowWidth(0)
, m_SnapLinkToDir(true)
, m_HasConnection(false)
, m_HadConnection(false)
{
}
virtual ObjectId ID() override { return m_ID; }
virtual void Reset() override final
{
m_HadConnection = m_HasConnection && m_IsLive;
m_HasConnection = false;
Object::Reset();
}
virtual void Draw(ImDrawList* drawList, DrawFlags flags = None) override final;
ImVec2 GetClosestPoint(const ImVec2& p) const;
ImLine GetClosestLine(const Pin* pin) const;
virtual ImRect GetBounds() const override final { return m_Bounds; }
virtual Pin* AsPin() override final { return this; }
};
enum class NodeType
{
Node,
Group
};
enum class NodeRegion : uint8_t
{
None = 0x00,
Top = 0x01,
Bottom = 0x02,
Left = 0x04,
Right = 0x08,
Center = 0x10,
Header = 0x20,
TopLeft = Top | Left,
TopRight = Top | Right,
BottomLeft = Bottom | Left,
BottomRight = Bottom | Right,
};
inline NodeRegion operator |(NodeRegion lhs, NodeRegion rhs) { return static_cast<NodeRegion>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs)); }
inline NodeRegion operator &(NodeRegion lhs, NodeRegion rhs) { return static_cast<NodeRegion>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs)); }
struct Node final: Object
{
using IdType = NodeId;
NodeId m_ID;
NodeType m_Type;
ImRect m_Bounds;
float m_ZPosition;
int m_Channel;
Pin* m_LastPin;
ImVec2 m_DragStart;
ImU32 m_Color;
ImU32 m_BorderColor;
float m_BorderWidth;
float m_Rounding;
ImU32 m_GroupColor;
ImU32 m_GroupBorderColor;
float m_GroupBorderWidth;
float m_GroupRounding;
ImRect m_GroupBounds;
bool m_HighlightConnectedLinks;
bool m_RestoreState;
bool m_CenterOnScreen;
Node(EditorContext* editor, NodeId id)
: Object(editor)
, m_ID(id)
, m_Type(NodeType::Node)
, m_Bounds()
, m_ZPosition(0.0f)
, m_Channel(0)
, m_LastPin(nullptr)
, m_DragStart()
, m_Color(IM_COL32_WHITE)
, m_BorderColor(IM_COL32_BLACK)
, m_BorderWidth(0)
, m_Rounding(0)
, m_GroupBounds()
, m_HighlightConnectedLinks(false)
, m_RestoreState(false)
, m_CenterOnScreen(false)
{
}
virtual ObjectId ID() override { return m_ID; }
bool AcceptDrag() override;
void UpdateDrag(const ImVec2& offset) override;
bool EndDrag() override; // return true, when changed
ImVec2 DragStartLocation() override { return m_DragStart; }
virtual bool IsSelectable() override { return true; }
virtual void Draw(ImDrawList* drawList, DrawFlags flags = None) override final;
void DrawBorder(ImDrawList* drawList, ImU32 color, float thickness = 1.0f, float offset = 0.0f);
void GetGroupedNodes(std::vector<Node*>& result, bool append = false);
void CenterOnScreenInNextFrame() { m_CenterOnScreen = true; }
ImRect GetRegionBounds(NodeRegion region) const;
NodeRegion GetRegion(const ImVec2& point) const;
virtual ImRect GetBounds() const override final { return m_Bounds; }
virtual Node* AsNode() override final { return this; }
};
struct Link final: Object
{
using IdType = LinkId;
LinkId m_ID;
Pin* m_StartPin;
Pin* m_EndPin;
ImU32 m_Color;
ImU32 m_HighlightColor;
float m_Thickness;
ImVec2 m_Start;
ImVec2 m_End;
Link(EditorContext* editor, LinkId id)
: Object(editor)
, m_ID(id)
, m_StartPin(nullptr)
, m_EndPin(nullptr)
, m_Color(IM_COL32_WHITE)
, m_Thickness(1.0f)
{
}
virtual ObjectId ID() override { return m_ID; }
virtual bool IsSelectable() override { return true; }
virtual void Draw(ImDrawList* drawList, DrawFlags flags = None) override final;
void Draw(ImDrawList* drawList, ImU32 color, float extraThickness = 0.0f) const;
void UpdateEndpoints();
ImCubicBezierPoints GetCurve() const;
virtual bool TestHit(const ImVec2& point, float extraThickness = 0.0f) const override final;
virtual bool TestHit(const ImRect& rect, bool allowIntersect = true) const override final;
virtual ImRect GetBounds() const override final;
virtual Link* AsLink() override final { return this; }
};
struct NodeSettings
{
NodeId m_ID;
ImVec2 m_Location;
ImVec2 m_Size;
ImVec2 m_GroupSize;
bool m_WasUsed;
bool m_Saved;
bool m_IsDirty;
SaveReasonFlags m_DirtyReason;
NodeSettings(NodeId id)
: m_ID(id)
, m_Location(0, 0)
, m_Size(0, 0)
, m_GroupSize(0, 0)
, m_WasUsed(false)
, m_Saved(false)
, m_IsDirty(false)
, m_DirtyReason(SaveReasonFlags::None)
{
}
void ClearDirty();
void MakeDirty(SaveReasonFlags reason);
json::value Serialize();
static bool Parse(const std::string& string, NodeSettings& settings);
static bool Parse(const json::value& data, NodeSettings& result);
};
struct Settings
{
bool m_IsDirty;
SaveReasonFlags m_DirtyReason;
vector<NodeSettings> m_Nodes;
vector<ObjectId> m_Selection;
ImVec2 m_ViewScroll;
float m_ViewZoom;
ImRect m_VisibleRect;
Settings()
: m_IsDirty(false)
, m_DirtyReason(SaveReasonFlags::None)
, m_ViewScroll(0, 0)
, m_ViewZoom(1.0f)
, m_VisibleRect()
{
}
NodeSettings* AddNode(NodeId id);
NodeSettings* FindNode(NodeId id);
void RemoveNode(NodeId id);
void ClearDirty(Node* node = nullptr);
void MakeDirty(SaveReasonFlags reason, Node* node = nullptr);
std::string Serialize();
static bool Parse(const std::string& string, Settings& settings);
};
struct Control
{
Object* HotObject;
Object* ActiveObject;
Object* ClickedObject;
Object* DoubleClickedObject;
Node* HotNode;
Node* ActiveNode;
Node* ClickedNode;
Node* DoubleClickedNode;
Pin* HotPin;
Pin* ActivePin;
Pin* ClickedPin;
Pin* DoubleClickedPin;
Link* HotLink;
Link* ActiveLink;
Link* ClickedLink;
Link* DoubleClickedLink;
bool BackgroundHot;
bool BackgroundActive;
int BackgroundClickButtonIndex;
int BackgroundDoubleClickButtonIndex;
Control()
: Control(nullptr, nullptr, nullptr, nullptr, false, false, -1, -1)
{
}
Control(Object* hotObject, Object* activeObject, Object* clickedObject, Object* doubleClickedObject,
bool backgroundHot, bool backgroundActive, int backgroundClickButtonIndex, int backgroundDoubleClickButtonIndex)
: HotObject(hotObject)
, ActiveObject(activeObject)
, ClickedObject(clickedObject)
, DoubleClickedObject(doubleClickedObject)
, HotNode(nullptr)
, ActiveNode(nullptr)
, ClickedNode(nullptr)
, DoubleClickedNode(nullptr)
, HotPin(nullptr)
, ActivePin(nullptr)
, ClickedPin(nullptr)
, DoubleClickedPin(nullptr)
, HotLink(nullptr)
, ActiveLink(nullptr)
, ClickedLink(nullptr)
, DoubleClickedLink(nullptr)
, BackgroundHot(backgroundHot)
, BackgroundActive(backgroundActive)
, BackgroundClickButtonIndex(backgroundClickButtonIndex)
, BackgroundDoubleClickButtonIndex(backgroundDoubleClickButtonIndex)
{
if (hotObject)
{
HotNode = hotObject->AsNode();
HotPin = hotObject->AsPin();
HotLink = hotObject->AsLink();
if (HotPin)
HotNode = HotPin->m_Node;
}
if (activeObject)
{
ActiveNode = activeObject->AsNode();
ActivePin = activeObject->AsPin();
ActiveLink = activeObject->AsLink();
}
if (clickedObject)
{
ClickedNode = clickedObject->AsNode();
ClickedPin = clickedObject->AsPin();
ClickedLink = clickedObject->AsLink();
}
if (doubleClickedObject)
{
DoubleClickedNode = doubleClickedObject->AsNode();
DoubleClickedPin = doubleClickedObject->AsPin();
DoubleClickedLink = doubleClickedObject->AsLink();
}
}
};
struct NavigateAction;
struct SizeAction;
struct DragAction;
struct SelectAction;
struct CreateItemAction;
struct DeleteItemsAction;
struct ContextMenuAction;
struct ShortcutAction;
struct AnimationController;
struct FlowAnimationController;
struct Animation
{
enum State
{
Playing,
Stopped
};
EditorContext* Editor;
State m_State;
float m_Time;
float m_Duration;
Animation(EditorContext* editor);
virtual ~Animation();
void Play(float duration);
void Stop();
void Finish();
void Update();
bool IsPlaying() const { return m_State == Playing; }
float GetProgress() const { return m_Time / m_Duration; }
protected:
virtual void OnPlay() {}
virtual void OnFinish() {}
virtual void OnStop() {}
virtual void OnUpdate(float progress) { IM_UNUSED(progress); }
};
struct NavigateAnimation final: Animation
{
NavigateAction& Action;
ImRect m_Start;
ImRect m_Target;
NavigateAnimation(EditorContext* editor, NavigateAction& scrollAction);
void NavigateTo(const ImRect& target, float duration);
private:
void OnUpdate(float progress) override final;
void OnStop() override final;
void OnFinish() override final;
};
struct FlowAnimation final: Animation
{
FlowAnimationController* Controller;
Link* m_Link;
float m_Speed;
float m_MarkerDistance;
float m_Offset;
FlowAnimation(FlowAnimationController* controller);
void Flow(Link* link, float markerDistance, float speed, float duration);
void Draw(ImDrawList* drawList);
private:
struct CurvePoint
{
float Distance;
ImVec2 Point;
};
ImVec2 m_LastStart;
ImVec2 m_LastEnd;
float m_PathLength;
vector<CurvePoint> m_Path;
bool IsLinkValid() const;
bool IsPathValid() const;
void UpdatePath();
void ClearPath();
ImVec2 SamplePath(float distance) const;
void OnUpdate(float progress) override final;
void OnStop() override final;
};
struct AnimationController
{
EditorContext* Editor;
AnimationController(EditorContext* editor)
: Editor(editor)
{
}
virtual ~AnimationController()
{
}
virtual void Draw(ImDrawList* drawList)
{
IM_UNUSED(drawList);
}
};
struct FlowAnimationController final : AnimationController
{
FlowAnimationController(EditorContext* editor);
virtual ~FlowAnimationController();
void Flow(Link* link, FlowDirection direction = FlowDirection::Forward);
virtual void Draw(ImDrawList* drawList) override final;
void Release(FlowAnimation* animation);
private:
FlowAnimation* GetOrCreate(Link* link);
vector<FlowAnimation*> m_Animations;
vector<FlowAnimation*> m_FreePool;
};
struct EditorAction
{
enum AcceptResult { False, True, Possible };
EditorAction(EditorContext* editor)
: Editor(editor)
{
}
virtual ~EditorAction() {}
virtual const char* GetName() const = 0;
virtual AcceptResult Accept(const Control& control) = 0;
virtual bool Process(const Control& control) = 0;
virtual void Reject() {} // celled when Accept return 'Possible' and was rejected
virtual ImGuiMouseCursor GetCursor() { return ImGuiMouseCursor_Arrow; }
virtual bool IsDragging() { return false; }
virtual void ShowMetrics() {}
virtual NavigateAction* AsNavigate() { return nullptr; }
virtual SizeAction* AsSize() { return nullptr; }
virtual DragAction* AsDrag() { return nullptr; }
virtual SelectAction* AsSelect() { return nullptr; }
virtual CreateItemAction* AsCreateItem() { return nullptr; }
virtual DeleteItemsAction* AsDeleteItems() { return nullptr; }
virtual ContextMenuAction* AsContextMenu() { return nullptr; }
virtual ShortcutAction* AsCutCopyPaste() { return nullptr; }
EditorContext* Editor;
};
struct NavigateAction final: EditorAction
{
enum class ZoomMode
{
None,
Exact,
WithMargin
};
enum class NavigationReason
{
Unknown,
MouseZoom,
Selection,
Object,
Content,
Edge
};
bool m_IsActive;
float m_Zoom;
ImRect m_VisibleRect;
ImVec2 m_Scroll;
ImVec2 m_ScrollStart;
ImVec2 m_ScrollDelta;
NavigateAction(EditorContext* editor, ImGuiEx::Canvas& canvas);
virtual const char* GetName() const override final { return "Navigate"; }
virtual AcceptResult Accept(const Control& control) override final;
virtual bool Process(const Control& control) override final;
virtual void ShowMetrics() override final;
virtual NavigateAction* AsNavigate() override final { return this; }
void NavigateTo(const ImRect& bounds, ZoomMode zoomMode, float duration = -1.0f, NavigationReason reason = NavigationReason::Unknown);
void StopNavigation();
void FinishNavigation();
bool MoveOverEdge(const ImVec2& canvasSize);
void StopMoveOverEdge();
bool IsMovingOverEdge() const { return m_MovingOverEdge; }
ImVec2 GetMoveScreenOffset() const { return m_MoveScreenOffset; }
void SetWindow(ImVec2 position, ImVec2 size);
ImVec2 GetWindowScreenPos() const { return m_WindowScreenPos; };
ImVec2 GetWindowScreenSize() const { return m_WindowScreenSize; };
ImGuiEx::CanvasView GetView() const;
ImVec2 GetViewOrigin() const;
float GetViewScale() const;
void SetViewRect(const ImRect& rect);
ImRect GetViewRect() const;
private:
ImGuiEx::Canvas& m_Canvas;
ImVec2 m_WindowScreenPos;
ImVec2 m_WindowScreenSize;
NavigateAnimation m_Animation;
NavigationReason m_Reason;
uint64_t m_LastSelectionId;
Object* m_LastObject;
bool m_MovingOverEdge;
ImVec2 m_MoveScreenOffset;
const float* m_ZoomLevels;
int m_ZoomLevelCount;
bool HandleZoom(const Control& control);
void NavigateTo(const ImRect& target, float duration = -1.0f, NavigationReason reason = NavigationReason::Unknown);
float GetNextZoom(float steps);
float MatchSmoothZoom(float steps);
float MatchZoom(int steps, float fallbackZoom);
int MatchZoomIndex(int direction);
static const float s_DefaultZoomLevels[];
static const int s_DefaultZoomLevelCount;
};
struct SizeAction final: EditorAction
{
bool m_IsActive;
bool m_Clean;
Node* m_SizedNode;
SizeAction(EditorContext* editor);
virtual const char* GetName() const override final { return "Size"; }
virtual AcceptResult Accept(const Control& control) override final;
virtual bool Process(const Control& control) override final;
virtual ImGuiMouseCursor GetCursor() override final { return m_Cursor; }
virtual void ShowMetrics() override final;
virtual SizeAction* AsSize() override final { return this; }
virtual bool IsDragging() override final { return m_IsActive; }
const ImRect& GetStartGroupBounds() const { return m_StartGroupBounds; }
private:
NodeRegion GetRegion(Node* node);
ImGuiMouseCursor ChooseCursor(NodeRegion region);
ImRect m_StartBounds;
ImRect m_StartGroupBounds;
ImVec2 m_LastSize;
ImVec2 m_MinimumSize;
ImVec2 m_LastDragOffset;
ed::NodeRegion m_Pivot;
ImGuiMouseCursor m_Cursor;
};
struct DragAction final: EditorAction
{
bool m_IsActive;
bool m_Clear;
Object* m_DraggedObject;
vector<Object*> m_Objects;
DragAction(EditorContext* editor);
virtual const char* GetName() const override final { return "Drag"; }
virtual AcceptResult Accept(const Control& control) override final;
virtual bool Process(const Control& control) override final;
virtual ImGuiMouseCursor GetCursor() override final { return ImGuiMouseCursor_ResizeAll; }
virtual bool IsDragging() override final { return m_IsActive; }
virtual void ShowMetrics() override final;
virtual DragAction* AsDrag() override final { return this; }
};
struct SelectAction final: EditorAction
{
bool m_IsActive;
bool m_SelectGroups;
bool m_SelectLinkMode;
bool m_CommitSelection;
ImVec2 m_StartPoint;
ImVec2 m_EndPoint;
vector<Object*> m_CandidateObjects;
vector<Object*> m_SelectedObjectsAtStart;
Animation m_Animation;
SelectAction(EditorContext* editor);
virtual const char* GetName() const override final { return "Select"; }
virtual AcceptResult Accept(const Control& control) override final;
virtual bool Process(const Control& control) override final;
virtual void ShowMetrics() override final;
virtual bool IsDragging() override final { return m_IsActive; }
virtual SelectAction* AsSelect() override final { return this; }
void Draw(ImDrawList* drawList);
};
struct ContextMenuAction final: EditorAction
{
enum Menu { None, Node, Pin, Link, Background };
Menu m_CandidateMenu;
Menu m_CurrentMenu;
ObjectId m_ContextId;
ContextMenuAction(EditorContext* editor);
virtual const char* GetName() const override final { return "Context Menu"; }
virtual AcceptResult Accept(const Control& control) override final;
virtual bool Process(const Control& control) override final;
virtual void Reject() override final;
virtual void ShowMetrics() override final;
virtual ContextMenuAction* AsContextMenu() override final { return this; }
bool ShowNodeContextMenu(NodeId* nodeId);
bool ShowPinContextMenu(PinId* pinId);
bool ShowLinkContextMenu(LinkId* linkId);
bool ShowBackgroundContextMenu();
};
struct ShortcutAction final: EditorAction
{
enum Action { None, Cut, Copy, Paste, Duplicate, CreateNode };
bool m_IsActive;
bool m_InAction;
Action m_CurrentAction;
vector<Object*> m_Context;
ShortcutAction(EditorContext* editor);
virtual const char* GetName() const override final { return "Shortcut"; }
virtual AcceptResult Accept(const Control& control) override final;
virtual bool Process(const Control& control) override final;
virtual void Reject() override final;
virtual void ShowMetrics() override final;
virtual ShortcutAction* AsCutCopyPaste() override final { return this; }
bool Begin();
void End();
bool AcceptCut();
bool AcceptCopy();
bool AcceptPaste();
bool AcceptDuplicate();
bool AcceptCreateNode();
};
struct CreateItemAction final : EditorAction
{
enum Stage
{
None,
Possible,
Create
};
enum Action
{
Unknown,
UserReject,
UserAccept
};
enum Type
{
NoItem,
Node,
Link
};
enum Result
{
True,
False,
Indeterminate
};
bool m_InActive;
Stage m_NextStage;
Stage m_CurrentStage;
Type m_ItemType;
Action m_UserAction;
ImU32 m_LinkColor;
float m_LinkThickness;
Pin* m_LinkStart;
Pin* m_LinkEnd;
bool m_IsActive;
Pin* m_DraggedPin;
int m_LastChannel = -1;
CreateItemAction(EditorContext* editor);
virtual const char* GetName() const override final { return "Create Item"; }
virtual AcceptResult Accept(const Control& control) override final;
virtual bool Process(const Control& control) override final;
virtual ImGuiMouseCursor GetCursor() override final { return ImGuiMouseCursor_Arrow; }
virtual void ShowMetrics() override final;
virtual bool IsDragging() override final { return m_IsActive; }
virtual CreateItemAction* AsCreateItem() override final { return this; }
void SetStyle(ImU32 color, float thickness);
bool Begin();
void End();
Result RejectItem();
Result AcceptItem();
Result QueryLink(PinId* startId, PinId* endId);
Result QueryNode(PinId* pinId);
private:
bool m_IsInGlobalSpace;
void DragStart(Pin* startPin);
void DragEnd();
void DropPin(Pin* endPin);
void DropNode();
void DropNothing();
};
struct DeleteItemsAction final: EditorAction
{
bool m_IsActive;
bool m_InInteraction;
DeleteItemsAction(EditorContext* editor);
virtual const char* GetName() const override final { return "Delete Items"; }
virtual AcceptResult Accept(const Control& control) override final;
virtual bool Process(const Control& control) override final;
virtual void ShowMetrics() override final;
virtual DeleteItemsAction* AsDeleteItems() override final { return this; }
bool Add(Object* object);
bool Begin();
void End();
bool QueryLink(LinkId* linkId, PinId* startId = nullptr, PinId* endId = nullptr);
bool QueryNode(NodeId* nodeId);
bool AcceptItem(bool deleteDependencies);
void RejectItem();
private:
enum IteratorType { Unknown, Link, Node };
enum UserAction { Undetermined, Accepted, Rejected };
void DeleteDeadLinks(NodeId nodeId);
void DeleteDeadPins(NodeId nodeId);
bool QueryItem(ObjectId* itemId, IteratorType itemType);
void RemoveItem(bool deleteDependencies);
Object* DropCurrentItem();
vector<Object*> m_ManuallyDeletedObjects;
IteratorType m_CurrentItemType;
UserAction m_UserAction;
vector<Object*> m_CandidateObjects;
int m_CandidateItemIndex;
};
struct NodeBuilder
{
EditorContext* const Editor;
Node* m_CurrentNode;
Pin* m_CurrentPin;
ImRect m_NodeRect;
ImRect m_PivotRect;
ImVec2 m_PivotAlignment;
ImVec2 m_PivotSize;
ImVec2 m_PivotScale;
bool m_ResolvePinRect;
bool m_ResolvePivot;
ImRect m_GroupBounds;
bool m_IsGroup;
ImDrawListSplitter m_Splitter;
ImDrawListSplitter m_PinSplitter;
NodeBuilder(EditorContext* editor);
~NodeBuilder();
void Begin(NodeId nodeId);
void End();
void BeginPin(PinId pinId, PinKind kind);
void EndPin();
void PinRect(const ImVec2& a, const ImVec2& b);
void PinPivotRect(const ImVec2& a, const ImVec2& b);
void PinPivotSize(const ImVec2& size);
void PinPivotScale(const ImVec2& scale);
void PinPivotAlignment(const ImVec2& alignment);
void Group(const ImVec2& size);
ImDrawList* GetUserBackgroundDrawList() const;
ImDrawList* GetUserBackgroundDrawList(Node* node) const;
};
struct HintBuilder
{
EditorContext* const Editor;
bool m_IsActive;
Node* m_CurrentNode;
float m_LastFringe = 1.0f;
int m_LastChannel = 0;
HintBuilder(EditorContext* editor);
bool Begin(NodeId nodeId);
void End();
ImVec2 GetGroupMin();
ImVec2 GetGroupMax();
ImDrawList* GetForegroundDrawList();
ImDrawList* GetBackgroundDrawList();
};
struct Style: ax::NodeEditor::Style
{
void PushColor(StyleColor colorIndex, const ImVec4& color);
void PopColor(int count = 1);
void PushVar(StyleVar varIndex, float value);
void PushVar(StyleVar varIndex, const ImVec2& value);
void PushVar(StyleVar varIndex, const ImVec4& value);
void PopVar(int count = 1);
const char* GetColorName(StyleColor colorIndex) const;
private:
struct ColorModifier
{
StyleColor Index;
ImVec4 Value;
};
struct VarModifier
{
StyleVar Index;
ImVec4 Value;
};
float* GetVarFloatAddr(StyleVar idx);
ImVec2* GetVarVec2Addr(StyleVar idx);
ImVec4* GetVarVec4Addr(StyleVar idx);
vector<ColorModifier> m_ColorStack;
vector<VarModifier> m_VarStack;
};
struct Config: ax::NodeEditor::Config
{
Config(const ax::NodeEditor::Config* config);
std::string Load();
std::string LoadNode(NodeId nodeId);
void BeginSave();
bool Save(const std::string& data, SaveReasonFlags flags);
bool SaveNode(NodeId nodeId, const std::string& data, SaveReasonFlags flags);
void EndSave();
};
enum class SuspendFlags : uint8_t
{
None = 0,
KeepSplitter = 1
};
inline SuspendFlags operator |(SuspendFlags lhs, SuspendFlags rhs) { return static_cast<SuspendFlags>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs)); }
inline SuspendFlags operator &(SuspendFlags lhs, SuspendFlags rhs) { return static_cast<SuspendFlags>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs)); }
struct EditorContext
{
EditorContext(const ax::NodeEditor::Config* config = nullptr);
~EditorContext();
const Config& GetConfig() const { return m_Config; }
Style& GetStyle() { return m_Style; }
void Begin(const char* id, const ImVec2& size = ImVec2(0, 0));
void End();
bool DoLink(LinkId id, PinId startPinId, PinId endPinId, ImU32 color, float thickness);
NodeBuilder& GetNodeBuilder() { return m_NodeBuilder; }
HintBuilder& GetHintBuilder() { return m_HintBuilder; }
EditorAction* GetCurrentAction() { return m_CurrentAction; }
CreateItemAction& GetItemCreator() { return m_CreateItemAction; }
DeleteItemsAction& GetItemDeleter() { return m_DeleteItemsAction; }
ContextMenuAction& GetContextMenu() { return m_ContextMenuAction; }
ShortcutAction& GetShortcut() { return m_ShortcutAction; }
const ImGuiEx::CanvasView& GetView() const { return m_Canvas.View(); }
const ImRect& GetViewRect() const { return m_Canvas.ViewRect(); }
const ImRect& GetRect() const { return m_Canvas.Rect(); }
void SetNodePosition(NodeId nodeId, const ImVec2& screenPosition);
void SetGroupSize(NodeId nodeId, const ImVec2& size);
ImVec2 GetNodePosition(NodeId nodeId);
ImVec2 GetNodeSize(NodeId nodeId);
void SetNodeZPosition(NodeId nodeId, float z);
float GetNodeZPosition(NodeId nodeId);
void MarkNodeToRestoreState(Node* node);
void UpdateNodeState(Node* node);
void RemoveSettings(Object* object);
void ClearSelection();
void SelectObject(Object* object);
void DeselectObject(Object* object);
void SetSelectedObject(Object* object);
void ToggleObjectSelection(Object* object);
bool IsSelected(Object* object);
const vector<Object*>& GetSelectedObjects();
bool IsAnyNodeSelected();
bool IsAnyLinkSelected();
bool HasSelectionChanged();
uint64_t GetSelectionId() const { return m_SelectionId; }
Node* FindNodeAt(const ImVec2& p);
void FindNodesInRect(const ImRect& r, vector<Node*>& result, bool append = false, bool includeIntersecting = true);
void FindLinksInRect(const ImRect& r, vector<Link*>& result, bool append = false);
bool HasAnyLinks(NodeId nodeId) const;
bool HasAnyLinks(PinId pinId) const;
int BreakLinks(NodeId nodeId);
int BreakLinks(PinId pinId);
void FindLinksForNode(NodeId nodeId, vector<Link*>& result, bool add = false);
bool PinHadAnyLinks(PinId pinId);
ImVec2 ToCanvas(const ImVec2& point) const { return m_Canvas.ToLocal(point); }
ImVec2 ToScreen(const ImVec2& point) const { return m_Canvas.FromLocal(point); }
void NotifyLinkDeleted(Link* link);
void Suspend(SuspendFlags flags = SuspendFlags::None);
void Resume(SuspendFlags flags = SuspendFlags::None);
bool IsSuspended();
bool IsFocused();
bool IsHovered() const;
bool IsHoveredWithoutOverlapp() const;
bool CanAcceptUserInput() const;
void MakeDirty(SaveReasonFlags reason);
void MakeDirty(SaveReasonFlags reason, Node* node);
int CountLiveNodes() const;
int CountLivePins() const;
int CountLiveLinks() const;
Pin* CreatePin(PinId id, PinKind kind);
Node* CreateNode(NodeId id);
Link* CreateLink(LinkId id);
Node* FindNode(NodeId id);
Pin* FindPin(PinId id);
Link* FindLink(LinkId id);
Object* FindObject(ObjectId id);
Node* GetNode(NodeId id);
Pin* GetPin(PinId id, PinKind kind);
Link* GetLink(LinkId id);
Link* FindLinkAt(const ImVec2& p);
template <typename T>
ImRect GetBounds(const std::vector<T*>& objects)
{
ImRect bounds(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
for (auto object : objects)
if (object->m_IsLive)
bounds.Add(object->GetBounds());
if (ImRect_IsEmpty(bounds))
bounds = ImRect();
return bounds;
}
template <typename T>
ImRect GetBounds(const std::vector<ObjectWrapper<T>>& objects)
{
ImRect bounds(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
for (auto object : objects)
if (object.m_Object->m_IsLive)
bounds.Add(object.m_Object->GetBounds());
if (ImRect_IsEmpty(bounds))
bounds = ImRect();
return bounds;
}
ImRect GetSelectionBounds() { return GetBounds(m_SelectedObjects); }
ImRect GetContentBounds() { return GetBounds(m_Nodes); }
ImU32 GetColor(StyleColor colorIndex) const;
ImU32 GetColor(StyleColor colorIndex, float alpha) const;
int GetNodeIds(NodeId* nodes, int size) const;
void NavigateTo(const ImRect& bounds, bool zoomIn = false, float duration = -1)
{
auto zoomMode = zoomIn ? NavigateAction::ZoomMode::WithMargin : NavigateAction::ZoomMode::None;
m_NavigateAction.NavigateTo(bounds, zoomMode, duration);
}
void RegisterAnimation(Animation* animation);
void UnregisterAnimation(Animation* animation);
void Flow(Link* link, FlowDirection direction);
void SetUserContext(bool globalSpace = false);
void EnableShortcuts(bool enable);
bool AreShortcutsEnabled();
NodeId GetHoveredNode() const { return m_HoveredNode; }
PinId GetHoveredPin() const { return m_HoveredPin; }
LinkId GetHoveredLink() const { return m_HoveredLink; }
NodeId GetDoubleClickedNode() const { return m_DoubleClickedNode; }
PinId GetDoubleClickedPin() const { return m_DoubleClickedPin; }
LinkId GetDoubleClickedLink() const { return m_DoubleClickedLink; }
bool IsBackgroundClicked() const { return m_BackgroundClickButtonIndex >= 0; }
bool IsBackgroundDoubleClicked() const { return m_BackgroundDoubleClickButtonIndex >= 0; }
ImGuiMouseButton GetBackgroundClickButtonIndex() const { return m_BackgroundClickButtonIndex; }
ImGuiMouseButton GetBackgroundDoubleClickButtonIndex() const { return m_BackgroundDoubleClickButtonIndex; }
float AlignPointToGrid(float p) const
{
if (!ImGui::GetIO().KeyAlt)
return p - ImFmod(p, 16.0f);
else
return p;
}
ImVec2 AlignPointToGrid(const ImVec2& p) const
{
return ImVec2(AlignPointToGrid(p.x), AlignPointToGrid(p.y));
}
ImDrawList* GetDrawList() { return m_DrawList; }
private:
void LoadSettings();
void SaveSettings();
Control BuildControl(bool allowOffscreen);
void ShowMetrics(const Control& control);
void UpdateAnimations();
Config m_Config;
ImGuiID m_EditorActiveId;
bool m_IsFirstFrame;
bool m_IsFocused;
bool m_IsHovered;
bool m_IsHoveredWithoutOverlapp;
bool m_ShortcutsEnabled;
Style m_Style;
vector<ObjectWrapper<Node>> m_Nodes;
vector<ObjectWrapper<Pin>> m_Pins;
vector<ObjectWrapper<Link>> m_Links;
vector<Object*> m_SelectedObjects;
vector<Object*> m_LastSelectedObjects;
uint64_t m_SelectionId;
Link* m_LastActiveLink;
vector<Animation*> m_LiveAnimations;
vector<Animation*> m_LastLiveAnimations;
ImGuiEx::Canvas m_Canvas;
bool m_IsCanvasVisible;
NodeBuilder m_NodeBuilder;
HintBuilder m_HintBuilder;
EditorAction* m_CurrentAction;
NavigateAction m_NavigateAction;
SizeAction m_SizeAction;
DragAction m_DragAction;
SelectAction m_SelectAction;
ContextMenuAction m_ContextMenuAction;
ShortcutAction m_ShortcutAction;
CreateItemAction m_CreateItemAction;
DeleteItemsAction m_DeleteItemsAction;
vector<AnimationController*> m_AnimationControllers;
FlowAnimationController m_FlowAnimationController;
NodeId m_HoveredNode;
PinId m_HoveredPin;
LinkId m_HoveredLink;
NodeId m_DoubleClickedNode;
PinId m_DoubleClickedPin;
LinkId m_DoubleClickedLink;
int m_BackgroundClickButtonIndex;
int m_BackgroundDoubleClickButtonIndex;
bool m_IsInitialized;
Settings m_Settings;
ImDrawList* m_DrawList;
int m_ExternalChannel;
ImDrawListSplitter m_Splitter;
};
//------------------------------------------------------------------------------
} // namespace Detail
} // namespace Editor
} // namespace ax
//------------------------------------------------------------------------------
# include "imgui_node_editor_internal.inl"
//------------------------------------------------------------------------------
# endif // __IMGUI_NODE_EDITOR_INTERNAL_H__