712 lines
24 KiB
C++
712 lines
24 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Framework/Docking/SDockingTabWell.h"
|
|
#include "Types/PaintArgs.h"
|
|
#include "Layout/ArrangedChildren.h"
|
|
#include "Framework/Docking/FDockingDragOperation.h"
|
|
#include "HAL/PlatformApplicationMisc.h"
|
|
|
|
const FVector2D FDockingConstants::MaxMinorTabSize(160.f, 25.0f);
|
|
const FVector2D FDockingConstants::MaxMajorTabSize(210.f, 50.f);
|
|
|
|
const FVector2D FDockingConstants::GetMaxTabSizeFor( ETabRole TabRole )
|
|
{
|
|
return (TabRole == ETabRole::MajorTab)
|
|
? MaxMajorTabSize
|
|
: MaxMinorTabSize;
|
|
}
|
|
|
|
SDockingTabWell::SDockingTabWell()
|
|
: Tabs(this)
|
|
{
|
|
}
|
|
|
|
void SDockingTabWell::Construct( const FArguments& InArgs )
|
|
{
|
|
ForegroundTabIndex = INDEX_NONE;
|
|
TabBeingDraggedPtr = nullptr;
|
|
ChildBeingDraggedOffset = 0.0f;
|
|
TabGrabOffsetFraction = FVector2D::ZeroVector;
|
|
|
|
SeparatorBrush = nullptr; // No separater between tabs
|
|
|
|
// We need a valid parent here. TabPanels must exist in a SDockingNode
|
|
check( InArgs._ParentStackNode.Get().IsValid() );
|
|
ParentTabStackPtr = InArgs._ParentStackNode.Get();
|
|
}
|
|
|
|
const TSlotlessChildren<SDockTab>& SDockingTabWell::GetTabs() const
|
|
{
|
|
return Tabs;
|
|
}
|
|
|
|
int32 SDockingTabWell::GetNumTabs() const
|
|
{
|
|
return Tabs.Num();
|
|
}
|
|
|
|
void SDockingTabWell::AddTab( const TSharedRef<SDockTab>& InTab, int32 AtIndex, bool bKeepInactive)
|
|
{
|
|
InTab->SetParent(SharedThis(this));
|
|
|
|
// Add the tab and implicitly activate it.
|
|
if (AtIndex == INDEX_NONE)
|
|
{
|
|
this->Tabs.Add( InTab );
|
|
if (!bKeepInactive)
|
|
{
|
|
BringTabToFront(Tabs.Num() - 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AtIndex = FMath::Clamp( AtIndex, 0, Tabs.Num() );
|
|
this->Tabs.Insert( InTab, AtIndex );
|
|
|
|
if (!bKeepInactive)
|
|
{
|
|
BringTabToFront(AtIndex);
|
|
}
|
|
}
|
|
|
|
|
|
const TSharedPtr<SDockingTabStack> ParentTabStack = ParentTabStackPtr.Pin();
|
|
if (ParentTabStack.IsValid() && ParentTabStack->GetDockArea().IsValid())
|
|
{
|
|
ParentTabStack->GetDockArea()->GetTabManager()->GetPrivateApi().OnTabOpening( InTab );
|
|
}
|
|
}
|
|
|
|
|
|
void SDockingTabWell::OnArrangeChildren( const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren ) const
|
|
{
|
|
// The specialized TabWell is dedicated to arranging tabs.
|
|
// Tabs have uniform sizing (all tabs the same size).
|
|
// TabWell also ignores widget visibility, as it is not really
|
|
// relevant.
|
|
|
|
|
|
// The tab that is being dragged by the user, if any.
|
|
TSharedPtr<SDockTab> TabBeingDragged = TabBeingDraggedPtr;
|
|
|
|
const int32 NumChildren = Tabs.Num();
|
|
|
|
// Tabs have a uniform size.
|
|
const FVector2D ChildSize = ComputeChildSize(AllottedGeometry);
|
|
|
|
|
|
const float DraggedChildCenter = ChildBeingDraggedOffset + ChildSize.X / 2;
|
|
|
|
// Arrange all the tabs left to right.
|
|
float XOffset = 0;
|
|
for( int32 TabIndex=0; TabIndex < NumChildren; ++TabIndex )
|
|
{
|
|
const TSharedRef<SDockTab> CurTab = Tabs[TabIndex];
|
|
const float ChildWidthWithOverlap = ChildSize.X - CurTab->GetOverlapWidth();
|
|
|
|
// This tab being dragged is arranged later. It should not be arranged twice
|
|
if (CurTab == TabBeingDragged)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Is this spot reserved from the tab that is being dragged?
|
|
if ( TabBeingDragged.IsValid() && XOffset <= DraggedChildCenter && DraggedChildCenter < (XOffset + ChildWidthWithOverlap) )
|
|
{
|
|
// if so, leave some room to signify that this is where the dragged tab would end up
|
|
XOffset += ChildWidthWithOverlap;
|
|
}
|
|
|
|
ArrangedChildren.AddWidget( AllottedGeometry.MakeChild(CurTab, FVector2D(XOffset, 0), ChildSize) );
|
|
|
|
XOffset += ChildWidthWithOverlap;
|
|
}
|
|
|
|
// Arrange the tab currently being dragged by the user, if any
|
|
if ( TabBeingDragged.IsValid() )
|
|
{
|
|
ArrangedChildren.AddWidget( AllottedGeometry.MakeChild( TabBeingDragged.ToSharedRef(), FVector2D(ChildBeingDraggedOffset,0), ChildSize) );
|
|
}
|
|
}
|
|
|
|
int32 SDockingTabWell::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
|
|
{
|
|
// When we are dragging a tab, it must be painted on top of the other tabs, so we cannot
|
|
// just reuse the Panel's default OnPaint.
|
|
|
|
|
|
// The TabWell has no visualization of its own; it just visualizes its child tabs.
|
|
FArrangedChildren ArrangedChildren(EVisibility::Visible);
|
|
this->ArrangeChildren(AllottedGeometry, ArrangedChildren);
|
|
|
|
// Because we paint multiple children, we must track the maximum layer id that they produced in case one of our parents
|
|
// wants to an overlay for all of its contents.
|
|
int32 MaxLayerId = LayerId;
|
|
|
|
TSharedPtr<SDockTab> ForegroundTab = GetForegroundTab();
|
|
FArrangedWidget* ForegroundTabGeometry = nullptr;
|
|
|
|
// Draw all inactive tabs first, from last, to first, so that the inactive tabs
|
|
// that come later, are drawn behind tabs that come before it.
|
|
for (int32 ChildIndex = ArrangedChildren.Num() - 1; ChildIndex >= 0; --ChildIndex)
|
|
{
|
|
FArrangedWidget& CurWidget = ArrangedChildren[ChildIndex];
|
|
if (CurWidget.Widget == ForegroundTab)
|
|
{
|
|
ForegroundTabGeometry = &CurWidget;
|
|
}
|
|
else
|
|
{
|
|
bool bShouldDrawSeparator = false;
|
|
if (SeparatorBrush && SeparatorBrush->DrawAs != ESlateBrushDrawType::NoDrawType && ArrangedChildren.IsValidIndex(ChildIndex + 1))
|
|
{
|
|
const FArrangedWidget& PrevWidget = ArrangedChildren[ChildIndex + 1];
|
|
bShouldDrawSeparator = !CurWidget.Widget->IsHovered() && !PrevWidget.Widget->IsHovered() && PrevWidget.Widget != ForegroundTab;
|
|
}
|
|
|
|
FSlateRect ChildClipRect = MyCullingRect.IntersectionWith( CurWidget.Geometry.GetLayoutBoundingRect() );
|
|
const int32 CurWidgetsMaxLayerId = CurWidget.Widget->Paint( Args.WithNewParent(this), CurWidget.Geometry, ChildClipRect, OutDrawElements, MaxLayerId, InWidgetStyle, ShouldBeEnabled( bParentEnabled ) );
|
|
|
|
if(bShouldDrawSeparator)
|
|
{
|
|
const float SeparatorHeight = CurWidget.Geometry.GetLocalSize().Y * .65f;
|
|
|
|
// Center the separator
|
|
float Offset = (CurWidget.Geometry.GetLocalSize().Y - SeparatorHeight) / 2.0f;
|
|
FPaintGeometry Geometry = CurWidget.Geometry.ToPaintGeometry(FVector2f(1.0f, SeparatorHeight), FSlateLayoutTransform(FVector2f(CurWidget.Geometry.GetLocalSize().X + 1.0f, Offset)));
|
|
|
|
// This code rounds the position of the widget so we don't end up on half a pixel and end up with a larger size separator than we want
|
|
FSlateRenderTransform NewTransform = Geometry.GetAccumulatedRenderTransform();
|
|
NewTransform.SetTranslation(NewTransform.GetTranslation().RoundToVector());
|
|
Geometry.SetRenderTransform(NewTransform);
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
OutDrawElements,
|
|
MaxLayerId,
|
|
Geometry,
|
|
SeparatorBrush,
|
|
ESlateDrawEffect::None,
|
|
SeparatorBrush->GetTint(InWidgetStyle)
|
|
);
|
|
}
|
|
|
|
MaxLayerId = FMath::Max( MaxLayerId, CurWidgetsMaxLayerId );
|
|
}
|
|
}
|
|
|
|
// Draw active tab in front
|
|
if (ForegroundTab != TSharedPtr<SDockTab>())
|
|
{
|
|
checkSlow(ForegroundTabGeometry);
|
|
FSlateRect ChildClipRect = MyCullingRect.IntersectionWith( ForegroundTabGeometry->Geometry.GetLayoutBoundingRect() );
|
|
const int32 CurWidgetsMaxLayerId = ForegroundTabGeometry->Widget->Paint( Args.WithNewParent(this), ForegroundTabGeometry->Geometry, ChildClipRect, OutDrawElements, MaxLayerId, InWidgetStyle, ShouldBeEnabled( bParentEnabled ) );
|
|
MaxLayerId = FMath::Max( MaxLayerId, CurWidgetsMaxLayerId );
|
|
}
|
|
|
|
return MaxLayerId;
|
|
}
|
|
|
|
FVector2D SDockingTabWell::ComputeDesiredSize( float ) const
|
|
{
|
|
FVector2D DesiredSizeResult(0,0);
|
|
|
|
TSharedPtr<SDockTab> TabBeingDragged = TabBeingDraggedPtr;
|
|
|
|
for ( int32 TabIndex=0; TabIndex < Tabs.Num(); ++TabIndex )
|
|
{
|
|
// Currently not respecting Visibility because tabs cannot be invisible.
|
|
const TSharedRef<SDockTab>& SomeTab = Tabs[TabIndex];
|
|
|
|
// Tab being dragged is computed later
|
|
if(SomeTab != TabBeingDragged)
|
|
{
|
|
const FVector2D SomeTabDesiredSize = SomeTab->GetDesiredSize();
|
|
DesiredSizeResult.X += SomeTabDesiredSize.X;
|
|
DesiredSizeResult.Y = FMath::Max(SomeTabDesiredSize.Y, DesiredSizeResult.Y);
|
|
}
|
|
}
|
|
|
|
if ( TabBeingDragged.IsValid() )
|
|
{
|
|
const FVector2D SomeTabDesiredSize = TabBeingDragged->GetDesiredSize();
|
|
DesiredSizeResult.X += SomeTabDesiredSize.X;
|
|
DesiredSizeResult.Y = FMath::Max(SomeTabDesiredSize.Y, DesiredSizeResult.Y);
|
|
}
|
|
|
|
return DesiredSizeResult;
|
|
}
|
|
|
|
|
|
FChildren* SDockingTabWell::GetChildren()
|
|
{
|
|
return &Tabs;
|
|
}
|
|
|
|
|
|
FVector2D SDockingTabWell::ComputeChildSize( const FGeometry& AllottedGeometry ) const
|
|
{
|
|
const int32 NumChildren = Tabs.Num();
|
|
|
|
/** Assume all tabs overlap the same amount. */
|
|
const float OverlapWidth = (NumChildren > 0)
|
|
? Tabs[0]->GetOverlapWidth()
|
|
: 0.0f;
|
|
|
|
// All children shall be the same size: evenly divide the alloted area.
|
|
// If we are dragging a tab, don't forget to take it into account when dividing.
|
|
const FVector2D ChildSize = TabBeingDraggedPtr.IsValid()
|
|
? FVector2D( (AllottedGeometry.GetLocalSize().X - OverlapWidth) / ( NumChildren + 1 ) + OverlapWidth, AllottedGeometry.GetLocalSize().Y )
|
|
: FVector2D( (AllottedGeometry.GetLocalSize().X - OverlapWidth) / NumChildren + OverlapWidth, AllottedGeometry.GetLocalSize().Y );
|
|
|
|
// Major vs. Minor tabs have different tab sizes.
|
|
// We will make our choice based on the first tab we encounter.
|
|
TSharedPtr<SDockTab> FirstTab = (NumChildren > 0)
|
|
? Tabs[0]
|
|
: TabBeingDraggedPtr;
|
|
|
|
// If there are no tabs in this tabwell, assume minor tabs.
|
|
FVector2D MaxTabSize(0,0);
|
|
if ( FirstTab.IsValid() )
|
|
{
|
|
const ETabRole RoleToUse = FirstTab->GetVisualTabRole();
|
|
MaxTabSize = FDockingConstants::GetMaxTabSizeFor(RoleToUse);
|
|
}
|
|
else
|
|
{
|
|
MaxTabSize = FDockingConstants::MaxMinorTabSize;
|
|
}
|
|
|
|
// Don't let the tabs get too big, or they'll look ugly.
|
|
return FVector2D (
|
|
FMath::Min( ChildSize.X, MaxTabSize.X ),
|
|
FMath::Min( ChildSize.Y, MaxTabSize.Y )
|
|
);
|
|
}
|
|
|
|
|
|
float SDockingTabWell::ComputeDraggedTabOffset( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, const FVector2D& InTabGrabOffsetFraction ) const
|
|
{
|
|
const FVector2D ComputedChildSize = ComputeChildSize(MyGeometry);
|
|
return MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ).X - InTabGrabOffsetFraction .X * ComputedChildSize.X;
|
|
}
|
|
|
|
|
|
int32 SDockingTabWell::ComputeChildDropIndex(const FGeometry& MyGeometry, const TSharedRef<SDockTab>& TabBeingDragged)
|
|
{
|
|
const float ChildWidth = ComputeChildSize(MyGeometry).X;
|
|
const float ChildWidthWithOverlap = ChildWidth - TabBeingDragged->GetOverlapWidth();
|
|
const float DraggedChildCenter = ChildBeingDraggedOffset + ChildWidth / 2;
|
|
const int32 DropLocationIndex = FMath::Clamp(static_cast<int32>(DraggedChildCenter / ChildWidthWithOverlap), 0, Tabs.Num());
|
|
return DropLocationIndex;
|
|
}
|
|
|
|
|
|
FReply SDockingTabWell::StartDraggingTab( TSharedRef<SDockTab> TabToStartDragging, FVector2D InTabGrabOffsetFraction, const FPointerEvent& MouseEvent )
|
|
{
|
|
TSharedPtr<FTabManager> TabManager = TabToStartDragging->GetTabManagerPtr();
|
|
if (!TabManager.IsValid())
|
|
{
|
|
return FReply::Handled();
|
|
}
|
|
|
|
const bool bCanLeaveTabWell = TabManager->GetPrivateApi().CanTabLeaveTabWell( TabToStartDragging );
|
|
|
|
// We are about to start dragging a tab, so make sure its offset is correct
|
|
this->ChildBeingDraggedOffset = ComputeDraggedTabOffset( MouseEvent.FindGeometry(SharedThis(this)), MouseEvent, InTabGrabOffsetFraction );
|
|
|
|
// Tha tab well keeps track of which tab we are dragging; we treat is specially during rendering and layout.
|
|
TabBeingDraggedPtr = TabToStartDragging;
|
|
TabGrabOffsetFraction = InTabGrabOffsetFraction;
|
|
Tabs.Remove(TabToStartDragging);
|
|
|
|
|
|
if (bCanLeaveTabWell)
|
|
{
|
|
// We just removed the foreground tab.
|
|
ForegroundTabIndex = INDEX_NONE;
|
|
ParentTabStackPtr.Pin()->OnTabRemoved(TabToStartDragging->GetLayoutIdentifier());
|
|
|
|
#if PLATFORM_MAC
|
|
// On Mac we need to activate the app as we may be dragging a window that is set to be invisible if the app is inactive
|
|
FPlatformApplicationMisc::ActivateApplication();
|
|
#endif
|
|
|
|
// Start dragging.
|
|
TSharedRef<FDockingDragOperation> DragDropOperation =
|
|
FDockingDragOperation::New(
|
|
TabToStartDragging,
|
|
InTabGrabOffsetFraction,
|
|
GetDockArea().ToSharedRef(),
|
|
ParentTabStackPtr.Pin()->GetTabStackGeometry().GetLocalSize()
|
|
);
|
|
|
|
return FReply::Handled().BeginDragDrop( DragDropOperation );
|
|
}
|
|
else
|
|
{
|
|
return FReply::Handled().CaptureMouse(SharedThis(this));
|
|
}
|
|
}
|
|
|
|
void SDockingTabWell::OnDragEnter( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
|
|
{
|
|
TSharedPtr<FDockingDragOperation> DragDropOperation = DragDropEvent.GetOperationAs<FDockingDragOperation>();
|
|
if ( DragDropOperation.IsValid() )
|
|
{
|
|
if (DragDropOperation->CanDockInNode(ParentTabStackPtr.Pin().ToSharedRef(), FDockingDragOperation::DockingViaTabWell))
|
|
{
|
|
// The user dragged a tab into this TabWell.
|
|
|
|
// Update the state of the DragDropOperation to reflect this change.
|
|
DragDropOperation->OnTabWellEntered( SharedThis(this) );
|
|
|
|
// Preview the position of the tab in the TabWell
|
|
this->TabBeingDraggedPtr = DragDropOperation->GetTabBeingDragged();
|
|
|
|
// Add the tab widget to the well when the tab is dragged in
|
|
Tabs.Add(TabBeingDraggedPtr.ToSharedRef());
|
|
|
|
this->TabGrabOffsetFraction = DragDropOperation->GetTabGrabOffsetFraction();
|
|
|
|
// The user should see the contents of the tab that we're dragging.
|
|
ParentTabStackPtr.Pin()->SetNodeContent(DragDropOperation->GetTabBeingDragged()->GetContent(), FDockingStackOptionalContent());
|
|
}
|
|
}
|
|
}
|
|
|
|
void SDockingTabWell::OnDragLeave( const FDragDropEvent& DragDropEvent )
|
|
{
|
|
TSharedPtr<FDockingDragOperation> DragDropOperation = DragDropEvent.GetOperationAs<FDockingDragOperation>();
|
|
if ( DragDropOperation.IsValid() )
|
|
{
|
|
TSharedRef<SDockingTabStack> ParentTabStack = ParentTabStackPtr.Pin().ToSharedRef();
|
|
TSharedPtr<SDockTab> TabBeingDragged = this->TabBeingDraggedPtr;
|
|
// Check for TabBeingDraggedPtr validity as it may no longer be valid when dragging tabs in game
|
|
if ( TabBeingDragged.IsValid() && DragDropOperation->CanDockInNode(ParentTabStack, FDockingDragOperation::DockingViaTabWell) )
|
|
{
|
|
// Update the DragAndDrop operation based on this change.
|
|
const int32 LastForegroundTabIndex = Tabs.Find(TabBeingDragged.ToSharedRef());
|
|
|
|
// Remove the tab from the well when it is dragged out
|
|
Tabs.Remove(TabBeingDraggedPtr.ToSharedRef());
|
|
|
|
// The user is pulling a tab out of this TabWell.
|
|
TabBeingDragged->SetParent();
|
|
|
|
// We are no longer dragging a tab in this tab well, so stop
|
|
// showing it in the TabWell.
|
|
this->TabBeingDraggedPtr.Reset();
|
|
|
|
// We may have removed the last tab that this DockNode had.
|
|
if ( Tabs.Num() == 0 )
|
|
{
|
|
// Let the DockNode know that it is no longer needed.
|
|
ParentTabStack->OnLastTabRemoved();
|
|
}
|
|
else
|
|
{
|
|
// Also stop showing its content; switch to the last tab that was active.
|
|
BringTabToFront(FMath::Max(LastForegroundTabIndex - 1, 0));
|
|
}
|
|
|
|
GetDockArea()->CleanUp( SDockingNode::TabRemoval_DraggedOut );
|
|
|
|
const FGeometry& DockNodeGeometry = ParentTabStack->GetTabStackGeometry();
|
|
DragDropOperation->OnTabWellLeft( SharedThis(this), DockNodeGeometry );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
FReply SDockingTabWell::OnDragOver( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
|
|
{
|
|
TSharedPtr<FDockingDragOperation> DragDropOperation = DragDropEvent.GetOperationAs<FDockingDragOperation>();
|
|
if ( DragDropOperation.IsValid() )
|
|
{
|
|
if (DragDropOperation->CanDockInNode(ParentTabStackPtr.Pin().ToSharedRef(), FDockingDragOperation::DockingViaTabWell))
|
|
{
|
|
// We are dragging the tab through a TabWell.
|
|
// Update the position of the Tab that we are dragging in the panel.
|
|
this->ChildBeingDraggedOffset = ComputeDraggedTabOffset(MyGeometry, DragDropEvent, TabGrabOffsetFraction);
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FReply SDockingTabWell::OnDrop( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
|
|
{
|
|
TSharedPtr<FDockingDragOperation> DragDropOperation = DragDropEvent.GetOperationAs<FDockingDragOperation>();
|
|
if (DragDropOperation.IsValid())
|
|
{
|
|
if (DragDropOperation->CanDockInNode(ParentTabStackPtr.Pin().ToSharedRef(), FDockingDragOperation::DockingViaTabWell))
|
|
{
|
|
// It's rare, but sometimes a drop operation can happen after we drag a tab out of a docking tab well but before the engine has a
|
|
// chance to notify the next docking tab well that a drag operation has entered it. In this case, just use the tab referenced by the
|
|
// drag/drop operation
|
|
if (!TabBeingDraggedPtr.IsValid())
|
|
{
|
|
TabBeingDraggedPtr = DragDropOperation->GetTabBeingDragged();
|
|
}
|
|
|
|
if ( ensure( TabBeingDraggedPtr.IsValid() ) )
|
|
{
|
|
// We dropped a Tab into this TabWell.
|
|
const TSharedRef<SDockTab> TabBeingDragged = TabBeingDraggedPtr.ToSharedRef();
|
|
|
|
// Figure out where in this TabWell to drop the Tab.
|
|
const int32 DropLocationIndex = ComputeChildDropIndex(MyGeometry, TabBeingDragged);
|
|
|
|
ensure( DragDropOperation->GetTabBeingDragged().ToSharedRef() == TabBeingDragged );
|
|
|
|
// Remove the tab when dropped. If it was being dragged in this it will be added again in a more permanent way by OpenTab
|
|
Tabs.Remove(TabBeingDraggedPtr.ToSharedRef());
|
|
|
|
// Actually insert the new tab.
|
|
ParentTabStackPtr.Pin()->OpenTab(TabBeingDragged, DropLocationIndex);
|
|
|
|
// We are no longer dragging a tab.
|
|
TabBeingDraggedPtr.Reset();
|
|
|
|
// We knew how to handled this drop operation!
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Someone just dropped something in here, but we have no idea what to do with it.
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
EWindowZone::Type SDockingTabWell::GetWindowZoneOverride() const
|
|
{
|
|
// Pretend we are a title bar so the user can grab the area to move the window around
|
|
return EWindowZone::TitleBar;
|
|
}
|
|
|
|
FReply SDockingTabWell::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
if (this->HasMouseCapture() && TabBeingDraggedPtr.IsValid())
|
|
{
|
|
const TSharedRef<SDockTab> TabBeingDragged = TabBeingDraggedPtr.ToSharedRef();
|
|
this->TabBeingDraggedPtr.Reset();
|
|
const int32 DropLocationIndex = ComputeChildDropIndex(MyGeometry, TabBeingDragged);
|
|
|
|
// Reorder the tab
|
|
Tabs.Remove(TabBeingDragged);
|
|
Tabs.Insert(TabBeingDragged, DropLocationIndex);
|
|
|
|
BringTabToFront(TabBeingDragged);
|
|
// We are no longer dragging a tab in this tab well, so stop showing it in the TabWell.
|
|
return FReply::Handled().ReleaseMouseCapture();
|
|
}
|
|
else
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
}
|
|
|
|
FReply SDockingTabWell::OnMouseMove( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
if (this->HasMouseCapture())
|
|
{
|
|
// We are dragging the tab through a TabWell.
|
|
// Update the position of the Tab that we are dragging in the panel.
|
|
this->ChildBeingDraggedOffset = ComputeDraggedTabOffset(MyGeometry, MouseEvent, TabGrabOffsetFraction);
|
|
return FReply::Handled();
|
|
}
|
|
else
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
}
|
|
|
|
|
|
void SDockingTabWell::BringTabToFront( int32 TabIndexToActivate )
|
|
{
|
|
const bool bActiveIndexChanging = TabIndexToActivate != ForegroundTabIndex;
|
|
|
|
if ( bActiveIndexChanging )
|
|
{
|
|
const int32 LastForegroundTabIndex = FMath::Min(ForegroundTabIndex, Tabs.Num()-1);
|
|
|
|
// For positive indexes, don't go out of bounds on the array.
|
|
ForegroundTabIndex = FMath::Min(TabIndexToActivate, Tabs.Num()-1);
|
|
|
|
TSharedPtr<SDockingArea> MyDockArea = GetDockArea();
|
|
if ( Tabs.Num() > 0 && MyDockArea.IsValid() )
|
|
{
|
|
const TSharedPtr<SDockTab> PreviousForegroundTab = (LastForegroundTabIndex == INDEX_NONE)
|
|
? TSharedPtr<SDockTab>()
|
|
: Tabs[LastForegroundTabIndex];
|
|
|
|
const TSharedPtr<SDockTab> NewForegroundTab = (ForegroundTabIndex == INDEX_NONE)
|
|
? TSharedPtr<SDockTab>()
|
|
: Tabs[ForegroundTabIndex];
|
|
|
|
MyDockArea->GetTabManager()->GetPrivateApi().OnTabForegrounded(NewForegroundTab, PreviousForegroundTab);
|
|
|
|
FGlobalTabmanager::Get()->GetPrivateApi().OnTabForegrounded(NewForegroundTab, PreviousForegroundTab);
|
|
}
|
|
}
|
|
|
|
// Always force a refresh, even if we don't think the active index changed.
|
|
RefreshParentContent();
|
|
|
|
// Update the native, global menu bar if a tab is in the foreground.
|
|
if( ForegroundTabIndex != INDEX_NONE )
|
|
{
|
|
const TSharedRef<SDockTab>& ForegroundTab = Tabs[ForegroundTabIndex];
|
|
TSharedPtr<FTabManager> TabManager = ForegroundTab->GetTabManagerPtr();
|
|
if (TabManager.IsValid())
|
|
{
|
|
if (TabManager == FGlobalTabmanager::Get())
|
|
{
|
|
FGlobalTabmanager::Get()->UpdateMainMenu(ForegroundTab, false);
|
|
}
|
|
else
|
|
{
|
|
TabManager->UpdateMainMenu(ForegroundTab, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Activate the tab specified by TabToActivate SDockTab. */
|
|
void SDockingTabWell::BringTabToFront( TSharedPtr<SDockTab> TabToActivate )
|
|
{
|
|
if (Tabs.Num() > 0)
|
|
{
|
|
for (int32 TabIndex=0; TabIndex < Tabs.Num(); ++TabIndex )
|
|
{
|
|
if (Tabs[TabIndex] == TabToActivate)
|
|
{
|
|
BringTabToFront( TabIndex );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Gets the currently active tab (or the currently dragged tab), or a null pointer if no tab is active. */
|
|
TSharedPtr<SDockTab> SDockingTabWell::GetForegroundTab() const
|
|
{
|
|
if (TabBeingDraggedPtr.IsValid())
|
|
{
|
|
return TabBeingDraggedPtr;
|
|
}
|
|
return (Tabs.Num() > 0 && ForegroundTabIndex > INDEX_NONE) ? Tabs[ForegroundTabIndex] : TSharedPtr<SDockTab>();
|
|
}
|
|
|
|
/** Gets the index of the currently active tab, or INDEX_NONE if no tab is active or a tab is being dragged. */
|
|
int32 SDockingTabWell::GetForegroundTabIndex() const
|
|
{
|
|
return (Tabs.Num() > 0) ? ForegroundTabIndex : INDEX_NONE;
|
|
}
|
|
|
|
void SDockingTabWell::RemoveAndDestroyTab(const TSharedRef<SDockTab>& TabToRemove, SDockingNode::ELayoutModification RemovalMethod)
|
|
{
|
|
int32 TabIndex = Tabs.Find(TabToRemove);
|
|
|
|
if (TabIndex != INDEX_NONE)
|
|
{
|
|
const TSharedPtr<SDockingTabStack> ParentTabStack = ParentTabStackPtr.Pin();
|
|
|
|
// Remove the old tab from the list of tabs and activate the new tab.
|
|
{
|
|
int32 OldTabIndex = ForegroundTabIndex;
|
|
|
|
BringTabToFront(TabIndex);
|
|
Tabs.RemoveAt(TabIndex);
|
|
// We no longer have a tab in the foreground.
|
|
// This is important because BringTabToFront triggers notifications based on the difference in active tab indexes.
|
|
ForegroundTabIndex = INDEX_NONE;
|
|
|
|
// Now bring the last tab that we were on to the foreground
|
|
if (OldTabIndex == INDEX_NONE || TabIndex <= OldTabIndex)
|
|
{
|
|
BringTabToFront(FMath::Max(OldTabIndex - 1, 0));
|
|
}
|
|
else
|
|
{
|
|
BringTabToFront(OldTabIndex);
|
|
}
|
|
|
|
if (RemovalMethod == SDockingNode::ELayoutModification::TabRemoval_Sidebar && ForegroundTabIndex == INDEX_NONE)
|
|
{
|
|
FGlobalTabmanager::Get()->SetActiveTab(nullptr);
|
|
}
|
|
}
|
|
|
|
if ( ensure(ParentTabStack.IsValid()) )
|
|
{
|
|
TSharedPtr<SDockingArea> DockAreaPtr = ParentTabStack->GetDockArea();
|
|
|
|
ParentTabStack->OnTabClosed( TabToRemove, RemovalMethod );
|
|
|
|
// We might be closing down an entire dock area, if this is a major tab.
|
|
// Use this opportunity to save its layout
|
|
if (RemovalMethod == SDockingNode::TabRemoval_Closed)
|
|
{
|
|
if (DockAreaPtr.IsValid())
|
|
{
|
|
DockAreaPtr->GetTabManager()->GetPrivateApi().OnTabClosing( TabToRemove );
|
|
}
|
|
}
|
|
|
|
if (Tabs.Num() == 0)
|
|
{
|
|
ParentTabStack->OnLastTabRemoved();
|
|
}
|
|
else
|
|
{
|
|
RefreshParentContent();
|
|
}
|
|
|
|
if (DockAreaPtr.IsValid())
|
|
{
|
|
DockAreaPtr->CleanUp( RemovalMethod );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SDockingTabWell::RefreshParentContent()
|
|
{
|
|
if (Tabs.Num() > 0 && ForegroundTabIndex != INDEX_NONE)
|
|
{
|
|
const TSharedRef<SDockTab>& ForegroundTab = Tabs[ForegroundTabIndex];
|
|
FGlobalTabmanager::Get()->SetActiveTab( ForegroundTab );
|
|
|
|
TSharedPtr<SWindow> ParentWindowPtr = ForegroundTab->GetParentWindow();
|
|
if (ParentWindowPtr.IsValid() && ParentWindowPtr != FGlobalTabmanager::Get()->GetRootWindow())
|
|
{
|
|
ParentWindowPtr->SetTitle( ForegroundTab->GetTabLabel() );
|
|
}
|
|
|
|
TSharedPtr<SDockingTabStack> ParentTabStack = ParentTabStackPtr.Pin();
|
|
|
|
FDockingStackOptionalContent OptionalContent;
|
|
OptionalContent.ContentLeft = ForegroundTab->GetLeftContent();
|
|
OptionalContent.ContentRight = ForegroundTab->GetRightContent();
|
|
OptionalContent.TitleBarContentRight = ForegroundTab->GetTitleBarRightContent();
|
|
|
|
ParentTabStack->SetNodeContent(ForegroundTab->GetContent(), OptionalContent);
|
|
}
|
|
else
|
|
{
|
|
ParentTabStackPtr.Pin()->SetNodeContent(SNullWidget::NullWidget, FDockingStackOptionalContent());
|
|
}
|
|
}
|
|
|
|
TSharedPtr<SDockingArea> SDockingTabWell::GetDockArea()
|
|
{
|
|
return ParentTabStackPtr.IsValid() ? ParentTabStackPtr.Pin()->GetDockArea() : TSharedPtr<SDockingArea>();
|
|
}
|
|
|
|
|
|
TSharedPtr<SDockingTabStack> SDockingTabWell::GetParentDockTabStack()
|
|
{
|
|
return ParentTabStackPtr.IsValid() ? ParentTabStackPtr.Pin() : TSharedPtr<SDockingTabStack>();
|
|
}
|