357 lines
16 KiB
C++
357 lines
16 KiB
C++
// 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<class FTreeMapNodeData>& 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<FTreeMapNodeData> 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<class ITreeMap> TreeMap;
|
|
|
|
/** Customization for this tree map */
|
|
TSharedPtr<class ITreeMapCustomization> Customization;
|
|
|
|
/** The previous tree map for the last view we were looking at */
|
|
TSharedPtr<class ITreeMap> 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<FVector2D> 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<struct FTreeMapNodeVisualInfo> CachedNodeVisuals;
|
|
|
|
/** Cached node visuals for last tree map */
|
|
TArray<struct FTreeMapNodeVisualInfo> LastCachedNodeVisuals;
|
|
|
|
/** Maps node visual indices to their last node visual indices. This is used for navigation transitions */
|
|
TMap<int32, int32> NodeVisualIndicesToLastIndices;
|
|
|
|
/** List of node visual indices which no longer map to a node in the current layout. This is used for navigation transitions */
|
|
TArray<int32> 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<bool> 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<SWidget> RenamingNodeWidget;
|
|
|
|
/** Weak pointer to the node that we're currently renaming, if any */
|
|
TWeakPtr<FTreeMapNodeData> 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<FTreeMapNodeData> HighlightPulseNode;
|
|
|
|
/** Time that we started playing a highlight pulse effect for the HighlightPulseNode */
|
|
double HighlightPulseStartTime;
|
|
|
|
};
|