Files
UnrealEngineUWP/Engine/Source/Editor/GraphEditor/Private/SGraphNodeComment.cpp
2014-03-14 14:13:41 -04:00

636 lines
18 KiB
C++

// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#include "GraphEditorCommon.h"
#include "SGraphNodeComment.h"
//#include "TextWrapperHelpers.h"
namespace SCommentNodeDefs
{
/** Size of the hit result border for the window borders */
/* L, T, R, B */
static const FSlateRect HitResultBorderSize(10,10,10,10);
/** Minimum resize width for comment */
static const float MinWidth = 30.0;
/** Minimum resize height for comment */
static const float MinHeight = 30.0;
/** TitleBarColor = CommnetColor * TitleBarColorMultiplier */
static const float TitleBarColorMultiplier = 0.6f;
/** Titlebar Offset - taken from the widget borders in UpdateGraphNode */
static const FSlateRect TitleBarOffset(13,8,-3,0);
}
void SGraphNodeComment::Construct(const FArguments& InArgs, UEdGraphNode* InNode)
{
this->GraphNode = InNode;
this->bIsSelected = false;
// Set up animation
{
ZoomCurve = SpawnAnim.AddCurve(0, 0.1f);
FadeCurve = SpawnAnim.AddCurve(0.15f, 0.15f);
}
// Cache these values so they do not force a re-build of the node next tick.
CachedCommentTitle = GetNodeComment();
CachedWidth = InNode->NodeWidth;
this->UpdateGraphNode();
// Pull out sizes
UserSize.X = InNode->NodeWidth;
UserSize.Y = InNode->NodeHeight;
MouseZone = CWZ_NotInWindow;
bUserIsDragging = false;
}
bool SGraphNodeComment::InSelectionArea(ECommentWindowZone InMouseZone) const
{
return ( (InMouseZone == CWZ_RightBorder) || (InMouseZone == CWZ_BottomBorder) || (InMouseZone == CWZ_BottomRightBorder) ||
(InMouseZone == CWZ_LeftBorder) || (InMouseZone == CWZ_TopBorder) || (InMouseZone == CWZ_TopLeftBorder) ||
(InMouseZone == CWZ_TopRightBorder) || (InMouseZone == CWZ_BottomLeftBorder) );
}
FCursorReply SGraphNodeComment::OnCursorQuery( const FGeometry& MyGeometry, const FPointerEvent& CursorEvent ) const
{
if (MouseZone == CWZ_RightBorder || MouseZone == CWZ_LeftBorder)
{
// right/left of comment
return FCursorReply::Cursor(EMouseCursor::ResizeLeftRight);
}
else if (MouseZone == CWZ_BottomRightBorder || MouseZone == CWZ_TopLeftBorder)
{
// bottom right / top left hand corner
return FCursorReply::Cursor( EMouseCursor::ResizeSouthEast );
}
else if (MouseZone == CWZ_BottomBorder || MouseZone == CWZ_TopBorder)
{
// bottom / top of comment
return FCursorReply::Cursor(EMouseCursor::ResizeUpDown);
}
else if (MouseZone == CWZ_BottomLeftBorder || MouseZone == CWZ_TopRightBorder)
{
// bottom left / top right hand corner
return FCursorReply::Cursor( EMouseCursor::ResizeSouthWest );
}
else if (MouseZone == CWZ_TitleBar)
{
return FCursorReply::Cursor(EMouseCursor::CardinalCross);
}
return FCursorReply::Unhandled();
}
FReply SGraphNodeComment::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
FVector2D LocalMouseCoordinates = MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() );
if (bUserIsDragging)
{
FVector2D GraphSpaceCoordinates = NodeCoordToGraphCoord( MouseEvent.GetScreenSpacePosition() );
FVector2D OldGraphSpaceCoordinates = NodeCoordToGraphCoord( MouseEvent.GetLastScreenSpacePosition() );
FVector2D Delta = GraphSpaceCoordinates - OldGraphSpaceCoordinates;
//Clamp delta value based on resizing direction
if( MouseZone == CWZ_LeftBorder || MouseZone == CWZ_RightBorder )
{
Delta.Y = 0.0f;
}
else if( MouseZone == CWZ_TopBorder || MouseZone == CWZ_BottomBorder )
{
Delta.X = 0.0f;
}
//Resize node delta value
FVector2D DeltaNodeSize = Delta;
//Modify node size delta value based on resizing direction
if( (MouseZone == CWZ_LeftBorder) || (MouseZone == CWZ_TopBorder) || (MouseZone == CWZ_TopLeftBorder) )
{
DeltaNodeSize = -DeltaNodeSize;
}
else if( MouseZone == CWZ_TopRightBorder )
{
DeltaNodeSize.Y = -DeltaNodeSize.Y;
}
else if( MouseZone == CWZ_BottomLeftBorder )
{
DeltaNodeSize.X = -DeltaNodeSize.X;
}
StoredUserSize.X += DeltaNodeSize.X;
StoredUserSize.Y += DeltaNodeSize.Y;
const FVector2D PreviousUserSize = UserSize;
const float SnapSize = SNodePanel::GetSnapGridSize();
UserSize.X = SnapSize * FMath::Round(StoredUserSize.X/SnapSize);
UserSize.Y = SnapSize * FMath::Round(StoredUserSize.Y/SnapSize);
FVector2D DeltaNodePos(0,0);
//Modify node position (resizing top and left sides)
if( MouseZone != CWZ_BottomBorder && MouseZone != CWZ_RightBorder && MouseZone != CWZ_BottomRightBorder )
{
//Delta value to move graph node position
DeltaNodePos = PreviousUserSize - UserSize;
//Clamp position delta based on resizing direction
if( MouseZone == CWZ_BottomLeftBorder )
{
DeltaNodePos.Y = 0.0f;
}
else if( MouseZone == CWZ_TopRightBorder )
{
DeltaNodePos.X = 0.0f;
}
}
if(UserSize.Y < SCommentNodeDefs::MinHeight)
{
UserSize.Y = SCommentNodeDefs::MinHeight;
DeltaNodePos.Y = GetCorrectedNodePosition().Y - GetPosition().Y;
}
if(UserSize.X < SCommentNodeDefs::MinWidth)
{
UserSize.X = SCommentNodeDefs::MinWidth;
DeltaNodePos.X = GetCorrectedNodePosition().X - GetPosition().X;
}
SGraphNode::MoveTo( GetPosition() + DeltaNodePos );
}
else
{
MouseZone = FindMouseZone(LocalMouseCoordinates);
}
return SGraphNode::OnMouseMove( MyGeometry, MouseEvent );
}
void SGraphNodeComment::InitNodeAnchorPoint()
{
NodeAnchorPoint = GetPosition();
if( (MouseZone == CWZ_LeftBorder) || (MouseZone == CWZ_TopBorder) || (MouseZone == CWZ_TopLeftBorder) )
{
NodeAnchorPoint += UserSize;
}
else if( MouseZone == CWZ_BottomLeftBorder )
{
NodeAnchorPoint.X += UserSize.X;
}
else if( MouseZone == CWZ_TopRightBorder )
{
NodeAnchorPoint.Y += UserSize.Y;
}
}
FVector2D SGraphNodeComment::GetCorrectedNodePosition() const
{
FVector2D CorrectedPos = NodeAnchorPoint;
if( (MouseZone == CWZ_LeftBorder) || (MouseZone == CWZ_TopBorder) || (MouseZone == CWZ_TopLeftBorder) )
{
CorrectedPos -= UserSize;
}
else if( MouseZone == CWZ_BottomLeftBorder )
{
CorrectedPos.X -= UserSize.X;
}
else if( MouseZone == CWZ_TopRightBorder )
{
CorrectedPos.Y -= UserSize.Y;
}
return CorrectedPos;
}
SGraphNodeComment::ECommentWindowZone SGraphNodeComment::FindMouseZone(const FVector2D& LocalMouseCoordinates) const
{
ECommentWindowZone InMouseZone = CWZ_NotInWindow;
const float InverseZoomFactor = 1.0f / FMath::Min(GetOwnerPanel()->GetZoomAmount(), 1.0f);
const FSlateRect HitResultBorderSize(
SCommentNodeDefs::HitResultBorderSize.Left * InverseZoomFactor,
SCommentNodeDefs::HitResultBorderSize.Right * InverseZoomFactor,
SCommentNodeDefs::HitResultBorderSize.Top * InverseZoomFactor,
SCommentNodeDefs::HitResultBorderSize.Bottom * InverseZoomFactor);
const float TitleBarHeight = TitleBar.IsValid() ? TitleBar->GetDesiredSize().Y : 0.0f;
// Test for hit in location of 'grab' zone
if (LocalMouseCoordinates.Y > ( UserSize.Y - HitResultBorderSize.Bottom))
{
InMouseZone = CWZ_BottomBorder;
}
else if (LocalMouseCoordinates.Y <= (HitResultBorderSize.Top))
{
InMouseZone = CWZ_TopBorder;
}
else if (LocalMouseCoordinates.Y <= (HitResultBorderSize.Top + TitleBarHeight))
{
InMouseZone = CWZ_TitleBar;
}
if (LocalMouseCoordinates.X > (UserSize.X - HitResultBorderSize.Right))
{
if (InMouseZone == CWZ_BottomBorder)
{
InMouseZone = CWZ_BottomRightBorder;
}
else if (InMouseZone == CWZ_TopBorder)
{
InMouseZone = CWZ_TopRightBorder;
}
else
{
InMouseZone = CWZ_RightBorder;
}
}
else if (LocalMouseCoordinates.X <= HitResultBorderSize.Left)
{
if (InMouseZone == CWZ_TopBorder)
{
InMouseZone = CWZ_TopLeftBorder;
}
else if (InMouseZone == CWZ_BottomBorder)
{
InMouseZone = CWZ_BottomLeftBorder;
}
else
{
InMouseZone = CWZ_LeftBorder;
}
}
// Test for hit on rest of frame
if (InMouseZone == CWZ_NotInWindow)
{
if (LocalMouseCoordinates.Y > HitResultBorderSize.Top)
{
InMouseZone = CWZ_InWindow;
}
else if (LocalMouseCoordinates.X > HitResultBorderSize.Left)
{
InMouseZone = CWZ_InWindow;
}
}
return InMouseZone;
}
void SGraphNodeComment::OnMouseEnter( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
// Determine the zone the mouse is in
FVector2D LocalMouseCoordinates = MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() );
MouseZone = FindMouseZone(LocalMouseCoordinates);
SCompoundWidget::OnMouseEnter( MyGeometry, MouseEvent );
}
void SGraphNodeComment::OnMouseLeave( const FPointerEvent& MouseEvent )
{
if( !bUserIsDragging )
{
// Reset our mouse zone
MouseZone = CWZ_NotInWindow;
SCompoundWidget::OnMouseLeave( MouseEvent );
}
}
void SGraphNodeComment::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
SGraphNode::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
const int32 CurrentWidth = static_cast<int32>(UserSize.X);
const FString CurrentCommentTitle = GetNodeComment();
if (( CurrentCommentTitle != CachedCommentTitle ) || ( CurrentWidth != CachedWidth ))
{
CachedCommentTitle = CurrentCommentTitle;
CachedWidth = CurrentWidth;
UpdateGraphNode();
}
}
FReply SGraphNodeComment::OnDrop( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
return FReply::Unhandled();
}
void SGraphNodeComment::OnDragEnter( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
}
float SGraphNodeComment::GetWrapAt() const
{
return (float)(CachedWidth - 16);
}
bool SGraphNodeComment::IsNameReadOnly() const
{
return !IsEditable.Get() || SGraphNode::IsNameReadOnly();
}
void SGraphNodeComment::UpdateGraphNode()
{
// No pins in a comment box
InputPins.Empty();
OutputPins.Empty();
// Avoid standard box model too
RightNodeBox.Reset();
LeftNodeBox.Reset();
TSharedPtr<SWidget> ErrorText = SetupErrorReporting();
bool bIsSet = GraphNode->IsA(UEdGraphNode_Comment::StaticClass());
this->ContentScale.Bind( this, &SGraphNode::GetContentScale );
this->ChildSlot
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(SBorder)
.BorderImage( FEditorStyle::GetBrush("Kismet.Comment.Background") )
.ColorAndOpacity( FLinearColor::White )
.BorderBackgroundColor( this, &SGraphNodeComment::GetCommentBodyColor )
.Padding( FMargin(3.0f) )
[
SNew(SVerticalBox)
.ToolTipText( this, &SGraphNode::GetNodeTooltip )
+SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Top)
[
SAssignNew(TitleBar, SBorder)
.BorderImage( FEditorStyle::GetBrush("Graph.Node.TitleBackground") )
.BorderBackgroundColor( this, &SGraphNodeComment::GetCommentTitleBarColor )
.Padding( FMargin(10,5,5,3) )
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
[
SAssignNew(InlineEditableText, SInlineEditableTextBlock)
.Style( FEditorStyle::Get(), "Graph.CommentBlock.TitleInlineEditableText" )
.Text( this, &SGraphNodeComment::GetEditableNodeTitleAsText )
.OnVerifyTextChanged(this, &SGraphNodeComment::OnVerifyNameTextChanged)
.OnTextCommitted(this, &SGraphNodeComment::OnNameTextCommited)
.IsReadOnly( this, &SGraphNodeComment::IsNameReadOnly )
.IsSelected( this, &SGraphNodeComment::IsSelectedExclusively )
.WrapTextAt( this, &SGraphNodeComment::GetWrapAt )
]
]
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(1.0f)
[
ErrorText->AsShared()
]
+SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
// NODE CONTENT AREA
SNew(SBorder)
.BorderImage( FEditorStyle::GetBrush("NoBorder") )
]
]
];
}
FVector2D SGraphNodeComment::ComputeDesiredSize() const
{
return UserSize;
}
FReply SGraphNodeComment::OnMouseButtonDoubleClick( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent )
{
// If user double-clicked in the title bar area
if(FindMouseZone(InMyGeometry.AbsoluteToLocal(InMouseEvent.GetScreenSpacePosition())) == CWZ_TitleBar && IsEditable.Get())
{
// Request a rename
RequestRename();
// Set the keyboard focus
if(!HasKeyboardFocus())
{
FSlateApplication::Get().SetKeyboardFocus(SharedThis(this), EKeyboardFocusCause::SetDirectly);
}
return FReply::Handled();
}
else
{
// Otherwise let the base class handle it
return SGraphNode::OnMouseButtonDoubleClick(InMyGeometry, InMouseEvent);
}
}
FReply SGraphNodeComment::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
if ( InSelectionArea() && (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) && IsEditable.Get() )
{
bUserIsDragging = true;
StoredUserSize = UserSize;
//Find node anchor point
InitNodeAnchorPoint();
return FReply::Handled().CaptureMouse( SharedThis(this) );
}
else
{
HandleSelection(true, true);
return FReply::Unhandled();
}
}
FReply SGraphNodeComment::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
if ( (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) && bUserIsDragging )
{
bUserIsDragging = false;
// Resize the node
UserSize.X = FPlatformMath::Round(UserSize.X);
UserSize.Y =FPlatformMath::Round(UserSize.Y);
GetNodeObj()->ResizeNode(UserSize);
return FReply::Handled().ReleaseMouseCapture();
}
return FReply::Unhandled();
}
void SGraphNodeComment::HandleSelection(bool bSelected, bool bUpdateNodesUnderComment) const
{
if ((!this->bIsSelected && bSelected) || bUpdateNodesUnderComment)
{
SGraphNodeComment* Comment = const_cast<SGraphNodeComment*> (this);
UEdGraphNode_Comment* CommentNode = Cast<UEdGraphNode_Comment>(GraphNode);
if (CommentNode)
{
// Get our geo
const FVector2D NodePosition = GetPosition();
const FVector2D NodeSize = GetDesiredSize();
const FSlateRect CommentRect( NodePosition.X, NodePosition.Y, NodePosition.X + NodeSize.X, NodePosition.Y + NodeSize.Y );
TSharedPtr< SGraphPanel > Panel = Comment->GetOwnerPanel();
FChildren* PanelChildren = Panel->GetChildren();
int32 NumChildren = PanelChildren->Num();
CommentNode->ClearNodesUnderComment();
for ( int32 NodeIndex=0; NodeIndex < NumChildren; ++NodeIndex )
{
const TSharedRef<SGraphNode> SomeNodeWidget = StaticCastSharedRef<SGraphNode>(PanelChildren->GetChildAt(NodeIndex));
UObject* GraphObject = SomeNodeWidget->GetObjectBeingDisplayed();
const FVector2D SomeNodePosition = SomeNodeWidget->GetPosition();
const FVector2D SomeNodeSize = SomeNodeWidget->GetDesiredSize();
const FSlateRect NodeGeometryGraphSpace( SomeNodePosition.X, SomeNodePosition.Y, SomeNodePosition.X + SomeNodeSize.X, SomeNodePosition.Y + SomeNodeSize.Y );
if ( FSlateRect::IsRectangleContained( CommentRect, NodeGeometryGraphSpace ) )
{
CommentNode->AddNodeUnderComment(GraphObject);
}
}
}
}
this->bIsSelected = bSelected;
}
const FSlateBrush* SGraphNodeComment::GetShadowBrush(bool bSelected) const
{
HandleSelection(bSelected);
return SGraphNode::GetShadowBrush(bSelected);
}
void SGraphNodeComment::GetOverlayBrushes(bool bSelected, const FVector2D WidgetSize, TArray<FOverlayBrushInfo>& Brushes) const
{
const float Fudge = 3.0f;
HandleSelection(bSelected);
FOverlayBrushInfo HandleBrush = FEditorStyle::GetBrush( TEXT("Kismet.Comment.Handle") );
HandleBrush.OverlayOffset.X = WidgetSize.X - HandleBrush.Brush->ImageSize.X - Fudge;
HandleBrush.OverlayOffset.Y = WidgetSize.Y - HandleBrush.Brush->ImageSize.Y - Fudge;
Brushes.Add(HandleBrush);
return SGraphNode::GetOverlayBrushes(bSelected, WidgetSize, Brushes);
}
bool SGraphNodeComment::OnHitTest( const FGeometry& MyGeometry, FVector2D InAbsoluteCursorPosition )
{
FVector2D LocalMouseCoordinates = MyGeometry.AbsoluteToLocal( InAbsoluteCursorPosition );
ECommentWindowZone InMouseZone = FindMouseZone(LocalMouseCoordinates);
//@TODO: really want to return true for CWZ_InWindow as well, *as long as* there are no other comment bubbles underneath that return one of the edges
// This is a brute force way to allow nested comment boxes to work usefully
return (InMouseZone != CWZ_NotInWindow) && (InMouseZone != CWZ_InWindow);
}
void SGraphNodeComment::MoveTo( const FVector2D& NewPosition )
{
FVector2D PositionDelta = NewPosition - GetPosition();
SGraphNode::MoveTo(NewPosition);
// Don't drag note content if either of the shift keys are down.
FModifierKeysState KeysState = FSlateApplication::Get().GetModifierKeys();
if(!KeysState.IsShiftDown())
{
UEdGraphNode_Comment* CommentNode = Cast<UEdGraphNode_Comment>(GraphNode);
if (CommentNode && CommentNode->MoveMode == ECommentBoxMode::GroupMovement)
{
// Now update any nodes which are touching the comment but *not* selected
// Selected nodes will be moved as part of the normal selection code
TSharedPtr< SGraphPanel > Panel = GetOwnerPanel();
for (FCommentNodeSet::TConstIterator NodeIt( CommentNode->GetNodesUnderComment() ); NodeIt; ++NodeIt)
{
if (UEdGraphNode* Node = Cast<UEdGraphNode>(*NodeIt))
{
if ( !Panel->SelectionManager.IsNodeSelected(Node))
{
Node->NodePosX += PositionDelta.X;
Node->NodePosY += PositionDelta.Y;
}
}
}
}
}
}
bool SGraphNodeComment::ShouldScaleNodeComment() const
{
return false;
}
FSlateColor SGraphNodeComment::GetCommentBodyColor() const
{
UEdGraphNode_Comment* CommentNode = Cast<UEdGraphNode_Comment>(GraphNode);
if (CommentNode)
{
return CommentNode->CommentColor;
}
else
{
return FLinearColor::White;
}
}
FSlateColor SGraphNodeComment::GetCommentTitleBarColor() const
{
UEdGraphNode_Comment* CommentNode = Cast<UEdGraphNode_Comment>(GraphNode);
if (CommentNode)
{
const FLinearColor Color = CommentNode->CommentColor * SCommentNodeDefs::TitleBarColorMultiplier;
return FLinearColor(Color.R, Color.G, Color.B);
}
else
{
const FLinearColor Color = FLinearColor::White * SCommentNodeDefs::TitleBarColorMultiplier;
return FLinearColor(Color.R, Color.G, Color.B);
}
}
bool SGraphNodeComment::CanBeSelected(const FVector2D& MousePositionInNode) const
{
const ECommentWindowZone InMouseZone = FindMouseZone(MousePositionInNode);
return CWZ_TitleBar == InMouseZone;
}
FVector2D SGraphNodeComment::GetDesiredSizeForMarquee() const
{
const float TitleBarHeight = TitleBar.IsValid() ? TitleBar->GetDesiredSize().Y : 0.0f;
return FVector2D(UserSize.X, TitleBarHeight);
}
FSlateRect SGraphNodeComment::GetTitleRect() const
{
const FVector2D NodePosition = GetPosition();
FVector2D NodeSize = TitleBar.IsValid() ? TitleBar->GetDesiredSize() : GetDesiredSize();
return FSlateRect( NodePosition.X, NodePosition.Y, NodePosition.X + NodeSize.X, NodePosition.Y + NodeSize.Y ) + SCommentNodeDefs::TitleBarOffset;
}