You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
1338 lines
42 KiB
C++
1338 lines
42 KiB
C++
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "PropertyEditorPrivatePCH.h"
|
|
#include "AssetSelection.h"
|
|
#include "PropertyNode.h"
|
|
#include "ItemPropertyNode.h"
|
|
#include "CategoryPropertyNode.h"
|
|
#include "ObjectPropertyNode.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "AssetThumbnail.h"
|
|
#include "SDetailNameArea.h"
|
|
#include "IPropertyUtilities.h"
|
|
#include "PropertyEditorHelpers.h"
|
|
#include "PropertyEditor.h"
|
|
#include "PropertyDetailsUtilities.h"
|
|
#include "SPropertyEditorEditInline.h"
|
|
#include "ObjectEditorUtils.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "SDetailsView"
|
|
|
|
SDetailsView::~SDetailsView()
|
|
{
|
|
SaveExpandedItems();
|
|
};
|
|
|
|
/**
|
|
* Constructs the widget
|
|
*
|
|
* @param InArgs Declaration from which to construct this widget.
|
|
*/
|
|
void SDetailsView::Construct(const FArguments& InArgs)
|
|
{
|
|
DetailsViewArgs = InArgs._DetailsViewArgs;
|
|
bHasActiveFilter = false;
|
|
bIsLocked = false;
|
|
bViewingClassDefaultObject = false;
|
|
bHasOpenColorPicker = false;
|
|
|
|
// Create the root property now
|
|
RootPropertyNode = MakeShareable( new FObjectPropertyNode );
|
|
|
|
PropertyUtilities = MakeShareable( new FPropertyDetailsUtilities( *this ) );
|
|
|
|
ColumnWidth = 0.65f;
|
|
ColumnSizeData.LeftColumnWidth = TAttribute<float>( this, &SDetailsView::OnGetLeftColumnWidth );
|
|
ColumnSizeData.RightColumnWidth = TAttribute<float>( this, &SDetailsView::OnGetRightColumnWidth );
|
|
ColumnSizeData.OnWidthChanged = SSplitter::FOnSlotResized::CreateSP( this, &SDetailsView::OnSetColumnWidth );
|
|
|
|
TSharedRef<SScrollBar> ExternalScrollbar =
|
|
SNew(SScrollBar)
|
|
.AlwaysShowScrollbar( true );
|
|
|
|
FMenuBuilder DetailViewOptions( true, NULL );
|
|
|
|
FUIAction ShowOnlyModifiedAction(
|
|
FExecuteAction::CreateSP( this, &SDetailsView::OnShowOnlyModifiedClicked ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SDetailsView::IsShowOnlyModifiedChecked )
|
|
);
|
|
|
|
if (DetailsViewArgs.bShowModifiedPropertiesOption)
|
|
{
|
|
DetailViewOptions.AddMenuEntry(
|
|
LOCTEXT("ShowOnlyModified", "Show Only Modified Properties"),
|
|
LOCTEXT("ShowOnlyModified_ToolTip", "Displays only properties which have been changed from their default"),
|
|
FSlateIcon(),
|
|
ShowOnlyModifiedAction,
|
|
NAME_None,
|
|
EUserInterfaceActionType::ToggleButton
|
|
);
|
|
}
|
|
|
|
FUIAction ShowAllAdvancedAction(
|
|
FExecuteAction::CreateSP( this, &SDetailsView::OnShowAllAdvancedClicked ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SDetailsView::IsShowAllAdvancedChecked )
|
|
);
|
|
|
|
DetailViewOptions.AddMenuEntry(
|
|
LOCTEXT("ShowAllAdvanced", "Show All Advanced Details"),
|
|
LOCTEXT("ShowAllAdvanced_ToolTip", "Shows all advanced detail sections in each category"),
|
|
FSlateIcon(),
|
|
ShowAllAdvancedAction,
|
|
NAME_None,
|
|
EUserInterfaceActionType::ToggleButton
|
|
);
|
|
|
|
DetailViewOptions.AddMenuEntry(
|
|
LOCTEXT("CollapseAll", "Collapse All Categories"),
|
|
LOCTEXT("CollapseAll_ToolTip", "Collapses all root level categories"),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &SDetailsView::SetRootExpansionStates, /*bExpanded=*/false, /*bRecurse=*/false )));
|
|
DetailViewOptions.AddMenuEntry(
|
|
LOCTEXT("ExpandAll", "Expand All Categories"),
|
|
LOCTEXT("ExpandAll_ToolTip", "Expands all root level categories"),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &SDetailsView::SetRootExpansionStates, /*bExpanded=*/true, /*bRecurse=*/false )));
|
|
|
|
TSharedRef<SHorizontalBox> FilterRow = SNew( SHorizontalBox )
|
|
.Visibility( this, &SDetailsView::GetFilterBoxVisibility )
|
|
+SHorizontalBox::Slot()
|
|
.FillWidth( 1 )
|
|
.VAlign( VAlign_Center )
|
|
[
|
|
// Create the search box
|
|
SAssignNew( SearchBox, SSearchBox )
|
|
.OnTextChanged( this, &SDetailsView::OnFilterTextChanged )
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.Padding( 4.0f, 0.0f, 0.0f, 0.0f )
|
|
.AutoWidth()
|
|
[
|
|
// Create the search box
|
|
SNew( SButton )
|
|
.OnClicked( this, &SDetailsView::OnOpenRawPropertyEditorClicked )
|
|
.IsEnabled( this, &SDetailsView::IsPropertyEditingEnabled )
|
|
.ToolTipText( LOCTEXT("RawPropertyEditorButtonLabel", "Open Selection in Property Matrix") )
|
|
[
|
|
SNew( SImage )
|
|
.Image( FEditorStyle::GetBrush("DetailsView.EditRawProperties") )
|
|
]
|
|
];
|
|
|
|
if (DetailsViewArgs.bShowOptions)
|
|
{
|
|
FilterRow->AddSlot()
|
|
.HAlign(HAlign_Right)
|
|
.AutoWidth()
|
|
[
|
|
SNew( SComboButton )
|
|
.ContentPadding(0)
|
|
.ForegroundColor( FSlateColor::UseForeground() )
|
|
.ButtonStyle( FEditorStyle::Get(), "ToggleButton" )
|
|
.MenuContent()
|
|
[
|
|
DetailViewOptions.MakeWidget()
|
|
]
|
|
.ButtonContent()
|
|
[
|
|
SNew(SImage)
|
|
.Image( FEditorStyle::GetBrush("GenericViewButton") )
|
|
]
|
|
];
|
|
}
|
|
|
|
|
|
ChildSlot
|
|
[
|
|
SNew( SVerticalBox )
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding( 0.0f, 0.0f, 0.0f, 4.0f )
|
|
[
|
|
// Create the name area which does not change when selection changes
|
|
SAssignNew( NameArea, SDetailNameArea, &SelectedObjects )
|
|
// the name area is only for actors
|
|
.Visibility( this, &SDetailsView::GetActorNameAreaVisibility )
|
|
.OnLockButtonClicked( this, &SDetailsView::OnLockButtonClicked )
|
|
.IsLocked( this, &SDetailsView::IsLocked )
|
|
.ShowLockButton( DetailsViewArgs.bLockable )
|
|
.ShowActorLabel( DetailsViewArgs.bShowActorLabel )
|
|
// only show the selection tip if we're not selecting objects
|
|
.SelectionTip( !DetailsViewArgs.bHideSelectionTip )
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding( 0.0f, 0.0f, 0.0f, 2.0f )
|
|
[
|
|
FilterRow
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.FillHeight(1)
|
|
.Padding(0)
|
|
[
|
|
SNew( SHorizontalBox )
|
|
+ SHorizontalBox::Slot()
|
|
[
|
|
ConstructTreeView( ExternalScrollbar )
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew( SBox )
|
|
.WidthOverride( 16.0f )
|
|
[
|
|
ExternalScrollbar
|
|
]
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
TSharedRef<SDetailTree> SDetailsView::ConstructTreeView( TSharedRef<SScrollBar>& ScrollBar )
|
|
{
|
|
check( !DetailTree.IsValid() || DetailTree.IsUnique() );
|
|
|
|
return
|
|
SAssignNew( DetailTree, SDetailTree )
|
|
.Visibility( this, &SDetailsView::GetTreeVisibility )
|
|
.TreeItemsSource( &RootTreeNodes )
|
|
.OnGetChildren( this, &SDetailsView::OnGetChildrenForDetailTree )
|
|
.OnSetExpansionRecursive( this, &SDetailsView::SetNodeExpansionStateRecursive )
|
|
.OnGenerateRow( this, &SDetailsView::OnGenerateRowForDetailTree )
|
|
.OnExpansionChanged( this, &SDetailsView::OnItemExpansionChanged )
|
|
.SelectionMode( ESelectionMode::None )
|
|
.ExternalScrollbar( ScrollBar );
|
|
}
|
|
|
|
void SDetailsView::SetColorPropertyFromColorPicker(FLinearColor NewColor)
|
|
{
|
|
const TSharedPtr< FPropertyNode > PinnedColorPropertyNode = ColorPropertyNode.Pin();
|
|
if( ensure(PinnedColorPropertyNode.IsValid()) )
|
|
{
|
|
UProperty* Property = PinnedColorPropertyNode->GetProperty();
|
|
check(Property);
|
|
|
|
FObjectPropertyNode* ObjectNode = PinnedColorPropertyNode->FindObjectItemParent();
|
|
|
|
if( ObjectNode && ObjectNode->GetNumObjects() )
|
|
{
|
|
FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "SetColorProperty", "Set Color Property") );
|
|
|
|
PinnedColorPropertyNode->NotifyPreChange(Property, GetNotifyHook());
|
|
|
|
FPropertyChangedEvent ChangeEvent(Property, false, EPropertyChangeType::ValueSet);
|
|
PinnedColorPropertyNode->NotifyPostChange( ChangeEvent, GetNotifyHook() );
|
|
}
|
|
}
|
|
}
|
|
|
|
FReply SDetailsView::OnOpenRawPropertyEditorClicked()
|
|
{
|
|
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>( "PropertyEditor" );
|
|
PropertyEditorModule.CreatePropertyEditorToolkit( EToolkitMode::Standalone, TSharedPtr<IToolkitHost>(), SelectedObjects );
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
EVisibility SDetailsView::GetActorNameAreaVisibility() const
|
|
{
|
|
const bool bVisible = !DetailsViewArgs.bHideActorNameArea && !bViewingClassDefaultObject;
|
|
return bVisible ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
EVisibility SDetailsView::GetFilterBoxVisibility() const
|
|
{
|
|
// Visible if we allow search and we have anything to search otherwise collapsed so it doesn't take up room
|
|
return DetailsViewArgs.bAllowSearch && RootPropertyNode->GetNumObjects() > 0 ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
/**
|
|
* Determines whether or not a property should be visible in the default generated detail layout
|
|
*
|
|
* @param PropertyNode The property node to check
|
|
* @param ParentNode The parent property node to check
|
|
* @return true if the property should be visible
|
|
*/
|
|
static bool IsVisibleStandaloneProperty( const FPropertyNode& PropertyNode, const FPropertyNode& ParentNode )
|
|
{
|
|
const UProperty* Property = PropertyNode.GetProperty();
|
|
const UArrayProperty* ParentArrayProperty = Cast<const UArrayProperty>( ParentNode.GetProperty() );
|
|
bool bIsVisibleStandalone = false;
|
|
if( Property )
|
|
{
|
|
if( Property->IsA( UObjectPropertyBase::StaticClass() ) )
|
|
{
|
|
// Do not add this child node to the current map if its a single object property in a category (serves no purpose for UI)
|
|
bIsVisibleStandalone = !ParentArrayProperty && (PropertyNode.GetNumChildNodes() == 0 || PropertyNode.GetNumChildNodes() > 1) ;
|
|
}
|
|
else if( Property->IsA( UArrayProperty::StaticClass() ) || Property->ArrayDim > 1 && PropertyNode.GetArrayIndex() == INDEX_NONE )
|
|
{
|
|
// Base array properties are always visible
|
|
bIsVisibleStandalone = true;
|
|
}
|
|
else
|
|
{
|
|
bIsVisibleStandalone = true;
|
|
}
|
|
|
|
}
|
|
|
|
return bIsVisibleStandalone;
|
|
}
|
|
|
|
void SDetailsView::UpdatePropertyMapRecursive( FPropertyNode& InNode, FDetailLayoutBuilderImpl& InDetailLayout, FName CurCategory, FObjectPropertyNode* CurObjectNode )
|
|
{
|
|
UProperty* ParentProperty = InNode.GetProperty();
|
|
UStructProperty* ParentStructProp = Cast<UStructProperty>(ParentProperty);
|
|
|
|
for( int32 ChildIndex = 0; ChildIndex < InNode.GetNumChildNodes(); ++ChildIndex )
|
|
{
|
|
TSharedPtr<FPropertyNode> ChildNodePtr = InNode.GetChildNode(ChildIndex);
|
|
FPropertyNode& ChildNode = *ChildNodePtr;
|
|
UProperty* Property = ChildNode.GetProperty();
|
|
|
|
{
|
|
FObjectPropertyNode* ObjNode = ChildNode.AsObjectNode();
|
|
FCategoryPropertyNode* CategoryNode = ChildNode.AsCategoryNode();
|
|
if( ObjNode )
|
|
{
|
|
// Currently object property nodes do not provide any useful information other than being a container for its children. We do not draw anything for them.
|
|
// When we encounter object property nodes, add their children instead of adding them to the tree.
|
|
UpdatePropertyMapRecursive( ChildNode, InDetailLayout, CurCategory, ObjNode );
|
|
}
|
|
else if( CategoryNode )
|
|
{
|
|
// For category nodes, we just set the current category and recurse through the children
|
|
UpdatePropertyMapRecursive( ChildNode, InDetailLayout, CategoryNode->GetCategoryName(), CurObjectNode );
|
|
}
|
|
else
|
|
{
|
|
// Whether or not the property can be visible in the default detail layout
|
|
bool bVisibleByDefault = IsVisibleStandaloneProperty( ChildNode, InNode );
|
|
|
|
// Whether or not the property is a struct
|
|
UStructProperty* StructProperty = Cast<UStructProperty>( Property );
|
|
|
|
bool bIsStruct = StructProperty != NULL;
|
|
|
|
static FName ShowOnlyInners("ShowOnlyInnerProperties");
|
|
|
|
bool bIsChildOfCustomizedStruct = false;
|
|
bool bIsCustomizedStruct = false;
|
|
|
|
const UStruct* Struct = StructProperty ? StructProperty->Struct : NULL;
|
|
const UStruct* ParentStruct = ParentStructProp ? ParentStructProp->Struct : NULL;
|
|
if (Struct || ParentStruct)
|
|
{
|
|
FPropertyEditorModule& ParentPlugin = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
if (Struct)
|
|
{
|
|
bIsCustomizedStruct = ParentPlugin.IsCustomizedStruct(Struct);
|
|
}
|
|
|
|
if (ParentStruct)
|
|
{
|
|
bIsChildOfCustomizedStruct = ParentPlugin.IsCustomizedStruct(ParentStruct);
|
|
}
|
|
}
|
|
|
|
// Whether or not to push out struct properties to their own categories or show them inside an expandable struct
|
|
bool bPushOutStructProps = bIsStruct && !bIsCustomizedStruct && !ParentStructProp && Property->HasMetaData(ShowOnlyInners);
|
|
|
|
// Is the property edit inline new
|
|
const bool bIsEditInlineNew = SPropertyEditorEditInline::Supports( &ChildNode, ChildNode.GetArrayIndex() );
|
|
|
|
// Is this a property of an array
|
|
bool bIsChildOfArray = PropertyEditorHelpers::IsChildOfArray( ChildNode );
|
|
|
|
// Edit inline new properties should be visible by default
|
|
bVisibleByDefault |= bIsEditInlineNew;
|
|
|
|
// Children of arrays are not visible directly,
|
|
bVisibleByDefault &= !bIsChildOfArray;
|
|
|
|
// See if the user requested specific property visibility
|
|
const bool bIsUserVisible = IsPropertyVisible( Property );
|
|
|
|
// Inners of customized in structs should not be taken into consideration for customizing. They are not designed to be individually customized when their parent is already customized
|
|
if( !bIsChildOfCustomizedStruct )
|
|
{
|
|
// Add any object classes with properties so we can ask them for custom property layouts later
|
|
ClassesWithProperties.Add( Property->GetOwnerStruct() );
|
|
}
|
|
|
|
// If there is no outer object then the class is the object root and there is only one instance
|
|
FName InstanceName = NAME_None;
|
|
if( CurObjectNode && CurObjectNode->GetParentNode() )
|
|
{
|
|
InstanceName = CurObjectNode->GetParentNode()->GetProperty()->GetFName();
|
|
}
|
|
else if( ParentStructProp )
|
|
{
|
|
InstanceName = ParentStructProp->GetFName();
|
|
}
|
|
|
|
// Do not add children of customized in struct properties or arrays
|
|
if( !bIsChildOfCustomizedStruct && !bIsChildOfArray )
|
|
{
|
|
// Get the class property map
|
|
FClassInstanceToPropertyMap& ClassInstanceMap = ClassToPropertyMap.FindOrAdd( Property->GetOwnerStruct()->GetFName() );
|
|
|
|
FPropertyNodeMap& PropertyNodeMap = ClassInstanceMap.FindOrAdd( InstanceName );
|
|
|
|
if( !PropertyNodeMap.ParentObjectProperty )
|
|
{
|
|
PropertyNodeMap.ParentObjectProperty = CurObjectNode;
|
|
}
|
|
else
|
|
{
|
|
ensure( PropertyNodeMap.ParentObjectProperty == CurObjectNode );
|
|
}
|
|
|
|
checkSlow( !PropertyNodeMap.Contains( Property->GetFName() ) );
|
|
|
|
PropertyNodeMap.Add( Property->GetFName(), ChildNodePtr );
|
|
}
|
|
|
|
if( bVisibleByDefault && bIsUserVisible && !bPushOutStructProps )
|
|
{
|
|
FName CategoryName = CurCategory;
|
|
|
|
// For properties inside a struct, add them to their own category unless they just take the name of the parent struct.
|
|
// In that case push them to the parent category
|
|
FName PropertyCatagoryName = FObjectEditorUtils::GetCategoryFName(Property);
|
|
if( !ParentStructProp || (PropertyCatagoryName != ParentStructProp->Struct->GetFName()) )
|
|
{
|
|
CategoryName = PropertyCatagoryName;
|
|
}
|
|
|
|
// Add a property to the default category
|
|
FDetailCategoryImpl& CategoryImpl = InDetailLayout.DefaultCategory( CategoryName );
|
|
CategoryImpl.AddPropertyNode( ChildNodePtr.ToSharedRef(), InstanceName );
|
|
}
|
|
|
|
bool bRecurseIntoChildren =
|
|
!bIsChildOfCustomizedStruct // Don't recurse into built in struct children, we already know what they are and how to display them
|
|
&& !bIsCustomizedStruct // Don't recurse into customized structs
|
|
&& !bIsChildOfArray // Do not recurse into arrays, the children are drawn by the array property parent
|
|
&& !bIsEditInlineNew // Edit inline new children are not supported for customization yet
|
|
&& bIsUserVisible // Properties must be allowed to be visible by a user if they are not then their children are not visible either
|
|
&& (!bIsStruct || bPushOutStructProps); // Only recurse into struct properties if they are going to be displayed as standalone properties in categories instead of inside an expandable area inside a category
|
|
|
|
if( bRecurseIntoChildren )
|
|
{
|
|
// Built in struct properties or children of arras
|
|
UpdatePropertyMapRecursive( ChildNode, InDetailLayout, CurCategory, CurObjectNode );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SDetailsView::UpdatePropertyMap()
|
|
{
|
|
check( RootPropertyNode.IsValid() );
|
|
|
|
// Reset everything
|
|
ClassToPropertyMap.Empty();
|
|
ClassesWithProperties.Empty();
|
|
|
|
// We need to be able to create a new detail layout and properly clean up the old one in the process
|
|
check( !DetailLayout.IsValid() || DetailLayout.IsUnique() );
|
|
|
|
RootTreeNodes.Empty();
|
|
|
|
DetailLayout = MakeShareable( new FDetailLayoutBuilderImpl( ClassToPropertyMap, PropertyUtilities.ToSharedRef(), SharedThis( this ) ) );
|
|
|
|
// Currently object property nodes do not provide any useful information other than being a container for its children. We do not draw anything for them.
|
|
// When we encounter object property nodes, add their children instead of adding them to the tree.
|
|
UpdatePropertyMapRecursive( *RootPropertyNode, *DetailLayout, NAME_None, RootPropertyNode.Get() );
|
|
|
|
// Ask for custom detail layouts
|
|
QueryCustomDetailLayout( *DetailLayout );
|
|
|
|
DetailLayout->GenerateDetailLayout();
|
|
|
|
UpdateFilteredDetails();
|
|
}
|
|
|
|
void SDetailsView::ForceRefresh()
|
|
{
|
|
TArray< TWeakObjectPtr< UObject > > NewObjectList;
|
|
|
|
// Simply re-add the same existing objects to cause a refresh
|
|
for ( TPropObjectIterator Itor( RootPropertyNode->ObjectIterator() ); Itor; ++Itor )
|
|
{
|
|
TWeakObjectPtr<UObject> Object = *Itor;
|
|
if( Object.IsValid() )
|
|
{
|
|
NewObjectList.Add( Object.Get() );
|
|
}
|
|
}
|
|
|
|
SetObjectArrayPrivate( NewObjectList );
|
|
}
|
|
|
|
|
|
void SDetailsView::SetObjects( const TArray<UObject*>& InObjects, bool bForceRefresh/* = false*/ )
|
|
{
|
|
if( !IsLocked() )
|
|
{
|
|
TArray< TWeakObjectPtr< UObject > > ObjectWeakPtrs;
|
|
|
|
for( auto ObjectIter = InObjects.CreateConstIterator(); ObjectIter; ++ObjectIter )
|
|
{
|
|
ObjectWeakPtrs.Add( *ObjectIter );
|
|
}
|
|
|
|
if( bForceRefresh || ShouldSetNewObjects( ObjectWeakPtrs ) )
|
|
{
|
|
SetObjectArrayPrivate( ObjectWeakPtrs );
|
|
}
|
|
}
|
|
}
|
|
|
|
void SDetailsView::SetObjects( const TArray< TWeakObjectPtr< UObject > >& InObjects, bool bForceRefresh/* = false*/ )
|
|
{
|
|
if( !IsLocked() )
|
|
{
|
|
if( bForceRefresh || ShouldSetNewObjects( InObjects ) )
|
|
{
|
|
SetObjectArrayPrivate( InObjects );
|
|
}
|
|
}
|
|
}
|
|
|
|
void SDetailsView::SetObject( UObject* InObject, bool bForceRefresh )
|
|
{
|
|
TArray< TWeakObjectPtr< UObject > > ObjectWeakPtrs;
|
|
ObjectWeakPtrs.Add( InObject );
|
|
|
|
SetObjects( ObjectWeakPtrs, bForceRefresh );
|
|
}
|
|
|
|
bool SDetailsView::ShouldSetNewObjects( const TArray< TWeakObjectPtr< UObject > >& InObjects ) const
|
|
{
|
|
bool bShouldSetObjects = false;
|
|
if( InObjects.Num() != RootPropertyNode->GetNumObjects() )
|
|
{
|
|
// If the object arrys differ in size then at least one object is different so we must reset
|
|
bShouldSetObjects = true;
|
|
}
|
|
else
|
|
{
|
|
// Check to see if the objects passed in are different. If not we do not need to set anything
|
|
TSet< TWeakObjectPtr< UObject > > NewObjects;
|
|
NewObjects.Append( InObjects );
|
|
for ( TPropObjectIterator Itor( RootPropertyNode->ObjectIterator() ); Itor; ++Itor )
|
|
{
|
|
TWeakObjectPtr<UObject> Object = *Itor;
|
|
if( Object.IsValid() && !NewObjects.Contains( Object ) )
|
|
{
|
|
// An existing object is not in the list of new objects to set
|
|
bShouldSetObjects = true;
|
|
break;
|
|
}
|
|
else if( !Object.IsValid() )
|
|
{
|
|
// An existing object is invalid
|
|
bShouldSetObjects = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bShouldSetObjects;
|
|
}
|
|
|
|
void SDetailsView::SetObjectArrayPrivate( const TArray< TWeakObjectPtr< UObject > >& InObjects )
|
|
{
|
|
double StartTime = FPlatformTime::Seconds();
|
|
|
|
PreSetObject();
|
|
|
|
check( RootPropertyNode.IsValid() );
|
|
|
|
// Selected actors for building SelectedActorInfo
|
|
TArray<AActor*> SelectedRawActors;
|
|
|
|
bViewingClassDefaultObject = false;
|
|
bool bOwnedByLockedLevel = false;
|
|
for( int32 ObjectIndex = 0 ; ObjectIndex < InObjects.Num() ; ++ObjectIndex )
|
|
{
|
|
TWeakObjectPtr< UObject > Object = InObjects[ObjectIndex];
|
|
|
|
if( Object.IsValid() )
|
|
{
|
|
bViewingClassDefaultObject |= Object->HasAnyFlags( RF_ClassDefaultObject );
|
|
|
|
RootPropertyNode->AddObject( Object.Get() );
|
|
SelectedObjects.Add( Object );
|
|
AActor* Actor = Cast<AActor>( Object.Get() );
|
|
if( Actor )
|
|
{
|
|
SelectedActors.Add( Actor );
|
|
SelectedRawActors.Add( Actor );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( InObjects.Num() == 0 )
|
|
{
|
|
// Unlock the view automatically if we are viewing nothing
|
|
bIsLocked = false;
|
|
}
|
|
|
|
// Selection changed, refresh the detail area
|
|
if ( DetailsViewArgs.bObjectsUseNameArea )
|
|
{
|
|
NameArea->Refresh( SelectedObjects );
|
|
}
|
|
else
|
|
{
|
|
NameArea->Refresh( SelectedActors );
|
|
}
|
|
|
|
// When selection changes rebuild information about the selection
|
|
SelectedActorInfo = AssetSelectionUtils::BuildSelectedActorInfo( SelectedRawActors );
|
|
|
|
// @todo Slate Property Window
|
|
//SetFlags(EPropertyWindowFlags::ReadOnly, bOwnedByLockedLevel);
|
|
|
|
|
|
PostSetObject();
|
|
|
|
// Set the title of the window based on the objects we are viewing
|
|
// Or call the delegate for handling when the title changed
|
|
FString Title;
|
|
|
|
if( !RootPropertyNode->GetObjectBaseClass() )
|
|
{
|
|
Title = NSLOCTEXT("PropertyView", "NothingSelectedTitle", "Nothing selected").ToString();
|
|
}
|
|
else if( RootPropertyNode->GetNumObjects() == 1 )
|
|
{
|
|
// if the object is the default metaobject for a UClass, use the UClass's name instead
|
|
const UObject* Object = RootPropertyNode->ObjectConstIterator()->Get();
|
|
FString ObjectName = Object->GetName();
|
|
if ( Object->GetClass()->GetDefaultObject() == Object )
|
|
{
|
|
ObjectName = Object->GetClass()->GetName();
|
|
}
|
|
else
|
|
{
|
|
// Is this an actor? If so, it might have a friendly name to display
|
|
const AActor* Actor = Cast<const AActor >( Object );
|
|
if( Actor != NULL )
|
|
{
|
|
// Use the friendly label for this actor
|
|
ObjectName = Actor->GetActorLabel();
|
|
}
|
|
}
|
|
|
|
Title = ObjectName;
|
|
}
|
|
else
|
|
{
|
|
Title = FString::Printf( *NSLOCTEXT("PropertyView", "MultipleSelected", "%s (%i selected)").ToString(), *RootPropertyNode->GetObjectBaseClass()->GetName(), RootPropertyNode->GetNumObjects() );
|
|
}
|
|
|
|
OnObjectArrayChanged.ExecuteIfBound(Title, InObjects);
|
|
|
|
double ElapsedTime = FPlatformTime::Seconds() - StartTime;
|
|
}
|
|
|
|
void SDetailsView::ReplaceObjects( const TMap<UObject*, UObject*>& OldToNewObjectMap )
|
|
{
|
|
TArray< TWeakObjectPtr< UObject > > NewObjectList;
|
|
bool bObjectsReplaced = false;
|
|
|
|
TArray< FObjectPropertyNode* > ObjectNodes;
|
|
PropertyEditorHelpers::CollectObjectNodes( RootPropertyNode, ObjectNodes );
|
|
|
|
for( int32 ObjectNodeIndex = 0; ObjectNodeIndex < ObjectNodes.Num(); ++ObjectNodeIndex )
|
|
{
|
|
FObjectPropertyNode* CurrentNode = ObjectNodes[ObjectNodeIndex];
|
|
|
|
// Scan all objects and look for objects which need to be replaced
|
|
for ( TPropObjectIterator Itor( CurrentNode->ObjectIterator() ); Itor; ++Itor )
|
|
{
|
|
UObject* Replacement = OldToNewObjectMap.FindRef( Itor->Get() );
|
|
if( Replacement && Replacement->GetClass() == Itor->Get()->GetClass() )
|
|
{
|
|
bObjectsReplaced = true;
|
|
if( CurrentNode == RootPropertyNode.Get() )
|
|
{
|
|
// Note: only root objects count for the new object list. Sub-Objects (i.e components count as needing to be replaced but they don't belong in the top level object list
|
|
NewObjectList.Add( Replacement );
|
|
}
|
|
}
|
|
else if( CurrentNode == RootPropertyNode.Get() )
|
|
{
|
|
// Note: only root objects count for the new object list. Sub-Objects (i.e components count as needing to be replaced but they don't belong in the top level object list
|
|
NewObjectList.Add( Itor->Get() );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if( bObjectsReplaced )
|
|
{
|
|
SetObjectArrayPrivate( NewObjectList );
|
|
}
|
|
|
|
}
|
|
|
|
void SDetailsView::RemoveDeletedObjects( const TArray<UObject*>& DeletedObjects )
|
|
{
|
|
TArray< TWeakObjectPtr< UObject > > NewObjectList;
|
|
bool bObjectsRemoved = false;
|
|
|
|
// Scan all objects and look for objects which need to be replaced
|
|
for ( TPropObjectIterator Itor( RootPropertyNode->ObjectIterator() ); Itor; ++Itor )
|
|
{
|
|
if( DeletedObjects.Contains( Itor->Get() ) )
|
|
{
|
|
// An object we had needs to be removed
|
|
bObjectsRemoved = true;
|
|
}
|
|
else
|
|
{
|
|
// If the deleted object list does not contain the current object, its ok to keep it in the list
|
|
NewObjectList.Add( Itor->Get() );
|
|
}
|
|
}
|
|
|
|
// if any objects were replaced update the observed objects
|
|
if( bObjectsRemoved )
|
|
{
|
|
SetObjectArrayPrivate( NewObjectList );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes actors from the property nodes object array which are no longer available
|
|
*
|
|
* @param ValidActors The list of actors which are still valid
|
|
*/
|
|
void SDetailsView::RemoveInvalidActors( const TSet<AActor*>& ValidActors )
|
|
{
|
|
TArray< TWeakObjectPtr< UObject > > ResetArray;
|
|
|
|
bool bAllFound = true;
|
|
for ( TPropObjectIterator Itor( RootPropertyNode->ObjectIterator() ); Itor; ++Itor )
|
|
{
|
|
AActor* Actor = Cast<AActor>( Itor->Get() );
|
|
|
|
bool bFound = ValidActors.Contains( Actor );
|
|
|
|
// If the selected actor no longer exists, remove it from the property window.
|
|
if( bFound )
|
|
{
|
|
ResetArray.Add(Actor);
|
|
}
|
|
else
|
|
{
|
|
bAllFound = false;
|
|
}
|
|
}
|
|
|
|
if ( !bAllFound )
|
|
{
|
|
SetObjectArrayPrivate( ResetArray );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively gets expanded items for a node
|
|
*
|
|
* @param InPropertyNode The node to get expanded items from
|
|
* @param OutExpandedItems List of expanded items that were found
|
|
*/
|
|
void GetExpandedItems( TSharedPtr<FPropertyNode> InPropertyNode, TArray<FString>& OutExpandedItems )
|
|
{
|
|
if( InPropertyNode->HasNodeFlags(EPropertyNodeFlags::Expanded) )
|
|
{
|
|
const bool bWithArrayIndex = true;
|
|
FString Path;
|
|
Path.Empty(128);
|
|
InPropertyNode->GetQualifiedName(Path, bWithArrayIndex);
|
|
|
|
OutExpandedItems.Add( Path );
|
|
}
|
|
|
|
for( int32 ChildIndex = 0; ChildIndex < InPropertyNode->GetNumChildNodes(); ++ChildIndex )
|
|
{
|
|
GetExpandedItems( InPropertyNode->GetChildNode(ChildIndex), OutExpandedItems );
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Recursively sets expanded items for a node
|
|
*
|
|
* @param InNode The node to set expanded items on
|
|
* @param OutExpandedItems List of expanded items to set
|
|
*/
|
|
void SetExpandedItems( TSharedPtr<FPropertyNode> InPropertyNode, const TArray<FString>& InExpandedItems )
|
|
{
|
|
if( InExpandedItems.Num() > 0 )
|
|
{
|
|
const bool bWithArrayIndex = true;
|
|
FString Path;
|
|
Path.Empty(128);
|
|
InPropertyNode->GetQualifiedName(Path, bWithArrayIndex);
|
|
|
|
for (int32 ItemIndex = 0; ItemIndex < InExpandedItems.Num(); ++ItemIndex)
|
|
{
|
|
if ( InExpandedItems[ItemIndex] == Path )
|
|
{
|
|
InPropertyNode->SetNodeFlags( EPropertyNodeFlags::Expanded, true );
|
|
break;
|
|
}
|
|
}
|
|
|
|
for( int32 NodeIndex = 0; NodeIndex < InPropertyNode->GetNumChildNodes(); ++NodeIndex )
|
|
{
|
|
SetExpandedItems( InPropertyNode->GetChildNode( NodeIndex ), InExpandedItems );
|
|
}
|
|
}
|
|
}
|
|
|
|
void SDetailsView::SaveExpandedItems()
|
|
{
|
|
UClass* BestBaseClass = RootPropertyNode->GetObjectBaseClass();
|
|
|
|
TArray<FString> ExpandedPropertyItems;
|
|
GetExpandedItems( RootPropertyNode, ExpandedPropertyItems );
|
|
|
|
TArray<FString> ExpandedCustomItems = ExpandedDetailNodes.Array();
|
|
|
|
// Expanded custom items may have spaces but SetSingleLineArray doesnt support spaces (treats it as another element in the array)
|
|
// Append a '|' after each element instead
|
|
FString ExpandedCustomItemsString;
|
|
for( auto It = ExpandedDetailNodes.CreateConstIterator(); It; ++It )
|
|
{
|
|
ExpandedCustomItemsString += *It;
|
|
ExpandedCustomItemsString += TEXT(",");
|
|
}
|
|
|
|
//while a valid class, and we're either the same as the base class (for multiple actors being selected and base class is AActor) OR we're not down to AActor yet)
|
|
for( UClass* Class = BestBaseClass; Class && ((BestBaseClass == Class) || (Class!=AActor::StaticClass())); Class = Class->GetSuperClass() )
|
|
{
|
|
if( RootPropertyNode->GetNumChildNodes() > 0 && ExpandedPropertyItems.Num() > 0 )
|
|
{
|
|
GConfig->SetSingleLineArray(TEXT("DetailPropertyExpansion"), *Class->GetName(), ExpandedPropertyItems, GEditorUserSettingsIni);
|
|
}
|
|
}
|
|
|
|
if( DetailLayout.IsValid() && BestBaseClass && !ExpandedCustomItemsString.IsEmpty() )
|
|
{
|
|
GConfig->SetString(TEXT("DetailCustomWidgetExpansion"), *BestBaseClass->GetName(), *ExpandedCustomItemsString, GEditorUserSettingsIni);
|
|
}
|
|
}
|
|
|
|
void SDetailsView::RestoreExpandedItems( TSharedPtr<FPropertyNode> InitialStartNode )
|
|
{
|
|
TSharedPtr<FPropertyNode> StartNode = InitialStartNode;
|
|
if( !StartNode.IsValid() )
|
|
{
|
|
StartNode = RootPropertyNode;
|
|
}
|
|
|
|
ExpandedDetailNodes.Empty();
|
|
|
|
TArray<FString> ExpandedPropertyItems;
|
|
FString ExpandedCustomItems;
|
|
UClass* BestBaseClass = RootPropertyNode->GetObjectBaseClass();
|
|
//while a valid class, and we're either the same as the base class (for multiple actors being selected and base class is AActor) OR we're not down to AActor yet)
|
|
for( UClass* Class = BestBaseClass; Class && ((BestBaseClass == Class) || (Class!=AActor::StaticClass())); Class = Class->GetSuperClass() )
|
|
{
|
|
GConfig->GetSingleLineArray(TEXT("DetailPropertyExpansion"), *Class->GetName(), ExpandedPropertyItems, GEditorUserSettingsIni);
|
|
SetExpandedItems( StartNode, ExpandedPropertyItems );
|
|
}
|
|
|
|
if( BestBaseClass )
|
|
{
|
|
GConfig->GetString(TEXT("DetailCustomWidgetExpansion"), *BestBaseClass->GetName(), ExpandedCustomItems, GEditorUserSettingsIni);
|
|
TArray<FString> ExpandedCustomItemsArray;
|
|
ExpandedCustomItems.ParseIntoArray( &ExpandedCustomItemsArray, TEXT(","), true );
|
|
|
|
ExpandedDetailNodes.Append( ExpandedCustomItemsArray );
|
|
}
|
|
|
|
}
|
|
|
|
/** Called before during SetObjectArray before we change the objects being observed */
|
|
void SDetailsView::PreSetObject()
|
|
{
|
|
ExternalRootPropertyNodes.Empty();
|
|
|
|
// Save existing expanded items first
|
|
SaveExpandedItems();
|
|
|
|
RootNodePendingKill = RootPropertyNode;
|
|
|
|
RootPropertyNode = MakeShareable( new FObjectPropertyNode );
|
|
|
|
SelectedActors.Empty();
|
|
SelectedObjects.Empty();
|
|
}
|
|
|
|
/** Called at the end of SetObjectArray after we change the objects being observed */
|
|
void SDetailsView::PostSetObject()
|
|
{
|
|
DestroyColorPicker();
|
|
ColorPropertyNode = NULL;
|
|
|
|
FPropertyNodeInitParams InitParams;
|
|
InitParams.ParentNode = NULL;
|
|
InitParams.Property = NULL;
|
|
InitParams.ArrayOffset = 0;
|
|
InitParams.ArrayIndex = INDEX_NONE;
|
|
InitParams.bAllowChildren = true;
|
|
InitParams.bForceHiddenPropertyVisibility = FPropertySettings::Get().ShowHiddenProperties();
|
|
|
|
RootPropertyNode->InitNode( InitParams );
|
|
|
|
bool bInitiallySeen = true;
|
|
bool bParentAllowsVisible = true;
|
|
// Restore existing expanded items
|
|
RestoreExpandedItems();
|
|
|
|
UpdatePropertyMap();
|
|
}
|
|
|
|
void SDetailsView::QueryLayoutForClass( FDetailLayoutBuilderImpl& CustomDetailLayout, UStruct* Class )
|
|
{
|
|
CustomDetailLayout.SetCurrentCustomizationClass( CastChecked<UClass>( Class ), NAME_None );
|
|
|
|
FPropertyEditorModule& ParentPlugin = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
FCustomDetailLayoutNameMap& GlobalCustomLayoutNameMap = ParentPlugin.ClassNameToDetailLayoutNameMap;
|
|
|
|
// Check the instanced map first
|
|
FDetailLayoutCallback* Callback = InstancedClassToDetailLayoutMap.Find( TWeakObjectPtr<UStruct>(Class) );
|
|
|
|
if( !Callback )
|
|
{
|
|
// callback wasn't found in the per instance map, try the global instances instead
|
|
Callback = GlobalCustomLayoutNameMap.Find( Class->GetFName() );
|
|
}
|
|
|
|
if( Callback && Callback->DetailLayoutDelegate.IsBound() )
|
|
{
|
|
// Create a new instance of the custom detail layout for the current class
|
|
TSharedRef<IDetailCustomization> CustomizationInstance = Callback->DetailLayoutDelegate.Execute();
|
|
|
|
// Ask for details immediately
|
|
CustomizationInstance->CustomizeDetails( CustomDetailLayout );
|
|
|
|
// Save the instance from destruction until we refresh
|
|
CustomizationClassInstances.Add( CustomizationInstance );
|
|
}
|
|
}
|
|
|
|
void SDetailsView::QueryCustomDetailLayout( FDetailLayoutBuilderImpl& CustomDetailLayout )
|
|
{
|
|
FPropertyEditorModule& ParentPlugin = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
|
|
// Get the registered classes that customize details
|
|
FCustomDetailLayoutNameMap& GlobalCustomLayoutNameMap = ParentPlugin.ClassNameToDetailLayoutNameMap;
|
|
|
|
UClass* BaseClass = GetBaseClass();
|
|
|
|
// All the current customization instances need to be deleted when it is safe
|
|
CustomizationClassInstancesPendingDelete = CustomizationClassInstances;
|
|
|
|
CustomizationClassInstances.Empty();
|
|
|
|
//Ask for generic details not specific to an object being viewed
|
|
if( GenericLayoutDelegate.IsBound() )
|
|
{
|
|
// Create a new instance of the custom detail layout for the current class
|
|
TSharedRef<IDetailCustomization> CustomizationInstance = GenericLayoutDelegate.Execute();
|
|
|
|
// Ask for details immediately
|
|
CustomizationInstance->CustomizeDetails( CustomDetailLayout );
|
|
|
|
// Save the instance from destruction until we refresh
|
|
CustomizationClassInstances.Add( CustomizationInstance );
|
|
}
|
|
|
|
|
|
// Sort them by query order. @todo not good enough
|
|
struct FCompareFDetailLayoutCallback
|
|
{
|
|
FORCEINLINE bool operator()( const FDetailLayoutCallback& A, const FDetailLayoutCallback& B ) const
|
|
{
|
|
return A.Order < B.Order;
|
|
}
|
|
};
|
|
|
|
TMap< TWeakObjectPtr<UStruct>, FDetailLayoutCallback*> FinalCallbackMap;
|
|
|
|
for( auto ClassIt = ClassesWithProperties.CreateConstIterator(); ClassIt; ++ClassIt )
|
|
{
|
|
// Check the instanced map first
|
|
FDetailLayoutCallback* Callback = InstancedClassToDetailLayoutMap.Find( *ClassIt );
|
|
|
|
if( !Callback )
|
|
{
|
|
// callback wasn't found in the per instance map, try the global instances instead
|
|
Callback = GlobalCustomLayoutNameMap.Find( (*ClassIt)->GetFName() );
|
|
}
|
|
|
|
if( Callback )
|
|
{
|
|
FinalCallbackMap.Add( *ClassIt, Callback );
|
|
}
|
|
}
|
|
|
|
|
|
FinalCallbackMap.ValueSort( FCompareFDetailLayoutCallback() );
|
|
|
|
TSet<UStruct*> QueriedClasses;
|
|
|
|
if( FinalCallbackMap.Num() > 0 )
|
|
{
|
|
// Ask each class that we have properties for to customize its layout
|
|
for( auto LayoutIt(FinalCallbackMap.CreateConstIterator()); LayoutIt; ++LayoutIt )
|
|
{
|
|
const TWeakObjectPtr<UStruct> WeakClass = LayoutIt.Key();
|
|
|
|
if( WeakClass.IsValid() )
|
|
{
|
|
UStruct* Class = WeakClass.Get();
|
|
|
|
FClassInstanceToPropertyMap& InstancedPropertyMap = ClassToPropertyMap.FindChecked( Class->GetFName() );
|
|
for( FClassInstanceToPropertyMap::TIterator InstanceIt(InstancedPropertyMap); InstanceIt; ++InstanceIt )
|
|
{
|
|
CustomDetailLayout.SetCurrentCustomizationClass( CastChecked<UClass>( Class ), InstanceIt.Key() );
|
|
|
|
const FOnGetDetailCustomizationInstance& DetailDelegate = LayoutIt.Value()->DetailLayoutDelegate;
|
|
|
|
if( DetailDelegate.IsBound() )
|
|
{
|
|
QueriedClasses.Add( Class );
|
|
|
|
// Create a new instance of the custom detail layout for the current class
|
|
TSharedRef<IDetailCustomization> CustomizationInstance = DetailDelegate.Execute();
|
|
|
|
// Ask for details immediately
|
|
CustomizationInstance->CustomizeDetails( CustomDetailLayout );
|
|
|
|
// Save the instance from destruction until we refresh
|
|
CustomizationClassInstances.Add( CustomizationInstance );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure that the base class and its parents are always queried
|
|
TSet<UStruct*> ParentClassesToQuery;
|
|
if( BaseClass && !QueriedClasses.Contains( BaseClass ) )
|
|
{
|
|
ParentClassesToQuery.Add( BaseClass );
|
|
ClassesWithProperties.Add( BaseClass );
|
|
}
|
|
|
|
// Find base classes of queried classes that were not queried and add them to the query list
|
|
// this supports cases where a parent class has no properties but still wants to add customization
|
|
for( auto QueriedClassIt = ClassesWithProperties.CreateConstIterator();QueriedClassIt; ++QueriedClassIt )
|
|
{
|
|
UStruct* ParentStruct = (*QueriedClassIt)->GetSuperStruct();
|
|
|
|
while( ParentStruct && ParentStruct->IsA( UClass::StaticClass() ) && !QueriedClasses.Contains(ParentStruct) && !ClassesWithProperties.Contains( ParentStruct ) )
|
|
{
|
|
ParentClassesToQuery.Add( ParentStruct );
|
|
ParentStruct = ParentStruct->GetSuperStruct();
|
|
|
|
}
|
|
}
|
|
|
|
// Query extra base classes
|
|
for( auto ParentIt = ParentClassesToQuery.CreateConstIterator(); ParentIt; ++ParentIt )
|
|
{
|
|
QueryLayoutForClass( CustomDetailLayout, *ParentIt );
|
|
}
|
|
}
|
|
|
|
void SDetailsView::UpdateFilteredDetails()
|
|
{
|
|
RootPropertyNode->FilterNodes( CurrentFilter.FilterStrings );
|
|
RootPropertyNode->ProcessSeenFlags( true );
|
|
|
|
for( int32 NodeIndex = 0; NodeIndex < ExternalRootPropertyNodes.Num(); ++NodeIndex )
|
|
{
|
|
TSharedPtr<FObjectPropertyNode> ObjectNode = ExternalRootPropertyNodes[NodeIndex].Pin();
|
|
|
|
if( ObjectNode.IsValid() )
|
|
{
|
|
ObjectNode->FilterNodes( CurrentFilter.FilterStrings );
|
|
ObjectNode->ProcessSeenFlags( true );
|
|
}
|
|
}
|
|
|
|
if( DetailLayout.IsValid() )
|
|
{
|
|
DetailLayout->FilterDetailLayout( CurrentFilter );
|
|
}
|
|
|
|
RootTreeNodes = DetailLayout->GetRootTreeNodes();
|
|
|
|
DetailTree->RequestTreeRefresh();
|
|
}
|
|
|
|
/** Ticks the property view. This function performs a data consistency check */
|
|
void SDetailsView::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
|
|
{
|
|
for( int32 i = 0; i < CustomizationClassInstancesPendingDelete.Num(); ++i )
|
|
{
|
|
ensure( CustomizationClassInstancesPendingDelete[i].IsUnique() );
|
|
}
|
|
|
|
if( RootNodePendingKill.IsValid() )
|
|
{
|
|
RootNodePendingKill->RemoveAllObjects();
|
|
RootNodePendingKill.Reset();
|
|
}
|
|
|
|
// Empty all the customization instances that need to be deleted
|
|
CustomizationClassInstancesPendingDelete.Empty();
|
|
|
|
// Purge any objects that are marked pending kill from the object list
|
|
RootPropertyNode->PurgeKilledObjects();
|
|
|
|
if( DeferredActions.Num() > 0 )
|
|
{
|
|
// Any deferred actions are likely to cause the node tree to be at least partially rebuilt
|
|
// Save the expansion state of existing nodes so we can expand them later
|
|
SaveExpandedItems();
|
|
|
|
// Execute any deferred actions
|
|
for( int32 ActionIndex = 0; ActionIndex < DeferredActions.Num(); ++ActionIndex )
|
|
{
|
|
DeferredActions[ActionIndex].ExecuteIfBound();
|
|
}
|
|
DeferredActions.Empty();
|
|
}
|
|
|
|
bool bValidateExternalNodes = true;
|
|
FPropertyNode::DataValidationResult Result = RootPropertyNode->EnsureDataIsValid();
|
|
if( Result == FPropertyNode::PropertiesChanged || Result == FPropertyNode::EditInlineNewValueChanged )
|
|
{
|
|
RestoreExpandedItems();
|
|
UpdatePropertyMap();
|
|
}
|
|
else if(Result == FPropertyNode::ArraySizeChanged )
|
|
{
|
|
RestoreExpandedItems();
|
|
UpdateFilteredDetails();
|
|
}
|
|
else if( Result == FPropertyNode::ObjectInvalid )
|
|
{
|
|
TArray< TWeakObjectPtr< UObject > > ResetArray;
|
|
for ( TPropObjectIterator Itor( RootPropertyNode->ObjectIterator() ) ; Itor ; ++Itor )
|
|
{
|
|
TWeakObjectPtr<UObject> Object = *Itor;
|
|
if( Object.IsValid() )
|
|
{
|
|
ResetArray.Add( Object.Get() );
|
|
}
|
|
}
|
|
|
|
SetObjectArrayPrivate(ResetArray);
|
|
|
|
// All objects are being reset, no need to validate external nodes
|
|
bValidateExternalNodes = false;
|
|
}
|
|
|
|
if( bValidateExternalNodes )
|
|
{
|
|
for( int32 NodeIndex = 0; NodeIndex < ExternalRootPropertyNodes.Num(); ++NodeIndex )
|
|
{
|
|
TSharedPtr<FObjectPropertyNode> ObjectNode = ExternalRootPropertyNodes[NodeIndex].Pin();
|
|
|
|
if( ObjectNode.IsValid() )
|
|
{
|
|
Result = ObjectNode->EnsureDataIsValid();
|
|
if( Result == FPropertyNode::PropertiesChanged || Result == FPropertyNode::EditInlineNewValueChanged )
|
|
{
|
|
RestoreExpandedItems( ObjectNode );
|
|
UpdatePropertyMap();
|
|
// Note this will invalidate all the external root nodes so there is no need to continue
|
|
ExternalRootPropertyNodes.Empty();
|
|
break;
|
|
}
|
|
else if(Result == FPropertyNode::ArraySizeChanged )
|
|
{
|
|
RestoreExpandedItems( ObjectNode );
|
|
UpdateFilteredDetails();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Remove the current node if it is no longer valid
|
|
ExternalRootPropertyNodes.RemoveAt( NodeIndex );
|
|
--NodeIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( ThumbnailPool.IsValid() )
|
|
{
|
|
ThumbnailPool->Tick( InDeltaTime );
|
|
}
|
|
|
|
if( DetailLayout.IsValid() )
|
|
{
|
|
DetailLayout->Tick( InDeltaTime );
|
|
}
|
|
|
|
if( !ColorPropertyNode.IsValid() && bHasOpenColorPicker )
|
|
{
|
|
// Destroy the color picker window if the color property node has become invalid
|
|
DestroyColorPicker();
|
|
bHasOpenColorPicker = false;
|
|
}
|
|
|
|
|
|
if( FilteredNodesRequestingExpansionState.Num() > 0 )
|
|
{
|
|
// change expansion state on the nodes that request it
|
|
for( TMap<TSharedRef<IDetailTreeNode>, bool >::TConstIterator It(FilteredNodesRequestingExpansionState); It; ++It )
|
|
{
|
|
DetailTree->SetItemExpansion( It.Key(), It.Value() );
|
|
}
|
|
|
|
FilteredNodesRequestingExpansionState.Empty();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the color picker window for this property view.
|
|
*
|
|
* @param Node The slate property node to edit.
|
|
* @param bUseAlpha Whether or not alpha is supported
|
|
*/
|
|
void SDetailsView::CreateColorPickerWindow(const TSharedRef< FPropertyEditor >& PropertyEditor, bool bUseAlpha)
|
|
{
|
|
const TSharedRef< FPropertyNode > PinnedColorPropertyNode = PropertyEditor->GetPropertyNode();
|
|
ColorPropertyNode = PinnedColorPropertyNode;
|
|
|
|
UProperty* Property = PinnedColorPropertyNode->GetProperty();
|
|
check(Property);
|
|
|
|
FReadAddressList ReadAddresses;
|
|
PinnedColorPropertyNode->GetReadAddress( false, ReadAddresses, false );
|
|
|
|
TArray<FLinearColor*> LinearColor;
|
|
TArray<FColor*> DWORDColor;
|
|
for( int32 ColorIndex = 0; ColorIndex < ReadAddresses.Num(); ++ColorIndex )
|
|
{
|
|
const uint8* Addr = ReadAddresses.GetAddress( ColorIndex );
|
|
if( Addr )
|
|
{
|
|
if( Cast<UStructProperty>(Property)->Struct->GetFName() == NAME_Color )
|
|
{
|
|
DWORDColor.Add((FColor*)Addr);
|
|
}
|
|
else
|
|
{
|
|
check( Cast<UStructProperty>(Property)->Struct->GetFName() == NAME_LinearColor );
|
|
LinearColor.Add((FLinearColor*)Addr);
|
|
}
|
|
}
|
|
}
|
|
|
|
bHasOpenColorPicker = true;
|
|
|
|
FColorPickerArgs PickerArgs;
|
|
PickerArgs.ParentWidget = AsShared();
|
|
PickerArgs.bUseAlpha = bUseAlpha;
|
|
PickerArgs.DisplayGamma = TAttribute<float>::Create( TAttribute<float>::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma) );
|
|
PickerArgs.ColorArray = &DWORDColor;
|
|
PickerArgs.LinearColorArray = &LinearColor;
|
|
PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateSP( this, &SDetailsView::SetColorPropertyFromColorPicker);
|
|
PickerArgs.OnColorPickerWindowClosed = FOnWindowClosed::CreateSP( this, &SDetailsView::OnColorPickerWindowClosed );
|
|
|
|
OpenColorPicker(PickerArgs);
|
|
}
|
|
|
|
void SDetailsView::SetOnObjectArrayChanged(FOnObjectArrayChanged OnObjectArrayChangedDelegate)
|
|
{
|
|
OnObjectArrayChanged = OnObjectArrayChangedDelegate;
|
|
}
|
|
|
|
const UClass* SDetailsView::GetBaseClass() const
|
|
{
|
|
if( RootPropertyNode.IsValid() )
|
|
{
|
|
return RootPropertyNode->GetObjectBaseClass();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
UClass* SDetailsView::GetBaseClass()
|
|
{
|
|
if( RootPropertyNode.IsValid() )
|
|
{
|
|
return RootPropertyNode->GetObjectBaseClass();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void SDetailsView::RegisterInstancedCustomPropertyLayout( UClass* Class, FOnGetDetailCustomizationInstance DetailLayoutDelegate )
|
|
{
|
|
check( Class );
|
|
|
|
FDetailLayoutCallback Callback;
|
|
Callback.DetailLayoutDelegate = DetailLayoutDelegate;
|
|
// @todo: DetailsView: Fix me: this specifies the order in which detail layouts should be queried
|
|
Callback.Order = InstancedClassToDetailLayoutMap.Num();
|
|
|
|
InstancedClassToDetailLayoutMap.Add( Class, Callback );
|
|
}
|
|
|
|
void SDetailsView::UnregisterInstancedCustomPropertyLayout( UClass* Class )
|
|
{
|
|
check( Class );
|
|
|
|
InstancedClassToDetailLayoutMap.Remove( Class );
|
|
}
|
|
|
|
bool SDetailsView::SupportsKeyboardFocus() const
|
|
{
|
|
return DetailsViewArgs.bSearchInitialKeyFocus && SearchBox->SupportsKeyboardFocus() && GetFilterBoxVisibility() == EVisibility::Visible;
|
|
}
|
|
|
|
FReply SDetailsView::OnKeyboardFocusReceived( const FGeometry& MyGeometry, const FKeyboardFocusEvent& InKeyboardFocusEvent )
|
|
{
|
|
FReply Reply = FReply::Handled();
|
|
|
|
if( InKeyboardFocusEvent.GetCause() != EKeyboardFocusCause::Cleared )
|
|
{
|
|
Reply.SetKeyboardFocus( SearchBox.ToSharedRef(), InKeyboardFocusEvent.GetCause() );
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
|
|
void SDetailsView::AddExternalRootPropertyNode( TSharedRef<FObjectPropertyNode> ExternalRootNode )
|
|
{
|
|
ExternalRootPropertyNodes.Add( ExternalRootNode );
|
|
}
|
|
|
|
bool SDetailsView::IsCategoryHiddenByClass( FName CategoryName ) const
|
|
{
|
|
return RootPropertyNode->GetHiddenCategories().Contains( CategoryName );
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|