// Copyright Epic Games, Inc. All Rights Reserved. #include "Widgets/Layout/SBox.h" #include "Types/PaintArgs.h" #include "Layout/ArrangedChildren.h" #include "Layout/LayoutUtils.h" SLATE_IMPLEMENT_WIDGET(SBox) void SBox::PrivateRegisterAttributes(FSlateAttributeInitializer& AttributeInitializer) { SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION_WITH_NAME(AttributeInitializer, "SlotPadding", ChildSlot.SlotPaddingAttribute, EInvalidateWidgetReason::Layout); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, WidthOverride, EInvalidateWidgetReason::Layout); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, HeightOverride, EInvalidateWidgetReason::Layout); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, MinDesiredWidth, EInvalidateWidgetReason::Layout); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, MinDesiredHeight, EInvalidateWidgetReason::Layout); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, MaxDesiredWidth, EInvalidateWidgetReason::Layout); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, MaxDesiredHeight, EInvalidateWidgetReason::Layout); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, MinAspectRatio, EInvalidateWidgetReason::Paint); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, MaxAspectRatio, EInvalidateWidgetReason::Paint); } SBox::SBox() : ChildSlot(this) , WidthOverride(*this) , HeightOverride(*this) , MinDesiredWidth(*this) , MinDesiredHeight(*this) , MaxDesiredWidth(*this) , MaxDesiredHeight(*this) , MinAspectRatio(*this) , MaxAspectRatio(*this) { SetCanTick(false); bCanSupportFocus = false; } SBox::~SBox() = default; void SBox::Construct( const FArguments& InArgs ) { SetWidthOverride(InArgs._WidthOverride); SetHeightOverride(InArgs._HeightOverride); SetMinDesiredWidth(InArgs._MinDesiredWidth); SetMinDesiredHeight(InArgs._MinDesiredHeight); SetMaxDesiredWidth(InArgs._MaxDesiredWidth); SetMaxDesiredHeight(InArgs._MaxDesiredHeight); SetMinAspectRatio(InArgs._MinAspectRatio); SetMaxAspectRatio( InArgs._MaxAspectRatio); ChildSlot .HAlign( InArgs._HAlign ) .VAlign( InArgs._VAlign ) .Padding( InArgs._Padding ) [ InArgs._Content.Widget ]; } void SBox::SetContent(const TSharedRef< SWidget >& InContent) { ChildSlot [ InContent ]; } void SBox::SetHAlign(EHorizontalAlignment HAlign) { ChildSlot.SetHorizontalAlignment(HAlign); } void SBox::SetVAlign(EVerticalAlignment VAlign) { ChildSlot.SetVerticalAlignment(VAlign); } void SBox::SetPadding(TAttribute InPadding) { ChildSlot.SetPadding(InPadding); } void SBox::SetWidthOverride(TAttribute InWidthOverride) { WidthOverride.Assign(*this, InWidthOverride); } void SBox::SetHeightOverride(TAttribute InHeightOverride) { HeightOverride.Assign(*this, InHeightOverride); } void SBox::SetMinDesiredWidth(TAttribute InMinDesiredWidth) { MinDesiredWidth.Assign(*this, InMinDesiredWidth); } void SBox::SetMinDesiredHeight(TAttribute InMinDesiredHeight) { MinDesiredHeight.Assign(*this, InMinDesiredHeight); } void SBox::SetMaxDesiredWidth(TAttribute InMaxDesiredWidth) { MaxDesiredWidth.Assign(*this, InMaxDesiredWidth); } void SBox::SetMaxDesiredHeight(TAttribute InMaxDesiredHeight) { MaxDesiredHeight.Assign(*this, InMaxDesiredHeight); } void SBox::SetMinAspectRatio(TAttribute InMinAspectRatio) { MinAspectRatio.Assign(*this, InMinAspectRatio); } void SBox::SetMaxAspectRatio(TAttribute InMaxAspectRatio) { MaxAspectRatio.Assign(*this, InMaxAspectRatio); } FVector2D SBox::ComputeDesiredSize( float ) const { EVisibility ChildVisibility = ChildSlot.GetWidget()->GetVisibility(); if ( ChildVisibility != EVisibility::Collapsed ) { const FOptionalSize CurrentWidthOverride = WidthOverride.Get(); const FOptionalSize CurrentHeightOverride = HeightOverride.Get(); return FVector2D( ( CurrentWidthOverride.IsSet() ) ? CurrentWidthOverride.Get() : ComputeDesiredWidth(), ( CurrentHeightOverride.IsSet() ) ? CurrentHeightOverride.Get() : ComputeDesiredHeight() ); } return FVector2D::ZeroVector; } float SBox::ComputeDesiredWidth() const { // If the user specified a fixed width or height, those values override the Box's content. const FVector2D& UnmodifiedChildDesiredSize = ChildSlot.GetWidget()->GetDesiredSize() + ChildSlot.GetPadding().GetDesiredSize(); const FOptionalSize CurrentMinDesiredWidth = MinDesiredWidth.Get(); const FOptionalSize CurrentMaxDesiredWidth = MaxDesiredWidth.Get(); float CurrentWidth = UnmodifiedChildDesiredSize.X; if (CurrentMinDesiredWidth.IsSet()) { CurrentWidth = FMath::Max(CurrentWidth, CurrentMinDesiredWidth.Get()); } if (CurrentMaxDesiredWidth.IsSet()) { CurrentWidth = FMath::Min(CurrentWidth, CurrentMaxDesiredWidth.Get()); } return CurrentWidth; } float SBox::ComputeDesiredHeight() const { // If the user specified a fixed width or height, those values override the Box's content. const FVector2D& UnmodifiedChildDesiredSize = ChildSlot.GetWidget()->GetDesiredSize() + ChildSlot.GetPadding().GetDesiredSize(); const FOptionalSize CurrentMinDesiredHeight = MinDesiredHeight.Get(); const FOptionalSize CurrentMaxDesiredHeight = MaxDesiredHeight.Get(); float CurrentHeight = UnmodifiedChildDesiredSize.Y; if (CurrentMinDesiredHeight.IsSet()) { CurrentHeight = FMath::Max(CurrentHeight, CurrentMinDesiredHeight.Get()); } if (CurrentMaxDesiredHeight.IsSet()) { CurrentHeight = FMath::Min(CurrentHeight, CurrentMaxDesiredHeight.Get()); } return CurrentHeight; } void SBox::OnArrangeChildren( const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren ) const { const EVisibility ChildVisibility = ChildSlot.GetWidget()->GetVisibility(); if ( ArrangedChildren.Accepts(ChildVisibility) ) { const FOptionalSize CurrentMinAspectRatio = MinAspectRatio.Get(); const FOptionalSize CurrentMaxAspectRatio = MaxAspectRatio.Get(); const FMargin SlotPadding(ChildSlot.GetPadding()); AlignmentArrangeResult XAlignmentResult = AlignChild(AllottedGeometry.GetLocalSize().X, ChildSlot, SlotPadding); AlignmentArrangeResult YAlignmentResult = AlignChild(AllottedGeometry.GetLocalSize().Y, ChildSlot, SlotPadding); if (CurrentMaxAspectRatio.IsSet() || CurrentMinAspectRatio.IsSet()) { const FVector2f CurrentSize = FVector2f(XAlignmentResult.Size, YAlignmentResult.Size); const float MinAspectRatioValue = CurrentMinAspectRatio.IsSet() ? CurrentMinAspectRatio.Get() : 0.0f; const float MaxAspectRatioValue = CurrentMaxAspectRatio.IsSet() ? CurrentMaxAspectRatio.Get() : 0.0f; if (CurrentSize.X > 0.0f && CurrentSize.Y > 0.0f) { const float CurrentRatio = CurrentSize.X / CurrentSize.Y; const bool bFitMaxRatio = (CurrentRatio > MaxAspectRatioValue && MaxAspectRatioValue != 0.0f); const bool bFitMinRatio = (CurrentRatio < MinAspectRatioValue && MinAspectRatioValue != 0.0f); if (bFitMaxRatio || bFitMinRatio) { FVector2f MaxSize = FVector2f(AllottedGeometry.Size.X - SlotPadding.GetTotalSpaceAlong(), AllottedGeometry.Size.Y - SlotPadding.GetTotalSpaceAlong()); const bool bXFillAlignment = ArrangeUtils::GetChildAlignment::AsInt(EFlowDirection::LeftToRight, ChildSlot) == HAlign_Fill; const bool bYFillAlignment = ArrangeUtils::GetChildAlignment::AsInt(EFlowDirection::LeftToRight, ChildSlot) == HAlign_Fill; FVector2f NewSize = MaxSize; auto RatioPredicate = [&](float MinMaxAspectRatioValue) { const FVector2f AspectRatioValue = FVector2f(MinMaxAspectRatioValue, 1.0f / MinMaxAspectRatioValue); const bool bIsRatioXFirst = (bXFillAlignment && bYFillAlignment) || (!bXFillAlignment && !bYFillAlignment) ? MinMaxAspectRatioValue >= 1.0f : bXFillAlignment; if (bIsRatioXFirst) { NewSize.X = MinMaxAspectRatioValue >= 1.0f ? AspectRatioValue.X * CurrentSize.X : CurrentSize.X / AspectRatioValue.X; NewSize.Y = AspectRatioValue.Y * NewSize.X; MaxSize.X = FMath::Min(AspectRatioValue.X * MaxSize.Y, MaxSize.X); MaxSize.Y = AspectRatioValue.Y * MaxSize.X; } else { NewSize.Y = MinMaxAspectRatioValue >= 1.0f ? CurrentSize.Y / AspectRatioValue.Y : AspectRatioValue.Y * CurrentSize.Y; NewSize.X = AspectRatioValue.X * NewSize.Y; MaxSize.Y = FMath::Min(AspectRatioValue.Y * MaxSize.X, MaxSize.Y); MaxSize.X = AspectRatioValue.X * MaxSize.Y; } }; if (bFitMaxRatio) { RatioPredicate(MaxAspectRatioValue); } else { RatioPredicate(MinAspectRatioValue); } // Make sure they are inside the max available size if (NewSize.X > MaxSize.X) { float Scale = NewSize.X != 0.0f ? MaxSize.X / NewSize.X : 0.0f; NewSize *= Scale; } if (NewSize.Y > MaxSize.Y) { float Scale = NewSize.Y != 0.0f ? MaxSize.Y / NewSize.Y : 0.0f; NewSize *= Scale; } // The size changed, realign them. If it's Fill, then center it if needed. if (!bXFillAlignment) { XAlignmentResult = AlignChild(AllottedGeometry.GetLocalSize().X, NewSize.X, ChildSlot, SlotPadding); } else { XAlignmentResult = ArrangeUtils::AlignCenter(AllottedGeometry.GetLocalSize().X, NewSize.X, SlotPadding); } if (!bYFillAlignment) { YAlignmentResult = AlignChild(AllottedGeometry.GetLocalSize().Y, NewSize.Y, ChildSlot, SlotPadding); } else { YAlignmentResult = ArrangeUtils::AlignCenter(AllottedGeometry.GetLocalSize().Y, NewSize.Y, SlotPadding); } } } } ArrangedChildren.AddWidget( AllottedGeometry.MakeChild( ChildSlot.GetWidget(), FVector2D(XAlignmentResult.Offset, YAlignmentResult.Offset), FVector2D(XAlignmentResult.Size, YAlignmentResult.Size) ) ); } } FChildren* SBox::GetChildren() { return &ChildSlot; } int32 SBox::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { // An SBox just draws its only child FArrangedChildren ArrangedChildren(EVisibility::Visible); this->ArrangeChildren(AllottedGeometry, ArrangedChildren); // Maybe none of our children are visible if( ArrangedChildren.Num() > 0 ) { check( ArrangedChildren.Num() == 1 ); FArrangedWidget& TheChild = ArrangedChildren[0]; return TheChild.Widget->Paint( Args.WithNewParent(this), TheChild.Geometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, ShouldBeEnabled( bParentEnabled ) ); } return LayerId; }