Auto bind functoinality:

* Allow drag-drop from Exposed Properties to Conftrollers
* Automatically create new Controller of correct type and apply Bind behaviour to it.
* Also allow drag-drop on to existing Controllers with auto bind upon drop.

#jira UE-162439
#rb paul.vincent
#preflight 6323489a67163bf660045eed

[CL 22038440 by venugopalan sreedha in ue5-main branch]
This commit is contained in:
venugopalan sreedha
2022-09-15 17:09:12 -04:00
parent 297bafa165
commit f5cf375f64
7 changed files with 592 additions and 93 deletions

View File

@@ -181,7 +181,7 @@ void SRCBehaviourPanelList::OnTreeSelectionChanged(TSharedPtr<FRCBehaviourModel>
}
}
void SRCBehaviourPanelList::OnBehaviourAdded(URCBehaviour* InBehaviour)
void SRCBehaviourPanelList::OnBehaviourAdded(const URCBehaviour* InBehaviour)
{
Reset();
}

View File

@@ -75,7 +75,7 @@ private:
void OnTreeSelectionChanged(TSharedPtr<FRCBehaviourModel> InItem , ESelectInfo::Type);
/** Responds to the selection of a newly created Behaviour. Resets UI state */
void OnBehaviourAdded(URCBehaviour* InBehaviour);
void OnBehaviourAdded(const URCBehaviour* InBehaviour);
/** Responds to the removal of all Behaviours. Rests UI state */
void OnEmptyBehaviours();

View File

