// Copyright Epic Games, Inc. All Rights Reserved. #include "SUIComponentView.h" #include "DragAndDrop/DecoratedDragDropOp.h" #include "ScopedTransaction.h" #include "Extensions/UIComponent.h" #include "Extensions/UIComponentUserWidgetExtension.h" #include "Framework/Commands/GenericCommands.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Input/DragAndDrop.h" #include "Kismet2/SClassPickerDialog.h" #include "SPositiveActionButton.h" #include "UIComponentUtils.h" #include "WidgetBlueprintEditor.h" #include "Widgets/Docking/SDockTab.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SScrollBorder.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Views/SListView.h" #include "Widgets/Views/STableRow.h" #include "Widgets/Views/STableViewBase.h" #include "Widgets/Text/SInlineEditableTextBlock.h" #if WITH_EDITOR #include "Styling/AppStyle.h" #endif // WITH_EDITOR #define LOCTEXT_NAMESPACE "UMG" struct FUIComponentListItem { explicit FUIComponentListItem(UUIComponent* InComponent, UWidget* InWidget) : Component(InComponent), Widget(InWidget) {} TWeakObjectPtr Component; TWeakObjectPtr Widget; }; class FUIComponentDragDropOp final : public FDecoratedDragDropOp { public: DRAG_DROP_OPERATOR_TYPE(FUIComponentDragDropOp, FDecoratedDragDropOp) /** The template to create an instance */ TSharedPtr ListItem; /** Constructs the drag drop operation */ static TSharedRef New(const TSharedPtr& InListItem, FText InDragText) { TSharedRef Operation = MakeShared(); Operation->ListItem = InListItem; Operation->DefaultHoverText = InDragText; Operation->CurrentHoverText = InDragText; Operation->Construct(); return Operation; } }; class SUIComponentListItem : public STableRow > { public: SLATE_BEGIN_ARGS(SUIComponentListView) {} SLATE_END_ARGS() void Construct(const FArguments& InArgs, const TSharedRef& InOwnerTableView, TSharedPtr InBlueprintEditor, TSharedPtr InListItem) { ListItem = InListItem; BlueprintEditor = InBlueprintEditor; STableRow>::Construct( STableRow>::FArguments() .Style(&FAppStyle::Get().GetWidgetStyle("TableView.AlternatingRow")) .Padding(FMargin(3.0f, 2.0f)) .OnDragDetected(this, &SUIComponentListItem::OnDragDetected) .OnCanAcceptDrop(this, &SUIComponentListItem::OnCanAcceptDrop) .OnAcceptDrop(this, &SUIComponentListItem::OnAcceptDrop) .Content() [ SAssignNew(InlineTextBlock, SInlineEditableTextBlock) .Font(FCoreStyle::Get().GetFontStyle("NormalFont")) .Text(this, &SUIComponentListItem::GetComponentName) .IsReadOnly(true) .OverflowPolicy(ETextOverflowPolicy::Ellipsis) .IsSelected(this, &SUIComponentListItem::IsSelectedExclusively) ], InOwnerTableView); } void BeginRename() { InlineTextBlock->EnterEditingMode(); } private: FText GetComponentName() const { if( TSharedPtr Item = ListItem.Pin() ) { if (Item->Component != nullptr) { return Item->Component->GetClass()->GetDisplayNameText(); } } return FText::GetEmpty(); } /** Called whenever a drag is detected by the list view. */ FReply OnDragDetected(const FGeometry& InGeometry, const FPointerEvent& InPointerEvent) { TSharedPtr ListItemPinned = ListItem.Pin(); if (ListItemPinned.IsValid()) { FText DefaultText = LOCTEXT("DefaultDragDropFormat", "Move 1 item(s)"); return FReply::Handled().BeginDragDrop(FUIComponentDragDropOp::New(ListItemPinned, DefaultText)); } return FReply::Unhandled(); } /** Called to determine whether a current drag operation is valid for this row. */ TOptional OnCanAcceptDrop(const FDragDropEvent& DragDropEvent, EItemDropZone InItemDropZone, TSharedPtr InListItem) { TSharedPtr DragDropOp = DragDropEvent.GetOperationAs(); if (DragDropOp.IsValid()) { if (InItemDropZone == EItemDropZone::OntoItem) { DragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); } else { DragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Ok")); } return InItemDropZone; } return TOptional(); } /** Called to complete a drag and drop onto this drop. */ FReply OnAcceptDrop(const FDragDropEvent& DragDropEvent, EItemDropZone InItemDropZone, TSharedPtr InListItem) { TSharedPtr DragDropOp = DragDropEvent.GetOperationAs(); if (DragDropOp.IsValid() && DragDropOp->ListItem.IsValid() && DragDropOp->ListItem->Component != nullptr && InListItem.IsValid() && InListItem->Component != nullptr && DragDropOp->ListItem->Component != InListItem->Component && InListItem->Widget != nullptr ) { TSharedPtr WidgetBlueprintEditor = BlueprintEditor.Pin(); if (UWidgetBlueprint* Blueprint = WidgetBlueprintEditor->GetWidgetBlueprintObj()) { // When we drop on another item, we expect to replace it, so we move it before that Item. // So we move it after only if it's Below const bool bMoveAfter = InItemDropZone == EItemDropZone::BelowItem; FUIComponentUtils::MoveComponent(WidgetBlueprintEditor.ToSharedRef(), DragDropOp->ListItem->Component.Get()->GetClass(), InListItem->Component.Get()->GetClass(), InListItem->Widget.Get()->GetFName(), bMoveAfter ); return FReply::Handled(); } } return FReply::Unhandled(); } private: TWeakPtr ListItem; TWeakPtr BlueprintEditor; TSharedPtr InlineTextBlock; }; SUIComponentView::~SUIComponentView() { if (TSharedPtr WidgetBlueprintEditorPin = BlueprintEditor.Pin()) { WidgetBlueprintEditorPin->OnWidgetPreviewReady.RemoveAll(this); WidgetBlueprintEditorPin->OnSelectedWidgetsChanged.RemoveAll(this); WidgetBlueprintEditorPin->GetOnWidgetBlueprintTransaction().RemoveAll(this); } } void SUIComponentView::Construct(const FArguments& InArgs, TSharedPtr InBlueprintEditor) { BlueprintEditor = InBlueprintEditor; InBlueprintEditor->GetOnWidgetBlueprintTransaction().AddSP(this, &SUIComponentView::OnWidgetBlueprintTransaction); InBlueprintEditor->OnSelectedWidgetsChanged.AddSP(this, &SUIComponentView::OnEditorSelectionChanged); InBlueprintEditor->OnWidgetPreviewReady.AddSP(this, &SUIComponentView::OnWidgetPreviewReady); ChildSlot [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .HAlign(EHorizontalAlignment::HAlign_Left) .Padding(4.0f, 0.0f) [ SNew(SPositiveActionButton) .Icon(FAppStyle::Get().GetBrush("Icons.Plus")) .Text(LOCTEXT("AddComponentLabel", "Add Component")) .ToolTipText(LOCTEXT("AddComponentToolTip", "Add a Component to this widget.")) .OnClicked(this, &SUIComponentView::OnAddComponentButtonClicked) ] + SVerticalBox::Slot() .AutoHeight() .HAlign(EHorizontalAlignment::HAlign_Fill) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder")) .Padding(4.f) [ SNew(SBox) .MinDesiredHeight(60) [ SNew(SScrollBorder, ComponentListView.ToSharedRef()) [ SAssignNew(ComponentListView, SUIComponentListView) .SelectionMode(ESelectionMode::Single) .OnGenerateRow(this, &SUIComponentView::OnGenerateWidgetForComponent) .OnContextMenuOpening(this, &SUIComponentView::OnContextMenuOpening) .ListItemsSource(&Components) ] ] ] ] ]; UpdateComponentList(); CreateCommandList(); } void SUIComponentView::CreateCommandList() { CommandList = MakeShareable(new FUICommandList); CommandList->MapAction( FGenericCommands::Get().Delete, FExecuteAction::CreateSP(this, &SUIComponentView::OnDeleteSelectedComponent), FCanExecuteAction::CreateSP(this, &SUIComponentView::CanExecuteDeleteSelectedComponent) ); } FReply SUIComponentView::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) { FReply Reply = FReply::Unhandled(); if (CommandList->ProcessCommandBindings(InKeyEvent)) { Reply = FReply::Handled(); } return Reply; } void SUIComponentView::OnEditorSelectionChanged() { // When the selection changes in the Widget Designer, we update the component List UpdateComponentList(); } void SUIComponentView::OnWidgetPreviewReady() { // When the the preview is recreated and is ready we update the component List UpdateComponentList(); } void SUIComponentView::OnWidgetBlueprintTransaction() { TSharedPtr WidgetBlueprintEditorPin = BlueprintEditor.Pin(); UpdateComponentList(); } FReply SUIComponentView::OnAddComponentButtonClicked() { if (const TSharedPtr WidgetBlueprintEditor = BlueprintEditor.Pin()) { UClass* ChosenClass = nullptr; const FText TitleText = LOCTEXT("AddComponentClassPickerTitle", "Pick a component to add to the widget."); const bool bPressedOk = SClassPickerDialog::PickClass(TitleText, FUIComponentUtils::CreateClassViewerInitializationOptions(), ChosenClass, UUIComponent::StaticClass()); if (ChosenClass) { const TSet& SelectedWidgets = WidgetBlueprintEditor->GetSelectedWidgets(); for (const FWidgetReference& SelectedObject : SelectedWidgets) { if (const UWidget* SelectedWidget = Cast(SelectedObject.GetPreview())) { FUIComponentUtils::AddComponent(WidgetBlueprintEditor.ToSharedRef(), ChosenClass, SelectedWidget->GetFName()); } } } } return FReply::Handled(); } TSharedPtr SUIComponentView::OnContextMenuOpening() { FMenuBuilder MenuBuilder(true, CommandList.ToSharedRef()); MenuBuilder.BeginSection("Edit", LOCTEXT("Edit", "Edit")); { MenuBuilder.AddMenuEntry(FGenericCommands::Get().Delete); } MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } bool SUIComponentView::CanExecuteDeleteSelectedComponent() const { if (ComponentListView) { return ComponentListView->GetNumItemsSelected() > 0; } return false; } void SUIComponentView::OnDeleteSelectedComponent() { if (!ComponentListView) { return; } if (const TSharedPtr WidgetBlueprintEditor = BlueprintEditor.Pin()) { if (UWidget* SelectedWidget = GetSelectedWidget()) { for (TSharedPtr& Item : ComponentListView->GetSelectedItems()) { if (Item && Item->Component != nullptr) { FUIComponentUtils::RemoveComponent(WidgetBlueprintEditor.ToSharedRef(), Item->Component->GetClass(), SelectedWidget->GetFName()); } } } } } TSharedRef SUIComponentView::OnGenerateWidgetForComponent(TSharedPtr InListItem, const TSharedRef< STableViewBase >& InOwnerTableView) { return SNew(SUIComponentListItem, InOwnerTableView, BlueprintEditor.Pin(), InListItem); } UWidget* SUIComponentView::GetSelectedWidget() const { UWidget* SelectedPreviewWidget = nullptr; if (const TSharedPtr WidgetBlueprintEditor = BlueprintEditor.Pin()) { const TSet& SelectedWidgets = WidgetBlueprintEditor->GetSelectedWidgets(); if (SelectedWidgets.Num() == 1) { // Get the first element. const TSet::TConstIterator SetIt(SelectedWidgets); SelectedPreviewWidget = SetIt->GetPreview(); } } return SelectedPreviewWidget; } void SUIComponentView::UpdateComponentList() { Components.Reset(); if (const TSharedPtr WidgetBlueprintEditor = BlueprintEditor.Pin()) { UWidget* SelectedPreviewWidget = GetSelectedWidget(); const UUserWidget* PreviewUserWidget = WidgetBlueprintEditor->GetPreview(); const UUIComponentUserWidgetExtension* UserWidgetExtension = PreviewUserWidget? PreviewUserWidget->GetExtension() : nullptr; if (SelectedPreviewWidget && UserWidgetExtension) { TArray ListOfComponents = UserWidgetExtension->GetComponentsFor(SelectedPreviewWidget); Components.Reserve(ListOfComponents.Num()); for (UUIComponent* Component : ListOfComponents) { Components.Emplace(MakeShared(Component, SelectedPreviewWidget)); } } } if (ComponentListView.IsValid()) { ComponentListView->RequestListRefresh(); } } #undef LOCTEXT_NAMESPACE