Files
UnrealEngine/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SSplitter.cpp
2025-05-18 13:04:45 +08:00

1267 lines
44 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Widgets/Layout/SSplitter.h"
#include "Layout/ArrangedChildren.h"
#include "Rendering/DrawElements.h"
#include "Framework/Application/SlateApplication.h"
void SSplitter::FSlot::Construct(const FChildren& SlotOwner, FSlotArguments&& InArgs)
{
TSlotBase<FSlot>::Construct(SlotOwner, MoveTemp(InArgs));
if (InArgs._Value.IsSet())
{
SizeValue = MoveTemp(InArgs._Value);
}
if (InArgs._Resizable.IsSet())
{
bIsResizable = InArgs._Resizable.GetValue();
}
MinSizeValue = InArgs._MinSize;
if (InArgs._OnSlotResized.IsBound())
{
OnSlotResized_Handler = MoveTemp(InArgs._OnSlotResized);
}
if (InArgs._SizeRule.IsSet())
{
SizingRule = MoveTemp(InArgs._SizeRule);
}
}
bool SSplitter::FSlot::CanBeResized() const
{
const ESizeRule CurrentSizingRule = SizingRule.Get();
if (CurrentSizingRule == SSplitter::ESizeRule::FractionOfParent)
{
return !bIsResizable.IsSet() || bIsResizable.GetValue();
}
else
{
return bIsResizable.IsSet() && bIsResizable.GetValue() && OnSlotResized_Handler.IsBound();
}
}
SSplitter::FSlot::FSlotArguments SSplitter::Slot()
{
return FSlot::FSlotArguments(MakeUnique<FSlot>());
}
SSplitter::FScopedWidgetSlotArguments SSplitter::AddSlot( int32 AtIndex )
{
return FScopedWidgetSlotArguments{ MakeUnique<FSlot>(), Children, AtIndex };
}
SSplitter::FSlot& SSplitter::SlotAt( int32 SlotIndex )
{
return Children[SlotIndex];
}
int32 SSplitter::RemoveSlot(const TSharedRef<SWidget>& SlotWidget)
{
return Children.Remove(SlotWidget);
}
void SSplitter::RemoveAt( int32 IndexToRemove )
{
Children.RemoveAt( IndexToRemove );
}
void SSplitter::ClearChildren()
{
Children.Empty();
}
int32 SSplitter::NumSlots() const
{
return Children.Num();
}
bool SSplitter::IsValidSlotIndex(int32 Index) const
{
return Children.IsValidIndex(Index);
}
/**
* Construct this widget
*
* @param InArgs The declaration data for this widget
*/
void SSplitter::Construct( const SSplitter::FArguments& InArgs )
{
check(InArgs._Style);
OnHandleHovered = InArgs._OnHandleHovered;
OnSplitterFinishedResizing = InArgs._OnSplitterFinishedResizing;
ResizeMode = InArgs._ResizeMode;
PhysicalSplitterHandleSize = InArgs._PhysicalSplitterHandleSize;
HitDetectionSplitterHandleSize = InArgs._HitDetectionSplitterHandleSize;
Orientation = InArgs._Orientation;
HoveredHandleIndex = INDEX_NONE;
HighlightedHandleIndex.Assign(*this, InArgs._HighlightedHandleIndex);
bIsResizing = false;
Style = InArgs._Style;
OnGetMaxSlotSize = InArgs._OnGetMaxSlotSize;
MinSplitterChildLength = InArgs._MinimumSlotHeight;
Children.AddSlots(MoveTemp(const_cast<TArray<FSlot::FSlotArguments>&>(InArgs._Slots)));
}
TArray<FLayoutGeometry> SSplitter::ArrangeChildrenForLayout(const FGeometry& AllottedGeometry) const
{
TArray<FLayoutGeometry> ResultGeometries;
ResultGeometries.Empty(Children.Num());
const int32 AxisIndex = (Orientation == Orient_Horizontal) ? 0 : 1;
// Splitters divide the space between their children proportionately based on size coefficients.
// The size coefficients are usually determined by a user, who grabs the handled between the child elements
// and moves them to resize the space available to the children.
// Some children are sized automatically based on their content; those children cannot be resized.
//
// e.g. _____________________________________ Children
// / / /
// v v v
// + - - - - - + + - - - + + - - - - - - - - - - - - - - +
// | | | | | |
// | Child 0 | |Child1 | | Child2 |
// + - - - - - + + - - - + + - - - - - - - - - - - - - - +
// ^ ^
// \_________\___________ Resize handles.
int32 NumNonCollapsedChildren = 0;
int32 NumResizeableChildren = 0;
float CoefficientTotal = 0;
// Some space is claimed by non-resizable elements (auto-sized elements)
float NonResizableSpace = 0;
float MinResizableSpace = 0;
auto IsSlotCollapsed = [](const FSlot& Slot)
{
return Slot.GetWidget()->GetVisibility() == EVisibility::Collapsed;
};
for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex)
{
const FSlot& CurSlot = Children[ChildIndex];
if (IsSlotCollapsed(CurSlot))
{
continue;
}
++NumNonCollapsedChildren;
if (CurSlot.GetSizingRule() == SSplitter::SizeToContent)
{
NonResizableSpace += CurSlot.GetWidget()->GetDesiredSize()[AxisIndex];
}
else // SizingRule == SSplitter::FractionOfParent
{
MinResizableSpace += FMath::Max(MinSplitterChildLength, CurSlot.GetMinSize());
CoefficientTotal += CurSlot.GetSizeValue();
}
}
// The user-sizable children must make room for the resize handles and for auto-sized children.
const float SpaceNeededForHandles = FMath::Max(0, NumNonCollapsedChildren - 1) * PhysicalSplitterHandleSize;
const float ResizableSpace = AllottedGeometry.Size.Component(AxisIndex) - SpaceNeededForHandles - NonResizableSpace;
TArray<float, FConcurrentLinearArrayAllocator> SlotSizes;
SlotSizes.Empty(Children.Num());
float ExtraRequiredSpace = 0;
// calculate slot sizes
for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex)
{
const FSlot& CurSlot = Children[ChildIndex];
// slot is collapsed, size is automatically 0
if (IsSlotCollapsed(CurSlot))
{
SlotSizes.Add(0);
continue;
}
float ChildSpace;
if (CurSlot.GetSizingRule() == SSplitter::SizeToContent)
{
ChildSpace = CurSlot.GetWidget()->GetDesiredSize()[AxisIndex];
}
else
{
ChildSpace = ResizableSpace * CurSlot.GetSizeValue() / CoefficientTotal - ExtraRequiredSpace;
ExtraRequiredSpace = 0;
}
// we have enough space to clamp this and steal space from earlier slots
if (ResizableSpace >= MinResizableSpace)
{
const float ClampedSpace = ClampChild(CurSlot, ChildSpace);
float CurrentRequiredSpace = ClampedSpace - ChildSpace;
for (int32 PrevIndex = ChildIndex - 1; PrevIndex >= 0 && CurrentRequiredSpace > 0; --PrevIndex)
{
if (Children[PrevIndex].GetSizingRule() == SSplitter::FractionOfParent && !IsSlotCollapsed(CurSlot))
{
const float MinChildSize = FMath::Max(MinSplitterChildLength, Children[PrevIndex].GetMinSize());
const float AvailableSpace = SlotSizes[PrevIndex] - MinChildSize;
if (AvailableSpace > 0)
{
const float SpaceToSteal = FMath::Min(AvailableSpace, CurrentRequiredSpace);
SlotSizes[PrevIndex] -= SpaceToSteal;
CurrentRequiredSpace -= SpaceToSteal;
}
}
}
if (CurrentRequiredSpace > 0)
{
// we weren't able to steal enough space from previous children, store this value to try and take it from following children
ExtraRequiredSpace = CurrentRequiredSpace;
}
ChildSpace = ClampedSpace;
}
SlotSizes.Add(ChildSpace);
}
// create geometries and calculate offsets
float Offset = 0;
for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex)
{
const FVector2D ChildOffset = Orientation == Orient_Horizontal ? FVector2D(Offset, 0) : FVector2D(0, Offset);
const FVector2D ChildSize = Orientation == Orient_Horizontal ? FVector2D(SlotSizes[ChildIndex], AllottedGeometry.Size.Y) : FVector2D(AllottedGeometry.Size.X, SlotSizes[ChildIndex]);
ResultGeometries.Emplace(FSlateLayoutTransform(ChildOffset), ChildSize);
// Advance to the next slot. If the child is collapsed, it takes up no room and does not need a splitter
if (!IsSlotCollapsed(Children[ChildIndex]))
{
Offset += FMath::RoundToInt(SlotSizes[ChildIndex] + PhysicalSplitterHandleSize);
}
}
return ResultGeometries;
}
/**
* Panels arrange their children in a space described by the AllottedGeometry parameter. The results of the arrangement
* should be returned by appending a FArrangedWidget pair for every child widget. See StackPanel for an example
*/
void SSplitter::OnArrangeChildren( const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren ) const
{
TArray<FLayoutGeometry> LayoutChildren = ArrangeChildrenForLayout(AllottedGeometry);
// Arrange the children horizontally or vertically.
for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex)
{
ArrangedChildren.AddWidget( AllottedGeometry.MakeChild( Children[ChildIndex].GetWidget(), LayoutChildren[ChildIndex] ) );
}
}
int32 SSplitter::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
{
FArrangedChildren ArrangedChildren( EVisibility::Visible );
ArrangeChildren( AllottedGeometry, ArrangedChildren );
int32 MaxLayerId = PaintArrangedChildren( Args, ArrangedChildren, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled );
const FSlateBrush* NormalHandleBrush = &Style->HandleNormalBrush;
// Draw the splitter above any children
MaxLayerId += 1;
// HandleIndex is an index into our Children array, but some of those may be hidden and missing from ArrangedChildren so we need to update the index for the loop below
auto FindArrangedHandleIndex = [this, &ArrangedChildren](int32 HandleIndex)
{
if (HandleIndex == INDEX_NONE || ArrangedChildren.Num() == Children.Num())
{
return HandleIndex;
}
const TSharedRef<SWidget>& HoveredChildToFind = Children[HandleIndex].GetWidget();
return ArrangedChildren.IndexOfByPredicate([&HoveredChildToFind](const FArrangedWidget& InArrangedWidget)
{
return InArrangedWidget.Widget == HoveredChildToFind;
});
};
int32 ArrangedHoveredHandleIndex = FindArrangedHandleIndex(HoveredHandleIndex);
int32 ArrangedHighlightedHandleIndex = FindArrangedHandleIndex(HighlightedHandleIndex.Get());
for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num() - 1; ++ChildIndex)
{
const FGeometry& GeometryAfterSplitter = ArrangedChildren[ FMath::Clamp(ChildIndex + 1, 0, ArrangedChildren.Num()-1) ].Geometry;
const float HalfHitDetectionSplitterHandleSize = FMath::RoundToFloat(HitDetectionSplitterHandleSize / 2.0f);
const float HalfPhysicalSplitterHandleSize = PhysicalSplitterHandleSize;// FMath::RoundToInt(PhysicalSplitterHandleSize / 2.0f);
FVector2f HandleSize;
FVector2f HandlePosition;
if (Orientation == Orient_Horizontal)
{
HandleSize.Set( PhysicalSplitterHandleSize, GeometryAfterSplitter.Size.Y );
HandlePosition.Set( -PhysicalSplitterHandleSize/*-(HalfHitDetectionSplitterHandleSize + HalfPhysicalSplitterHandleSize)*/, 0 );
}
else
{
HandleSize.Set( GeometryAfterSplitter.Size.X, PhysicalSplitterHandleSize );
HandlePosition.Set( 0, -PhysicalSplitterHandleSize/*-(HalfHitDetectionSplitterHandleSize + HalfPhysicalSplitterHandleSize) */);
}
if (ArrangedHoveredHandleIndex == ChildIndex || ArrangedHighlightedHandleIndex == ChildIndex)
{
// handle is hovered
FSlateDrawElement::MakeBox(
OutDrawElements,
MaxLayerId,
GeometryAfterSplitter.ToPaintGeometry(HandleSize, FSlateLayoutTransform(1.0f, TransformPoint(1.0f, HandlePosition))),
NormalHandleBrush,
ShouldBeEnabled(bParentEnabled) ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect,
InWidgetStyle.GetColorAndOpacityTint() * Style->HandleHighlightBrush.TintColor.GetSpecifiedColor()
);
}
else
{
FSlateDrawElement::MakeBox(
OutDrawElements,
MaxLayerId,
GeometryAfterSplitter.ToPaintGeometry(HandleSize, FSlateLayoutTransform(1.0f, TransformPoint(1.0f, HandlePosition))),
&Style->HandleHighlightBrush,
ShouldBeEnabled(bParentEnabled) ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect,
InWidgetStyle.GetColorAndOpacityTint() * NormalHandleBrush->TintColor.GetSpecifiedColor()
);
}
}
return MaxLayerId;
}
template<EOrientation Orientation>
static FVector2D ComputeDesiredSizeForSplitter( const float PhysicalSplitterHandleSize, const TPanelChildren<SSplitter::FSlot>& Children )
{
FVector2D MyDesiredSize(0, 0);
int32 NumNonCollapsed = 0;
for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex)
{
const SSplitter::FSlot& CurSlot = Children[ChildIndex];
const EVisibility ChildVisibility = CurSlot.GetWidget()->GetVisibility();
if ( ChildVisibility != EVisibility::Collapsed )
{
++NumNonCollapsed;
FVector2D ChildDesiredSize( CurSlot.GetWidget()->GetDesiredSize() );
if ( Orientation == Orient_Horizontal )
{
MyDesiredSize.X += ChildDesiredSize.X;
MyDesiredSize.Y = FMath::Max(ChildDesiredSize.Y, MyDesiredSize.Y);
}
else
{
MyDesiredSize.X = FMath::Max(ChildDesiredSize.X, MyDesiredSize.X);
MyDesiredSize.Y += ChildDesiredSize.Y;
}
}
}
float SpaceNeededForHandles = FMath::Max(0, NumNonCollapsed-1) * PhysicalSplitterHandleSize;
if (Orientation == Orient_Horizontal)
{
MyDesiredSize.X += SpaceNeededForHandles;
}
else
{
MyDesiredSize.Y += SpaceNeededForHandles;
}
return MyDesiredSize;
}
/**
* A Panel's desired size in the space required to arrange of its children on the screen while respecting all of
* the children's desired sizes and any layout-related options specified by the user. See StackPanel for an example.
*/
FVector2D SSplitter::ComputeDesiredSize( float ) const
{
FVector2D MyDesiredSize = (Orientation == Orient_Horizontal)
? ComputeDesiredSizeForSplitter<Orient_Horizontal>( PhysicalSplitterHandleSize, Children )
: ComputeDesiredSizeForSplitter<Orient_Vertical>( PhysicalSplitterHandleSize, Children );
return MyDesiredSize;
}
/**
* All widgets must provide a way to access their children in a layout-agnostic way.
* Panels store their children in Slots, which creates a dilemma. Most panels
* can store their children in a TPanelChildren<Slot>, where the Slot class
* provides layout information about the child it stores. In that case
* GetChildren should simply return the TPanelChildren<Slot>. See StackPanel for an example.
*/
FChildren* SSplitter::GetChildren()
{
return &Children;
}
/**
* The system calls this method to notify the widget that a mouse button was pressed within it. This event is bubbled.
*
* @param MyGeometry The Geometry of the widget receiving the event
* @param MouseEvent Information about the input event
*
* @return Whether the event was handled along with possible requests for the system to take action.
*/
FReply SSplitter::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
if ( MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && HoveredHandleIndex != INDEX_NONE )
{
bIsResizing = true;
return FReply::Handled().CaptureMouse( SharedThis(this) );
}
else
{
return FReply::Unhandled();
}
}
/**
* The system calls this method to notify the widget that a mouse button was release within it. This event is bubbled.
*
* @param MyGeometry The Geometry of the widget receiving the event
* @param MouseEvent Information about the input event
*
* @return Whether the event was handled along with possible requests for the system to take action.
*/
FReply SSplitter::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
if ( MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && bIsResizing == true )
{
OnSplitterFinishedResizing.ExecuteIfBound();
bIsResizing = false;
return FReply::Handled().ReleaseMouseCapture();
}
return FReply::Unhandled();
}
/**
* The system calls this method to notify the widget that a mouse moved within it. This event is bubbled.
*
* @param MyGeometry The Geometry of the widget receiving the event
* @param MouseEvent Information about the input event
*
* @return Whether the event was handled along with possible requests for the system to take action.
*/
FReply SSplitter::OnMouseMove( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
const FVector2D LocalMousePosition = MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() );
TArray<FLayoutGeometry> LayoutChildren = ArrangeChildrenForLayout(MyGeometry);
if (bIsResizing)
{
if (!MouseEvent.GetCursorDelta().IsZero())
{
HandleResizingByMousePosition(Orientation, PhysicalSplitterHandleSize, ResizeMode, HoveredHandleIndex, LocalMousePosition, Children, LayoutChildren );
}
return FReply::Handled();
}
else
{
// Hit test which handle we are hovering over.
int32 PreviousHoveredHandleIndex = HoveredHandleIndex;
HoveredHandleIndex = (Orientation == Orient_Horizontal)
? GetHandleBeingResizedFromMousePosition<Orient_Horizontal>( PhysicalSplitterHandleSize, HitDetectionSplitterHandleSize, LocalMousePosition, LayoutChildren )
: GetHandleBeingResizedFromMousePosition<Orient_Vertical>( PhysicalSplitterHandleSize, HitDetectionSplitterHandleSize, LocalMousePosition, LayoutChildren );
if (HoveredHandleIndex != INDEX_NONE)
{
if (FindResizeableSlotBeforeHandle(HoveredHandleIndex, Children) <= INDEX_NONE || FindResizeableSlotAfterHandle(HoveredHandleIndex, Children) >= Children.Num())
{
HoveredHandleIndex = INDEX_NONE;
}
}
if (HoveredHandleIndex != PreviousHoveredHandleIndex)
{
OnHandleHovered.ExecuteIfBound(HoveredHandleIndex);
}
return FReply::Unhandled();
}
}
FReply SSplitter::OnMouseButtonDoubleClick(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent)
{
if (OnGetMaxSlotSize.IsBound())
{
FVector2D MaxSlotSize = OnGetMaxSlotSize.Execute(HoveredHandleIndex);
if (!MaxSlotSize.IsZero())
{
TArray<FLayoutGeometry> LayoutChildren = ArrangeChildrenForLayout(InMyGeometry);
HandleResizingBySize(Orientation, PhysicalSplitterHandleSize, ResizeMode, HoveredHandleIndex, MaxSlotSize, Children, LayoutChildren);
return FReply::Handled();
}
}
return SWidget::OnMouseButtonDoubleClick(InMyGeometry, InMouseEvent);
}
void SSplitter::OnMouseLeave( const FPointerEvent& MouseEvent )
{
int32 PreviousHoveredHandleIndex = HoveredHandleIndex;
if (!bIsResizing)
{
HoveredHandleIndex = INDEX_NONE;
}
if (HoveredHandleIndex != PreviousHoveredHandleIndex)
{
OnHandleHovered.ExecuteIfBound(HoveredHandleIndex);
}
}
/**
* The system asks each widget under the mouse to provide a cursor. This event is bubbled.
*
* @return FCursorReply::Unhandled() if the event is not handled; return FCursorReply::Cursor() otherwise.
*/
FCursorReply SSplitter::OnCursorQuery( const FGeometry& MyGeometry, const FPointerEvent& CursorEvent ) const
{
if (bIsResizing)
{
return Orientation == Orient_Horizontal
? FCursorReply::Cursor( EMouseCursor::ResizeLeftRight )
: FCursorReply::Cursor( EMouseCursor::ResizeUpDown );
}
const FVector2D LocalMousePosition = MyGeometry.AbsoluteToLocal( CursorEvent.GetScreenSpacePosition() );
TArray<FLayoutGeometry> LayoutChildren = ArrangeChildrenForLayout(MyGeometry);
// Hit test which handle we are hovering over.
const int32 CurrentHoveredHandleIndex = (Orientation == Orient_Horizontal)
? GetHandleBeingResizedFromMousePosition<Orient_Horizontal>( PhysicalSplitterHandleSize, HitDetectionSplitterHandleSize, LocalMousePosition, LayoutChildren )
: GetHandleBeingResizedFromMousePosition<Orient_Vertical>( PhysicalSplitterHandleSize, HitDetectionSplitterHandleSize, LocalMousePosition, LayoutChildren );
if (CurrentHoveredHandleIndex != INDEX_NONE &&
Children[CurrentHoveredHandleIndex].CanBeResized())
{
return Orientation == Orient_Horizontal
? FCursorReply::Cursor( EMouseCursor::ResizeLeftRight )
: FCursorReply::Cursor( EMouseCursor::ResizeUpDown );
}
else
{
return FCursorReply::Unhandled();
}
}
/**
* Change the orientation of the splitter
*
* @param NewOrientation Should the splitter be horizontal or vertical
*/
void SSplitter::SetOrientation( EOrientation NewOrientation )
{
Orientation = NewOrientation;
}
/**
* @return the current orientation of the splitter.
*/
EOrientation SSplitter::GetOrientation() const
{
return Orientation;
}
SSplitter::SSplitter()
: Children(this)
, HoveredHandleIndex(INDEX_NONE)
, HighlightedHandleIndex(*this, INDEX_NONE)
, bIsResizing( false )
, Orientation( Orient_Horizontal )
, Style( nullptr )
{
}
int32 SSplitter::FindResizeableSlotBeforeHandle( int32 DraggedHandle, const TPanelChildren<FSlot>& Children )
{
// Resizing collapsed or autosizing slots does not make sense (their size is predetermined).
// Search out from the DraggedHandle to find the first non-collapsed, non-autosizing slot we can resize.
int32 SlotBeforeDragHandle = DraggedHandle;
while (SlotBeforeDragHandle >= 0
&& (Children[SlotBeforeDragHandle].GetWidget()->GetVisibility() == EVisibility::Collapsed || !Children[SlotBeforeDragHandle].CanBeResized()))
{
--SlotBeforeDragHandle;
}
return SlotBeforeDragHandle;
}
int32 SSplitter::FindResizeableSlotAfterHandle( int32 DraggedHandle, const TPanelChildren<FSlot>& Children )
{
const int32 NumChildren = Children.Num();
// But the slots list does contain collapsed children! Make sure that we are not resizing a collapsed slot.
// We also cannot resize auto-sizing slots.
int32 SlotAfterDragHandle = DraggedHandle+1;
while (SlotAfterDragHandle < NumChildren
&& (Children[SlotAfterDragHandle].GetWidget()->GetVisibility() == EVisibility::Collapsed || !Children[SlotAfterDragHandle].CanBeResized()))
{
++SlotAfterDragHandle;
}
return SlotAfterDragHandle;
}
void SSplitter::FindAllResizeableSlotsAfterHandle( int32 DraggedHandle, const TPanelChildren<FSlot>& Children, TArray< int32, FConcurrentLinearArrayAllocator >& OutSlotIndicies )
{
const int32 NumChildren = Children.Num();
for (int SlotIndex = DraggedHandle+1; SlotIndex < NumChildren; SlotIndex++)
{
if (Children[ SlotIndex ].GetWidget()->GetVisibility() == EVisibility::Collapsed || !Children[ SlotIndex ].CanBeResized())
{
continue;
}
OutSlotIndicies.Add( SlotIndex );
}
};
void SSplitter::HandleResizingDelta(EOrientation SplitterOrientation, const float InPhysicalSplitterHandleSize, const ESplitterResizeMode::Type InResizeMode, int32 DraggedHandle, float Delta, TPanelChildren<FSlot>& InChildren, const TArray<FLayoutGeometry>& ChildGeometries)
{
const int32 NumChildren = InChildren.Num();
const int32 AxisIndex = (SplitterOrientation == Orient_Horizontal) ? 0 : 1;
// Note:
// - Prev vs. Next refers to the widgets in the order they are laid out (left->right, top->bottom).
// - New vs. Old refers to the Old values for width/height vs. the post-resize values.
TArray<int32, FConcurrentLinearArrayAllocator> SlotsAfterDragHandleIndices;
SlotsAfterDragHandleIndices.Reserve(NumChildren);
if (InResizeMode == ESplitterResizeMode::FixedPosition)
{
int32 SlotAfterDragHandle = FindResizeableSlotAfterHandle(DraggedHandle, Children);
if (InChildren.IsValidIndex(SlotAfterDragHandle))
{
SlotsAfterDragHandleIndices.Add(SlotAfterDragHandle);
}
}
else if (InResizeMode == ESplitterResizeMode::Fill || InResizeMode == ESplitterResizeMode::FixedSize)
{
FindAllResizeableSlotsAfterHandle( DraggedHandle, InChildren, /*OUT*/ SlotsAfterDragHandleIndices );
}
const int32 NumSlotsAfterDragHandle = SlotsAfterDragHandleIndices.Num();
if (NumSlotsAfterDragHandle > 0)
{
struct FSlotInfo
{
FSlot* Slot;
const FLayoutGeometry* Geometry;
float NewSize;
};
TArray<FSlotInfo, FConcurrentLinearArrayAllocator> SlotsAfterDragHandle;
SlotsAfterDragHandle.Reserve(NumSlotsAfterDragHandle);
for (int32 SlotIndex = 0; SlotIndex < NumSlotsAfterDragHandle; SlotIndex++)
{
FSlotInfo SlotInfo;
SlotInfo.Slot = &InChildren[ SlotsAfterDragHandleIndices[ SlotIndex ] ];
SlotInfo.Geometry = &ChildGeometries[ SlotsAfterDragHandleIndices[ SlotIndex ] ];
SlotInfo.NewSize = ClampChild(*SlotInfo.Slot, SlotInfo.Geometry->GetSizeInParentSpace().Component( AxisIndex ));
SlotsAfterDragHandle.Add( SlotInfo );
}
// Get references the prev and next children and their layout settings so that we can modify them.
const int32 SlotBeforeDragHandle = FindResizeableSlotBeforeHandle(DraggedHandle, InChildren);
check(InChildren.IsValidIndex(SlotBeforeDragHandle));
FSlot& PrevChild = InChildren[SlotBeforeDragHandle];
const FLayoutGeometry& PrevChildGeom = ChildGeometries[SlotBeforeDragHandle];
// Compute the new sizes of the children
const float PrevChildLength = PrevChildGeom.GetSizeInParentSpace().Component(AxisIndex);
float NewPrevChildLength = ClampChild(PrevChild, PrevChildLength + Delta);
Delta = NewPrevChildLength - PrevChildLength;
// Distribute the Delta across the affected slots after the drag handle
float UnusedDelta = Delta;
for (int32 DistributionCount = 0; DistributionCount < NumSlotsAfterDragHandle && UnusedDelta != 0; DistributionCount++)
{
float DividedDelta = InResizeMode != ESplitterResizeMode::FixedSize ? UnusedDelta / NumSlotsAfterDragHandle : UnusedDelta;
// Reset the unused delta to keep track of any leftover space to distribute later through stretching.
UnusedDelta = 0;
int32 SlotIndex = 0;
// Resize only the last handle in the case of fixed size
if (InResizeMode == ESplitterResizeMode::FixedSize)
{
SlotIndex = NumSlotsAfterDragHandle - 1;
}
for (/*SlotIndex*/; SlotIndex < NumSlotsAfterDragHandle; SlotIndex++)
{
FSlotInfo& SlotInfo = SlotsAfterDragHandle[SlotIndex];
float CurrentSize = SlotInfo.NewSize;
SlotInfo.NewSize = ClampChild(*SlotInfo.Slot, CurrentSize - DividedDelta);
// If one of the slots couldn't be fully adjusted by the delta due to min/max constraints then
// the leftover delta needs to be evenly distributed to all of the other slots
UnusedDelta += SlotInfo.NewSize - (CurrentSize - DividedDelta);
}
}
Delta = Delta - UnusedDelta;
// PrevChildLength needs to be updated: it's value has to take into account the next child's min/max restrictions
NewPrevChildLength = ClampChild(PrevChild, PrevChildLength + Delta);
// Cells being resized are both stretch values -> redistribute the stretch coefficients proportionately
// to match the new child sizes on the screen.
{
float TotalStretchLength = 0.f;
float TotalStretchCoefficients = 0.f;
if (PrevChild.GetSizingRule() == ESizeRule::FractionOfParent)
{
TotalStretchLength = NewPrevChildLength;
TotalStretchCoefficients = PrevChild.GetSizeValue();
}
for (int32 SlotIndex = 0; SlotIndex < NumSlotsAfterDragHandle; SlotIndex++)
{
const FSlotInfo& SlotInfo = SlotsAfterDragHandle[SlotIndex];
if (SlotInfo.Slot->GetSizingRule() == ESizeRule::FractionOfParent)
{
TotalStretchLength += SlotInfo.NewSize;
TotalStretchCoefficients += SlotInfo.Slot->GetSizeValue();
}
}
auto SetSize = [](FSlot& Slot, float TotalStretchCoefficients, float NewLength, float TotalLength)
{
if (Slot.GetSizingRule() == ESizeRule::FractionOfParent)
{
float NewFillSize = TotalLength > 0.f ? (TotalStretchCoefficients * (NewLength / TotalLength)) : TotalStretchCoefficients;
if (Slot.OnSlotResized().IsBound())
{
Slot.OnSlotResized().Execute(NewFillSize);
}
else
{
Slot.SetSizeValue(NewFillSize);
}
}
else
{
ensure(Slot.OnSlotResized().IsBound());
Slot.OnSlotResized().ExecuteIfBound(NewLength);
}
};
SetSize(PrevChild, TotalStretchCoefficients, NewPrevChildLength, TotalStretchLength);
for (int32 SlotIndex = 0; SlotIndex < NumSlotsAfterDragHandle; SlotIndex++)
{
const FSlotInfo& SlotInfo = SlotsAfterDragHandle[ SlotIndex ];
SetSize(*(SlotInfo.Slot), TotalStretchCoefficients, SlotInfo.NewSize, TotalStretchLength);
}
}
}
}
void SSplitter::HandleResizingBySize(EOrientation SplitterOrientation, const float InPhysicalSplitterHandleSize, const ESplitterResizeMode::Type InResizeMode, int32 DraggedHandle, const FVector2D& InDesiredSize, TPanelChildren<FSlot>& InChildren, const TArray<FLayoutGeometry>& ChildGeometries)
{
const int32 AxisIndex = (SplitterOrientation == Orient_Horizontal) ? 0 : 1;
float CurrentSlotSize = ChildGeometries[DraggedHandle].GetSizeInParentSpace().Component(AxisIndex);
float Delta = InDesiredSize.Component(AxisIndex) - CurrentSlotSize;
HandleResizingDelta(SplitterOrientation, InPhysicalSplitterHandleSize, InResizeMode, DraggedHandle, Delta, InChildren, ChildGeometries);
}
void SSplitter::HandleResizingByMousePosition(EOrientation SplitterOrientation, const float InPhysicalSplitterHandleSize, const ESplitterResizeMode::Type InResizeMode, int32 DraggedHandle, const FVector2D& LocalMousePos, TPanelChildren<FSlot>& InChildren, const TArray<FLayoutGeometry>& ChildGeometries )
{
const int32 AxisIndex = (SplitterOrientation == Orient_Horizontal) ? 0 : 1;
const float HandlePos = ChildGeometries[DraggedHandle+1].GetLocalToParentTransform().GetTranslation().Component(AxisIndex) - InPhysicalSplitterHandleSize / 2;
float Delta = LocalMousePos.Component(AxisIndex) - HandlePos;
HandleResizingDelta(SplitterOrientation, InPhysicalSplitterHandleSize, InResizeMode, DraggedHandle, Delta, InChildren, ChildGeometries);
}
/**
* @param ProposedSize A size that a child would like to be
*
* @return A size that is clamped against the minimum size allowed for children.
*/
float SSplitter::ClampChild(const FSlot& ChildSlot, float ProposedSize) const
{
float ClampedSize = FMath::Max(MinSplitterChildLength, ProposedSize);
ClampedSize = FMath::Max(ChildSlot.GetMinSize(), ProposedSize);
return ClampedSize;
}
template<EOrientation SplitterOrientation>
int32 SSplitter::GetHandleBeingResizedFromMousePosition( float InPhysicalSplitterHandleSize, float HitDetectionSplitterHandleSize, FVector2D LocalMousePos, const TArray<FLayoutGeometry>& ChildGeometries )
{
const int32 AxisIndex = (SplitterOrientation == Orient_Horizontal) ? 0 : 1;
const float HalfHitDetectionSplitterHandleSize = ( HitDetectionSplitterHandleSize / 2 );
const float HalfPhysicalSplitterHandleSize = ( InPhysicalSplitterHandleSize / 2 );
// Search for the two widgets between which the cursor currently resides.
for ( int32 ChildIndex = 1; ChildIndex < ChildGeometries.Num(); ++ChildIndex )
{
FSlateRect PrevChildRect = ChildGeometries[ChildIndex - 1].GetRectInParentSpace();
FVector2D NextChildOffset = ChildGeometries[ChildIndex].GetOffsetInParentSpace();
float PrevBound = PrevChildRect.GetTopLeft().Component(AxisIndex) + PrevChildRect.GetSize().Component(AxisIndex) - HalfHitDetectionSplitterHandleSize + HalfPhysicalSplitterHandleSize;
float NextBound = NextChildOffset.Component(AxisIndex) + HalfHitDetectionSplitterHandleSize - HalfPhysicalSplitterHandleSize;
if ( LocalMousePos.Component(AxisIndex) > PrevBound && LocalMousePos.Component(AxisIndex) < NextBound )
{
return ChildIndex - 1;
}
}
return INDEX_NONE;
}
/********************************************************************
* SSplitter2x2 *
* A splitter which has exactly 4 children and allows simultaneous *
* of all children along an axis as well as resizing all children *
* by dragging the center of the splitter. *
********************************************************************/
SSplitter2x2::FSlot::FSlot(const TSharedRef<SWidget>& InWidget)
: TSlotBase<FSlot>(InWidget)
, PercentageAttribute(FVector2D(0.5, 0.5))
{
}
void SSplitter2x2::FSlot::Construct(const FChildren& SlotOwner, FSlotArguments&& InArg)
{
TSlotBase<FSlot>::Construct(SlotOwner, MoveTemp(InArg));
if (InArg._Percentage.IsSet())
{
PercentageAttribute = MoveTemp(InArg._Percentage);
}
}
SSplitter2x2::SSplitter2x2()
: Children(this)
{
}
void SSplitter2x2::Construct( const FArguments& InArgs )
{
Children.AddSlot(FSlot::FSlotArguments(MakeUnique<FSlot>(InArgs._TopLeft.Widget)));
Children.AddSlot(FSlot::FSlotArguments(MakeUnique<FSlot>(InArgs._BottomLeft.Widget)));
Children.AddSlot(FSlot::FSlotArguments(MakeUnique<FSlot>(InArgs._TopRight.Widget)));
Children.AddSlot(FSlot::FSlotArguments(MakeUnique<FSlot>(InArgs._BottomRight.Widget)));
Style = InArgs._Style;
SplitterHandleSize = 5.0f;
MinSplitterChildLength = 20.0f;
bIsResizing = false;
ResizingAxisMask = EResizingAxis::None;
}
TArray<FLayoutGeometry> SSplitter2x2::ArrangeChildrenForLayout( const FGeometry& AllottedGeometry ) const
{
check( Children.Num() == 4 );
TArray<FLayoutGeometry> Result;
Result.Empty(Children.Num());
int32 NumNonCollapsedChildren = 0;
FVector2D CoefficientTotal(0,0);
// The allotted space for our children is our geometry minus a little space to show splitter handles
const FVector2D SpaceAllottedForChildren = AllottedGeometry.GetLocalSize() - FVector2D(SplitterHandleSize, SplitterHandleSize);
// The current offset that the next child should be positioned at.
FVector2D Offset(0,0);
for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex)
{
const FSlot& CurSlot = Children[ChildIndex];
// Calculate the amount of space that this child should take up.
// It is based on the current percentage of space it should take up which is defined by a user moving the splitters
const FVector2D ChildSpace = SpaceAllottedForChildren * CurSlot.GetPercentage();
// put them in their spot
Result.Emplace(FSlateLayoutTransform(Offset), ChildSpace);
// Advance to the next slot. If the child is collapsed, it takes up no room and does not need a splitter
if( ChildIndex == 1 )
{
// ChildIndex of 1 means we are starting the next column so reset the Y offset.
Offset.Y = 0.0f;
Offset += FVector2D( ChildSpace.X + SplitterHandleSize, 0);
}
else
{
Offset += FVector2D( 0, ChildSpace.Y + SplitterHandleSize );
}
}
return Result;
}
void SSplitter2x2::OnArrangeChildren( const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren ) const
{
TArray<FLayoutGeometry> LayoutChildren = ArrangeChildrenForLayout(AllottedGeometry);
for (int32 ChildIndex=0; ChildIndex < Children.Num(); ++ChildIndex)
{
const FSlot& CurSlot = Children[ChildIndex];
// put them in their spot
ArrangedChildren.AddWidget( AllottedGeometry.MakeChild( Children[ChildIndex].GetWidget(), LayoutChildren[ChildIndex] ) );
}
}
int32 SSplitter2x2::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
FArrangedChildren ArrangedChildren(EVisibility::Visible);
ArrangeChildren(AllottedGeometry, ArrangedChildren);
int32 MaxLayerId = PaintArrangedChildren(Args, ArrangedChildren, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
const FVector2D LocalMousePos = GetTickSpaceGeometry().AbsoluteToLocal(FSlateApplication::Get().GetCursorPos());
EResizingAxis HoveredAxis = CalculateResizingAxis(GetTickSpaceGeometry(), LocalMousePos);
// First two children are top left and bottom left. Use that to calculate horizontal separator
const FVector2D HorizontalSplitterPos(0, ArrangedChildren[0].Geometry.GetLocalSize().Y);
const FVector2D HorizontalSplitterSize(AllottedGeometry.GetLocalSize().X, ArrangedChildren[1].Geometry.Position.Y - ArrangedChildren[0].Geometry.GetLocalSize().Y);
const FSlateBrush* HorizontalBrush = EnumHasAnyFlags(HoveredAxis, EResizingAxis::UpDownMask) || EnumHasAnyFlags(ResizingAxisMask, EResizingAxis::UpDownMask) ? &Style->HandleHighlightBrush : &Style->HandleNormalBrush;
FSlateDrawElement::MakeBox(
OutDrawElements,
MaxLayerId,
AllottedGeometry.ToPaintGeometry(HorizontalSplitterSize, FSlateLayoutTransform(HorizontalSplitterPos)),
HorizontalBrush,
ShouldBeEnabled(bParentEnabled) ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect,
InWidgetStyle.GetColorAndOpacityTint() * HorizontalBrush->TintColor.GetSpecifiedColor());
const FVector2D VerticalSplitterPos(ArrangedChildren[0].Geometry.GetLocalSize().X,0);
const FVector2D VerticalSplitterSize(ArrangedChildren[2].Geometry.Position.X - ArrangedChildren[0].Geometry.GetLocalSize().X, AllottedGeometry.GetLocalSize().Y);
const FSlateBrush* VerticalBrush = EnumHasAnyFlags(HoveredAxis, EResizingAxis::LeftRightMask) || EnumHasAnyFlags(ResizingAxisMask, EResizingAxis::LeftRightMask) ? &Style->HandleHighlightBrush : &Style->HandleNormalBrush;
FSlateDrawElement::MakeBox(
OutDrawElements,
MaxLayerId,
AllottedGeometry.ToPaintGeometry(VerticalSplitterSize, FSlateLayoutTransform(VerticalSplitterPos)),
VerticalBrush,
ShouldBeEnabled(bParentEnabled) ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect,
InWidgetStyle.GetColorAndOpacityTint() * VerticalBrush->TintColor.GetSpecifiedColor());
return MaxLayerId;
}
FVector2D SSplitter2x2::ComputeDesiredSize( float ) const
{
return FVector2D(100,100);
}
FChildren* SSplitter2x2::GetChildren()
{
return &Children;
}
FReply SSplitter2x2::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
FReply Reply = FReply::Unhandled();
if ( MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton )
{
const FVector2D LocalMousePos = MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() );
ResizingAxisMask = CalculateResizingAxis( MyGeometry, LocalMousePos );
if (ResizingAxisMask != EResizingAxis::None )
{
bIsResizing = true;
Reply = FReply::Handled().CaptureMouse( SharedThis(this) );
}
}
return Reply;
}
FReply SSplitter2x2::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
if ( MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && bIsResizing == true )
{
bIsResizing = false;
ResizingAxisMask = EResizingAxis::None;
return FReply::Handled().ReleaseMouseCapture();
}
return FReply::Unhandled();
}
FReply SSplitter2x2::OnMouseMove( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
const FVector2D LocalMousePos = MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() );
if ( bIsResizing && this->HasMouseCapture() )
{
TArray<FLayoutGeometry> LayoutChildren = ArrangeChildrenForLayout(MyGeometry);
ResizeChildren( MyGeometry, LayoutChildren, LocalMousePos );
return FReply::Handled();
}
return FReply::Unhandled();
}
FCursorReply SSplitter2x2::OnCursorQuery( const FGeometry& MyGeometry, const FPointerEvent& CursorEvent ) const
{
const FVector2D LocalMousePos = MyGeometry.AbsoluteToLocal(CursorEvent.GetScreenSpacePosition());
EResizingAxis HoveredAxis = CalculateResizingAxis(GetTickSpaceGeometry(), LocalMousePos);
if (EnumHasAnyFlags(HoveredAxis, EResizingAxis::CrossMask) || EnumHasAnyFlags(ResizingAxisMask, EResizingAxis::CrossMask))
{
return FCursorReply::Cursor(EMouseCursor::CardinalCross);
}
else if(EnumHasAnyFlags(HoveredAxis,EResizingAxis::LeftRightMask) || EnumHasAnyFlags(ResizingAxisMask, EResizingAxis::LeftRightMask) )
{
return FCursorReply::Cursor( EMouseCursor::ResizeLeftRight );
}
else if(EnumHasAnyFlags(HoveredAxis, EResizingAxis::UpDownMask) || EnumHasAnyFlags(ResizingAxisMask, EResizingAxis::UpDownMask))
{
return FCursorReply::Cursor( EMouseCursor::ResizeUpDown );
}
return FCursorReply::Unhandled();
}
void SSplitter2x2::ResizeChildren( const FGeometry& MyGeometry, const TArray<FLayoutGeometry>& ArrangedChildren, const FVector2D& LocalMousePos )
{
// Compute the handle position. The last child is used because it is always the furthest away from the origin
const FVector2D HandlePos = ArrangedChildren[3].GetOffsetInParentSpace() - (FVector2D( SplitterHandleSize , SplitterHandleSize ) * .5f);
FVector2D Delta = LocalMousePos - HandlePos;
FVector2D TopLeftSize = ArrangedChildren[0].GetSizeInParentSpace();
FVector2D BotLeftSize = ArrangedChildren[1].GetSizeInParentSpace();
FVector2D TopRightSize = ArrangedChildren[2].GetSizeInParentSpace();
FVector2D BotRightSize = ArrangedChildren[3].GetSizeInParentSpace();
FSlot& TopLeft = Children[0];
FSlot& BotLeft = Children[1];
FSlot& TopRight = Children[2];
FSlot& BotRight = Children[3];
if(ResizingAxisMask == EResizingAxis::LeftRightMask )
{
// Ensure deltas along the Y axis are not taken into account
Delta.Y = 0;
}
else if(ResizingAxisMask == EResizingAxis::UpDownMask )
{
// Ensure deltas along the X axis are not taken into account
Delta.X = 0;
}
// The new size of each child
FVector2D NewSizeTL;
FVector2D NewSizeBL;
FVector2D NewSizeTR;
FVector2D NewSizeBR;
if( ResizingAxisMask == EResizingAxis::LeftRightMask)
{
NewSizeTL = TopLeftSize + Delta;
NewSizeBL = BotLeftSize + Delta;
NewSizeTR = TopRightSize - Delta;
NewSizeBR = BotRightSize - Delta;
}
else if( ResizingAxisMask == EResizingAxis::UpDownMask)
{
NewSizeTL = TopLeftSize + Delta;
NewSizeBL = BotLeftSize - Delta;
NewSizeTR = TopRightSize + Delta;
NewSizeBR = BotRightSize - Delta;
}
else
{
// Resize X and Y independently as they have different rules for X and Y
NewSizeTL.X = TopLeftSize.X + Delta.X;
NewSizeBL.X = BotLeftSize.X + Delta.X;
// @todo: revisit workaround for compiler crash on clang version > 9
#if PLATFORM_ANDROID_X64
volatile float workaround = 0.0f;
NewSizeTR.X = TopRightSize.X - Delta.X + workaround;
#else
NewSizeTR.X = TopRightSize.X - Delta.X;
#endif
NewSizeBR.X = BotRightSize.X - Delta.X;
NewSizeTL.Y = TopLeftSize.Y + Delta.Y;
NewSizeBL.Y = BotLeftSize.Y - Delta.Y;
NewSizeTR.Y = TopRightSize.Y + Delta.Y;
NewSizeBR.Y = BotRightSize.Y - Delta.Y;
}
// Clamp all values so they cant be too small
// Must be done independently on each axis because of how FVector2D handles greater than
NewSizeTL.X = FMath::Max(MinSplitterChildLength, NewSizeTL.X);
NewSizeBL.X = FMath::Max(MinSplitterChildLength, NewSizeBL.X);
NewSizeTR.X = FMath::Max(MinSplitterChildLength, NewSizeTR.X);
NewSizeBR.X = FMath::Max(MinSplitterChildLength, NewSizeBR.X);
NewSizeTL.Y = FMath::Max(MinSplitterChildLength, NewSizeTL.Y);
NewSizeBL.Y = FMath::Max(MinSplitterChildLength, NewSizeBL.Y);
NewSizeTR.Y = FMath::Max(MinSplitterChildLength, NewSizeTR.Y);
NewSizeBR.Y = FMath::Max(MinSplitterChildLength, NewSizeBR.Y);
// Set the percentage space within the allotted area that each child should take up
FVector2D TotalLength = NewSizeTL+NewSizeBR;
TopLeft.SetPercentage( NewSizeTL / TotalLength );
TopRight.SetPercentage( NewSizeTR / TotalLength );
BotLeft.SetPercentage( NewSizeBL / TotalLength );
BotRight.SetPercentage( NewSizeBR / TotalLength );
}
EResizingAxis SSplitter2x2::CalculateResizingAxis( const FGeometry& MyGeometry, const FVector2D& LocalMousePos ) const
{
EResizingAxis Axis = EResizingAxis::None;
TArray<FLayoutGeometry> LayoutChildren = ArrangeChildrenForLayout(MyGeometry);
// The axis is in the center if it passes all hit tests
bool bInCenter = true;
// Search for the two widgets between which the cursor currently resides.
for ( int32 ChildIndex = 1; ChildIndex < LayoutChildren.Num(); ++ChildIndex )
{
FLayoutGeometry& PrevChild = LayoutChildren[ChildIndex - 1];
FLayoutGeometry& NextChild = LayoutChildren[ChildIndex];
FVector2D PrevBound = PrevChild.GetOffsetInParentSpace() + PrevChild.GetSizeInParentSpace();
FVector2D NextBound = NextChild.GetOffsetInParentSpace();
if( LocalMousePos.X > PrevBound.X && LocalMousePos.X < NextBound.X )
{
// The mouse is in between two viewports vertically
// Resizing axis is the X axis
Axis = EResizingAxis::LeftRightMask;
}
else if( LocalMousePos.Y > PrevBound.Y && LocalMousePos.Y < NextBound.Y )
{
// The mouse is in between two viewports horizontally
// Resizing axis is the Y axis
Axis = EResizingAxis::UpDownMask;
}
else
{
// Failed a hit test
bInCenter = false;
}
}
if( bInCenter )
{
Axis = EResizingAxis::CrossMask;
}
return Axis;
}
TSharedRef< SWidget > SSplitter2x2::GetTopLeftContent()
{
return Children[ 0 ].GetWidget();
}
TSharedRef< SWidget > SSplitter2x2::GetBottomLeftContent()
{
return Children[ 1 ].GetWidget();
}
TSharedRef< SWidget > SSplitter2x2::GetTopRightContent()
{
return Children[ 2 ].GetWidget();
}
TSharedRef< SWidget > SSplitter2x2::GetBottomRightContent()
{
return Children[ 3 ].GetWidget();
}
void SSplitter2x2::SetTopLeftContent( TSharedRef< SWidget > TopLeftContent )
{
Children[ 0 ][ TopLeftContent ];
}
void SSplitter2x2::SetBottomLeftContent( TSharedRef< SWidget > BottomLeftContent )
{
Children[ 1 ][ BottomLeftContent ];
}
void SSplitter2x2::SetTopRightContent( TSharedRef< SWidget > TopRightContent )
{
Children[ 2 ][ TopRightContent ];
}
void SSplitter2x2::SetBottomRightContent( TSharedRef< SWidget > BottomRightContent )
{
Children[ 3 ][ BottomRightContent ];
}
void SSplitter2x2::GetSplitterPercentages( TArray< FVector2D >& OutPercentages ) const
{
OutPercentages.Reset(4);
for (int32 i = 0; i < 4; ++i)
{
OutPercentages.Add(Children[i].GetPercentage());
}
}
void SSplitter2x2::SetSplitterPercentages( TArrayView< FVector2D > InPercentages )
{
int32 MaxSize = FMath::Min(InPercentages.Num(), 4);
for (int32 i = 0; i < MaxSize; ++i)
{
Children[i].SetPercentage(InPercentages[i]);
}
}