@@ -2,14 +2,19 @@
#include "SRCControllerPanelList.h"
#include "Behaviour/Builtin/Bind/RCBehaviourBind.h"
#include "Behaviour/Builtin/Bind/RCBehaviourBindNode.h"
#include "Commands/RemoteControlCommands.h"
#include "Controller/RCController.h"
#include "Controller/RCControllerContainer.h"
#include "IDetailTreeNode.h"
#include "Interfaces/IMainFrameModule.h"
#include "IPropertyRowGenerator.h"
#include "IRemoteControlModule.h"
#include "RCControllerModel.h"
#include "RCVirtualProperty.h"
#include "RCVirtualPropertyContainer.h"
#include "RemoteControlField.h"
#include "RemoteControlPreset.h"
#include "SDropTarget.h"
#include "SlateOptMacros.h"
@@ -31,6 +36,13 @@
namespace UE::RCControllerPanelList
{
enum class EDragDropSupportedModes : uint8
{
ReorderOnly,
All,
None
};
namespace Columns
{
const FName Name = TEXT("Controller Name");
@@ -74,7 +86,7 @@ namespace UE::RCControllerPanelList
.Widget(DragDropBorderWidget)
];
return WrapWithDropTarget(DragHandleWidget);
return WrapWithDropTarget(DragHandleWidget, EDragDropSupportedModes::ReorderOnly /*Allow reorder operation only*/);
}
return SNullWidget::NullWidget;
@@ -82,16 +94,31 @@ namespace UE::RCControllerPanelList
TSharedPtr<SBorder> DragDropBorderWidget;
void OnDragEnter(FGeometry const& MyGeometry, FDragDropEvent const& DragDropEvent) override
{
bIsDragActive = true;
}
void OnDragLeave(FDragDropEvent const& DragDropEvent) override
{
bIsDragActive = false;
}
protected:
private:
TSharedRef<SWidget> WrapWithDropTarget(const TSharedRef<SWidget> InWidget)
TSharedRef<SWidget> WrapWithDropTarget(const TSharedRef<SWidget> InWidget, const EDragDropSupportedModes SupportedMode = EDragDropSupportedModes::All)
{
return SNew(SDropTarget)
.ValidColor(FStyleColors::AccentOrange)
.VerticalImage(FRemoteControlPanelStyle::Get()->GetBrush("RemoteControlPanel.VerticalDash"))
.HorizontalImage(FRemoteControlPanelStyle::Get()->GetBrush("RemoteControlPanel.HorizontalDash"))
.OnDropped_Lambda([this](const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent) { return SControllerItemListRow::OnControllerItemDragDrop(InDragDropEvent.GetOperation()); })
.OnAllowDrop(this, &SControllerItemListRow::OnAllowDrop)
.OnIsRecognized(this, &SControllerItemListRow::OnAllowDrop)
.OnAllowDrop(this, &SControllerItemListRow::OnAllowDrop, SupportedMode)
.OnIsRecognized(this, &SControllerItemListRow::OnAllowDrop, SupportedMode)
[
InWidget
];
@@ -99,6 +126,9 @@ namespace UE::RCControllerPanelList
FReply OnControllerItemDragDrop(TSharedPtr<FDragDropOperation> DragDropOperation)
{
bIsDragActive = false;
ControllerPanelList->bIsAnyControllerItemEligibleForDragDrop = false;
if (!DragDropOperation)
{
return FReply::Handled();
@@ -121,12 +151,36 @@ namespace UE::RCControllerPanelList
}
}
}
else if (DragDropOperation->IsOfType<FExposedEntityDragDrop>())
{
if (TSharedPtr<FExposedEntityDragDrop> DragDropOp = StaticCastSharedPtr<FExposedEntityDragDrop>(DragDropOperation))
{
// Fetch the Exposed Entity
const FGuid ExposedEntityId = DragDropOp->GetId();
if (URemoteControlPreset* Preset = ControllerPanelList->GetPreset())
{
if (TSharedPtr<const FRemoteControlProperty> RemoteControlProperty = Preset->GetExposedEntity<FRemoteControlProperty>(ExposedEntityId).Pin())
{
if (URCController* Controller = Cast<URCController>(ControllerItem->GetVirtualProperty()))
{
ControllerPanelList->CreateBindBehaviourAndAssignTo(Controller, RemoteControlProperty.ToSharedRef(), true);
}
}
}
}
}
return FReply::Handled();
}
bool OnAllowDrop(TSharedPtr<FDragDropOperation> DragDropOperation)
bool OnAllowDrop(TSharedPtr<FDragDropOperation> DragDropOperation, const EDragDropSupportedModes SupportedMode)
{
if (!ensure(ControllerPanelList))
{
return false;
}
if (DragDropOperation && ControllerItem)
{
// Dragging Controllers onto Controllers (Reordering)
@@ -137,6 +191,40 @@ namespace UE::RCControllerPanelList
return true;
}
}
else
{
if (SupportedMode == EDragDropSupportedModes::ReorderOnly)
{
return false; // This widget only supports Reordering operation as a drag-drop action
}
if (DragDropOperation->IsOfType<FExposedEntityDragDrop>())
{
if (TSharedPtr<FExposedEntityDragDrop> DragDropOp = StaticCastSharedPtr<FExposedEntityDragDrop>(DragDropOperation))
{
// Fetch the Exposed Entity
const FGuid ExposedEntityId = DragDropOp->GetId();
if (URemoteControlPreset* Preset = ControllerPanelList->GetPreset())
{
if (TSharedPtr<const FRemoteControlField> RemoteControlField = Preset->GetExposedEntity<FRemoteControlField>(ExposedEntityId).Pin())
{
if (URCController* Controller = Cast<URCController>(ControllerItem->GetVirtualProperty()))
{
const bool bAllowNumericInputAsStrings = true;
const bool bAllowDrop = URCBehaviourBind::CanHaveActionForField(Controller, RemoteControlField.ToSharedRef(), bAllowNumericInputAsStrings);
ControllerPanelList->bIsAnyControllerItemEligibleForDragDrop |= (bAllowDrop && bIsDragActive);
return bAllowDrop;
}
}
}
}
}
}
}
return false;
@@ -156,6 +244,7 @@ namespace UE::RCControllerPanelList
private:
TSharedPtr<FRCControllerModel> ControllerItem;
TSharedPtr<SRCControllerPanelList> ControllerPanelList;
bool bIsDragActive = false;
};
}
@@ -195,7 +284,15 @@ void SRCControllerPanelList::Construct(const FArguments& InArgs, const TSharedRe
ChildSlot
[
ListView.ToSharedRef()
SNew(SDropTarget)
.VerticalImage(FRemoteControlPanelStyle::Get()->GetBrush("RemoteControlPanel.VerticalDash"))
.HorizontalImage(FRemoteControlPanelStyle::Get()->GetBrush("RemoteControlPanel.HorizontalDash"))
.OnDropped_Lambda([this](const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent) { return SRCControllerPanelList::OnControllerListViewDragDrop(InDragDropEvent.GetOperation()); })
.OnAllowDrop(this, &SRCControllerPanelList::OnAllowDrop)
.OnIsRecognized(this, &SRCControllerPanelList::OnAllowDrop)
[
ListView.ToSharedRef()
]
];
// Add delegates
@@ -241,7 +338,7 @@ void SRCControllerPanelList::Reset()
TSharedPtr<SRCControllerPanel> ControllerPanel = ControllerPanelWeakPtr.Pin();
URemoteControlPreset* Preset = ControllerPanel->GetPreset();
TSharedPtr<SRemoteControlPanel> RemoteControlPanel = ControllerPanel->GetRemoteControlPanel();
check(Preset);
PropertyRowGenerator->SetStructure(Preset->GetControllerContainerStructOnScope());
@@ -260,7 +357,7 @@ void SRCControllerPanelList::Reset()
for (TSharedRef<IDetailTreeNode>& Child : Children)
{
FProperty* Property = Child->CreatePropertyHandle()->GetProperty();
check(Property);
check(Property);
if (URCVirtualPropertyBase* Controller = Preset->GetVirtualProperty(Property->GetFName()))
{
@@ -298,6 +395,22 @@ void SRCControllerPanelList::OnTreeSelectionChanged(TSharedPtr<FRCControllerMode
}
}
void SRCControllerPanelList::SelectController(URCController* InController)
{
for (TSharedPtr<FRCControllerModel> ControllerItem : ControllerItems)
{
if (!ensure(ControllerItem))
{
continue;
}
if (ControllerItem->GetVirtualProperty() == InController)
{
ListView->SetSelection(ControllerItem);
}
}
}
void SRCControllerPanelList::OnControllerAdded(const FName& InNewPropertyName)
{
Reset();
@@ -452,4 +565,179 @@ void SRCControllerPanelList::ReorderControllerItem(TSharedRef<FRCControllerModel
ListView->RequestListRefresh();
}
#undef LOCTEXT_NAMESPACE
static TSharedPtr<FExposedEntityDragDrop> GetExposedEntityDragDrop(TSharedPtr<FDragDropOperation> DragDropOperation)
{
if (DragDropOperation)
{
if (DragDropOperation->IsOfType<FExposedEntityDragDrop>())
{
return StaticCastSharedPtr<FExposedEntityDragDrop>(DragDropOperation);
}
}
return nullptr;
}
bool SRCControllerPanelList::OnAllowDrop(TSharedPtr<FDragDropOperation> DragDropOperation)
{
if (IsListViewHovered())
{
bIsAnyControllerItemEligibleForDragDrop = false;
}
// Ensures that this drop target is visually disabled whenever the user is attempting to drop onto an existing Controller (rather than the ListView's empty space)
else if (bIsAnyControllerItemEligibleForDragDrop)
{
return false;
}
if (TSharedPtr<FExposedEntityDragDrop> DragDropOp = GetExposedEntityDragDrop(DragDropOperation))
{
// Fetch the Exposed Entity
const FGuid ExposedEntityId = DragDropOp->GetId();
if (URemoteControlPreset* Preset = GetPreset())
{
if (TSharedPtr<const FRemoteControlField> RemoteControlField = Preset->GetExposedEntity<FRemoteControlField>(ExposedEntityId).Pin())
{
return RemoteControlField->FieldType == EExposedFieldType::Property;
}
}
}
return false;
}
FReply SRCControllerPanelList::OnControllerListViewDragDrop(TSharedPtr<FDragDropOperation> DragDropOperation)
{
if (TSharedPtr<FExposedEntityDragDrop> DragDropOp = GetExposedEntityDragDrop(DragDropOperation))
{
// Fetch the Exposed Entity
const FGuid ExposedEntityId = DragDropOp->GetId();
if (URemoteControlPreset* Preset = GetPreset())
{
if (TSharedPtr<const FRemoteControlProperty> RemoteControlProperty = Preset->GetExposedEntity<FRemoteControlProperty>(ExposedEntityId).Pin())
{
CreateAutoBindForProperty(RemoteControlProperty);
}
}
}
return FReply::Handled();
}
void SRCControllerPanelList::CreateAutoBindForProperty(TSharedPtr<const FRemoteControlProperty> RemoteControlProperty)
{
if (URemoteControlPreset* Preset = GetPreset())
{
// Derive the input data needed for creating a new Controller
FProperty* Property = RemoteControlProperty->GetProperty();
EPropertyBagPropertyType PropertyBagType = EPropertyBagPropertyType::None;
UObject* StructObject = nullptr;
// In the Logic realm we use a single type like (eg: String / Int) to represent various related types (String/Name/Text, Int32, Int64, etc)
// For this reason explicit mapping conversion is required between a given FProperty type and the desired Controller type
bool bSuccess = URCBehaviourBind::GetPropertyBagTypeFromFieldProperty(Property, PropertyBagType, StructObject);
if(bSuccess)
{
// Step 1. Create a Controller of matching type
URCController* NewController = Cast<URCController>(Preset->AddVirtualProperty(URCController::StaticClass(), PropertyBagType, StructObject));
NewController->DisplayIndex = Preset->GetNumVirtualProperties() - 1;
// Transfer property value from Exposed Property to the New Controller.
// The goal is to keep values synced for a Controller newly created via "Auto Bind"
URCBehaviourBind::CopyPropertyValueToController(NewController, RemoteControlProperty.ToSharedRef());
// Step 2. Refresh UI
Reset();
// Step 3. Create Bind Behaviour and Bind to the property
CreateBindBehaviourAndAssignTo(NewController, RemoteControlProperty.ToSharedRef(), false);
}
}
}
void SRCControllerPanelList::CreateBindBehaviourAndAssignTo(URCController* Controller, TSharedRef<const FRemoteControlProperty> InRemoteControlProperty, const bool bExecuteBind)
{
const URCBehaviourBind* BindBehaviour = nullptr;
bool bRequiresNumericConversion = false;
if (!URCBehaviourBind::CanHaveActionForField(Controller, InRemoteControlProperty, false))
{
if (URCBehaviourBind::CanHaveActionForField(Controller, InRemoteControlProperty, true))
{
bRequiresNumericConversion = true;
}
else
{
ensureAlwaysMsgf(false, TEXT("Incompatible property provided for Auto Bind!"));
return;
}
}
for (const URCBehaviour* Behaviour : Controller->Behaviours)
{
if (Behaviour && Behaviour->IsA(URCBehaviourBind::StaticClass()))
{
BindBehaviour = Cast<URCBehaviourBind>(Behaviour);
// In case numeric conversion is required we might have multiple Bind behaviours with different settings,
// so we do not break in case there is at least one Bind behaviour with a matching clause. If not, we will create a new Bind behaviour with the requried setting (see below)
if (!bRequiresNumericConversion || BindBehaviour->AreNumericInputsAllowedAsStrings())
{
break;
}
}
}
if (BindBehaviour)
{
if (bRequiresNumericConversion && !BindBehaviour->AreNumericInputsAllowedAsStrings())
{
// If the requested Bind operation requires numeric conversion but the existing Bind behaviour doesn't support this, then we prefer creating a new Bind behaviour to facilitate this operation.
// This allows the the user to successfully perform the Auto Bind as desired without disrupting the setting on the existing Bind behaviour
BindBehaviour = nullptr;
}
}
if (!BindBehaviour)
{
URCBehaviourBind* NewBindBehaviour = Cast<URCBehaviourBind>(Controller->AddBehaviour(URCBehaviourBindNode::StaticClass()));
// If this is a new Bind Behaviour and we are attempting to link unrelated (but compatible types), via numeric conversion, then we apply the bAllowNumericInputAsStrings flag
NewBindBehaviour->SetAllowNumericInputAsStrings(bRequiresNumericConversion);
BindBehaviour = NewBindBehaviour;
// Broadcast new behaviour
if (const TSharedPtr<SRCControllerPanel> ControllerPanel = ControllerPanelWeakPtr.Pin())
{
if (const TSharedPtr<SRemoteControlPanel> RemoteControlPanel = ControllerPanel->GetRemoteControlPanel())
{
RemoteControlPanel->OnBehaviourAdded.Broadcast(BindBehaviour);
}
}
}
if (ensure(BindBehaviour))
{
URCBehaviourBind* BindBehaviourMutable = const_cast<URCBehaviourBind*>(BindBehaviour);
URCAction* BindAction = BindBehaviourMutable->AddPropertyBindAction(InRemoteControlProperty);
if (bExecuteBind)
{
BindAction->Execute();
}
}
// Update the UI selection to provide the user visual feedback indicating that a Bind behaviour has been created/updated
SelectController(Controller);
}
bool SRCControllerPanelList::IsListViewHovered()
{
return ListView->IsDirectlyHovered();
}
#undef LOCTEXT_NAMESPACE

View File

@@ -6,12 +6,14 @@
#include "UI/RemoteControlPanelStyle.h"
#include "Widgets/Layout/SBorder.h"
class FDragDropOperation;
struct FRCPanelStyle;
class FRCControllerModel;
class FRCLogicModeBase;
class IPropertyRowGenerator;
class ITableRow;
class ITableBase;
class SDropTarget;
class SRCControllerPanel;
class SRemoteControlPanel;
class STableViewBase;
@@ -115,6 +117,29 @@ public:
Reset();
}
/** Drag-Drop validation delegate for the Controllers Panel List */
bool OnAllowDrop(TSharedPtr<FDragDropOperation> DragDropOperation);
/** Drag-Drop action delegate for the Controllers Panel List*/
FReply OnControllerListViewDragDrop(TSharedPtr<FDragDropOperation> DragDropOperation);
/** Fetches the Remote Control preset associated with the parent panel */
virtual URemoteControlPreset* GetPreset() override;
/** Creates a Bind Behaviour for the given Controller and binds the given remote control property to it*/
void CreateBindBehaviourAndAssignTo(URCController* Controller, TSharedRef<const FRemoteControlProperty> InRemoteControlProperty, const bool bExecuteBind);
/** Whether the user's cursor is directly hovered over the List View*/
bool IsListViewHovered();
/** Flag that facilitates usage of two mutually exclusive drag-drop zones within a single panel
*
* The first drag-drop zone is empty panel space for "Bind To New Controller"
* The second drag-drop zone is the Controller name widget for "Bind To Existing Controller"
*
* This flag is set if any Controller has active drag-drop focus, in which case we disable the first drag-drop zone. This is purely for visual clarity*/
bool bIsAnyControllerItemEligibleForDragDrop = false;
private:
/** OnGenerateRow delegate for the Actions List View */
@@ -123,6 +148,9 @@ private:
/** OnSelectionChanged delegate for Actions List View */
void OnTreeSelectionChanged(TSharedPtr<FRCControllerModel> InItem , ESelectInfo::Type);
/** Selects the Controller UI item corresponding to a given Controller UObject */
void SelectController(URCController* InController);
/** Responds to the selection of a newly created Controller. Resets UI state */
void OnControllerAdded(const FName& InNewPropertyName);
@@ -136,6 +164,9 @@ private:
*/
void OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent);
/** Creates a new Controller for the given Remote Control Property and also binds to it */
void CreateAutoBindForProperty(TSharedPtr<const FRemoteControlProperty> RemoteControlProperty);
/** The row generator used to represent each Controller as a row, when used with SListView */
TSharedPtr<IPropertyRowGenerator> PropertyRowGenerator;
@@ -157,9 +188,6 @@ private:
/** Handles broadcasting of a successful remove item operation.*/
virtual void BroadcastOnItemRemoved() override;
/** Fetches the Remote Control preset associated with the parent panel */
virtual URemoteControlPreset* GetPreset() override;
/** Removes the given Controller UI model item from the list of UI models*/
virtual int32 RemoveModel(const TSharedPtr<FRCLogicModeBase> InModel) override;

View File

@@ -66,7 +66,7 @@ public:
// Remote Control Logic Delegates
DECLARE_MULTICAST_DELEGATE_OneParam(FOnControllerAdded, const FName& /* InPropertyName */);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnControllerSelectionChanged, TSharedPtr<FRCControllerModel> /* InControllerItem */);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnBehaviourAdded, URCBehaviour* /* InBehaviour */);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnBehaviourAdded, const URCBehaviour* /* InBehaviour */);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnBehaviourSelectionChanged, TSharedPtr<FRCBehaviourModel> /* InBehaviourItem */);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnActionAdded, URCAction* /* InAction */);