Files
UnrealEngine/Engine/Source/Runtime/Slate/Private/Framework/Docking/FDockingDragOperation.cpp
2025-05-18 13:04:45 +08:00

409 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Framework/Docking/FDockingDragOperation.h"
#include "Framework/Application/SlateApplication.h"
#include "HAL/PlatformApplicationMisc.h"
/**
* Check if the given PotentialParent is a parent of PotentialChild.
*/
static bool IsParentWidgetOf(const TSharedPtr<SWidget>& PotentialParent, const TSharedPtr<SWidget>& PotentialChild)
{
const SWidget* Parent = PotentialChild->GetParentWidget().Get();
while (Parent != nullptr)
{
if (PotentialParent.Get() == Parent)
{
return true;
}
Parent = Parent->GetParentWidget().Get();
}
return false;
}
/**
* Invoked when the drag and drop operation has ended.
*
* @param bDropWasHandled true when the drop was handled by some widget; false otherwise
*/
void FDockingDragOperation::OnDrop( bool bDropWasHandled, const FPointerEvent& MouseEvent )
{
check(CursorDecoratorWindow.IsValid());
const FVector2D WindowSize = CursorDecoratorWindow->GetSizeInScreen();
TabBeingDragged->SetDraggedOverDockArea( NULL );
if (!bDropWasHandled)
{
DroppedOntoNothing();
}
else
{
// The event was handled, so we HAVE to have some window that we dropped onto.
TSharedRef<SWindow> WindowDroppedInto = MouseEvent.GetWindow();
TabOwnerAreaOfOrigin->GetTabManager()->GetPrivateApi().SetCanDoDeferredLayoutSave(true);
TabOwnerAreaOfOrigin->GetTabManager()->GetPrivateApi().OnTabRelocated(TabBeingDragged.ToSharedRef(), WindowDroppedInto);
}
// Destroy the CursorDecoratorWindow by calling the base class implementation because we are relocating the content into a more permanent home.
FDragDropOperation::OnDrop(bDropWasHandled, MouseEvent);
TabBeingDragged.Reset();
}
/**
* Called when the mouse was moved during a drag and drop operation
*
* @param DragDropEvent The event that describes this drag drop operation.
*/
void FDockingDragOperation::OnDragged( const FDragDropEvent& DragDropEvent )
{
const bool bPreviewingTarget = HoveredDockTarget.TargetNode.IsValid();
if ( !bPreviewingTarget )
{
// The tab is being dragged. Move the the decorator window to match the cursor position.
FVector2D TargetPosition = DragDropEvent.GetScreenSpacePosition() - GetDecoratorOffsetFromCursor();
CursorDecoratorWindow->UpdateMorphTargetShape( FSlateRect(TargetPosition.X, TargetPosition.Y, TargetPosition.X + LastContentSize.X, TargetPosition.Y + LastContentSize.Y) );
CursorDecoratorWindow->MoveWindowTo( TargetPosition );
}
}
/**
* DragTestArea widgets invoke this method when a drag enters them
*
* @param ThePanel That tab well that we just dragged something into.
*/
void FDockingDragOperation::OnTabWellEntered( const TSharedRef<class SDockingTabWell>& ThePanel )
{
if (IsParentWidgetOf(this->TabOwnerAreaOfOrigin, ThePanel->GetDockArea()))
{
return;
}
// We just pulled the tab into some TabWell (in some DockNode).
// Hide our decorator window and let the DockNode handle previewing what will happen if we drop the node.
HoveredTabPanelPtr = ThePanel;
CursorDecoratorWindow->HideWindow();
TabBeingDragged->SetDraggedOverDockArea( ThePanel->GetDockArea() );
}
/**
* DragTestArea widgets invoke this method when a drag leaves them
*
* @param ThePanel That tab well that we just dragged something out of.
*/
void FDockingDragOperation::OnTabWellLeft( const TSharedRef<class SDockingTabWell>& ThePanel, const FGeometry& DockNodeGeometry )
{
// We just pulled out of some DockNode's TabWell
HoveredTabPanelPtr.Reset();
// Show the Preview Window again.
CursorDecoratorWindow->Resize( DockNodeGeometry.GetLocalSize() );
CursorDecoratorWindow->ShowWindow();
CursorDecoratorWindow->ReshapeWindow( DockNodeGeometry.GetLayoutBoundingRect() );
FCurveSequence Sequence;
Sequence.AddCurve( 0, 0.05f, ECurveEaseFunction::QuadOut );
CursorDecoratorWindow->MorphToShape( Sequence, CursorDecoratorWindow->GetOpacity(), CursorDecoratorWindow->GetMorphTargetShape() );
LastContentSize = DesiredSizeFrom( DockNodeGeometry.GetLocalSize() );
CursorDecoratorStackNode->OpenTab(TabBeingDragged.ToSharedRef());
TabBeingDragged->SetDraggedOverDockArea( NULL );
}
FSlateRect FDockingDragOperation::GetPreviewAreaForDirection ( const FSlateRect& DockableArea, SDockingArea::RelativeDirection DockingDirection )
{
FSlateRect TargetRect = DockableArea;
const FVector2D Size (TargetRect.Right-TargetRect.Left, TargetRect.Bottom-TargetRect.Top);
switch( DockingDirection )
{
case SDockingNode::LeftOf:
TargetRect.Right = TargetRect.Left + Size.X * 0.5f;
break;
case SDockingNode::Above:
TargetRect.Bottom = TargetRect.Top + Size.Y * 0.5f;
break;
case SDockingNode::RightOf:
TargetRect.Left = TargetRect.Left + Size.X * 0.5f;
break;
case SDockingNode::Below:
TargetRect.Top = TargetRect.Top + Size.Y * 0.5f;
break;
case SDockingNode::Center:
break;
}
return TargetRect;
}
void FDockingDragOperation::SetHoveredTarget( const FDockTarget& InTarget, const FInputEvent& InputEvent )
{
if ( HoveredDockTarget != InTarget )
{
HoveredDockTarget = InTarget;
TSharedPtr<SDockingNode> HoveredTargetNode = InTarget.TargetNode.Pin();
FCurveSequence Sequence;
Sequence.AddCurve( 0, 0.1f, ECurveEaseFunction::QuadOut );
if ( HoveredTargetNode.IsValid() )
{
const FGeometry TargetDockNodeGeometry = InputEvent.FindGeometry( HoveredTargetNode.ToSharedRef() );
FSlateRect TabStackArea = GetPreviewAreaForDirection(
TargetDockNodeGeometry.GetLayoutBoundingRect(),
InTarget.DockDirection );
const float TargetOpacity = CursorDecoratorWindow->GetOpacity();
CursorDecoratorWindow->MorphToShape( Sequence, TargetOpacity, TabStackArea );
CursorDecoratorWindow->SetColorAndOpacity( FCoreStyle::Get().GetColor( TEXT("Docking.Cross.PreviewWindowTint") ) );
TabBeingDragged->SetDraggedOverDockArea( HoveredTargetNode->GetDockArea() );
}
else
{
CursorDecoratorWindow->MorphToShape( Sequence, CursorDecoratorWindow->GetOpacity(), CursorDecoratorWindow->GetMorphTargetShape() );
CursorDecoratorWindow->SetColorAndOpacity( FLinearColor::White );
TabBeingDragged->SetDraggedOverDockArea( NULL );
}
}
}
TSharedRef<FDockingDragOperation> FDockingDragOperation::New( const TSharedRef<class SDockTab>& InTabToBeDragged, const FVector2D InTabGrabOffset, TSharedRef<class SDockingArea> InTabOwnerArea, const FVector2D& OwnerAreaSize )
{
const TSharedRef<FDockingDragOperation> Operation = MakeShareable( new FDockingDragOperation( InTabToBeDragged, InTabGrabOffset, InTabOwnerArea, OwnerAreaSize ) );
return Operation;
}
TSharedPtr<class SDockTab> FDockingDragOperation::GetTabBeingDragged()
{
return TabBeingDragged;
}
FVector2D FDockingDragOperation::GetTabGrabOffsetFraction() const
{
return TabGrabOffsetFraction;
}
bool FDockingDragOperation::CanDockInNode(const TSharedRef<SDockingNode>& DockNode, EViaTabwell IsDockingViaTabwell ) const
{
const TSharedRef<FTabManager> TargetTabManager = DockNode->GetDockArea()->GetTabManager();
TSharedRef<FTabManager> TabManagerOfOriginToUse = this->TabOwnerAreaOfOrigin->GetTabManager();
// if we are dragging a NomadTab force it to use the FGlobalTabManager as the TabManager of origin
// There are cases where the NomadTab is spawned like a normal tab and his TabManager is not correct
if (TabBeingDragged->GetTabRole() == ETabRole::NomadTab)
{
TabManagerOfOriginToUse = FGlobalTabmanager::Get();
}
if (IsParentWidgetOf(TabBeingDragged->GetContent(), DockNode))
{
return false;
}
if (TabBeingDragged->GetTabRole() == ETabRole::NomadTab)
{
if ( IsDockingViaTabwell == FDockingDragOperation::DockingViaTabWell )
{
// Nomad tabs can be docked in in any tab well.
return true;
}
else
{
return TargetTabManager != FGlobalTabmanager::Get();
}
}
else if (TabBeingDragged->GetTabRole() == ETabRole::MajorTab)
{
// Major tabs can only be stacked; they should not
// be allowed to split areas. They are also confined to their
// tab manager of origin.
// The only exception is an empty area, where docking the tab should be really easy.
const bool bTabManagerMatches = TargetTabManager == TabManagerOfOriginToUse;
const bool bCanDockInEmptyArea = DockNode->GetNodeType() == SDockingNode::DockArea && StaticCastSharedRef<SDockingArea>(DockNode)->GetChildNodes().Num() == 0;
return bTabManagerMatches && (IsDockingViaTabwell == FDockingDragOperation::DockingViaTabWell || bCanDockInEmptyArea);
}
else
{
// Most commonly, tabs are confined to their tab manager of origin.
return (TargetTabManager == TabManagerOfOriginToUse);
}
}
FDockingDragOperation::~FDockingDragOperation()
{
if (TabBeingDragged.IsValid())
{
DroppedOntoNothing();
}
}
/** The constructor is protected, so that this class can only be instantiated as a shared pointer. */
FDockingDragOperation::FDockingDragOperation( const TSharedRef<SDockTab>& InTabToBeDragged, const FVector2D InTabGrabOffsetFraction, TSharedRef<class SDockingArea> InTabOwnerArea, const FVector2D& OwnerAreaSize )
: TabBeingDragged( InTabToBeDragged )
, TabGrabOffsetFraction( InTabGrabOffsetFraction )
, TabOwnerAreaOfOrigin( InTabOwnerArea )
, TabStackOfOrigin( InTabToBeDragged->GetParent()->GetParentDockTabStack() )
, HoveredTabPanelPtr( )
, HoveredDockTarget()
, LastContentSize( OwnerAreaSize )
{
// Create the decorator window that we will use during this drag and drop to make the user feel like
// they are actually dragging a piece of UI.
// Start the window off hidden.
const bool bShowImmediately = false;
CursorDecoratorWindow = FSlateApplication::Get().AddWindow( SWindow::MakeCursorDecorator(), bShowImmediately );
// Usually cursor decorators figure out their size automatically from content, but we will drive it
// here because the window will reshape itself to better reflect what will happen when the user drops the Tab.
CursorDecoratorWindow->SetSizingRule( ESizingRule::FixedSize );
CursorDecoratorWindow->SetOpacity(0.45f);
CursorDecoratorWindow->SetContent
(
SNew(SBorder)
. BorderImage(FCoreStyle::Get().GetBrush("Docking.Background"))
[
SNew(SDockingArea, TabBeingDragged->GetTabManagerPtr().ToSharedRef(), FTabManager::NewPrimaryArea())
//. OriginalDockArea(OriginalDockArea)
. InitialContent
(
SAssignNew(CursorDecoratorStackNode, SDockingTabStack, FTabManager::NewStack())
)
]
);
TabOwnerAreaOfOrigin->GetTabManager()->GetPrivateApi().SetCanDoDeferredLayoutSave(false);
if ( TabBeingDragged->IsActive() )
{
FGlobalTabmanager::Get()->SetActiveTab(NULL);
}
}
/** @return The offset into the tab where the user grabbed in Slate Units. */
const FVector2D FDockingDragOperation::GetDecoratorOffsetFromCursor()
{
const ETabRole RoleToUse = TabBeingDragged->GetVisualTabRole();
const FVector2D TabDesiredSize = TabBeingDragged->GetDesiredSize();
const FVector2D MaxTabSize = FDockingConstants::GetMaxTabSizeFor(RoleToUse);
return TabGrabOffsetFraction * FVector2D (
FMath::Min( TabDesiredSize.X, MaxTabSize.X ),
FMath::Min( TabDesiredSize.Y, MaxTabSize.Y )
);
}
/** @return the size of the DockNode that looks good in a preview given the initial size of the tab that we grabbed. */
FVector2D FDockingDragOperation::DesiredSizeFrom( const FVector2D& InitialTabSize )
{
// Just make sure it isn't too big so it doesn't cover up the whole screen.
const float MaxSideSizeAllowed = 800;
const float SizeCoefficient = FMath::Clamp( MaxSideSizeAllowed / InitialTabSize.GetMax(), 0.1f, 1.0f );
return InitialTabSize * SizeCoefficient;
}
void FDockingDragOperation::DroppedOntoNothing()
{
// If we dropped the tab into an existing DockNode then it would have handled the DropEvent.
// We are here because that didn't happen, so make a new window with a new DockNode and drop the tab into that.
TSharedPtr<FTabManager> MyTabManager = TabBeingDragged->GetTabManagerPtr();
if (!MyTabManager.IsValid())
{
return;
}
const FVector2D PositionToDrop = CursorDecoratorWindow->GetPositionInScreen();
const float DPIScale = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(PositionToDrop.X, PositionToDrop.Y);
TSharedPtr<SWindow> NewWindowParent = MyTabManager->GetPrivateApi().GetParentWindow();
TSharedRef<SWindow> NewWindow = SNew(SWindow)
.Title(FGlobalTabmanager::Get()->GetApplicationTitle())
.AutoCenter(EAutoCenter::None)
// Divide out scale, it is already factored into position
.ScreenPosition(PositionToDrop/DPIScale)
// Make room for the title bar; otherwise windows will get progressive smaller whenver you float them.
.ClientSize(SWindow::ComputeWindowSizeForContent(CursorDecoratorWindow->GetSizeInScreen()))
.CreateTitleBar(false);
TSharedPtr<SDockingTabStack> NewDockNode;
TSharedPtr<FTabManager> TabManagerToUse;
if (TabBeingDragged->GetTabRole() == ETabRole::NomadTab)
{
TabManagerToUse = FGlobalTabmanager::Get();
TabBeingDragged->SetTabManager(TabManagerToUse);
}
else
{
TabManagerToUse = MyTabManager;
}
// Create a new dockarea
TSharedRef<SDockingArea> NewDockArea =
SNew(SDockingArea, TabManagerToUse.ToSharedRef(), FTabManager::NewPrimaryArea())
.ParentWindow(NewWindow)
.InitialContent
(
SAssignNew(NewDockNode, SDockingTabStack, FTabManager::NewStack())
);
if (TabBeingDragged->GetTabRole() == ETabRole::MajorTab || TabBeingDragged->GetTabRole() == ETabRole::NomadTab)
{
TSharedPtr<SWindow> RootWindow = FGlobalTabmanager::Get()->GetRootWindow();
if (RootWindow.IsValid())
{
// We have a root window, so all MajorTabs are nested under it.
FSlateApplication::Get().AddWindowAsNativeChild(NewWindow, RootWindow.ToSharedRef())->SetContent(NewDockArea);
}
else
{
// App tabs get put in top-level windows. They show up on the taskbar.
FSlateApplication::Get().AddWindow(NewWindow)->SetContent(NewDockArea);
}
}
else
{
// Other tab types are placed in child windows. Their life is controlled by the top-level windows.
// They do not show up on the taskbar.
if (NewWindowParent.IsValid())
{
FSlateApplication::Get().AddWindowAsNativeChild(NewWindow, NewWindowParent.ToSharedRef())->SetContent(NewDockArea);
}
else
{
FSlateApplication::Get().AddWindow(NewWindow)->SetContent(NewDockArea);
}
}
// Do this after the window parenting so that the window title is set correctly
NewDockNode->OpenTab(TabBeingDragged.ToSharedRef());
TabOwnerAreaOfOrigin->GetTabManager()->GetPrivateApi().SetCanDoDeferredLayoutSave(true);
// Let every widget under this tab manager know that this tab has found a new home.
TabOwnerAreaOfOrigin->GetTabManager()->GetPrivateApi().OnTabRelocated(TabBeingDragged.ToSharedRef(), NewWindow);
}