Files
UnrealEngine/Engine/Source/Editor/DetailCustomizations/Private/SlateBrushCustomization.cpp
2025-05-18 13:04:45 +08:00

1653 lines
51 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Customizations/SlateBrushCustomization.h"
#include "Containers/Array.h"
#include "Containers/BitArray.h"
#include "Containers/EnumAsByte.h"
#include "Containers/Set.h"
#include "Containers/SparseArray.h"
#include "Containers/UnrealString.h"
#include "Delegates/Delegate.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "Engine/Texture2D.h"
#include "Fonts/SlateFontInfo.h"
#include "GenericPlatform/ICursor.h"
#include "HAL/PlatformCrt.h"
#include "IDetailChildrenBuilder.h"
#include "IDetailGroup.h"
#include "IDetailPropertyRow.h"
#include "Input/CursorReply.h"
#include "Input/Events.h"
#include "Input/Reply.h"
#include "InputCoreTypes.h"
#include "Internationalization/Internationalization.h"
#include "Internationalization/Text.h"
#include "Layout/Children.h"
#include "Layout/Geometry.h"
#include "Layout/Margin.h"
#include "MaterialDomain.h"
#include "MaterialShared.h"
#include "Materials/Material.h"
#include "Materials/MaterialInterface.h"
#include "Math/UnrealMathSSE.h"
#include "Misc/AssertionMacros.h"
#include "Misc/Attribute.h"
#include "Misc/Optional.h"
#include "PropertyCustomizationHelpers.h"
#include "PropertyEditorModule.h"
#include "PropertyHandle.h"
#include "ScopedTransaction.h"
#include "Serialization/Archive.h"
#include "Slate/SlateTextureAtlasInterface.h"
#include "SlotBase.h"
#include "Styling/AppStyle.h"
#include "Styling/CoreStyle.h"
#include "Styling/ISlateStyle.h"
#include "Styling/SlateBrush.h"
#include "Styling/SlateColor.h"
#include "Templates/Casts.h"
#include "Templates/TypeHash.h"
#include "Templates/UnrealTemplate.h"
#include "Types/SlateEnums.h"
#include "Types/SlateStructs.h"
#include "UObject/Class.h"
#include "UObject/Object.h"
#include "UObject/UnrealType.h"
#include "TextureCompiler.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SComboBox.h"
#include "Widgets/Input/SHyperlink.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SSpacer.h"
#include "Widgets/Layout/SUniformGridPanel.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/SOverlay.h"
#include "Widgets/Text/STextBlock.h"
class SWidget;
/**
* Slate Brush Preview widget
*/
class SSlateBrushPreview : public SBorder
{
/**
* The widget zone the user is interacting with
*/
enum EWidgetZone
{
WZ_NotInWidget = 0,
WZ_InWidget = 1,
WZ_RightBorder = 2,
WZ_BottomBorder = 3,
WZ_BottomRightBorder = 4
};
public:
SLATE_BEGIN_ARGS( SSlateBrushPreview )
{}
SLATE_ARGUMENT( TSharedPtr<IPropertyHandle>, DrawAsProperty )
SLATE_ARGUMENT( TSharedPtr<IPropertyHandle>, TilingProperty )
SLATE_ARGUMENT( TSharedPtr<IPropertyHandle>, ImageSizeProperty )
SLATE_ARGUMENT( TSharedPtr<IPropertyHandle>, MarginProperty )
SLATE_ARGUMENT( TSharedPtr<IPropertyHandle>, ResourceObjectProperty )
SLATE_ARGUMENT( TSharedPtr<IPropertyHandle>, ImageTypeProperty )
SLATE_ARGUMENT( FSlateBrush*, SlateBrush )
SLATE_END_ARGS()
/**
* Construct this widget
*/
void Construct( const FArguments& InArgs )
{
DrawAsProperty = InArgs._DrawAsProperty;
TilingProperty = InArgs._TilingProperty;
ImageSizeProperty = InArgs._ImageSizeProperty;
MarginProperty = InArgs._MarginProperty;
ResourceObjectProperty = InArgs._ResourceObjectProperty;
ImageTypeProperty = InArgs._ImageTypeProperty;
FSimpleDelegate OnDrawAsChangedDelegate = FSimpleDelegate::CreateSP( this, &SSlateBrushPreview::OnDrawAsChanged );
DrawAsProperty->SetOnPropertyValueChanged( OnDrawAsChangedDelegate );
FSimpleDelegate OnTilingChangedDelegate = FSimpleDelegate::CreateSP( this, &SSlateBrushPreview::OnTilingChanged );
TilingProperty->SetOnPropertyValueChanged( OnTilingChangedDelegate );
FSimpleDelegate OnBrushResourceChangedDelegate = FSimpleDelegate::CreateSP( this, &SSlateBrushPreview::OnBrushResourceChanged );
ResourceObjectProperty->SetOnPropertyValueChanged( OnBrushResourceChangedDelegate );
FSimpleDelegate OnImageSizeChangedDelegate = FSimpleDelegate::CreateSP( this, &SSlateBrushPreview::OnImageSizeChanged );
ImageSizeProperty->SetOnPropertyValueChanged( OnImageSizeChangedDelegate );
uint32 NumChildren = 0;
ImageSizeProperty->GetNumChildren( NumChildren );
for( uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex )
{
TSharedPtr<IPropertyHandle> Child = ImageSizeProperty->GetChildHandle( ChildIndex );
Child->SetOnPropertyValueChanged( OnImageSizeChangedDelegate );
}
FSimpleDelegate OnMarginChangedDelegate = FSimpleDelegate::CreateSP( this, &SSlateBrushPreview::OnMarginChanged );
MarginProperty->SetOnPropertyValueChanged( OnMarginChangedDelegate );
MarginProperty->GetNumChildren( NumChildren );
for( uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex )
{
TSharedPtr<IPropertyHandle> Child = MarginProperty->GetChildHandle( ChildIndex );
Child->SetOnPropertyValueChanged( OnMarginChangedDelegate );
}
HorizontalAlignment = EHorizontalAlignment::HAlign_Fill;
VerticalAlignment = EVerticalAlignment::VAlign_Fill;
bUserIsResizing = false;
MouseZone = WZ_NotInWidget;
SBorder::Construct(
SBorder::FArguments()
.BorderImage( FAppStyle::GetBrush( "PropertyEditor.SlateBrushPreview" ) )
.Padding( FMargin( 4.0f, 4.0f, 4.0f, 14.0f ) )
[
SNew( SBox )
.WidthOverride( this, &SSlateBrushPreview::GetPreviewWidth )
.HeightOverride( this, &SSlateBrushPreview::GetPreviewHeight )
[
SNew( SOverlay )
+SOverlay::Slot()
[
SNew( SImage )
.Image( FAppStyle::GetBrush( "Checkerboard" ) )
]
+SOverlay::Slot()
.Padding( FMargin( ImagePadding ) )
.Expose( OverlaySlot )
[
SNew( SImage )
.Image( InArgs._SlateBrush )
]
+SOverlay::Slot()
.HAlign( HAlign_Left )
.VAlign( VAlign_Fill )
[
SNew( SHorizontalBox )
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew( SSpacer )
.Size( this, &SSlateBrushPreview::GetLeftMarginLinePosition )
]
+SHorizontalBox::Slot()
[
SNew( SImage )
.Image( FAppStyle::GetBrush( "PropertyEditor.VerticalDottedLine" ) )
.Visibility( this, &SSlateBrushPreview::GetMarginLineVisibility )
]
]
+SOverlay::Slot()
.HAlign( HAlign_Left )
.VAlign( VAlign_Fill )
[
SNew( SHorizontalBox )
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew( SSpacer )
.Size( this, &SSlateBrushPreview::GetRightMarginLinePosition )
]
+SHorizontalBox::Slot()
[
SNew( SImage )
.Image( FAppStyle::GetBrush( "PropertyEditor.VerticalDottedLine" ) )
.Visibility( this, &SSlateBrushPreview::GetMarginLineVisibility )
]
]
+SOverlay::Slot()
.HAlign( HAlign_Fill )
.VAlign( VAlign_Top )
[
SNew( SVerticalBox )
+SVerticalBox::Slot()
.AutoHeight()
[
SNew( SSpacer )
.Size( this, &SSlateBrushPreview::GetTopMarginLinePosition )
]
+SVerticalBox::Slot()
[
SNew( SImage )
.Image( FAppStyle::GetBrush( "PropertyEditor.HorizontalDottedLine" ) )
.Visibility( this, &SSlateBrushPreview::GetMarginLineVisibility )
]
]
+SOverlay::Slot()
.HAlign( HAlign_Fill )
.VAlign( VAlign_Top )
[
SNew( SVerticalBox )
+SVerticalBox::Slot()
.AutoHeight()
[
SNew( SSpacer )
.Size( this, &SSlateBrushPreview::GetBottomMarginLinePosition )
]
+SVerticalBox::Slot()
[
SNew( SImage )
.Image( FAppStyle::GetBrush( "PropertyEditor.HorizontalDottedLine" ) )
.Visibility( this, &SSlateBrushPreview::GetMarginLineVisibility )
]
]
]
]
);
CachedTextureSize = FVector2f::ZeroVector;
CachePropertyValues();
SetDefaultAlignment();
UpdatePreviewImageSize();
UpdateMarginLinePositions();
}
/**
* Generate the alignment combo box widgets
*/
TSharedRef<SWidget> GenerateAlignmentComboBoxes()
{
HorizontalAlignmentComboItems.Add( MakeShareable( new EHorizontalAlignment( EHorizontalAlignment::HAlign_Fill ) ) );
HorizontalAlignmentComboItems.Add( MakeShareable( new EHorizontalAlignment( EHorizontalAlignment::HAlign_Left ) ) );
HorizontalAlignmentComboItems.Add( MakeShareable( new EHorizontalAlignment( EHorizontalAlignment::HAlign_Center ) ) );
HorizontalAlignmentComboItems.Add( MakeShareable( new EHorizontalAlignment( EHorizontalAlignment::HAlign_Right ) ) );
VerticalAlignmentComboItems.Add( MakeShareable( new EVerticalAlignment( EVerticalAlignment::VAlign_Fill ) ) );
VerticalAlignmentComboItems.Add( MakeShareable( new EVerticalAlignment( EVerticalAlignment::VAlign_Top ) ) );
VerticalAlignmentComboItems.Add( MakeShareable( new EVerticalAlignment( EVerticalAlignment::VAlign_Center ) ) );
VerticalAlignmentComboItems.Add( MakeShareable( new EVerticalAlignment( EVerticalAlignment::VAlign_Bottom ) ) );
return
SNew( SUniformGridPanel )
.SlotPadding( FAppStyle::GetMargin( "StandardDialog.SlotPadding" ) )
+SUniformGridPanel::Slot( 0, 0 )
.HAlign( HAlign_Right )
.VAlign( VAlign_Center )
[
SNew( STextBlock )
.Font( IDetailLayoutBuilder::GetDetailFont() )
.Text( NSLOCTEXT( "UnrealEd", "HorizontalAlignment", "Horizontal Alignment" ) )
.ToolTipText( NSLOCTEXT( "UnrealEd", "PreviewHorizontalAlignment", "Horizontal alignment for the preview" ) )
]
+SUniformGridPanel::Slot( 1, 0 )
[
SAssignNew( HorizontalAlignmentCombo, SComboBox< TSharedPtr<EHorizontalAlignment> > )
.OptionsSource( &HorizontalAlignmentComboItems )
.OnGenerateWidget( this, &SSlateBrushPreview::MakeHorizontalAlignmentComboButtonItemWidget )
.InitiallySelectedItem( HorizontalAlignmentComboItems[ 0 ] )
.OnSelectionChanged( this, &SSlateBrushPreview::OnHorizontalAlignmentComboSelectionChanged )
.Content()
[
SNew( STextBlock )
.Font( IDetailLayoutBuilder::GetDetailFont() )
.Text( this, &SSlateBrushPreview::GetHorizontalAlignmentComboBoxContent )
.ToolTipText( this, &SSlateBrushPreview::GetHorizontalAlignmentComboBoxContentToolTip )
]
]
+SUniformGridPanel::Slot( 2, 0 )
.HAlign( HAlign_Right )
.VAlign( VAlign_Center )
[
SNew( STextBlock )
.Font( IDetailLayoutBuilder::GetDetailFont() )
.Text( NSLOCTEXT( "UnrealEd", "VerticalAlignment", "Vertical Alignment" ) )
.ToolTipText( NSLOCTEXT( "UnrealEd", "PreviewVerticalAlignment", "Vertical alignment for the preview" ) )
]
+SUniformGridPanel::Slot( 3, 0 )
[
SAssignNew( VerticalAlignmentCombo, SComboBox< TSharedPtr<EVerticalAlignment> > )
.OptionsSource( &VerticalAlignmentComboItems )
.OnGenerateWidget( this, &SSlateBrushPreview::MakeVerticalAlignmentComboButtonItemWidget )
.InitiallySelectedItem( VerticalAlignmentComboItems[ 0 ] )
.OnSelectionChanged( this, &SSlateBrushPreview::OnVerticalAlignmentComboSelectionChanged )
.Content()
[
SNew( STextBlock )
.Font( IDetailLayoutBuilder::GetDetailFont() )
.Text( this, &SSlateBrushPreview::GetVerticalAlignmentComboBoxContent )
.ToolTipText( this, &SSlateBrushPreview::GetVerticalAlignmentComboBoxContentToolTip )
]
];
}
private:
/** Margin line types */
enum EMarginLine
{
MarginLine_Left,
MarginLine_Top,
MarginLine_Right,
MarginLine_Bottom,
MarginLine_Count
};
/**
* SWidget interface
*/
virtual FReply OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override
{
if( MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton )
{
bUserIsResizing = true;
ResizeAnchorPosition = MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() );
ResizeAnchorSize = PreviewImageSize;
return FReply::Handled().CaptureMouse( SharedThis( this ) );
}
else
{
return FReply::Unhandled();
}
}
virtual FReply OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override
{
if( MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && bUserIsResizing )
{
bUserIsResizing = false;
return FReply::Handled().ReleaseMouseCapture();
}
else
{
return FReply::Unhandled();
}
}
virtual FReply OnMouseMove( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override
{
const FVector2f LocalMouseCoordinates( MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ) );
if( bUserIsResizing )
{
if( MouseZone >= WZ_RightBorder && MouseZone <= WZ_BottomRightBorder )
{
FVector2f Delta( LocalMouseCoordinates - ResizeAnchorPosition );
if( MouseZone == WZ_RightBorder )
{
Delta.Y = 0.0f;
}
else if( MouseZone == WZ_BottomBorder )
{
Delta.X = 0.0f;
}
PreviewImageSize.Set( FMath::Max( ResizeAnchorSize.X + Delta.X, 16.0f ), FMath::Max( ResizeAnchorSize.Y + Delta.Y, 16.0f ) );
UpdateMarginLinePositions();
}
}
else
{
MouseZone = FindMouseZone( LocalMouseCoordinates );
}
return FReply::Unhandled();
}
virtual void OnMouseEnter( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override
{
const FVector2f LocalMouseCoordinates( MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ) );
MouseZone = FindMouseZone( LocalMouseCoordinates );
SBorder::OnMouseEnter( MyGeometry, MouseEvent );
}
virtual void OnMouseLeave( const FPointerEvent& MouseEvent ) override
{
if( !bUserIsResizing )
{
MouseZone = WZ_NotInWidget;
SBorder::OnMouseLeave( MouseEvent );
}
}
virtual FCursorReply OnCursorQuery( const FGeometry& MyGeometry, const FPointerEvent& CursorEvent ) const override
{
if( MouseZone == WZ_RightBorder )
{
return FCursorReply::Cursor( EMouseCursor::ResizeLeftRight );
}
else if( MouseZone == WZ_BottomBorder)
{
return FCursorReply::Cursor( EMouseCursor::ResizeUpDown );
}
else if( MouseZone == WZ_BottomRightBorder )
{
return FCursorReply::Cursor( EMouseCursor::ResizeSouthEast );
}
else
{
return FCursorReply::Unhandled();
}
}
/**
* End of SWidget interface
*/
/**
* Determine which zone of the widget that the mouse is in
*/
EWidgetZone FindMouseZone( const FVector2f& LocalMouseCoordinates ) const
{
EWidgetZone InMouseZone = WZ_NotInWidget;
const FVector2f DesiredZoneSize( GetDesiredSize() );
if( LocalMouseCoordinates.X > DesiredZoneSize.X - BorderHitSize )
{
InMouseZone = LocalMouseCoordinates.Y > DesiredZoneSize.Y - BorderHitSize ? WZ_BottomRightBorder : WZ_RightBorder;
}
else if( LocalMouseCoordinates.Y > DesiredZoneSize.Y - BorderHitSize )
{
InMouseZone = WZ_BottomBorder;
}
else if( LocalMouseCoordinates.X >= BorderHitSize && LocalMouseCoordinates.Y >= BorderHitSize )
{
InMouseZone = WZ_InWidget;
}
return InMouseZone;
}
/**
* Return the text for the specified horizontal alignment
*/
FText MakeHorizontalAlignmentComboText( EHorizontalAlignment Alignment ) const
{
FText AlignmentText;
switch( Alignment )
{
case EHorizontalAlignment::HAlign_Fill:
AlignmentText = NSLOCTEXT( "UnrealEd", "AlignmentFill", "Fill" );
break;
case EHorizontalAlignment::HAlign_Left:
AlignmentText = NSLOCTEXT( "UnrealEd", "AlignmentLeft", "Left" );
break;
case EHorizontalAlignment::HAlign_Center:
AlignmentText = NSLOCTEXT( "UnrealEd", "AlignmentCenter", "Center" );
break;
case EHorizontalAlignment::HAlign_Right:
AlignmentText = NSLOCTEXT( "UnrealEd", "AlignmentRight", "Right" );
break;
}
return AlignmentText;
}
/**
* Return the text for the specified vertical alignment
*/
FText MakeVerticalAlignmentComboText( EVerticalAlignment Alignment ) const
{
FText AlignmentText;
switch( Alignment )
{
case EVerticalAlignment::VAlign_Fill:
AlignmentText = NSLOCTEXT( "UnrealEd", "AlignmentFill", "Fill" );
break;
case EVerticalAlignment::VAlign_Top:
AlignmentText = NSLOCTEXT( "UnrealEd", "AlignmentTop", "Top" );
break;
case EVerticalAlignment::VAlign_Center:
AlignmentText = NSLOCTEXT( "UnrealEd", "AlignmentCenter", "Center" );
break;
case EVerticalAlignment::VAlign_Bottom:
AlignmentText = NSLOCTEXT( "UnrealEd", "AlignmentBottom", "Bottom" );
break;
}
return AlignmentText;
}
/**
* Return the tooltip text for the specified horizontal alignment
*/
FText MakeHorizontalAlignmentComboToolTipText( EHorizontalAlignment Alignment ) const
{
FText ToolTipText;
switch( Alignment )
{
case EHorizontalAlignment::HAlign_Fill:
ToolTipText = NSLOCTEXT( "UnrealEd", "AlignmentFillToolTip", "The image will fill the preview" );
break;
case EHorizontalAlignment::HAlign_Left:
ToolTipText = NSLOCTEXT( "UnrealEd", "AlignmentLeftToolTip", "The image will be aligned to the left of the preview" );
break;
case EHorizontalAlignment::HAlign_Center:
ToolTipText = NSLOCTEXT( "UnrealEd", "AlignmentCenterToolTip", "The image will be positioned in the centre of the preview" );
break;
case EHorizontalAlignment::HAlign_Right:
ToolTipText = NSLOCTEXT( "UnrealEd", "AlignmentRightToolTip", "The image will be aligned from the right of the preview" );
break;
}
return ToolTipText;
}
/**
* Return the tooltip text for the specified vertical alignment
*/
FText MakeVerticalAlignmentComboToolTipText( EVerticalAlignment Alignment ) const
{
FText ToolTipText;
switch( Alignment )
{
case EVerticalAlignment::VAlign_Fill:
ToolTipText = NSLOCTEXT( "UnrealEd", "AlignmentFillToolTip", "The image will fill the preview" );
break;
case EVerticalAlignment::VAlign_Top:
ToolTipText = NSLOCTEXT( "UnrealEd", "AlignmentTopToolTip", "The image will be aligned to the top of the preview" );
break;
case EVerticalAlignment::VAlign_Center:
ToolTipText = NSLOCTEXT( "UnrealEd", "AlignmentCenterToolTip", "The image will be positioned in the centre of the preview" );
break;
case EVerticalAlignment::VAlign_Bottom:
ToolTipText = NSLOCTEXT( "UnrealEd", "AlignmentBottomToolTip", "The image will be aligned from the bottom of the preview" );
break;
}
return ToolTipText;
}
/**
* Make the horizontal alignment combo button item widget
*/
TSharedRef<SWidget> MakeHorizontalAlignmentComboButtonItemWidget( TSharedPtr<EHorizontalAlignment> Alignment )
{
return
SNew( STextBlock )
.Text( MakeHorizontalAlignmentComboText( *Alignment ) )
.ToolTipText( MakeHorizontalAlignmentComboToolTipText( *Alignment ) )
.Font( IDetailLayoutBuilder::GetDetailFont() );
}
/**
* Make the vertical alignment combo button item widget
*/
TSharedRef<SWidget> MakeVerticalAlignmentComboButtonItemWidget( TSharedPtr<EVerticalAlignment> Alignment )
{
return
SNew( STextBlock )
.Text( MakeVerticalAlignmentComboText( *Alignment ) )
.ToolTipText( MakeVerticalAlignmentComboToolTipText( *Alignment ) )
.Font( IDetailLayoutBuilder::GetDetailFont() );
}
/**
* Get the horizontal alignment combo box content text
*/
FText GetHorizontalAlignmentComboBoxContent() const
{
return MakeHorizontalAlignmentComboText( HorizontalAlignment );
}
/**
* Get the vertical alignment combo box content text
*/
FText GetVerticalAlignmentComboBoxContent() const
{
return MakeVerticalAlignmentComboText( VerticalAlignment );
}
/**
* Get the horizontal alignment combo box content tooltip text
*/
FText GetHorizontalAlignmentComboBoxContentToolTip() const
{
return MakeHorizontalAlignmentComboToolTipText( HorizontalAlignment );
}
/**
* Get the vertical alignment combo box content tooltip text
*/
FText GetVerticalAlignmentComboBoxContentToolTip() const
{
return MakeVerticalAlignmentComboToolTipText( VerticalAlignment );
}
/**
* Cache the slate brush property values
*/
void CachePropertyValues()
{
UObject* ResourceObject;
FPropertyAccess::Result Result = ResourceObjectProperty->GetValue( ResourceObject );
if( Result == FPropertyAccess::Success )
{
using ImageSizeType = decltype(FSlateBrush::ImageSize);
TArray<void*> RawData;
ImageSizeProperty->AccessRawData(RawData);
if (RawData.Num() > 0 && RawData[0] != NULL)
{
CachedImageSizeValue = *static_cast<ImageSizeType*>(RawData[0]);
}
UTexture2D* BrushTexture = Cast<UTexture2D>(ResourceObject);
if( BrushTexture )
{
if ( BrushTexture->IsDefaultTexture() )
{
// GetSizeX/Y will return the incorrect value if this texture is being compiled so we need to wait for it here
UTexture* const BaseTexture = BrushTexture;
FTextureCompilingManager::Get().FinishCompilation( MakeArrayView(&BaseTexture, 1) );
}
CachedTextureSize = FVector2f( static_cast<float>(BrushTexture->GetSizeX()), static_cast<float>(BrushTexture->GetSizeY()) );
}
else if ( ISlateTextureAtlasInterface* AtlasedTextureObject = Cast<ISlateTextureAtlasInterface>(ResourceObject) )
{
CachedTextureSize = UE::Slate::CastToVector2f(AtlasedTextureObject->GetSlateAtlasData().GetSourceDimensions());
}
else if( CachedTextureSize == FVector2f::ZeroVector )
{
// If the cached texture size is not initialized, create a default value now for materials
CachedTextureSize = CachedImageSizeValue;
}
uint8 DrawAsType;
Result = DrawAsProperty->GetValue( DrawAsType );
if( Result == FPropertyAccess::Success )
{
CachedDrawAsType = static_cast<ESlateBrushDrawType::Type>(DrawAsType);
}
uint8 TilingType;
Result = TilingProperty->GetValue( TilingType );
if( Result == FPropertyAccess::Success )
{
CachedTilingType = static_cast<ESlateBrushTileType::Type>(TilingType);
}
MarginProperty->AccessRawData( RawData );
if( RawData.Num() > 0 && RawData[ 0 ] != NULL )
{
CachedMarginPropertyValue = *static_cast<FMargin*>(RawData[ 0 ]);
}
}
}
/**
* On horizontal alignment selection change
*/
void OnHorizontalAlignmentComboSelectionChanged( TSharedPtr<EHorizontalAlignment> NewSelection, ESelectInfo::Type /*SelectInfo*/ )
{
HorizontalAlignment = *NewSelection;
UpdateOverlayAlignment();
UpdateMarginLinePositions();
}
/**
* On vertical alignment selection change
*/
void OnVerticalAlignmentComboSelectionChanged( TSharedPtr<EVerticalAlignment> NewSelection, ESelectInfo::Type /*SelectInfo*/ )
{
VerticalAlignment = *NewSelection;
UpdateOverlayAlignment();
UpdateMarginLinePositions();
}
/**
* Get the horizontal alignment
*/
EHorizontalAlignment GetHorizontalAlignment() const
{
return HorizontalAlignment;
}
/**
* Get the vertical alignment
*/
EVerticalAlignment GetVerticalAlignment() const
{
return VerticalAlignment;
}
/**
* Update the margin line positions
*/
void UpdateMarginLinePositions()
{
const FVector2f DrawSize( (HorizontalAlignment == EHorizontalAlignment::HAlign_Fill || PreviewImageSize.X < CachedImageSizeValue.X) ? PreviewImageSize.X : CachedImageSizeValue.X,
(VerticalAlignment == EVerticalAlignment::VAlign_Fill || PreviewImageSize.Y < CachedImageSizeValue.Y) ? PreviewImageSize.Y : CachedImageSizeValue.Y );
FVector2f Position( 0.0f, 0.0f );
if( PreviewImageSize.X > DrawSize.X )
{
if( HorizontalAlignment == EHorizontalAlignment::HAlign_Center )
{
Position.X = (PreviewImageSize.X - DrawSize.X) * 0.5f;
}
else if( HorizontalAlignment == EHorizontalAlignment::HAlign_Right )
{
Position.X = PreviewImageSize.X - DrawSize.X;
}
}
if( PreviewImageSize.Y > DrawSize.Y )
{
if( VerticalAlignment == EVerticalAlignment::VAlign_Center )
{
Position.Y = (PreviewImageSize.Y - DrawSize.Y) * 0.5f;
}
else if( VerticalAlignment == EVerticalAlignment::VAlign_Bottom )
{
Position.Y = PreviewImageSize.Y - DrawSize.Y;
}
}
float LeftMargin = CachedTextureSize.X * CachedMarginPropertyValue.Left;
float RightMargin = DrawSize.X - CachedTextureSize.X * CachedMarginPropertyValue.Right;
float TopMargin = CachedTextureSize.Y * CachedMarginPropertyValue.Top;
float BottomMargin = DrawSize.Y - CachedTextureSize.Y * CachedMarginPropertyValue.Bottom;
if( RightMargin < LeftMargin )
{
LeftMargin = DrawSize.X * 0.5f;
RightMargin = LeftMargin;
}
if( BottomMargin < TopMargin )
{
TopMargin = DrawSize.Y * 0.5f;
BottomMargin = TopMargin;
}
MarginLinePositions[ MarginLine_Left ] = FVector2f( ImagePadding + Position.X + LeftMargin, 1.0f );
MarginLinePositions[ MarginLine_Right ] = FVector2f( ImagePadding + Position.X + RightMargin, 1.0f );
MarginLinePositions[ MarginLine_Top ] = FVector2f( 1.0f, ImagePadding + Position.Y + TopMargin );
MarginLinePositions[ MarginLine_Bottom ] = FVector2f( 1.0f, ImagePadding + Position.Y + BottomMargin );
}
/**
* Set the default preview alignment based on the DrawAs and Tiling properties
*/
void SetDefaultAlignment()
{
HorizontalAlignment = EHorizontalAlignment::HAlign_Fill;
VerticalAlignment = EVerticalAlignment::VAlign_Fill;
if( CachedDrawAsType == ESlateBrushDrawType::Image )
{
switch( CachedTilingType )
{
case ESlateBrushTileType::NoTile:
HorizontalAlignment = EHorizontalAlignment::HAlign_Center;
VerticalAlignment = EVerticalAlignment::VAlign_Center;
break;
case ESlateBrushTileType::Horizontal:
VerticalAlignment = EVerticalAlignment::VAlign_Center;
break;
case ESlateBrushTileType::Vertical:
HorizontalAlignment = EHorizontalAlignment::HAlign_Center;
break;
case ESlateBrushTileType::Both:
break;
}
}
UpdateOverlayAlignment();
if( HorizontalAlignmentCombo.IsValid() )
{
HorizontalAlignmentCombo->SetSelectedItem( HorizontalAlignmentComboItems[ HorizontalAlignment ] );
HorizontalAlignmentCombo->RefreshOptions();
VerticalAlignmentCombo->SetSelectedItem( VerticalAlignmentComboItems[ VerticalAlignment ] );
VerticalAlignmentCombo->RefreshOptions();
}
}
/**
* Update the preview image overlay slot alignment
*/
void UpdateOverlayAlignment()
{
OverlaySlot->SetHorizontalAlignment( HorizontalAlignment );
OverlaySlot->SetVerticalAlignment( VerticalAlignment );
}
/**
* Update the preview image size
*/
void UpdatePreviewImageSize()
{
PreviewImageSize = CachedTextureSize;
}
/**
* Called on change of Slate Brush DrawAs property
*/
void OnDrawAsChanged()
{
CachePropertyValues();
if( CachedDrawAsType != ESlateBrushDrawType::Box && CachedDrawAsType != ESlateBrushDrawType::Border )
{
TArray<void*> RawData;
if ( MarginProperty.IsValid() && MarginProperty->GetProperty() )
{
MarginProperty->AccessRawData( RawData );
check( RawData[ 0 ] != NULL );
*static_cast<FMargin*>(RawData[ 0 ]) = FMargin();
}
}
else
{
CachedTilingType = ESlateBrushTileType::NoTile;
FPropertyAccess::Result Result = TilingProperty->SetValue( static_cast<uint8>(ESlateBrushTileType::NoTile) );
check( Result == FPropertyAccess::Success );
}
SetDefaultAlignment();
UpdateMarginLinePositions();
}
/**
* Called on change of Slate Brush Tiling property
*/
void OnTilingChanged()
{
CachePropertyValues();
SetDefaultAlignment();
UpdateMarginLinePositions();
}
/**
* Called on change of Slate Brush ResourceObject property
*/
void OnBrushResourceChanged()
{
CachePropertyValues();
UpdatePreviewImageSize();
UpdateMarginLinePositions();
}
/**
* Called on change of Slate Brush ImageSize property
*/
void OnImageSizeChanged()
{
CachePropertyValues();
UpdateMarginLinePositions();
}
/**
* Called on change of Slate Brush Margin property
*/
void OnMarginChanged()
{
CachePropertyValues();
UpdateMarginLinePositions();
}
/**
* Get the preview width
*/
FOptionalSize GetPreviewWidth() const
{
return PreviewImageSize.X + ImagePadding * 2.0f;
}
/**
* Get the preview height
*/
FOptionalSize GetPreviewHeight() const
{
return PreviewImageSize.Y + ImagePadding * 2.0f;
}
/**
* Get the margin line visibility
*/
EVisibility GetMarginLineVisibility() const
{
return (CachedDrawAsType == ESlateBrushDrawType::Box || CachedDrawAsType == ESlateBrushDrawType::Border) ? EVisibility::Visible : EVisibility::Hidden;
}
/**
* Get the left margin line position
*/
FVector2D GetLeftMarginLinePosition() const
{
return FVector2D(MarginLinePositions[ MarginLine_Left ]);
}
/**
* Get the right margin line position
*/
FVector2D GetRightMarginLinePosition() const
{
return FVector2D(MarginLinePositions[ MarginLine_Right ]);
}
/**
* Get the top margin line position
*/
FVector2D GetTopMarginLinePosition() const
{
return FVector2D(MarginLinePositions[ MarginLine_Top ]);
}
/**
* Get the bottom margin line position
*/
FVector2D GetBottomMarginLinePosition() const
{
return FVector2D(MarginLinePositions[ MarginLine_Bottom ]);
}
/** Padding between the preview image and the checkerboard background */
static const float ImagePadding;
/** The thickness of the border for mouse hit testing */
static const float BorderHitSize;
/** Alignment combo items */
TArray< TSharedPtr<EHorizontalAlignment> > HorizontalAlignmentComboItems;
TArray< TSharedPtr<EVerticalAlignment> > VerticalAlignmentComboItems;
/** Alignment combos */
TSharedPtr< SComboBox< TSharedPtr<EHorizontalAlignment> > > HorizontalAlignmentCombo;
TSharedPtr< SComboBox< TSharedPtr<EVerticalAlignment> > > VerticalAlignmentCombo;
/** Overlay slot which contains the preview image */
SOverlay::FOverlaySlot* OverlaySlot;
/** Slate Brush properties */
TSharedPtr<IPropertyHandle> DrawAsProperty;
TSharedPtr<IPropertyHandle> TilingProperty;
TSharedPtr<IPropertyHandle> ImageSizeProperty;
TSharedPtr<IPropertyHandle> MarginProperty;
TSharedPtr<IPropertyHandle> ResourceObjectProperty;
TSharedPtr<IPropertyHandle> ImageTypeProperty;
/** Cached Slate Brush property values */
FVector2f CachedTextureSize;
FVector2f CachedImageSizeValue;
ESlateBrushDrawType::Type CachedDrawAsType;
ESlateBrushTileType::Type CachedTilingType;
FMargin CachedMarginPropertyValue;
/** Preview alignment */
EHorizontalAlignment HorizontalAlignment;
EVerticalAlignment VerticalAlignment;
/** Preview image size */
FVector2f PreviewImageSize;
/** Margin line positions */
FVector2f MarginLinePositions[ MarginLine_Count ];
/** The current widget zone the mouse is in */
EWidgetZone MouseZone;
/** If true the user is resizing the preview */
bool bUserIsResizing;
/** Preview resize anchor position */
FVector2f ResizeAnchorPosition;
/** Size of the preview image on begin of resize */
FVector2f ResizeAnchorSize;
};
const float SSlateBrushPreview::ImagePadding = 5.0f;
const float SSlateBrushPreview::BorderHitSize = 8.0f;
// SSlateBrushStaticPreview
////////////////////////////////////////////////////////////////////////////////
class SSlateBrushStaticPreview : public SCompoundWidget
{
SLATE_BEGIN_ARGS(SSlateBrushStaticPreview) {}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, TSharedPtr<IPropertyHandle> InResourceObjectProperty)
{
ResourceObjectProperty = InResourceObjectProperty;
UpdateBrush();
ChildSlot
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SBorder)
.Visibility(this, &SSlateBrushStaticPreview::GetPreviewVisibilityBorder)
.BorderImage(this, &SSlateBrushStaticPreview::GetPropertyBrush)
[
SNew(SSpacer)
.Size(FVector2f(1.f, 1.f))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SBox)
.WidthOverride(this, &SSlateBrushStaticPreview::GetScaledImageBrushWidth)
.HeightOverride(TargetHeight)
[
SNew(SImage)
.Visibility(this, &SSlateBrushStaticPreview::GetPreviewVisibilityImage)
.Image(this, &SSlateBrushStaticPreview::GetPropertyBrush)
]
]
];
}
void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
UpdateBrush();
}
private:
const FSlateBrush* GetPropertyBrush() const
{
return &TemporaryBrush;
}
FOptionalSize GetScaledImageBrushWidth() const
{
if (TemporaryBrush.DrawAs == ESlateBrushDrawType::Image)
{
const FVector2f& Size = TemporaryBrush.ImageSize;
if (Size.X > 0 && Size.Y > 0)
{
return Size.X * TargetHeight / Size.Y;
}
}
// Default square
return TargetHeight;
}
EVisibility GetPreviewVisibilityBorder() const
{
return TemporaryBrush.DrawAs == ESlateBrushDrawType::Image ? EVisibility::Collapsed : EVisibility::Visible;
}
EVisibility GetPreviewVisibilityImage() const
{
return TemporaryBrush.DrawAs == ESlateBrushDrawType::Image ? EVisibility::Visible : EVisibility::Collapsed;
}
void UpdateBrush()
{
if (ResourceObjectProperty.IsValid() && ResourceObjectProperty->GetProperty())
{
TArray<void*> RawData;
ResourceObjectProperty->AccessRawData(RawData);
// RawData will be empty when creating a new Data Table, an idiosyncrasy
// of the Data Table Editor...
if (RawData.Num() > 0)
{
FSlateBrush* SlateBrush = static_cast<FSlateBrush*>(RawData[0]);
if (SlateBrush)
{
TemporaryBrush = *SlateBrush;
}
}
}
}
private:
/**
* Temporary brush data used to store the structure returned from the property handy so that we have stable
* pointer to give to slate.
*/
FSlateBrush TemporaryBrush;
TSharedPtr<IPropertyHandle> ResourceObjectProperty;
static float TargetHeight;
};
float SSlateBrushStaticPreview::TargetHeight = 18.0f;
class SBrushResourceError : public SBorder
{
public:
SLATE_BEGIN_ARGS( SBrushResourceError ) {}
SLATE_DEFAULT_SLOT( FArguments, Content )
SLATE_END_ARGS()
void Construct( const FArguments& InArgs )
{
SBorder::Construct( SBorder::FArguments()
.BorderBackgroundColor( FCoreStyle::Get().GetColor("ErrorReporting.BackgroundColor") )
.BorderImage( FCoreStyle::Get().GetBrush("ErrorReporting.Box") )
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Padding( FMargin(3,0) )
[
InArgs._Content.Widget
]
);
}
};
// SBrushResourceObjectBox
////////////////////////////////////////////////////////////////////////////////
class SBrushResourceObjectBox : public SCompoundWidget
{
SLATE_BEGIN_ARGS( SBrushResourceObjectBox ) {}
SLATE_END_ARGS()
struct FPropertyParams
{
TSharedPtr<IPropertyHandle> ResourceObjectProperty;
TSharedPtr<IPropertyHandle> ResourceNameProperty;
TSharedPtr<IPropertyHandle> ImageSizeProperty;
TSharedPtr<IPropertyHandle> ImageTypeProperty;
TSharedPtr<IPropertyHandle> DrawAsProperty;
};
void Construct(const FArguments& InArgs
, IStructCustomizationUtils* StructCustomizationUtils
, FPropertyParams InParams)
{
ResourceObjectProperty = InParams.ResourceObjectProperty;
ResourceNameProperty = InParams.ResourceNameProperty;
ImageSizeProperty = InParams.ImageSizeProperty;
ImageTypeProperty = InParams.ImageTypeProperty;
DrawAsProperty = InParams.DrawAsProperty;
FSimpleDelegate OnBrushResourceChangedDelegate = FSimpleDelegate::CreateSP(this, &SBrushResourceObjectBox::OnBrushResourceChanged);
ResourceObjectProperty->SetOnPropertyValueChanged(OnBrushResourceChangedDelegate);
ChildSlot
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.FillHeight(1)
[
SNew(SObjectPropertyEntryBox)
.PropertyHandle(InParams.ResourceObjectProperty)
.ThumbnailPool(StructCustomizationUtils->GetThumbnailPool())
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding( 0.0f, 3.0f )
[
SAssignNew(ResourceError, SBrushResourceError )
[
SNew( SVerticalBox )
+ SVerticalBox::Slot()
.HAlign( HAlign_Left )
[
SNew( STextBlock )
.Text( NSLOCTEXT("FSlateBrushStructCustomization", "ResourceErrorText", "This material does not use the UI material domain" ) )
]
+ SVerticalBox::Slot()
.HAlign( HAlign_Left )
[
SAssignNew(ChangeDomainLink, SHyperlink )
.Text( NSLOCTEXT("FSlateBrushStructCustomization", "ChangeMaterialDomain_ErrorMessage", "Change the Material Domain?" ) )
.OnNavigate( this, &SBrushResourceObjectBox::OnErrorLinkClicked )
]
+ SVerticalBox::Slot()
.HAlign( HAlign_Left )
[
SAssignNew(IsEngineMaterialError, STextBlock)
.Text( NSLOCTEXT("FSlateBrushStructCustomization", "IsEngineMaterialErrorText", "Assign a parent UI Material" ) )
]
]
]
];
UpdateResourceError();
}
void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
UpdateResourceError();
}
private:
void OnBrushResourceChanged()
{
UObject* ResourceObject;
FPropertyAccess::Result Result = ResourceObjectProperty->GetValue(ResourceObject);
if ( Result == FPropertyAccess::Success )
{
TSharedPtr<IPropertyHandle> BrushHandle = ResourceObjectProperty->GetParentHandle();
TArray<void*> RawBrushData;
BrushHandle->AccessRawData(RawBrushData);
for (int32 BrushIndex = 0; BrushIndex < RawBrushData.Num(); BrushIndex++)
{
FSlateBrush* TemporaryBrush = static_cast<FSlateBrush*>(RawBrushData[BrushIndex]);
if (TemporaryBrush)
{
TemporaryBrush->InvalidateResourceHandle();
}
}
using ImageSizeType = decltype(FSlateBrush::ImageSize);
ImageSizeType CachedTextureSize = {};
TArray<void*> RawData;
ImageSizeProperty->AccessRawData(RawData);
if ( RawData.Num() > 0 && RawData[0] != NULL )
{
CachedTextureSize = *static_cast<ImageSizeType*>( RawData[0] );
}
UTexture2D* BrushTexture = Cast<UTexture2D>(ResourceObject);
if ( BrushTexture )
{
if ( BrushTexture->IsDefaultTexture() )
{
UTexture* const BaseTexture = BrushTexture;
// GetSizeX/Y will return the incorrect value if this texture is being compiled so we need to wait for it here
FTextureCompilingManager::Get().FinishCompilation( MakeArrayView(&BaseTexture, 1) );
}
CachedTextureSize = ImageSizeType(static_cast<float>(BrushTexture->GetSizeX()), static_cast<float>(BrushTexture->GetSizeY()));
}
else if ( ISlateTextureAtlasInterface* AtlasedTextureObject = Cast<ISlateTextureAtlasInterface>(ResourceObject) )
{
CachedTextureSize = AtlasedTextureObject->GetSlateAtlasData().GetSourceDimensions();
}
// Update the image size to match that of the incoming new texture.
// TODO: Should we always do this? Or should we avoid doing it if there's already some 'set value'
// problem is we don't have a way to track that right now.
ImageSizeProperty->SetValue(CachedTextureSize);
// When you assign a resource object, if the current draw type is 'None' we go ahead and update it to 'Image'.
// Also update ResourceName to be null (Object name will be used), & set ImageType
if (ResourceObject)
{
TArray<FString> OutPerObjectValues;
DrawAsProperty->GetPerObjectValues(OutPerObjectValues);
TArray<FString> NewPerObjectValues;
for (int32 ObjectIndex = 0; ObjectIndex < OutPerObjectValues.Num(); ObjectIndex++)
{
FString& ExistingValue = OutPerObjectValues[ObjectIndex];
NewPerObjectValues.Add(ExistingValue == TEXT("NoDrawType") ? TEXT("Image") : ExistingValue);
}
DrawAsProperty->SetPerObjectValues(NewPerObjectValues);
ResourceNameProperty->SetValue(NAME_None);
static_assert(sizeof(decltype(FSlateBrush::ImageType)) == sizeof(uint8));
uint8 Value = ESlateBrushImageType::FullColor;
ImageTypeProperty->SetValue(Value);
}
}
}
void OnErrorLinkClicked()
{
UObject* Resource = nullptr;
if( ResourceObjectProperty->GetValue(Resource) == FPropertyAccess::Success && Resource && Resource->IsA<UMaterialInterface>() )
{
UMaterialInterface* MaterialInterface = Cast<UMaterialInterface>( Resource );
UMaterial* BaseMaterial = MaterialInterface->GetBaseMaterial();
if ( BaseMaterial && !BaseMaterial->IsUIMaterial() )
{
FProperty* MaterialDomainProp = FindFProperty<FProperty>(UMaterial::StaticClass(), GET_MEMBER_NAME_CHECKED(UMaterial,MaterialDomain) );
FScopedTransaction Transaction( FText::Format( NSLOCTEXT("FSlateBrushStructCustomization", "ChangeMaterialDomainTransaction", "Changed {0} to use the UI material domain"), FText::FromString( BaseMaterial->GetName() ) ) );
FMaterialUpdateContext MaterialUpdateContext;
MaterialUpdateContext.AddMaterial(BaseMaterial);
BaseMaterial->PreEditChange( MaterialDomainProp );
BaseMaterial->MaterialDomain = MD_UI;
FPropertyChangedEvent ChangeEvent( MaterialDomainProp );
BaseMaterial->PostEditChangeProperty( ChangeEvent );
}
}
}
void UpdateResourceError()
{
UObject* Resource = nullptr;
if( ResourceObjectProperty->GetValue(Resource) == FPropertyAccess::Success && Resource && Resource->IsA<UMaterialInterface>() )
{
UMaterialInterface* MaterialInterface = Cast<UMaterialInterface>( Resource );
UMaterial* BaseMaterial = MaterialInterface->GetBaseMaterial();
if( BaseMaterial && !BaseMaterial->IsUIMaterial() )
{
ResourceError->SetVisibility( EVisibility::Visible );
// Special engine materials cannot change domain. This typically occurs when
// the user creates or assigns a material instance with no parent material.
// In this case, we warn the user rather than offer to change the domain.
if (BaseMaterial->bUsedAsSpecialEngineMaterial)
{
ChangeDomainLink->SetVisibility( EVisibility::Collapsed );
IsEngineMaterialError->SetVisibility( EVisibility::Visible );
}
else
{
ChangeDomainLink->SetVisibility( EVisibility::Visible );
IsEngineMaterialError->SetVisibility( EVisibility::Collapsed );
}
}
else
{
ResourceError->SetVisibility( EVisibility::Collapsed );
}
}
else if( ResourceError->GetVisibility() != EVisibility::Collapsed )
{
ResourceError->SetVisibility( EVisibility::Collapsed );
}
}
private:
TSharedPtr<IPropertyHandle> ResourceObjectProperty;
TSharedPtr<IPropertyHandle> ResourceNameProperty;
TSharedPtr<IPropertyHandle> ImageSizeProperty;
TSharedPtr<IPropertyHandle> ImageTypeProperty;
TSharedPtr<IPropertyHandle> DrawAsProperty;
TSharedPtr<SBrushResourceError> ResourceError;
TSharedPtr<SHyperlink> ChangeDomainLink;
TSharedPtr<STextBlock> IsEngineMaterialError;
};
// FSlateBrushStructCustomization
////////////////////////////////////////////////////////////////////////////////
TSharedRef<IPropertyTypeCustomization> FSlateBrushStructCustomization::MakeInstance(bool bIncludePreview)
{
return MakeShareable( new FSlateBrushStructCustomization(bIncludePreview) );
}
FSlateBrushStructCustomization::FSlateBrushStructCustomization(bool bInIncludePreview)
: bIncludePreview(bInIncludePreview)
{
}
void FSlateBrushStructCustomization::CustomizeHeader( TSharedRef<IPropertyHandle> StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils )
{
bool ShowOnlyInnerProperties = StructPropertyHandle->GetProperty()->HasMetaData(TEXT("ShowOnlyInnerProperties"));
if ( !ShowOnlyInnerProperties )
{
HeaderRow
.NameContent()
[
StructPropertyHandle->CreatePropertyNameWidget()
]
.ValueContent()
[
SNew(SSlateBrushStaticPreview, StructPropertyHandle)
];
}
}
void FSlateBrushStructCustomization::CustomizeChildren( TSharedRef<IPropertyHandle> StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils )
{
// Add the child properties
ImageSizeProperty = StructPropertyHandle->GetChildHandle( TEXT("ImageSize") );
DrawAsProperty = StructPropertyHandle->GetChildHandle( TEXT("DrawAs") );
TSharedPtr<IPropertyHandle> TilingProperty = StructPropertyHandle->GetChildHandle( TEXT("Tiling") );
TSharedPtr<IPropertyHandle> MarginProperty = StructPropertyHandle->GetChildHandle( TEXT("Margin") );
TSharedPtr<IPropertyHandle> TintProperty = StructPropertyHandle->GetChildHandle( TEXT("TintColor") );
TSharedPtr<IPropertyHandle> OutlineSettingsProperty = StructPropertyHandle->GetChildHandle(TEXT("OutlineSettings"));
ResourceObjectProperty = StructPropertyHandle->GetChildHandle( TEXT("ResourceObject") );
ResourceNameProperty = StructPropertyHandle->GetChildHandle(TEXT("ResourceName"));
ImageTypeProperty = StructPropertyHandle->GetChildHandle(TEXT("ImageType"));
FDetailWidgetRow& ResourceObjectRow = StructBuilder.AddProperty(ResourceObjectProperty.ToSharedRef()).CustomWidget();
SBrushResourceObjectBox::FPropertyParams Params;
Params.ResourceObjectProperty = ResourceObjectProperty;
Params.ResourceNameProperty = ResourceNameProperty;
Params.ImageSizeProperty = ImageSizeProperty;
Params.ImageTypeProperty = ImageTypeProperty;
Params.DrawAsProperty = DrawAsProperty;
ResourceObjectRow
.NameContent()
[
ResourceObjectProperty->CreatePropertyNameWidget()
]
.ValueContent()
.MinDesiredWidth(250.0f)
.MaxDesiredWidth(0.0f)
[
SNew(SBrushResourceObjectBox, &StructCustomizationUtils, Params)
];
// Add the image size property with custom reset delegates that also affect the child properties (the components)
const bool bOverrideDefaultOnVectorChildren = true;
StructBuilder.AddProperty( ImageSizeProperty.ToSharedRef() )
.OverrideResetToDefault(FResetToDefaultOverride::Create(
FIsResetToDefaultVisible::CreateSP(this, &FSlateBrushStructCustomization::IsImageSizeResetToDefaultVisible),
FResetToDefaultHandler::CreateSP(this, &FSlateBrushStructCustomization::OnImageSizeResetToDefault),
bOverrideDefaultOnVectorChildren));
StructBuilder.AddProperty( TintProperty.ToSharedRef() );
StructBuilder.AddProperty( DrawAsProperty.ToSharedRef() );
StructBuilder.AddProperty( OutlineSettingsProperty.ToSharedRef() )
.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP( this, &FSlateBrushStructCustomization::GetOutlineSettingsPropertyVisibility ) ) );
StructBuilder.AddProperty( TilingProperty.ToSharedRef() )
.Visibility( TAttribute<EVisibility>::Create( TAttribute<EVisibility>::FGetter::CreateSP( this, &FSlateBrushStructCustomization::GetTilingPropertyVisibility ) ) );
StructBuilder.AddProperty( MarginProperty.ToSharedRef() )
.Visibility( TAttribute<EVisibility>::Create( TAttribute<EVisibility>::FGetter::CreateSP( this, &FSlateBrushStructCustomization::GetMarginPropertyVisibility ) ) );
// Don't show the preview area when in slim view mode.
if ( bIncludePreview )
{
// Create the Slate Brush Preview widget and add the Preview group
TArray<void*> RawData;
StructPropertyHandle->AccessRawData(RawData);
// Can only display the preview with one brush
if ( RawData.Num() == 1 )
{
FSlateBrush* Brush = static_cast<FSlateBrush*>( RawData[0] );
TSharedRef<SSlateBrushPreview> Preview = SNew(SSlateBrushPreview)
.DrawAsProperty(DrawAsProperty)
.TilingProperty(TilingProperty)
.ImageSizeProperty(ImageSizeProperty)
.MarginProperty(MarginProperty)
.ResourceObjectProperty(ResourceObjectProperty)
.ImageTypeProperty(ImageTypeProperty)
.SlateBrush(Brush);
IDetailGroup& PreviewGroup = StructBuilder.AddGroup(TEXT("Preview"), FText::GetEmpty());
PreviewGroup
.HeaderRow()
.NameContent()
[
StructPropertyHandle->CreatePropertyNameWidget(NSLOCTEXT("UnrealEd", "Preview", "Preview"))
]
.ValueContent()
.MinDesiredWidth(1)
.MaxDesiredWidth(4096)
[
Preview->GenerateAlignmentComboBoxes()
];
PreviewGroup
.AddWidgetRow()
.ValueContent()
.MinDesiredWidth(1)
.MaxDesiredWidth(4096)
[
Preview
];
}
}
}
EVisibility FSlateBrushStructCustomization::GetOutlineSettingsPropertyVisibility() const
{
uint8 DrawAsType;
FPropertyAccess::Result Result = DrawAsProperty->GetValue(DrawAsType);
return (Result == FPropertyAccess::MultipleValues || DrawAsType == ESlateBrushDrawType::RoundedBox) ? EVisibility::Visible : EVisibility::Collapsed;
}
EVisibility FSlateBrushStructCustomization::GetTilingPropertyVisibility() const
{
uint8 DrawAsType;
FPropertyAccess::Result Result = DrawAsProperty->GetValue( DrawAsType );
return (Result == FPropertyAccess::MultipleValues || DrawAsType == ESlateBrushDrawType::Image) ? EVisibility::Visible : EVisibility::Collapsed;
}
EVisibility FSlateBrushStructCustomization::GetMarginPropertyVisibility() const
{
uint8 DrawAsType;
FPropertyAccess::Result Result = DrawAsProperty->GetValue( DrawAsType );
return (Result == FPropertyAccess::MultipleValues || DrawAsType == ESlateBrushDrawType::Box || DrawAsType == ESlateBrushDrawType::Border) ? EVisibility::Visible : EVisibility::Collapsed;
}
bool FSlateBrushStructCustomization::IsImageSizeResetToDefaultVisible(TSharedPtr<IPropertyHandle> PropertyHandle) const
{
UObject* ResourceObject;
if (FPropertyAccess::Success == ResourceObjectProperty->GetValue(ResourceObject) && ResourceObject)
{
// get texture size from ResourceObjectProperty and compare to image size prop value
const FVector2f SizeDefault = GetDefaultImageSize();
FVector2f Size;
{
FVector2D SizeAsDouble;
ImageSizeProperty->GetValue(SizeAsDouble);
Size = UE::Slate::CastToVector2f(SizeAsDouble);
}
if (PropertyHandle->GetProperty() == ImageSizeProperty->GetProperty())
{
// reseting the whole vector
return SizeDefault != Size;
}
else if (PropertyHandle->GetProperty() == ImageSizeProperty->GetChildHandle(0)->GetProperty()) // X
{
// reseting the vector.X
return SizeDefault.X != Size.X;
}
else if (PropertyHandle->GetProperty() == ImageSizeProperty->GetChildHandle(1)->GetProperty()) // Y
{
// reseting the vector.Y
return SizeDefault.Y != Size.Y;
}
ensureMsgf(false, TEXT("Property handle mismatch in brush size FVector2D struct"));
return false;
}
// Fall back to default handler
return PropertyHandle->DiffersFromDefault();
}
void FSlateBrushStructCustomization::OnImageSizeResetToDefault(TSharedPtr<IPropertyHandle> PropertyHandle) const
{
UObject* ResourceObject;
if (FPropertyAccess::Success == ResourceObjectProperty->GetValue(ResourceObject) && ResourceObject)
{
// Set image size prop value to the texture size in ResourceObjectProperty
const FVector2f SizeDefault = GetDefaultImageSize();
if (PropertyHandle->GetProperty() == ImageSizeProperty->GetProperty())
{
// reseting the whole vector
PropertyHandle->SetValue(FVector2D(SizeDefault));
}
else if (PropertyHandle->GetProperty() == ImageSizeProperty->GetChildHandle(0)->GetProperty()) // X
{
// reseting the vector.X
PropertyHandle->SetValue(SizeDefault.X);
}
else if (PropertyHandle->GetProperty() == ImageSizeProperty->GetChildHandle(1)->GetProperty()) // Y
{
// reseting the vector.Y
PropertyHandle->SetValue(SizeDefault.Y);
}
else
{
ensureMsgf(false, TEXT("Property handle mismatch in brush size FVector2D struct"));
}
}
else
{
// Fall back to default handler.
PropertyHandle->ResetToDefault();
}
}
FVector2f FSlateBrushStructCustomization::GetDefaultImageSize() const
{
// Custom default behavior using the texture's size, if one is set as the resource object
UObject* ResourceObject;
if (FPropertyAccess::Success == ResourceObjectProperty->GetValue(ResourceObject))
{
if ( UTexture2D* Texture = Cast<UTexture2D>(ResourceObject) )
{
if ( Texture->IsDefaultTexture() )
{
// GetSizeX/Y will return the incorrect value if this texture is being compiled so we need to wait for it here
UTexture* const BaseTexture = Texture;
FTextureCompilingManager::Get().FinishCompilation(MakeArrayView(&BaseTexture, 1));
}
return FVector2f(static_cast<float>(Texture->GetSizeX()), static_cast<float>(Texture->GetSizeY()));
}
else if ( ISlateTextureAtlasInterface* AtlasedTextureObject = Cast<ISlateTextureAtlasInterface>(ResourceObject) )
{
return UE::Slate::CastToVector2f(AtlasedTextureObject->GetSlateAtlasData().GetSourceDimensions());
}
}
// Fall back on the standard default size for brush images
return FVector2f(SlateBrushDefs::DefaultImageSize, SlateBrushDefs::DefaultImageSize);
}