// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Misc/Attribute.h" #include "Input/Reply.h" #include "Fonts/SlateFontInfo.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Animation/CurveSequence.h" #include "Widgets/SLeafWidget.h" #include "ITreeMap.h" #include "ITreeMapCustomization.h" #include "TreeMapStyle.h" class FPaintArgs; class FSlateWindowElementList; class FWidgetPath; /** * Graphical tree map widget with interactive controls */ class TREEMAP_API STreeMap : public SLeafWidget { public: /** Delegate used when clicking or interacting with a specific node */ DECLARE_DELEGATE_TwoParams( FOnTreeMapNodeInteracted, FTreeMapNodeData&, const FPointerEvent& ); SLATE_BEGIN_ARGS( STreeMap ) : _AllowEditing( false ) , _BackgroundImage( FTreeMapStyle::Get().GetBrush( "TreeMap.Background" ) ) , _NodeBackground( FTreeMapStyle::Get().GetBrush( "TreeMap.NodeBackground" ) ) , _HoveredNodeBackground( FTreeMapStyle::Get().GetBrush( "TreeMap.HoveredNodeBackground" ) ) , _NameFont() , _Name2Font() , _CenterTextFont() , _BorderPadding( FTreeMapStyle::Get().GetVector( "TreeMap.BorderPadding" ) ) , _MinimumInteractiveTreeNodeSize( 64 * 64 ) , _MinimumVisibleTreeNodeSize( 6 * 6 ) , _NavigationTransitionTime( 0.25f ) , _TopLevelContainerOuterPadding( 4.0f ) , _NestedContainerOuterPadding( 0.0f ) , _ContainerInnerPadding( 4.0f ) , _ChildContainerTextPadding( 2.0f ) { _Clipping = EWidgetClipping::ClipToBounds; } /** Sets whether the user can edit the tree map interactively by dragging nodes around and typing new node labels */ SLATE_ATTRIBUTE( bool, AllowEditing ) /** Background image to use for the tree map canvas area */ SLATE_ATTRIBUTE( const FSlateBrush*, BackgroundImage ) /** Background to use for each tree node */ SLATE_ATTRIBUTE( const FSlateBrush*, NodeBackground ) /** Background to use for nodes that the mouse is hovering over */ SLATE_ATTRIBUTE( const FSlateBrush*, HoveredNodeBackground ) /** Sets the font used to draw the text. Note the height of the font may change automatically based on the tree node size */ SLATE_ATTRIBUTE( FSlateFontInfo, NameFont ) /** Font for second line of text, under the title. Leaf nodes only. Usually a bit smaller. Works just like NameFont */ SLATE_ATTRIBUTE( FSlateFontInfo, Name2Font ); /** Font for any text that's centered inside the middle of the node. Leaf nodes only. Usually a bit larger. Works just like NameFont */ SLATE_ATTRIBUTE( FSlateFontInfo, CenterTextFont ); /** Border Padding around fill bar */ SLATE_ATTRIBUTE( FVector2D, BorderPadding ) /** Minimum size of node that can be interacted with, if small you need to drill down into parent first */ SLATE_ARGUMENT( int32, MinimumInteractiveTreeNodeSize ); /** Minimum size in pixels of a tree node that we should bother including in the UI. Below this size, you'll need to drill down to see the node. */ SLATE_ARGUMENT( int32, MinimumVisibleTreeNodeSize ); /** How many seconds to animate the visual transition when the user navigates to a new tree node, or after a modification of the tree takes place */ SLATE_ARGUMENT( float, NavigationTransitionTime ); /** How many pixels of padding around the outside of the root-level tree node box. Adding padding makes the tree look better, but you lose some sizing accuracy */ SLATE_ARGUMENT( float, TopLevelContainerOuterPadding ); /** How many pixels of padding around the outside of a non-root tree node's box. Adding padding makes the tree look better, but you lose some sizing accuracy */ SLATE_ARGUMENT( float, NestedContainerOuterPadding ); /** How many pixels of spacing between the container and its child containers. Adding padding makes the tree look better, but you lose some sizing accuracy */ SLATE_ARGUMENT( float, ContainerInnerPadding ); /** How many pixels to pad text that's drawn inside of a container (not the top-level container, though) */ SLATE_ARGUMENT( float, ChildContainerTextPadding ); /** Optional delegate that fires when the node is double-clicked in the tree. If you don't override this, the tree will use its default handling of double-click, which will re-root the tree on the node under the cursor */ SLATE_EVENT( FOnTreeMapNodeInteracted, OnTreeMapNodeDoubleClicked ) /** Optional delegate that fires when the node is right-clicked in the tree. If you don't override this nothing will happen unless a customization is specified */ SLATE_EVENT( FOnTreeMapNodeInteracted, OnTreeMapNodeRightClicked ) SLATE_END_ARGS() /** * Construct the widget * * @param InArgs A declaration from which to construct the widget * @param InTreeMapNodeData The node data we'll be visualizing and editing (root node) * @param InCustomization An optional 'customization' of the tree that allows for filtering and displaying the tree map data according to various attributes */ void Construct( const FArguments& InArgs, const TSharedRef& InTreeMapNodeData, const TSharedPtr< ITreeMapCustomization >& InCustomization ); /** SWidget overrides */ virtual int32 OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const override; virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override; virtual void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) override; virtual FReply OnMouseButtonDown( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) override; virtual FReply OnMouseButtonUp( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) override; virtual FReply OnMouseMove( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) override; virtual void OnMouseLeave( const FPointerEvent& InMouseEvent ) override; virtual FReply OnMouseButtonDoubleClick( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) override; virtual FReply OnMouseWheel( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override; virtual bool SupportsKeyboardFocus() const override; virtual FReply OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyboardEvent ) override; /** * Sets a new "active root" for the tree. This is used to "drill down" to child tree nodes or "climb up" back to the root. * This also happens automatically when the user presses the mouse wheel to zoom in and out over nodes. * * @param NewRoot The tree's new root. This can be any node within the tree * @param bShouldPlayTransition If enabled, an animation will play */ void SetTreeRoot( const FTreeMapNodeDataRef& NewRoot, const bool bShouldPlayTransition ); /** Returns true if it is possible to zoom out */ bool CanZoomOut() const; /** Zooms out, setting root to it's parent if possible, returns false if not */ bool ZoomOut(); /** Returns the current tree root, which is the largest node visible */ FTreeMapNodeDataPtr GetTreeRoot() const; /** * Refreshes the tree map from its source data. The current active root will remain as the base of the tree. Call this * after you've changed data in the tree. * * @param bShouldPlayTransition If enabled, an animation will play */ void RebuildTreeMap( const bool bShouldPlayTransition ); protected: /** Finds the node visual that's under the cursor */ struct FTreeMapNodeVisualInfo* FindNodeVisualUnderCursor( const FGeometry& MyGeometry, const FVector2D& ScreenSpaceCursorPosition ); /** Blends between the current visual state and the state before the last navigation transition, by the specified amount */ void MakeBlendedNodeVisual( const int32 VisualIndex, const float NavigationAlpha, FTreeMapNodeVisualInfo& OutVisual ) const; /** @return True if we're playing a transition after a navigation action (zoom!) */ bool IsNavigationTransitionActive() const; /** Grabs a snapshot of the current tree so we can restore it if the user performs an Undo action */ void TakeUndoSnapshot(); /** Undoes the last action */ void Undo(); /** Redoes the last action */ void Redo(); /** Reparents DroppedNode to NewParentNode (undoable!) */ void ReparentNode( const FTreeMapNodeDataRef DroppedNode, const FTreeMapNodeDataRef NewParentNode ); /** Deletes the node under the mouse cursor, if any (undoable!) */ FReply DeleteHoveredNode(); /** Insert a new node as a child of the node under the cursor */ FReply InsertNewNodeAsChildOfHoveredNode( const FGeometry& MyGeometry ); /** Searches for the specified node in an identical copy of the node tree. */ FTreeMapNodeDataPtr FindNodeInCopiedTree( const FTreeMapNodeDataRef& NodeToFind, const FTreeMapNodeDataRef& OriginalNode, const FTreeMapNodeDataRef& CopiedRootNode ) const; /** Pops up a box to allow the user to start renaming a node's title (undoable!) */ void StartRenamingNode( const FGeometry& MyGeometry, const FTreeMapNodeDataRef& NodeData, const FVector2D& RelativePosition, const bool bIsNewNode ); /** Called when the user commits a rename change */ void RenamingNode_OnTextCommitted( const FText& NewText, ETextCommit::Type, TSharedRef NodeToRename ); /** Stops renaming a node, committing whatever text was entered */ void StopRenamingNode(); /** Called to apply the current 'size based on' and 'color based on' settings to the tree */ void ApplyVisualizationToNodes( const FTreeMapNodeDataRef& Node ); /** Recursively applies the active visualization to all nodes, such as size by attribute, or color by attribute */ void ApplyVisualizationToNodesRecursively( const FTreeMapNodeDataRef& Node, const FLinearColor& DefaultColor, const int32 TreeDepth ); /** Displays a context menu at the specified location with options for configuring the tree display */ void ShowOptionsMenuAt( const FPointerEvent& InMouseEvent ); private: void ShowOptionsMenuAtInternal(const FVector2D& ScreenSpacePosition, const FWidgetPath& WidgetPath); private: /** * TreeMap data */ /** The tree map that we're presenting using this widget */ TSharedPtr TreeMap; /** Customization for this tree map */ TSharedPtr Customization; /** The previous tree map for the last view we were looking at */ TSharedPtr LastTreeMap; /** The source data for the tree map */ FTreeMapNodeDataPtr TreeMapNodeData; /** Current root of the tree that we're visualizing. The user can re-root the tree by double-clicking on tree map nodes */ FTreeMapNodeDataPtr ActiveRootTreeMapNode; /** Which attribute to visualize the size of the nodes based on, or null for default sizes */ FTreeMapAttributePtr SizeNodesByAttribute; /** Which attribute to visualize the color of the nodes based on, or null for automatic colors */ FTreeMapAttributePtr ColorNodesByAttribute; /** * Widget layout */ /** Background image to use for the tree map canvas area */ TAttribute< const FSlateBrush* > BackgroundImage; /** Background to use for each tree node */ TAttribute< const FSlateBrush* > NodeBackground; /** Background to use for nodes that the mouse is hovering over */ TAttribute< const FSlateBrush* > HoveredNodeBackground; /** Border Padding */ TAttribute BorderPadding; /** The font name used to draw the text */ TAttribute< FSlateFontInfo > NameFont; /** Font for second line of text, under the title. Leaf nodes only. Usually a bit smaller. Works just like NameFont */ TAttribute< FSlateFontInfo > Name2Font; /** Font for any text that's centered inside the middle of the node. Leaf nodes only. Usually a bit larger. Works just like NameFont */ TAttribute< FSlateFontInfo > CenterTextFont; /** * Navigation */ /** The visual that the mouse was over last */ FTreeMapNodeVisualInfo* MouseOverVisual; /** Optional delegate that fires when the node is double-clicked in the tree. If you don't override this, the tree will use its default handling of double-click, which will re-root the tree on the node under the cursor */ FOnTreeMapNodeInteracted OnTreeMapNodeDoubleClicked; /** Right click event */ FOnTreeMapNodeInteracted OnTreeMapNodeRightClicked; /** * TreeMap visuals */ /** Minimum size of node that can be interacted with, if small you need to drill down into parent first */ int32 MinimumInteractiveTreeNodeSize; /** Minimum size in pixels of a tree node that we should bother including in the UI. Below this size, you'll need to drill down to see the node. */ int32 MinimumVisibleTreeNodeSize; /** Size of our geometry the last time we rebuilt the tree map */ FVector2D TreeMapSize; /** Cached set of visuals for the tree map the last time it was generated */ TArray CachedNodeVisuals; /** Cached node visuals for last tree map */ TArray LastCachedNodeVisuals; /** Maps node visual indices to their last node visual indices. This is used for navigation transitions */ TMap NodeVisualIndicesToLastIndices; /** List of node visual indices which no longer map to a node in the current layout. This is used for navigation transitions */ TArray OrphanedLastIndices; /** Time that tree view was changed last after navigation */ FCurveSequence NavigateAnimationCurve; /** How many pixels of padding around the outside of the root-level tree node box. Adding padding makes the tree look better, but you lose some sizing accuracy */ float TopLevelContainerOuterPadding; /** How many pixels of padding around the outside of a non-root tree node's box. Adding padding makes the tree look better, but you lose some sizing accuracy */ float NestedContainerOuterPadding; /** How many pixels of spacing between the container and its child containers. Adding padding makes the tree look better, but you lose some sizing accuracy */ float ContainerInnerPadding; /** How many pixels to pad text that's drawn inside of a container (not the top-level container, though) */ float ChildContainerTextPadding; /** * Live editing */ /** True if we should allow real-time editing right now. The user can still navigate between nodes even if editing is disbled, so its not exactly "read only" */ TAttribute AllowEditing; /** Node that we're currently dragging around. This points to an entry in the CachedNodeVisuals array! */ FTreeMapNodeVisualInfo* DraggingVisual; /** Distance we've LMB+dragged the cursor. Used to determine if we're ready to start dragging and dropping */ float DragVisualDistance; /** Position of the mouse cursor relative to the widget, where the user picked up a dragged visual */ FVector2D RelativeDragStartMouseCursorPos; /** Position of the mouse cursor relative to the widget, the last time it moved. Used for drag and drop. */ FVector2D RelativeMouseCursorPos; /** Undo history buffer. */ TArray< TSharedPtr< FTreeMapNodeData > > UndoStates; /** Current undo level, or INDEX_NONE if we haven't undone anything */ int32 CurrentUndoLevel; /** Weak pointer to the widget used to rename a node inline. We might need to close the window in some cases. */ TWeakPtr RenamingNodeWidget; /** Weak pointer to the node that we're currently renaming, if any */ TWeakPtr RenamingNodeData; /** True if the node we're renaming is a newly-created node that should be added to the tree after we finish naming it */ bool bIsNamingNewNode; /** Node that we're currently drawing a highlight pulse effect for. This points to an entry in the CachedNodesVisuals array */ TWeakPtr HighlightPulseNode; /** Time that we started playing a highlight pulse effect for the HighlightPulseNode */ double HighlightPulseStartTime; };