//------------------------------------------------------------------------------ // 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 # include # include "imgui_extra_math.h" # include "imgui_bezier_math.h" # include "imgui_canvas.h" # include "crude_json.h" # include # include //------------------------------------------------------------------------------ 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 \ 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 struct aux {}; \ \ template static no_type test(aux<&U::__member_name__>*); \ template static yes_type test(...); \ \ public: \ \ static constexpr bool value = (sizeof(yes_type) == sizeof(test(0))); \ } DECLARE_HAS_MEMBER(HasFringeScale, _FringeScale); # undef DECLARE_HAS_MEMBER struct FringeScaleRef { // Overload is present when ImDrawList does have _FringeScale member variable. template static float& Get(typename std::enable_if::value, T>::type* drawList) { return drawList->_FringeScale; } // Overload is present when ImDrawList does not have _FringeScale member variable. template static float& Get(typename std::enable_if::value, T>::type*) { static float placeholder = 1.0f; return placeholder; } }; static inline float& ImFringeScaleRef(ImDrawList* drawList) { return FringeScaleRef::Get(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 { using Super = Details::SafePointerType; 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 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(static_cast(lhs) | static_cast(rhs)); } inline friend DrawFlags operator&(DrawFlags lhs, DrawFlags rhs) { return static_cast(static_cast(lhs) & static_cast(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(static_cast(lhs) | static_cast(rhs)); } inline NodeRegion operator &(NodeRegion lhs, NodeRegion rhs) { return static_cast(static_cast(lhs) & static_cast(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& 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 m_Nodes; vector 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 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 m_Animations; vector 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 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 m_CandidateObjects; vector 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 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 m_ManuallyDeletedObjects; IteratorType m_CurrentItemType; UserAction m_UserAction; vector 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 m_ColorStack; vector 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(static_cast(lhs) | static_cast(rhs)); } inline SuspendFlags operator &(SuspendFlags lhs, SuspendFlags rhs) { return static_cast(static_cast(lhs) & static_cast(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& 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& result, bool append = false, bool includeIntersecting = true); void FindLinksInRect(const ImRect& r, vector& 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& 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 ImRect GetBounds(const std::vector& 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 ImRect GetBounds(const std::vector>& 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> m_Nodes; vector> m_Pins; vector> m_Links; vector m_SelectedObjects; vector m_LastSelectedObjects; uint64_t m_SelectionId; Link* m_LastActiveLink; vector m_LiveAnimations; vector 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 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__