// 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& PotentialParent, const TSharedPtr& 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 WindowDroppedInto = MouseEvent.GetWindow(); 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& 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& 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 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::New( const TSharedRef& InTabToBeDragged, const FVector2D InTabGrabOffset, TSharedRef InTabOwnerArea, const FVector2D& OwnerAreaSize ) { const TSharedRef Operation = MakeShareable( new FDockingDragOperation( InTabToBeDragged, InTabGrabOffset, InTabOwnerArea, OwnerAreaSize ) ); return Operation; } TSharedPtr FDockingDragOperation::GetTabBeingDragged() { return TabBeingDragged; } FVector2D FDockingDragOperation::GetTabGrabOffsetFraction() const { return TabGrabOffsetFraction; } bool FDockingDragOperation::CanDockInNode(const TSharedRef& DockNode, EViaTabwell IsDockingViaTabwell ) const { const TSharedRef TargetTabManager = DockNode->GetDockArea()->GetTabManager(); const TSharedRef TabManagerOfOrigin = this->TabOwnerAreaOfOrigin->GetTabManager(); 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 == TabManagerOfOrigin; const bool bCanDockInEmptyArea = DockNode->GetNodeType() == SDockingNode::DockArea && StaticCastSharedRef(DockNode)->GetChildNodes().Num() == 0; return bTabManagerMatches && (IsDockingViaTabwell == FDockingDragOperation::DockingViaTabWell || bCanDockInEmptyArea); } else { // Most commonly, tabs are confined to their tab manager of origin. return (TargetTabManager == TabManagerOfOrigin); } } 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& InTabToBeDragged, const FVector2D InTabGrabOffsetFraction, TSharedRef 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()) ) ] ); 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 MyTabManager = TabBeingDragged->GetTabManagerPtr(); if (!MyTabManager.IsValid()) { return; } const FVector2D PositionToDrop = CursorDecoratorWindow->GetPositionInScreen(); const float DPIScale = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(PositionToDrop.X, PositionToDrop.Y); TSharedPtr NewWindowParent = MyTabManager->GetPrivateApi().GetParentWindow(); TSharedRef 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 NewDockNode; if (TabBeingDragged->GetTabRole() == ETabRole::NomadTab) { TabBeingDragged->SetTabManager(FGlobalTabmanager::Get()); } // Create a new dockarea TSharedRef NewDockArea = SNew(SDockingArea, MyTabManager.ToSharedRef(), FTabManager::NewPrimaryArea()) .ParentWindow(NewWindow) .InitialContent ( SAssignNew(NewDockNode, SDockingTabStack, FTabManager::NewStack()) ); if (TabBeingDragged->GetTabRole() == ETabRole::MajorTab || TabBeingDragged->GetTabRole() == ETabRole::NomadTab) { TSharedPtr 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()); // Let every widget under this tab manager know that this tab has found a new home. TabOwnerAreaOfOrigin->GetTabManager()->GetPrivateApi().OnTabRelocated(TabBeingDragged.ToSharedRef(), NewWindow); }