Files
UnrealEngineUWP/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.cpp
Jeff Fisher f750198e15 Fixed .IsEnabled for single column widgets (ie buttons).
-The enabled state was being stomped by IsPropertyEditingEnabled.
#jira UE-71585
#rb Matt.Kuhlenschmidt Dan.OConner

[CL 5689713 by Jeff Fisher in Dev-VR branch]
2019-04-02 13:49:29 -04:00

824 lines
26 KiB
C++

// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "SDetailSingleItemRow.h"
#include "ObjectPropertyNode.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SButton.h"
#include "Settings/EditorExperimentalSettings.h"
#include "DetailWidgetRow.h"
#include "IDetailKeyframeHandler.h"
#include "IDetailPropertyExtensionHandler.h"
#include "DetailPropertyRow.h"
#include "DetailGroup.h"
#include "HAL/PlatformApplicationMisc.h"
#include "Editor.h"
#include "PropertyHandleImpl.h"
void SConstrainedBox::Construct(const FArguments& InArgs)
{
MinWidth = InArgs._MinWidth;
MaxWidth = InArgs._MaxWidth;
ChildSlot
[
InArgs._Content.Widget
];
}
FVector2D SConstrainedBox::ComputeDesiredSize(float LayoutScaleMultiplier) const
{
const float MinWidthVal = MinWidth.Get().Get(0.0f);
const float MaxWidthVal = MaxWidth.Get().Get(0.0f);
if (MinWidthVal == 0.0f && MaxWidthVal == 0.0f)
{
return SCompoundWidget::ComputeDesiredSize(LayoutScaleMultiplier);
}
else
{
FVector2D ChildSize = ChildSlot.GetWidget()->GetDesiredSize();
float XVal = FMath::Max(MinWidthVal, ChildSize.X);
if (MaxWidthVal >= MinWidthVal)
{
XVal = FMath::Min(MaxWidthVal, XVal);
}
return FVector2D(XVal, ChildSize.Y);
}
}
namespace DetailWidgetConstants
{
const FMargin LeftRowPadding( 0.0f, 2.5f, 2.0f, 2.5f );
const FMargin RightRowPadding( 3.0f, 2.5f, 2.0f, 2.5f );
}
namespace SDetailSingleItemRow_Helper
{
//Get the node item number in case it is expand we have to recursively count all expanded children
void RecursivelyGetItemShow(TSharedRef<FDetailTreeNode> ParentItem, int32 &ItemShowNum)
{
if (ParentItem->GetVisibility() == ENodeVisibility::Visible)
{
ItemShowNum++;
}
if (ParentItem->ShouldBeExpanded())
{
TArray< TSharedRef<FDetailTreeNode> > Childrens;
ParentItem->GetChildren(Childrens);
for (TSharedRef<FDetailTreeNode> ItemChild : Childrens)
{
RecursivelyGetItemShow(ItemChild, ItemShowNum);
}
}
}
}
FReply SDetailSingleItemRow::OnFavoriteToggle()
{
if (Customization->GetPropertyNode().IsValid() && Customization->GetPropertyNode()->CanDisplayFavorite())
{
bool toggle = !Customization->GetPropertyNode()->IsFavorite();
Customization->GetPropertyNode()->SetFavorite(toggle);
if (OwnerTreeNode.IsValid())
{
//////////////////////////////////////////////////////////////////////////
// Calculate properly the scrolling offset (by item) to make sure the mouse stay over the same property
//Get the node item number in case it is expand we have to recursively count all childrens
int32 ExpandSize = 0;
if (OwnerTreeNode.Pin()->ShouldBeExpanded())
{
SDetailSingleItemRow_Helper::RecursivelyGetItemShow(OwnerTreeNode.Pin().ToSharedRef(), ExpandSize);
}
else
{
//if the item is not expand count is 1
ExpandSize = 1;
}
//Get the number of favorite child (simple and advance) to know if the favorite category will be create or remove
FString CategoryFavoritesName = TEXT("Favorites");
FName CatFavName = *CategoryFavoritesName;
int32 SimplePropertiesNum = 0;
int32 AdvancePropertiesNum = 0;
FDetailLayoutBuilderImpl& DetailLayout = OwnerTreeNode.Pin()->GetParentCategory()->GetParentLayoutImpl();
bool HasCategoryFavorite = DetailLayout.HasCategory(CatFavName);
if(HasCategoryFavorite)
{
DetailLayout.DefaultCategory(CatFavName).GetCategoryInformation(SimplePropertiesNum, AdvancePropertiesNum);
}
//Check if the property we toggle is an advance property
bool IsAdvanceProperty = Customization->GetPropertyNode()->HasNodeFlags(EPropertyNodeFlags::IsAdvanced) == 0 ? false : true;
//Compute the scrolling offset by item
int32 ScrollingOffsetAdd = ExpandSize;
int32 ScrollingOffsetRemove = -ExpandSize;
if (HasCategoryFavorite)
{
//Adding the advance button in a category add 1 item
ScrollingOffsetAdd += (IsAdvanceProperty && AdvancePropertiesNum == 0) ? 1 : 0;
if (IsAdvanceProperty && AdvancePropertiesNum == 1)
{
//Removing the advance button count as 1 item
ScrollingOffsetRemove -= 1;
}
if (AdvancePropertiesNum + SimplePropertiesNum == 1)
{
//Removing a full category count as 2 items
ScrollingOffsetRemove -= 2;
}
}
else
{
//Adding new category (2 items) adding advance button (1 item)
ScrollingOffsetAdd += IsAdvanceProperty ? 3 : 2;
//We should never remove an item from favorite if there is no favorite category
//Set the remove offset to 0
ScrollingOffsetRemove = 0;
}
//Apply the calculated offset
OwnerTreeNode.Pin()->GetDetailsView()->MoveScrollOffset(toggle ? ScrollingOffsetAdd : ScrollingOffsetRemove);
//Refresh the tree
OwnerTreeNode.Pin()->GetDetailsView()->ForceRefresh();
}
}
return FReply::Handled();
}
void SDetailSingleItemRow::OnArrayDragEnter(const FDragDropEvent& DragDropEvent)
{
bIsHoveredDragTarget = true;
}
void SDetailSingleItemRow::OnArrayDragLeave(const FDragDropEvent& DragDropEvent)
{
bIsHoveredDragTarget = false;
}
FReply SDetailSingleItemRow::OnArrayDrop(const FDragDropEvent& DragDropEvent)
{
bIsHoveredDragTarget = false;
TSharedPtr<FArrayRowDragDropOp> ArrayDropOp = DragDropEvent.GetOperationAs< FArrayRowDragDropOp >();
TSharedPtr<SDetailSingleItemRow> RowPtr = nullptr;
if (ArrayDropOp.IsValid() && ArrayDropOp->Row.IsValid())
{
RowPtr = ArrayDropOp->Row.Pin();
}
if (!RowPtr.IsValid())
{
return FReply::Unhandled();
}
TSharedPtr<FPropertyNode> SwappingPropertyNode = RowPtr->SwappablePropertyNode;
if (SwappingPropertyNode.IsValid() && SwappablePropertyNode.IsValid())
{
if (SwappingPropertyNode != SwappablePropertyNode)
{
int32 OriginalIndex = SwappingPropertyNode->GetArrayIndex();
int32 NewIndex = SwappablePropertyNode->GetArrayIndex();
if (NewIndex > OriginalIndex)
{
NewIndex += 1;
}
TSharedPtr<IPropertyHandle> SwappingHandle = PropertyEditorHelpers::GetPropertyHandle(SwappingPropertyNode.ToSharedRef(), OwnerTreeNode.Pin()->GetDetailsView()->GetNotifyHook(), OwnerTreeNode.Pin()->GetDetailsView()->GetPropertyUtilities());
TSharedPtr<IPropertyHandleArray> ParentHandle = SwappingHandle->GetParentHandle()->AsArray();
if (ParentHandle.IsValid() && SwappablePropertyNode->GetParentNode() == SwappingPropertyNode->GetParentNode())
{
// Need to swap the moving and target expansion states before saving
bool bOriginalSwappableExpansion = SwappablePropertyNode->HasNodeFlags(EPropertyNodeFlags::Expanded) != 0;
bool bOriginalSwappingExpansion = SwappingPropertyNode->HasNodeFlags(EPropertyNodeFlags::Expanded) != 0;
SwappablePropertyNode->SetNodeFlags(EPropertyNodeFlags::Expanded, bOriginalSwappingExpansion);
SwappingPropertyNode->SetNodeFlags(EPropertyNodeFlags::Expanded, bOriginalSwappableExpansion);
IDetailsViewPrivate* DetailsView = OwnerTreeNode.Pin()->GetDetailsView();
DetailsView->SaveExpandedItems(SwappablePropertyNode->GetParentNodeSharedPtr().ToSharedRef());
FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MoveRow", "Move Row"));
SwappingHandle->GetParentHandle()->NotifyPreChange();
ParentHandle->MoveElementTo(OriginalIndex, NewIndex);
FPropertyChangedEvent MoveEvent(SwappingHandle->GetParentHandle()->GetProperty(), EPropertyChangeType::Unspecified);
SwappingHandle->GetParentHandle()->NotifyPostChange();
if (DetailsView->GetPropertyUtilities().IsValid())
{
DetailsView->GetPropertyUtilities()->NotifyFinishedChangingProperties(MoveEvent);
}
}
}
}
return FReply::Handled();
}
TSharedPtr<FPropertyNode> SDetailSingleItemRow::GetCopyPastePropertyNode() const
{
TSharedPtr<FPropertyNode> PropertyNode = Customization->GetPropertyNode();
if (!PropertyNode.IsValid() && Customization->DetailGroup.IsValid())
{
PropertyNode = Customization->DetailGroup->GetHeaderPropertyNode();
}
// See if a custom builder has an associated node
if (!PropertyNode.IsValid() && Customization->HasCustomBuilder())
{
TSharedPtr<IPropertyHandle> PropertyHandle = Customization->CustomBuilderRow->GetPropertyHandle();
if (PropertyHandle.IsValid())
{
PropertyNode = StaticCastSharedPtr<FPropertyHandleBase>(PropertyHandle)->GetPropertyNode();
}
}
return PropertyNode;
}
const FSlateBrush* SDetailSingleItemRow::GetFavoriteButtonBrush() const
{
if (Customization->GetPropertyNode().IsValid() && Customization->GetPropertyNode()->CanDisplayFavorite())
{
return FEditorStyle::GetBrush(Customization->GetPropertyNode()->IsFavorite() ? TEXT("DetailsView.PropertyIsFavorite") : IsHovered() ? TEXT("DetailsView.PropertyIsNotFavorite") : TEXT("DetailsView.NoFavoritesSystem"));
}
//Adding a transparent brush make sure all property are left align correctly
return FEditorStyle::GetBrush(TEXT("DetailsView.NoFavoritesSystem"));
}
void SDetailSingleItemRow::Construct( const FArguments& InArgs, FDetailLayoutCustomization* InCustomization, bool bHasMultipleColumns, TSharedRef<FDetailTreeNode> InOwnerTreeNode, const TSharedRef<STableViewBase>& InOwnerTableView )
{
OwnerTreeNode = InOwnerTreeNode;
bAllowFavoriteSystem = InArgs._AllowFavoriteSystem;
ColumnSizeData = InArgs._ColumnSizeData;
TSharedRef<SWidget> Widget = SNullWidget::NullWidget;
Customization = InCustomization;
EHorizontalAlignment HorizontalAlignment = HAlign_Fill;
EVerticalAlignment VerticalAlignment = VAlign_Fill;
TAttribute<bool> NameWidgetEnabled;
FOnTableRowDragEnter ArrayDragDelegate;
FOnTableRowDragLeave ArrayDragLeaveDelegate;
FOnTableRowDrop ArrayDropDelegate;
const bool bIsValidTreeNode = InOwnerTreeNode->GetParentCategory().IsValid() && InOwnerTreeNode->GetParentCategory()->IsParentLayoutValid();
if(bIsValidTreeNode)
{
if(InCustomization->IsValidCustomization())
{
FDetailWidgetRow Row = InCustomization->GetWidgetRow();
TSharedPtr<SWidget> NameWidget;
TSharedPtr<SWidget> ValueWidget;
NameWidget = Row.NameWidget.Widget;
if(Row.IsEnabledAttr.IsBound())
{
NameWidgetEnabled = Row.IsEnabledAttr;
NameWidget->SetEnabled(Row.IsEnabledAttr);
}
ValueWidget =
SNew(SConstrainedBox)
.MinWidth(Row.ValueWidget.MinWidth)
.MaxWidth(Row.ValueWidget.MaxWidth)
[
Row.ValueWidget.Widget
];
ValueWidget = CreateExtensionWidget(ValueWidget.ToSharedRef(), *Customization, InOwnerTreeNode);
if(Row.IsEnabledAttr.IsBound())
{
ValueWidget->SetEnabled(Row.IsEnabledAttr);
}
TSharedRef<SWidget> KeyFrameButton = CreateKeyframeButton(*Customization, InOwnerTreeNode);
TAttribute<bool> IsPropertyEditingEnabled = InOwnerTreeNode->IsPropertyEditingEnabled();
bool const bEnableFavoriteSystem = GIsRequestingExit ? false : (GetDefault<UEditorExperimentalSettings>()->bEnableFavoriteSystem && bAllowFavoriteSystem);
TSharedRef<SHorizontalBox> InternalLeftColumnRowBox =
SNew(SHorizontalBox)
.Clipping(EWidgetClipping::OnDemand);
if(bEnableFavoriteSystem)
{
InternalLeftColumnRowBox->AddSlot()
.Padding(0.0f, 0.0f)
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(SButton)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.IsFocusable(false)
.ButtonStyle(FEditorStyle::Get(), "NoBorder")
.OnClicked(this, &SDetailSingleItemRow::OnFavoriteToggle)
[
SNew(SImage).Image(this, &SDetailSingleItemRow::GetFavoriteButtonBrush)
]
];
}
TSharedPtr<SOverlay> LeftSideOverlay;
LeftSideOverlay = SNew(SOverlay);
LeftSideOverlay->AddSlot()
.Padding(3.0f, 0.0f)
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SNew(SExpanderArrow, SharedThis(this))
.BaseIndentLevel(1)
];
TSharedPtr<FPropertyNode> PropertyNode = Customization->GetPropertyNode();
if (PropertyNode.IsValid() && PropertyNode->IsReorderable())
{
TSharedPtr<SDetailSingleItemRow> InRow = SharedThis(this);
TSharedRef<SWidget> Handle = PropertyEditorHelpers::MakePropertyReorderHandle(PropertyNode.ToSharedRef(), InRow);
Handle->SetEnabled(IsPropertyEditingEnabled);
LeftSideOverlay->AddSlot()
.Padding(0.0f, 0.0f, 10.0f, 0.0f)
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
[
Handle
];
ArrayDragDelegate = FOnTableRowDragEnter::CreateSP(this, &SDetailSingleItemRow::OnArrayDragEnter);
ArrayDragLeaveDelegate = FOnTableRowDragLeave::CreateSP(this, &SDetailSingleItemRow::OnArrayDragLeave);
ArrayDropDelegate = FOnTableRowDrop::CreateSP(this, &SDetailSingleItemRow::OnArrayDrop);
SwappablePropertyNode = PropertyNode;
}
InternalLeftColumnRowBox->AddSlot()
.Padding(0.0f, 0.0f)
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.AutoWidth()
[
LeftSideOverlay.ToSharedRef()
];
if(bHasMultipleColumns)
{
// If the NameWidget has already been disabled, don't re-enable it if IsPropertyEditingEnabled is true.
NameWidget->SetEnabled(NameWidgetEnabled.IsBound() ?
TAttribute<bool>::Create(TAttribute<bool>::FGetter::CreateLambda(
[NameWidgetEnabled, IsPropertyEditingEnabled]()
{
return NameWidgetEnabled.Get() && IsPropertyEditingEnabled.Get();
}))
: IsPropertyEditingEnabled);
TSharedPtr<SHorizontalBox> HBox;
InternalLeftColumnRowBox->AddSlot()
.HAlign(Row.NameWidget.HorizontalAlignment)
.VAlign(Row.NameWidget.VerticalAlignment)
.Padding(DetailWidgetConstants::LeftRowPadding)
[
NameWidget.ToSharedRef()
];
InternalLeftColumnRowBox->AddSlot()
.Padding(3.0f, 0.0f)
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.AutoWidth()
[
KeyFrameButton
];
TSharedRef<SSplitter> Splitter =
SNew(SSplitter)
.Style(FEditorStyle::Get(), "DetailsView.Splitter")
.PhysicalSplitterHandleSize(1.0f)
.HitDetectionSplitterHandleSize(5.0f)
+ SSplitter::Slot()
.Value(ColumnSizeData.LeftColumnWidth)
.OnSlotResized(SSplitter::FOnSlotResized::CreateSP(this, &SDetailSingleItemRow::OnLeftColumnResized))
[
InternalLeftColumnRowBox
]
+ SSplitter::Slot()
.Value(ColumnSizeData.RightColumnWidth)
.OnSlotResized(ColumnSizeData.OnWidthChanged)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
[
SAssignNew(HBox, SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(DetailWidgetConstants::RightRowPadding)
.HAlign(Row.ValueWidget.HorizontalAlignment)
.VAlign(Row.ValueWidget.VerticalAlignment)
[
SNew(SBox)
.IsEnabled(IsPropertyEditingEnabled)
[
ValueWidget.ToSharedRef()
]
]
]
];
Widget = Splitter;
}
else
{
InternalLeftColumnRowBox->SetEnabled(IsPropertyEditingEnabled);
InternalLeftColumnRowBox->AddSlot()
.HAlign(Row.WholeRowWidget.HorizontalAlignment)
.VAlign(Row.WholeRowWidget.VerticalAlignment)
.Padding(DetailWidgetConstants::LeftRowPadding)
[
Row.WholeRowWidget.Widget
];
InternalLeftColumnRowBox->AddSlot()
.Padding(3.0f, 0.0f)
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
[
KeyFrameButton
];
Widget = InternalLeftColumnRowBox;
}
}
}
else
{
// details panel layout became invalid. This is probably a scenario where a widget is coming into view in the parent tree but some external event previous in the frame has invalidated the contents of the details panel.
// The next frame update of the details panel will fix it
Widget = SNew(SSpacer);
}
this->ChildSlot
[
SNew( SBorder )
.BorderImage( this, &SDetailSingleItemRow::GetBorderImage )
.Padding( FMargin( 0.0f, 0.0f, SDetailTableRowBase::ScrollbarPaddingSize, 0.0f ) )
[
Widget
]
];
STableRow< TSharedPtr< FDetailTreeNode > >::ConstructInternal(
STableRow::FArguments()
.Style(FEditorStyle::Get(), "DetailsView.TreeView.TableRow")
.ShowSelection(false)
.OnDragEnter(ArrayDragDelegate)
.OnDragLeave(ArrayDragLeaveDelegate)
.OnDrop(ArrayDropDelegate),
InOwnerTableView
);
}
bool SDetailSingleItemRow::OnContextMenuOpening(FMenuBuilder& MenuBuilder)
{
const bool bIsCopyPasteBound = Customization->GetWidgetRow().IsCopyPasteBound();
FUIAction CopyAction;
FUIAction PasteAction;
if(bIsCopyPasteBound)
{
CopyAction = Customization->GetWidgetRow().CopyMenuAction;
PasteAction = Customization->GetWidgetRow().PasteMenuAction;
}
else
{
TSharedPtr<FPropertyNode> PropertyNode = GetCopyPastePropertyNode();
static const FName DisableCopyPasteMetaDataName("DisableCopyPaste");
if (PropertyNode.IsValid() && !PropertyNode->ParentOrSelfHasMetaData(DisableCopyPasteMetaDataName))
{
CopyAction.ExecuteAction = FExecuteAction::CreateSP(this, &SDetailSingleItemRow::OnCopyProperty);
PasteAction.ExecuteAction = FExecuteAction::CreateSP(this, &SDetailSingleItemRow::OnPasteProperty);
PasteAction.CanExecuteAction = FCanExecuteAction::CreateSP(this, &SDetailSingleItemRow::CanPasteProperty);
}
}
bool bAddedMenuEntry = false;
if (CopyAction.IsBound() && PasteAction.IsBound())
{
// Hide separator line if it only contains the SearchWidget, making the next 2 elements the top of the list
if (MenuBuilder.GetMultiBox()->GetBlocks().Num() > 1)
{
MenuBuilder.AddMenuSeparator();
}
MenuBuilder.AddMenuEntry(
NSLOCTEXT("PropertyView", "CopyProperty", "Copy"),
NSLOCTEXT("PropertyView", "CopyProperty_ToolTip", "Copy this property value"),
FSlateIcon(FCoreStyle::Get().GetStyleSetName(), "GenericCommands.Copy"),
CopyAction);
MenuBuilder.AddMenuEntry(
NSLOCTEXT("PropertyView", "PasteProperty", "Paste"),
NSLOCTEXT("PropertyView", "PasteProperty_ToolTip", "Paste the copied value here"),
FSlateIcon(FCoreStyle::Get().GetStyleSetName(), "GenericCommands.Paste"),
PasteAction);
bAddedMenuEntry = true;
}
const TArray<FDetailWidgetRow::FCustomMenuData>& CustomMenuActions = Customization->GetWidgetRow().CustomMenuItems;
if (CustomMenuActions.Num() > 0)
{
// Hide separator line if it only contains the SearchWidget, making the next 2 elements the top of the list
if (MenuBuilder.GetMultiBox()->GetBlocks().Num() > 1)
{
MenuBuilder.AddMenuSeparator();
}
for (const FDetailWidgetRow::FCustomMenuData& CustomMenuData : CustomMenuActions)
{
//Add the menu entry
MenuBuilder.AddMenuEntry(
CustomMenuData.Name,
CustomMenuData.Tooltip,
CustomMenuData.SlateIcon,
CustomMenuData.Action);
bAddedMenuEntry = true;
}
}
return bAddedMenuEntry;
}
void SDetailSingleItemRow::OnLeftColumnResized( float InNewWidth )
{
// This has to be bound or the splitter will take it upon itself to determine the size
// We do nothing here because it is handled by the column size data
}
void SDetailSingleItemRow::OnCopyProperty()
{
if (OwnerTreeNode.IsValid())
{
TSharedPtr<FPropertyNode> PropertyNode = GetCopyPastePropertyNode();
if (PropertyNode.IsValid())
{
TSharedPtr<IPropertyHandle> Handle = PropertyEditorHelpers::GetPropertyHandle(PropertyNode.ToSharedRef(), OwnerTreeNode.Pin()->GetDetailsView()->GetNotifyHook(), OwnerTreeNode.Pin()->GetDetailsView()->GetPropertyUtilities());
FString Value;
if (Handle->GetValueAsFormattedString(Value, PPF_Copy) == FPropertyAccess::Success)
{
FPlatformApplicationMisc::ClipboardCopy(*Value);
}
}
}
}
void SDetailSingleItemRow::OnPasteProperty()
{
FString ClipboardContent;
FPlatformApplicationMisc::ClipboardPaste(ClipboardContent);
if (!ClipboardContent.IsEmpty() && OwnerTreeNode.IsValid())
{
TSharedPtr<FPropertyNode> PropertyNode = GetCopyPastePropertyNode();
if (!PropertyNode.IsValid() && Customization->DetailGroup.IsValid())
{
PropertyNode = Customization->DetailGroup->GetHeaderPropertyNode();
}
if (PropertyNode.IsValid())
{
TSharedPtr<IPropertyHandle> Handle = PropertyEditorHelpers::GetPropertyHandle(PropertyNode.ToSharedRef(), OwnerTreeNode.Pin()->GetDetailsView()->GetNotifyHook(), OwnerTreeNode.Pin()->GetDetailsView()->GetPropertyUtilities());
Handle->SetValueFromFormattedString(ClipboardContent);
TArray<TSharedPtr<IPropertyHandle>> CopiedHandles;
CopiedHandles.Add(Handle);
while (CopiedHandles.Num() > 0)
{
Handle = CopiedHandles.Pop();
// Add all child properties to the list so we can check them next
uint32 NumChildren;
Handle->GetNumChildren(NumChildren);
for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ChildIndex++)
{
CopiedHandles.Add(Handle->GetChildHandle(ChildIndex));
}
UObject* NewValueAsObject = nullptr;
if (FPropertyAccess::Success == Handle->GetValue(NewValueAsObject))
{
// if the object is instanced, then we need to do a deep copy.
if (Handle->GetProperty() != nullptr
&& (Handle->GetProperty()->PropertyFlags & (CPF_InstancedReference | CPF_ContainsInstancedReference)) != 0)
{
UObject* DuplicateOuter = nullptr;
TArray<UObject*> Outers;
Handle->GetOuterObjects(Outers);
// Update the duplicate's outer to point to this outer. The source's outer may be some other object/asset
// but we want this to own the duplicate.
if (Outers.Num() > 0)
{
DuplicateOuter = Outers[0];
}
// This does a deep copy of NewValueAsObject. It's subobjects and property data will be
// copied.
UObject* DuplicateOfNewValue = DuplicateObject<UObject>(NewValueAsObject, DuplicateOuter);
TArray<FString> DuplicateValueAsString;
DuplicateValueAsString.Add(DuplicateOfNewValue->GetPathName());
Handle->SetPerObjectValues(DuplicateValueAsString);
}
}
}
// Need to refresh the details panel in case a property was pasted over another.
OwnerTreeNode.Pin()->GetDetailsView()->ForceRefresh();
}
}
}
bool SDetailSingleItemRow::CanPasteProperty() const
{
// Prevent paste from working if the property's edit condition is not met.
TSharedPtr<FDetailPropertyRow> PropertyRow = Customization->PropertyRow;
if (!PropertyRow.IsValid() && Customization->DetailGroup.IsValid())
{
PropertyRow = Customization->DetailGroup->GetHeaderPropertyRow();
}
if (PropertyRow.IsValid())
{
FPropertyEditor* PropertyEditor = PropertyRow->GetPropertyEditor().Get();
if (PropertyEditor)
{
return !PropertyEditor->IsEditConst() && (!PropertyEditor->HasEditCondition() || PropertyEditor->IsEditConditionMet());
}
}
FString ClipboardContent;
if( OwnerTreeNode.IsValid() )
{
FPlatformApplicationMisc::ClipboardPaste(ClipboardContent);
}
return !ClipboardContent.IsEmpty();
}
const FSlateBrush* SDetailSingleItemRow::GetBorderImage() const
{
if( IsHighlighted() )
{
return FEditorStyle::GetBrush("DetailsView.CategoryMiddle_Highlighted");
}
else if (IsHovered() && !bIsHoveredDragTarget)
{
return FEditorStyle::GetBrush("DetailsView.CategoryMiddle_Hovered");
}
else if (bIsHoveredDragTarget)
{
return FEditorStyle::GetBrush("DetailsView.CategoryMiddle_Highlighted");
}
else
{
return FEditorStyle::GetBrush("DetailsView.CategoryMiddle");
}
}
TSharedRef<SWidget> SDetailSingleItemRow::CreateExtensionWidget(TSharedRef<SWidget> ValueWidget, FDetailLayoutCustomization& InCustomization, TSharedRef<FDetailTreeNode> InTreeNode)
{
if(InTreeNode->GetParentCategory().IsValid())
{
IDetailsViewPrivate* DetailsView = InTreeNode->GetDetailsView();
TSharedPtr<IDetailPropertyExtensionHandler> ExtensionHandler = DetailsView->GetExtensionHandler();
if(ExtensionHandler.IsValid() && InCustomization.HasPropertyNode())
{
TSharedPtr<IPropertyHandle> Handle = PropertyEditorHelpers::GetPropertyHandle(InCustomization.GetPropertyNode().ToSharedRef(), nullptr, nullptr);
UClass* ObjectClass = InCustomization.GetPropertyNode()->FindObjectItemParent()->GetObjectBaseClass();
if(Handle->IsValidHandle() && ExtensionHandler->IsPropertyExtendable(ObjectClass, *Handle))
{
ValueWidget = SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
ValueWidget
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
ExtensionHandler->GenerateExtensionWidget(ObjectClass, Handle)
];
}
}
}
return ValueWidget;
}
TSharedRef<SWidget> SDetailSingleItemRow::CreateKeyframeButton( FDetailLayoutCustomization& InCustomization, TSharedRef<FDetailTreeNode> InTreeNode )
{
IDetailsViewPrivate* DetailsView = InTreeNode->GetDetailsView();
KeyframeHandler = DetailsView->GetKeyframeHandler();
EVisibility SetKeyVisibility = EVisibility::Collapsed;
if (InCustomization.HasPropertyNode() && KeyframeHandler.IsValid() )
{
TSharedPtr<IPropertyHandle> Handle = PropertyEditorHelpers::GetPropertyHandle(InCustomization.GetPropertyNode().ToSharedRef(), nullptr, nullptr);
FObjectPropertyNode* ObjectItemParent = InCustomization.GetPropertyNode()->FindObjectItemParent();
UClass* ObjectClass = ObjectItemParent != nullptr ? ObjectItemParent->GetObjectBaseClass() : nullptr;
SetKeyVisibility = ObjectClass != nullptr && KeyframeHandler.Pin()->IsPropertyKeyable(ObjectClass, *Handle) ? EVisibility::Visible : EVisibility::Collapsed;
}
return
SNew(SButton)
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.ContentPadding(0.0f)
.ButtonStyle(FEditorStyle::Get(), "Sequencer.AddKey.Details")
.Visibility(SetKeyVisibility)
.IsEnabled( this, &SDetailSingleItemRow::IsKeyframeButtonEnabled, InTreeNode )
.ToolTipText( NSLOCTEXT("PropertyView", "AddKeyframeButton_ToolTip", "Adds a keyframe for this property to the current animation") )
.OnClicked( this, &SDetailSingleItemRow::OnAddKeyframeClicked );
}
bool SDetailSingleItemRow::IsKeyframeButtonEnabled(TSharedRef<FDetailTreeNode> InTreeNode) const
{
return
InTreeNode->IsPropertyEditingEnabled().Get() &&
KeyframeHandler.IsValid() &&
KeyframeHandler.Pin()->IsPropertyKeyingEnabled();
}
FReply SDetailSingleItemRow::OnAddKeyframeClicked()
{
if( KeyframeHandler.IsValid() )
{
TSharedPtr<IPropertyHandle> Handle = PropertyEditorHelpers::GetPropertyHandle(Customization->GetPropertyNode().ToSharedRef(), nullptr, nullptr);
KeyframeHandler.Pin()->OnKeyPropertyClicked(*Handle);
}
return FReply::Handled();
}
bool SDetailSingleItemRow::IsHighlighted() const
{
return OwnerTreeNode.Pin()->IsHighlighted();
}
void SArrayRowHandle::Construct(const FArguments& InArgs)
{
ParentRow = InArgs._ParentRow;
ChildSlot
[
InArgs._Content.Widget
];
}
FReply SArrayRowHandle::OnDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
{
{
TSharedPtr<FDragDropOperation> DragDropOp = CreateDragDropOperation(ParentRow.Pin());
if (DragDropOp.IsValid())
{
return FReply::Handled().BeginDragDrop(DragDropOp.ToSharedRef());
}
}
}
return FReply::Unhandled();
}
TSharedPtr<FArrayRowDragDropOp> SArrayRowHandle::CreateDragDropOperation(TSharedPtr<SDetailSingleItemRow> InRow)
{
TSharedPtr<FArrayRowDragDropOp> Operation = MakeShareable(new FArrayRowDragDropOp(InRow));
return Operation;
}