// Copyright Epic Games, Inc. All Rights Reserved. #include "Hierarchy/SHierarchyViewItem.h" #include "Components/NamedSlotInterface.h" #include "Blueprint/UserWidget.h" #include "Widgets/Text/STextBlock.h" #include "WidgetBlueprint.h" #include "WidgetBlueprintEditor.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Views/SListView.h" #include "EditorFontGlyphs.h" #include "Framework/Application/SlateApplication.h" #include "Styling/CoreStyle.h" #if WITH_EDITOR #include "Styling/AppStyle.h" #endif // WITH_EDITOR #include "Components/PanelWidget.h" #include "Kismet2/BlueprintEditorUtils.h" #include "DragAndDrop/DecoratedDragDropOp.h" #include "DragAndDrop/AssetDragDropOp.h" #include "DragAndDrop/ClassDragDropOp.h" #include "DragDrop/WidgetTemplateDragDropOp.h" #include "DragDrop/SelectedWidgetDragDropOp.h" #include "Hierarchy/HierarchyWidgetDragDropOp.h" #include "WidgetTemplate.h" #include "Widgets/Text/SInlineEditableTextBlock.h" #include "Blueprint/WidgetTree.h" #include "WidgetBlueprintEditorUtils.h" #include "ScopedTransaction.h" #include "Styling/SlateIconFinder.h" #include "Templates/WidgetTemplateBlueprintClass.h" #include "Templates/WidgetTemplateImageClass.h" #define LOCTEXT_NAMESPACE "UMG" namespace UE::UMG::Editor { extern bool bDisplayWidgetVariableGuids; } /** * */ class FHierarchyWidgetDragDropOpImpl : public FHierarchyWidgetDragDropOp { public: DRAG_DROP_OPERATOR_TYPE(FHierarchyWidgetDragDropOpImpl, FHierarchyWidgetDragDropOp) FHierarchyWidgetDragDropOpImpl(FHierarchyWidgetDragDropOp& HierarchyWidgetDragDropOp); virtual ~FHierarchyWidgetDragDropOpImpl(); virtual void OnDrop(bool bDropWasHandled, const FPointerEvent& MouseEvent) override; struct FItem { /** The slot properties for the old slot the widget was in, is used to attempt to reapply the same layout information */ TMap ExportedSlotProperties; /** The widget being dragged and dropped */ FWidgetReference Widget; /** The original parent of the widget. */ UWidget* WidgetParent; }; TArray DraggedWidgets; /** The widget being dragged and dropped */ FScopedTransaction* Transaction; /** Constructs a new drag/drop operation */ static TSharedRef New(UWidgetBlueprint* Blueprint, const TArray& InWidgets); }; FHierarchyWidgetDragDropOpImpl::FHierarchyWidgetDragDropOpImpl(FHierarchyWidgetDragDropOp& HierarchyWidgetDragDropOp) : FHierarchyWidgetDragDropOp(HierarchyWidgetDragDropOp) { } TSharedRef FHierarchyWidgetDragDropOpImpl::New(UWidgetBlueprint* Blueprint, const TArray& InWidgets) { check(InWidgets.Num() > 0); TSharedRef HierarchyWidgetDragDropOp = FHierarchyWidgetDragDropOp::New(Blueprint, InWidgets); TSharedRef Operation = MakeShareable(new FHierarchyWidgetDragDropOpImpl(*HierarchyWidgetDragDropOp)); // Set the display text and the transaction name based on whether we're dragging a single or multiple widgets if (InWidgets.Num() == 1) { Operation->CurrentHoverText = Operation->DefaultHoverText = InWidgets[0].GetTemplate()->GetLabelText(); Operation->Transaction = new FScopedTransaction(LOCTEXT("Designer_MoveWidget", "Move Widget")); } else { Operation->CurrentHoverText = Operation->DefaultHoverText = LOCTEXT("Designer_DragMultipleWidgets", "Multiple Widgets"); Operation->Transaction = new FScopedTransaction(LOCTEXT("Designer_MoveWidgets", "Move Widgets")); } // Add an FItem for each widget in the drag operation for (const auto& Widget : InWidgets) { FItem DraggedWidget; DraggedWidget.Widget = Widget; FWidgetBlueprintEditorUtils::ExportPropertiesToText(Widget.GetTemplate()->Slot, DraggedWidget.ExportedSlotProperties); UWidget* WidgetTemplate = Widget.GetTemplate(); WidgetTemplate->Modify(); DraggedWidget.WidgetParent = WidgetTemplate->GetParent(); if (DraggedWidget.WidgetParent) { DraggedWidget.WidgetParent->Modify(); } Operation->DraggedWidgets.Add(DraggedWidget); } Operation->Construct(); Blueprint->WidgetTree->SetFlags(RF_Transactional); Blueprint->WidgetTree->Modify(); return Operation; } FHierarchyWidgetDragDropOpImpl::~FHierarchyWidgetDragDropOpImpl() { delete Transaction; } void FHierarchyWidgetDragDropOpImpl::OnDrop(bool bDropWasHandled, const FPointerEvent& MouseEvent) { if ( !bDropWasHandled ) { Transaction->Cancel(); } } ////////////////////////////////////////////////////////////////////////// TOptional ProcessHierarchyDragDrop(const FDragDropEvent& DragDropEvent, EItemDropZone DropZone, bool bIsDrop, TSharedPtr BlueprintEditor, FWidgetReference TargetItem, TOptional Index = TOptional()) { UWidget* TargetTemplate = TargetItem.GetTemplate(); if (TSharedPtr HierarchyDragDropOp = DragDropEvent.GetOperationAs()) { if (!HierarchyDragDropOp->HasOriginatedFrom(BlueprintEditor)) { return TOptional(); } } // We do not support to dragging a Widget from the Viewport to the Hierarchy panel if (TSharedPtr SelectedWidgetDragDropOp = DragDropEvent.GetOperationAs()) { return TOptional(); } if ( TargetTemplate && ( DropZone == EItemDropZone::AboveItem || DropZone == EItemDropZone::BelowItem ) ) { if ( UPanelWidget* TargetParentTemplate = TargetTemplate->GetParent() ) { int32 InsertIndex = TargetParentTemplate->GetChildIndex(TargetTemplate); InsertIndex += ( DropZone == EItemDropZone::AboveItem ) ? 0 : 1; InsertIndex = FMath::Clamp(InsertIndex, 0, TargetParentTemplate->GetChildrenCount()); FWidgetReference TargetParentTemplateRef = BlueprintEditor->GetReferenceFromTemplate(TargetParentTemplate); TOptional ParentZone = ProcessHierarchyDragDrop(DragDropEvent, EItemDropZone::OntoItem, bIsDrop, BlueprintEditor, TargetParentTemplateRef, InsertIndex); if ( ParentZone.IsSet() ) { return DropZone; } else { DropZone = EItemDropZone::OntoItem; } } } else { DropZone = EItemDropZone::OntoItem; } UWidgetBlueprint* Blueprint = BlueprintEditor->GetWidgetBlueprintObj(); check( Blueprint != nullptr && Blueprint->WidgetTree != nullptr ); const auto ShouldPreventDropOnTargetExtensions = [](UWidget* Target, const TSharedPtr& DecoratedDragDropOp) -> bool { FText DropOnTargetFailureText = FText::GetEmpty(); const bool bShouldPreventDropOnTargetExtensions = FWidgetBlueprintEditorUtils::ShouldPreventDropOnTargetExtensions(Target, DecoratedDragDropOp, DropOnTargetFailureText); if (bShouldPreventDropOnTargetExtensions && DecoratedDragDropOp.IsValid()) { DecoratedDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); DecoratedDragDropOp->CurrentHoverText = DropOnTargetFailureText; } return bShouldPreventDropOnTargetExtensions; }; // Is this a drag/drop op to create a new widget in the tree? TSharedPtr DragDropOp = DragDropEvent.GetOperation(); if (DragDropOp.IsValid() && !DragDropOp->IsOfType()) { TSharedPtr DecoratedDragDropOp = nullptr; if (DragDropOp->IsOfType()) { DecoratedDragDropOp = StaticCastSharedPtr(DragDropOp); DecoratedDragDropOp->ResetToDefaultToolTip(); } if ( ShouldPreventDropOnTargetExtensions(TargetTemplate, DecoratedDragDropOp) ) { return TOptional(); } // Are we adding to a locked widget? if ( TargetItem.IsValid() && TargetItem.GetPreview()->IsLockedInDesigner() ) { if (DecoratedDragDropOp.IsValid()) { DecoratedDragDropOp->CurrentHoverText = LOCTEXT("LockedWidget", "Widget is locked."); } } // Are we adding to the root? else if ( !TargetItem.IsValid() && Blueprint->WidgetTree->RootWidget == nullptr ) { // TODO UMG Allow showing a preview of this. if ( bIsDrop ) { FScopedTransaction Transaction(LOCTEXT("AddWidgetFromTemplate", "Add Widget")); Blueprint->WidgetTree->SetFlags(RF_Transactional); Blueprint->WidgetTree->Modify(); if (UWidget* Widget = FWidgetBlueprintEditorUtils::GetWidgetTemplateFromDragDrop(Blueprint, Blueprint->WidgetTree, DragDropOp)) { Blueprint->WidgetTree->RootWidget = Widget; Blueprint->OnVariableAdded(Widget->GetFName()); } FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); } if (DecoratedDragDropOp.IsValid()) { DecoratedDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK")); } return EItemDropZone::OntoItem; } // Are we adding to a panel? else if ( UPanelWidget* Parent = Cast(TargetItem.GetTemplate()) ) { if (!Parent->CanAddMoreChildren()) { if (DecoratedDragDropOp.IsValid()) { DecoratedDragDropOp->CurrentHoverText = LOCTEXT("NoAdditionalChildren", "Widget can't accept additional children."); } } else { // TODO UMG Allow showing a preview of this. if (bIsDrop) { FScopedTransaction Transaction(LOCTEXT("AddWidgetFromTemplate", "Add Widget")); Blueprint->WidgetTree->SetFlags(RF_Transactional); Blueprint->WidgetTree->Modify(); Parent->SetFlags(RF_Transactional); Parent->Modify(); if (UWidget* Widget = FWidgetBlueprintEditorUtils::GetWidgetTemplateFromDragDrop(Blueprint, Blueprint->WidgetTree, DragDropOp)) { UPanelSlot* NewSlot = nullptr; if (Index.IsSet()) { NewSlot = Parent->InsertChildAt(Index.GetValue(), Widget); } else { NewSlot = Parent->AddChild(Widget); } check(NewSlot); Blueprint->OnVariableAdded(Widget->GetFName()); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); } } if (DecoratedDragDropOp.IsValid()) { DecoratedDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK")); } return EItemDropZone::OntoItem; } } else { if (DecoratedDragDropOp.IsValid()) { DecoratedDragDropOp->CurrentHoverText = LOCTEXT("CantHaveChildren", "Widget can't have children."); } } if (DecoratedDragDropOp.IsValid()) { DecoratedDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); } return TOptional(); } TSharedPtr HierarchyDragDropOp = DragDropEvent.GetOperationAs(); if ( HierarchyDragDropOp.IsValid() ) { HierarchyDragDropOp->ResetToDefaultToolTip(); // If the target item is valid we're dealing with a normal widget in the hierarchy, otherwise we should assume it's // the null case and we should be adding it as the root widget. if ( TargetItem.IsValid() ) { const bool bIsDraggedObject = HierarchyDragDropOp->DraggedWidgets.ContainsByPredicate([TargetItem](const FHierarchyWidgetDragDropOpImpl::FItem& DraggedItem) { return DraggedItem.Widget == TargetItem; }); if ( bIsDraggedObject ) { HierarchyDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); return TOptional(); } if (TargetItem.GetPreview()->IsLockedInDesigner()) { HierarchyDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); HierarchyDragDropOp->CurrentHoverText = LOCTEXT("LockedWidget", "Widget is locked."); return TOptional(); } UPanelWidget* NewParent = Cast(TargetItem.GetTemplate()); if (!NewParent) { HierarchyDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); HierarchyDragDropOp->CurrentHoverText = LOCTEXT("CantHaveChildren", "Widget can't have children."); return TOptional(); } if (!NewParent->CanAddMoreChildren()) { HierarchyDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); HierarchyDragDropOp->CurrentHoverText = LOCTEXT("NoAdditionalChildren", "Widget can't accept additional children."); return TOptional(); } if (!NewParent->CanHaveMultipleChildren() && HierarchyDragDropOp->DraggedWidgets.Num() > 1) { HierarchyDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); HierarchyDragDropOp->CurrentHoverText = LOCTEXT("CantHaveMultipleChildren", "Widget can't have multiple children."); return TOptional(); } if (ShouldPreventDropOnTargetExtensions(TargetTemplate, HierarchyDragDropOp)) { return TOptional(); } bool bFoundNewParentInChildSet = false; for (const auto& DraggedWidget : HierarchyDragDropOp->DraggedWidgets) { UWidget* TemplateWidget = DraggedWidget.Widget.GetTemplate(); // Verify that the new location we're placing the widget is not inside of its existing children. Blueprint->WidgetTree->ForWidgetAndChildren(TemplateWidget, [&](UWidget* Widget) { if (NewParent == Widget) { bFoundNewParentInChildSet = true; } }); } if (bFoundNewParentInChildSet) { HierarchyDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); HierarchyDragDropOp->CurrentHoverText = LOCTEXT("CantMakeWidgetChildOfChildren", "Can't make widget a child of its children."); return TOptional(); } if ( bIsDrop ) { NewParent->SetFlags(RF_Transactional); NewParent->Modify(); TSet SelectedTemplates; for (const auto& DraggedWidget : HierarchyDragDropOp->DraggedWidgets) { UWidget* TemplateWidget = DraggedWidget.Widget.GetTemplate(); TemplateWidget->SetFlags(RF_Transactional); TemplateWidget->Modify(); if (Index.IsSet()) { // If we're inserting at an index, and the widget we're moving is already // in the hierarchy before the point we're moving it to, we need to reduce the index // count by one, because the whole set is about to be shifted when it's removed. const bool bInsertInSameParent = TemplateWidget->GetParent() == NewParent; const bool bNeedToDropIndex = NewParent->GetChildIndex(TemplateWidget) < Index.GetValue(); if (bInsertInSameParent && bNeedToDropIndex) { Index = Index.GetValue() - 1; } } // We don't know if this widget is being removed from a named slot and RemoveFromParent is not enough to take care of this UWidget* NamedSlotHostWidget = FWidgetBlueprintEditorUtils::FindNamedSlotHostWidgetForContent(TemplateWidget, Blueprint->WidgetTree); if (NamedSlotHostWidget != nullptr) { if (TScriptInterface NamedSlotHost = TScriptInterface(NamedSlotHostWidget)) { NamedSlotHostWidget->SetFlags(RF_Transactional); NamedSlotHostWidget->Modify(); FWidgetBlueprintEditorUtils::RemoveNamedSlotHostContent(TemplateWidget, NamedSlotHost); } } // If this widget inherits from another one, we can't access the inherited named slots by traversing the widget tree from its root. // So we have to look at the NamedSlotBindings to find a named slot for the moved content. else if (Blueprint->ParentClass && Blueprint->ParentClass != UUserWidget::StaticClass()) { TArray SlotNames; Blueprint->WidgetTree->GetSlotNames(SlotNames); for (FName SlotName : SlotNames) { if (UWidget* SlotContent = Blueprint->WidgetTree->GetContentForSlot(SlotName)) { if (SlotContent == TemplateWidget) { Blueprint->WidgetTree->SetContentForSlot(SlotName, nullptr); } } } } const FName OriginalWidgetName = TemplateWidget->GetFName(); UPanelWidget* OriginalParent = TemplateWidget->GetParent(); UWidgetBlueprint* OriginalBP = nullptr; // The widget's parent is changing if (OriginalParent != NewParent) { NewParent->SetFlags(RF_Transactional); NewParent->Modify(); Blueprint->WidgetTree->SetFlags(RF_Transactional); Blueprint->WidgetTree->Modify(); UWidgetTree* OriginalWidgetTree = Cast(TemplateWidget->GetOuter()); if (OriginalWidgetTree != nullptr && UWidgetTree::TryMoveWidgetToNewTree(TemplateWidget, Blueprint->WidgetTree)) { OriginalWidgetTree->SetFlags(RF_Transactional); OriginalWidgetTree->Modify(); OriginalBP = OriginalWidgetTree->GetTypedOuter(); } } TemplateWidget->RemoveFromParent(); if (OriginalBP != nullptr && OriginalBP != Blueprint) { OriginalBP->OnVariableRemoved(OriginalWidgetName); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(OriginalBP); } UPanelSlot* NewSlot = nullptr; if (Index.IsSet()) { NewSlot = NewParent->InsertChildAt(Index.GetValue(), TemplateWidget); Index = Index.GetValue() + 1; } else { NewSlot = NewParent->AddChild(TemplateWidget); } check(NewSlot); if (OriginalBP != nullptr && OriginalBP != Blueprint) { Blueprint->OnVariableAdded(TemplateWidget->GetFName()); } // Import the old slot properties FWidgetBlueprintEditorUtils::ImportPropertiesFromText(NewSlot, DraggedWidget.ExportedSlotProperties); SelectedTemplates.Add(BlueprintEditor->GetReferenceFromTemplate(TemplateWidget)); } FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); BlueprintEditor->SelectWidgets(SelectedTemplates, false); } HierarchyDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK")); return EItemDropZone::OntoItem; } else { HierarchyDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); HierarchyDragDropOp->CurrentHoverText = LOCTEXT("CantHaveChildren", "Widget can't have children."); } return TOptional(); } return TOptional(); } ////////////////////////////////////////////////////////////////////////// FHierarchyModel::FHierarchyModel(TSharedPtr InBlueprintEditor) : bInitialized(false) , bIsSelected(false) , BlueprintEditor(InBlueprintEditor) { } TOptional FHierarchyModel::HandleCanAcceptDrop(const FDragDropEvent& DragDropEvent, EItemDropZone DropZone) { return TOptional(); } FReply FHierarchyModel::HandleDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { if (!IsRoot() && !IsLockedInDesigner()) { TArray DraggedItems; // Dragging multiple items? if (bIsSelected) { const TSet& SelectedWidgets = BlueprintEditor.Pin()->GetSelectedWidgets(); if (SelectedWidgets.Num() > 1) { for (const auto& SelectedWidget : SelectedWidgets) { DraggedItems.Add(SelectedWidget); } } } if (DraggedItems.Num() == 0) { FWidgetReference ThisItem = AsDraggedWidgetReference(); if (ThisItem.IsValid()) { DraggedItems.Add(ThisItem); } } if (DraggedItems.Num() > 0) { return FReply::Handled().BeginDragDrop(FHierarchyWidgetDragDropOpImpl::New(BlueprintEditor.Pin()->GetWidgetBlueprintObj(), DraggedItems)); } } return FReply::Unhandled(); } void FHierarchyModel::HandleDragEnter(const FDragDropEvent& DragDropEvent) { TArray DragDropPreviewWidgets; DetermineDragDropPreviewWidgets(DragDropPreviewWidgets, DragDropEvent); // Move the remaining widgets into the transient package. Otherwise, they will remain outered to the WidgetTree and end up as properties in the BP class layout as a result. UWidgetBlueprint* BP = Cast(BlueprintEditor.Pin()->GetBlueprintObj()); if (BP) { for (UWidget* Widget : DragDropPreviewWidgets) { FHierarchyModel::RemovePreviewWidget(BP, Widget); } } } void FHierarchyModel::HandleDragLeave(const FDragDropEvent& DragDropEvent) { TSharedPtr DecoratedDragDropOp = DragDropEvent.GetOperationAs(); if ( DecoratedDragDropOp.IsValid() ) { DecoratedDragDropOp->ResetToDefaultToolTip(); } TArray DragDropPreviewWidgets; DetermineDragDropPreviewWidgets(DragDropPreviewWidgets, DragDropEvent); // Move the remaining widgets into the transient package. Otherwise, they will remain outered to the WidgetTree and end up as properties in the BP class layout as a result. UWidgetBlueprint* BP = Cast(BlueprintEditor.Pin()->GetBlueprintObj()); if (BP) { for (UWidget* Widget : DragDropPreviewWidgets) { FHierarchyModel::RemovePreviewWidget(BP, Widget); } } } FReply FHierarchyModel::HandleAcceptDrop(FDragDropEvent const& DragDropEvent, EItemDropZone DropZone) { return FReply::Unhandled(); } bool FHierarchyModel::OnVerifyNameTextChanged(const FText& InText, FText& OutErrorMessage) { return false; } void FHierarchyModel::OnNameTextCommited(const FText& InText, ETextCommit::Type CommitInfo) { } bool FHierarchyModel::HasCircularReferences(UWidgetBlueprint* Blueprint, UWidget* Widget, TSharedPtr& DragDropOp) { bool bHasCircularReferences = false; if (Widget) { if (!Blueprint->IsWidgetFreeFromCircularReferences(Cast(Widget))) { if (DragDropOp->IsOfType()) { TSharedPtr DecoratedDragDropOp = StaticCastSharedPtr(DragDropOp); DecoratedDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); DecoratedDragDropOp->CurrentHoverText = LOCTEXT("CircularReference", "This would cause a circular reference."); } bHasCircularReferences = true; } RemovePreviewWidget(Blueprint, Widget); } return bHasCircularReferences; } void FHierarchyModel::DetermineDragDropPreviewWidgets(TArray& OutWidgets, const FDragDropEvent& DragDropEvent) { OutWidgets.Empty(); UWidgetBlueprint* Blueprint = Cast(BlueprintEditor.Pin()->GetBlueprintObj()); if (!Blueprint) { return; } TSharedPtr DragDropOp = DragDropEvent.GetOperation(); UWidget* Widget = FWidgetBlueprintEditorUtils::GetWidgetTemplateFromDragDrop(Blueprint, Blueprint->WidgetTree, DragDropOp); if (Widget) { OutWidgets.Add(Widget); } // Mark the widgets for design-time rendering for (UWidget* OutWidget : OutWidgets) { OutWidget->SetDesignerFlags(BlueprintEditor.Pin()->GetCurrentDesignerFlags()); } } void FHierarchyModel::RemovePreviewWidget(UWidgetBlueprint* Blueprint, UWidget* Widget) { Blueprint->WidgetTree->SetFlags(RF_Transactional); Blueprint->WidgetTree->Modify(); Blueprint->WidgetTree->RemoveWidget(Widget); if (Widget->GetOutermost() != GetTransientPackage()) { Widget->SetFlags(RF_NoFlags); Widget->Rename(nullptr, GetTransientPackage()); } } void FHierarchyModel::InitializeChildren() { if ( !bInitialized ) { bInitialized = true; GetChildren(Models); } } void FHierarchyModel::GatherChildren(TArray< TSharedPtr >& Children) { InitializeChildren(); Children.Append(Models); } bool FHierarchyModel::ContainsSelection() { InitializeChildren(); for ( TSharedPtr& Model : Models ) { if ( Model->IsSelected() || Model->ContainsSelection() ) { return true; } } return false; } void FHierarchyModel::RefreshSelection() { InitializeChildren(); UpdateSelection(); for ( TSharedPtr& Model : Models ) { Model->RefreshSelection(); } } bool FHierarchyModel::IsSelected() const { return bIsSelected; } ////////////////////////////////////////////////////////////////////////// FHierarchyRoot::FHierarchyRoot(TSharedPtr InBlueprintEditor) : FHierarchyModel(InBlueprintEditor) { RootText = FText::Format(LOCTEXT("RootWidgetFormat", "[{0}]"), FText::FromString(BlueprintEditor.Pin()->GetBlueprintObj()->GetName())); } FName FHierarchyRoot::GetUniqueName() const { static const FName DesignerRootName(TEXT("WidgetDesignerRoot")); return DesignerRootName; } FText FHierarchyRoot::GetText() const { return RootText; } const FSlateBrush* FHierarchyRoot::GetImage() const { return nullptr; } FSlateFontInfo FHierarchyRoot::GetFont() const { return FCoreStyle::GetDefaultFontStyle("Bold", 10); } void FHierarchyRoot::GetChildren(TArray< TSharedPtr >& Children) { TSharedPtr BPEd = BlueprintEditor.Pin(); UWidgetBlueprint* Blueprint = BPEd->GetWidgetBlueprintObj(); // We only show the hierarchy of the widget tree we own. If this is a subclass of a widget with a parent tree // we won't actually show those widgets here because we can't affect them in a useful inheritable way. if ( Blueprint->WidgetTree->RootWidget ) { TSharedPtr RootChild = MakeShareable(new FHierarchyWidget(BPEd->GetReferenceFromTemplate(Blueprint->WidgetTree->RootWidget), BPEd)); Children.Add(RootChild); } // Grab any exposed named slots from the super classes CDO. These slots can have content slotted into them by this subclass. TSet InheritedNamedSlotsWithContentInSameTree = Blueprint->GetInheritedNamedSlotsWithContentInSameTree(); for ( const FName& SlotName : Blueprint->GetInheritedAvailableNamedSlots() ) { if (InheritedNamedSlotsWithContentInSameTree.Contains(SlotName)) { if (!Blueprint->WidgetTree->GetContentForSlot(SlotName)) { continue; } } TSharedPtr ChildItem = MakeShareable(new FNamedSlotModelSubclass(Blueprint, SlotName, BPEd)); Children.Add(ChildItem); } } void FHierarchyRoot::OnSelection() { TSharedPtr BPEd = BlueprintEditor.Pin(); if ( UWidget* Default = BPEd->GetWidgetBlueprintObj()->GeneratedClass->GetDefaultObject() ) { TSet SelectedObjects; //Switched from adding CDO to adding the preview, so that the root (owner) widget can be properly animate UUserWidget* PreviewWidget = BPEd->GetPreview(); SelectedObjects.Add(PreviewWidget); BPEd->SelectObjects(SelectedObjects); } } void FHierarchyRoot::UpdateSelection() { TSharedPtr BPEd = BlueprintEditor.Pin(); if ( UWidget* Default = BPEd->GetWidgetBlueprintObj()->GeneratedClass->GetDefaultObject() ) { const TSet< TWeakObjectPtr >& SelectedObjects = BlueprintEditor.Pin()->GetSelectedObjects(); TWeakObjectPtr PreviewWidget = BPEd->GetPreview(); bIsSelected = SelectedObjects.Contains(PreviewWidget); } else { bIsSelected = false; } } bool FHierarchyRoot::DoesWidgetOverrideFlowDirection() const { TSharedPtr BPEd = BlueprintEditor.Pin(); if (UWidget* Default = BPEd->GetWidgetBlueprintObj()->GeneratedClass->GetDefaultObject()) { return Default->GetFlowDirectionPreference() != EFlowDirectionPreference::Inherit; } return false; } TOptional FHierarchyRoot::HandleCanAcceptDrop(const FDragDropEvent& DragDropEvent, EItemDropZone DropZone) { bool bIsFreeFromCircularReferences = true; TSharedPtr DragDropOp = DragDropEvent.GetOperation(); if (DragDropOp.IsValid()) { if(UWidgetBlueprint* Blueprint = BlueprintEditor.Pin()->GetWidgetBlueprintObj()) { if (UWidget* Widget = FWidgetBlueprintEditorUtils::GetWidgetTemplateFromDragDrop(Blueprint, Blueprint->WidgetTree, DragDropOp)) { bIsFreeFromCircularReferences = !HasCircularReferences(Blueprint, Widget, DragDropOp); } } } const bool bIsDrop = false; return bIsFreeFromCircularReferences ? ProcessHierarchyDragDrop(DragDropEvent, DropZone, bIsDrop, BlueprintEditor.Pin(), FWidgetReference()) : TOptional(); } FReply FHierarchyRoot::HandleAcceptDrop(FDragDropEvent const& DragDropEvent, EItemDropZone DropZone) { const bool bIsDrop = true; TOptional Zone = ProcessHierarchyDragDrop(DragDropEvent, DropZone, bIsDrop, BlueprintEditor.Pin(), FWidgetReference()); if (Zone.IsSet()) { TArray DragDropPreviewWidgets; DetermineDragDropPreviewWidgets(DragDropPreviewWidgets, DragDropEvent); // Move the remaining widgets into the transient package. Otherwise, they will remain outered to the WidgetTree and end up as properties in the BP class layout as a result. UWidgetBlueprint* BP = Cast(BlueprintEditor.Pin()->GetBlueprintObj()); if (BP) { for (UWidget* Widget : DragDropPreviewWidgets) { FHierarchyModel::RemovePreviewWidget(BP, Widget); } } } return Zone.IsSet() ? FReply::Handled() : FReply::Unhandled(); } ////////////////////////////////////////////////////////////////////////// FNamedSlotModelBase::FNamedSlotModelBase(FName InSlotName, TSharedPtr InBlueprintEditor) : FHierarchyModel(InBlueprintEditor) , SlotName(InSlotName) { } const FSlateBrush* FNamedSlotModelBase::GetImage() const { return nullptr; } FSlateFontInfo FNamedSlotModelBase::GetFont() const { return FCoreStyle::GetDefaultFontStyle("Bold", 10); } FText FNamedSlotModelBase::GetText() const { if (INamedSlotInterface* NamedSlotHost = GetNamedSlotHost()) { TSet SelectedWidgets; if ( UWidget* SlotContent = NamedSlotHost->GetContentForSlot(SlotName) ) { return FText::Format(LOCTEXT("NamedSlotTextFormat", "{0} ({1})"), FText::FromName(SlotName), FText::FromName(SlotContent->GetFName())); } } return FText::FromName(SlotName); } void FNamedSlotModelBase::GetChildren(TArray< TSharedPtr >& Children) { if (INamedSlotInterface* NamedSlotHost = GetNamedSlotHost()) { TSet SelectedWidgets; if ( UWidget* TemplateSlotContent = NamedSlotHost->GetContentForSlot(SlotName) ) { TSharedPtr BPEd = BlueprintEditor.Pin(); TSharedPtr RootChild = MakeShareable(new FHierarchyWidget(BPEd->GetReferenceFromTemplate(TemplateSlotContent), BPEd)); Children.Add(RootChild); } } } void FNamedSlotModelBase::OnSelection() { // No-Op intentionally. } void FNamedSlotModelBase::UpdateSelection() { // No-Op intentionally. } FWidgetReference FNamedSlotModelBase::AsDraggedWidgetReference() const { if (INamedSlotInterface* NamedSlotHost = GetNamedSlotHost()) { // Only assign content to the named slot if it is null. if (UWidget* Content = NamedSlotHost->GetContentForSlot(SlotName)) { return BlueprintEditor.Pin()->GetReferenceFromTemplate(Content); } } return FWidgetReference(); } TOptional FNamedSlotModelBase::HandleCanAcceptDrop(const FDragDropEvent& DragDropEvent, EItemDropZone DropZone) { UWidgetBlueprint* Blueprint = BlueprintEditor.Pin()->GetWidgetBlueprintObj(); TSharedPtr DragDropOp = DragDropEvent.GetOperation(); if (DragDropOp.IsValid() && !DragDropOp->IsOfType()) { TSharedPtr DecoratedDragDropOp = nullptr; if (DragDropOp->IsOfType()) { DecoratedDragDropOp = StaticCastSharedPtr(DragDropOp); DecoratedDragDropOp->ResetToDefaultToolTip(); } if (INamedSlotInterface* NamedSlotHost = GetNamedSlotHost()) { // Only assign content to the named slot if it is null. if (NamedSlotHost->GetContentForSlot(SlotName) != nullptr) { if (DecoratedDragDropOp.IsValid()) { DecoratedDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); DecoratedDragDropOp->CurrentHoverText = LOCTEXT("NamedSlotAlreadyFull", "Named Slot already has a child."); } return TOptional(); } if (UWidget* Widget = FWidgetBlueprintEditorUtils::GetWidgetTemplateFromDragDrop(Blueprint, Blueprint->WidgetTree, DragDropOp)) { const bool bIsFreeFromCircularReferences = !HasCircularReferences(Blueprint, Widget, DragDropOp); if (DecoratedDragDropOp.IsValid()) { DecoratedDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK")); } return bIsFreeFromCircularReferences ? EItemDropZone::OntoItem : TOptional(); } } } TSharedPtr HierarchyDragDropOp = DragDropEvent.GetOperationAs(); if (HierarchyDragDropOp.IsValid() && HierarchyDragDropOp->DraggedWidgets.Num() == 1) { HierarchyDragDropOp->ResetToDefaultToolTip(); if (INamedSlotInterface* NamedSlotHost = GetNamedSlotHost()) { // Only assign content to the named slot if it is null. if (NamedSlotHost->GetContentForSlot(SlotName) != nullptr) { HierarchyDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); HierarchyDragDropOp->CurrentHoverText = LOCTEXT("NamedSlotAlreadyFull", "Named Slot already has a child."); return TOptional(); } bool bFoundNewParentInChildSet = false; UWidget* TemplateWidget = HierarchyDragDropOp->DraggedWidgets[0].Widget.GetTemplate(); // Verify that the new location we're placing the widget is not inside of its existing children. Blueprint->WidgetTree->ForWidgetAndChildren(TemplateWidget, [&](UWidget* Widget) { if (GetNamedSlotHostWidget() == Widget) { bFoundNewParentInChildSet = true; } }); if (bFoundNewParentInChildSet) { HierarchyDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); HierarchyDragDropOp->CurrentHoverText = LOCTEXT("CantMakeWidgetChildOfChildren", "Can't make widget a child of its children."); return TOptional(); } HierarchyDragDropOp->CurrentIconBrush = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK")); return EItemDropZone::OntoItem; } } return TOptional(); } FReply FNamedSlotModelBase::HandleAcceptDrop(FDragDropEvent const& DragDropEvent, EItemDropZone DropZone) { INamedSlotInterface* NamedSlotHost = GetNamedSlotHost(); if (NamedSlotHost == nullptr) { return FReply::Unhandled(); } if (NamedSlotHost->GetContentForSlot(SlotName) != nullptr) { return FReply::Unhandled(); } TSharedPtr DragDropOp = DragDropEvent.GetOperation(); if (DragDropOp.IsValid() && !DragDropOp->IsOfType()) { FScopedTransaction Transaction(LOCTEXT("AddWidgetFromTemplate", "Add Widget")); UWidgetBlueprint* Blueprint = BlueprintEditor.Pin()->GetWidgetBlueprintObj(); Blueprint->WidgetTree->SetFlags(RF_Transactional); Blueprint->WidgetTree->Modify(); if (UWidget* DroppingWidget = FWidgetBlueprintEditorUtils::GetWidgetTemplateFromDragDrop(Blueprint, Blueprint->WidgetTree, DragDropOp)) { Blueprint->OnVariableAdded(DroppingWidget->GetFName()); DoDrop(NamedSlotHost, DroppingWidget); return FReply::Handled(); } else { return FReply::Unhandled(); } } TSharedPtr HierarchyDragDropOp = DragDropEvent.GetOperationAs(); if (HierarchyDragDropOp.IsValid() && HierarchyDragDropOp->DraggedWidgets.Num() == 1) { UWidgetBlueprint* Blueprint = BlueprintEditor.Pin()->GetWidgetBlueprintObj(); Blueprint->WidgetTree->SetFlags(RF_Transactional); Blueprint->WidgetTree->Modify(); UWidget* DroppingWidget = HierarchyDragDropOp->DraggedWidgets[0].Widget.GetTemplate(); // We don't know if this widget is being removed from a named slot and RemoveFromParent is not enough to take care of this if (UWidget* SourceNamedSlotHostWidget = FWidgetBlueprintEditorUtils::FindNamedSlotHostWidgetForContent(DroppingWidget, Blueprint->WidgetTree)) { if (TScriptInterface SourceNamedSlotHost = TScriptInterface(SourceNamedSlotHostWidget)) { SourceNamedSlotHostWidget->SetFlags(RF_Transactional); SourceNamedSlotHostWidget->Modify(); FWidgetBlueprintEditorUtils::RemoveNamedSlotHostContent(DroppingWidget, SourceNamedSlotHost); } } else { FName SourceSlotName = Blueprint->WidgetTree->FindSlotForContent(DroppingWidget); if (SourceSlotName != NAME_None) { Blueprint->WidgetTree->SetContentForSlot(SourceSlotName, nullptr); } } DroppingWidget->RemoveFromParent(); DoDrop(NamedSlotHost, DroppingWidget); return FReply::Handled(); } return FReply::Unhandled(); } void FNamedSlotModelBase::DoDrop(INamedSlotInterface* NamedSlotHost, UWidget* DroppingWidget) { UWidgetBlueprint* Blueprint = BlueprintEditor.Pin()->GetWidgetBlueprintObj(); if (UObject* NamedSlotHostObject = Cast(NamedSlotHost)) { NamedSlotHostObject->SetFlags(RF_Transactional); NamedSlotHostObject->Modify(); } NamedSlotHost->SetContentForSlot(SlotName, DroppingWidget); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); TSet SelectedTemplates; SelectedTemplates.Add(BlueprintEditor.Pin()->GetReferenceFromTemplate(DroppingWidget)); BlueprintEditor.Pin()->SelectWidgets(SelectedTemplates, false); } ////////////////////////////////////////////////////////////////////////// FNamedSlotModel::FNamedSlotModel(FWidgetReference InItem, FName InSlotName, TSharedPtr InBlueprintEditor) : FNamedSlotModelBase(InSlotName, InBlueprintEditor) , Item(InItem) { #if WITH_EDITOR // Revive trashed child of this NamedSlot, if any. if (INamedSlotInterface* TemplateWidget = Cast(Item.GetTemplate())) { if (UWidget* SlotContent = TemplateWidget->GetContentForSlot(SlotName)) { if (SlotContent->HasAllFlags(RF_Transient) && SlotContent->GetOuter() == GetTransientPackage() && SlotContent->GetFName().ToString().StartsWith("TRASH_") && BlueprintEditor.IsValid()) { if (UWidgetBlueprint* Blueprint = BlueprintEditor.Pin()->GetWidgetBlueprintObj()) { FString NewName = SlotContent->GetFName().ToString(); NewName.RemoveFromStart("TRASH_"); SlotContent->ClearFlags(RF_Transient); SlotContent->Rename(*NewName, Blueprint->WidgetTree); TemplateWidget->SetContentForSlot(SlotName, SlotContent); } } } } // Update the list of bindings if any renaming has occurred. if (UUserWidget* TemplateWidget = Cast(Item.GetTemplate())) { TemplateWidget->AssignGUIDToBindings(); if (!TemplateWidget->GetContentForSlot(SlotName)) { TemplateWidget->UpdateBindingForSlot(SlotName); } } #endif } FName FNamedSlotModel::GetUniqueName() const { if ( const UWidget* WidgetTemplate = Item.GetTemplate() ) { TStringBuilder<256> StringBuilder; StringBuilder.Append(WidgetTemplate->GetName()); StringBuilder.Append(TEXT(".")); SlotName.AppendString(StringBuilder); return FName(StringBuilder); } return FName(); } INamedSlotInterface* FNamedSlotModel::GetNamedSlotHost() const { return Cast(Item.GetTemplate()); } UWidget* FNamedSlotModel::GetNamedSlotHostWidget() const { return Cast(Item.GetTemplate()); } void FNamedSlotModel::OnSelection() { TSharedPtr Editor = BlueprintEditor.Pin(); check(Editor.IsValid()); FNamedSlotSelection Selection; Selection.NamedSlotHostWidget = Item; Selection.SlotName = SlotName; Editor->SetSelectedNamedSlot(Selection); } ////////////////////////////////////////////////////////////////////////// FNamedSlotModelSubclass::FNamedSlotModelSubclass(UWidgetBlueprint* InBlueprint, FName InSlotName, TSharedPtr InBlueprintEditor) : FNamedSlotModelBase(InSlotName, InBlueprintEditor) , Blueprint(InBlueprint) { } FName FNamedSlotModelSubclass::GetUniqueName() const { const FString UniqueSlot = TEXT("This.") + SlotName.ToString(); return FName(*UniqueSlot); } INamedSlotInterface* FNamedSlotModelSubclass::GetNamedSlotHost() const { return Blueprint->WidgetTree; } UWidget* FNamedSlotModelSubclass::GetNamedSlotHostWidget() const { // CDO stored named slot elements don't have a host widget they can use/reference/talk about. return nullptr; } void FNamedSlotModelSubclass::OnSelection() { TSharedPtr Editor = BlueprintEditor.Pin(); FNamedSlotSelection Selection; Selection.SlotName = SlotName; Editor->SetSelectedNamedSlot(Selection); } ////////////////////////////////////////////////////////////////////////// FHierarchyWidget::FHierarchyWidget(FWidgetReference InItem, TSharedPtr InBlueprintEditor) : FHierarchyModel(InBlueprintEditor) , Item(InItem) , bEditing(false) , bNameTextValid(false) { } FName FHierarchyWidget::GetUniqueName() const { UWidget* WidgetTemplate = Item.GetTemplate(); if ( WidgetTemplate ) { return WidgetTemplate->GetFName(); } return NAME_None; } FText FHierarchyWidget::GetText() const { UWidget* WidgetTemplate = Item.GetTemplate(); if ( WidgetTemplate ) { return bEditing ? WidgetTemplate->GetLabelText() : WidgetTemplate->GetLabelTextWithMetadata(); } return FText::GetEmpty(); } FText FHierarchyWidget::GetImageToolTipText() const { UWidget* WidgetTemplate = Item.GetTemplate(); if ( WidgetTemplate ) { UClass* WidgetClass = WidgetTemplate->GetClass(); if ( WidgetClass->IsChildOf( UUserWidget::StaticClass() ) && WidgetClass->ClassGeneratedBy ) { auto& Description = Cast( WidgetClass->ClassGeneratedBy )->BlueprintDescription; if ( Description.Len() > 0 ) { return FText::FromString( Description ); } } return WidgetClass->GetToolTipText(); } return FText::GetEmpty(); } FText FHierarchyWidget::GetLabelToolTipText() const { FText ToolTipText = FText::GetEmpty(); auto AppendNewLineOrSetValue = [&ToolTipText](const FText& NewText) { static const FTextFormat ToolTipNewLineFormat = LOCTEXT("ToolTipNewLineFormat", "{0}\n\n{1}"); ToolTipText = ToolTipText.IsEmpty() ? NewText : FText::Format(ToolTipNewLineFormat, ToolTipText, NewText); }; if (UWidget* WidgetTemplate = Item.GetTemplate()) { // If the user has provided a name, give a tooltip with the widget type for easy reference if (!WidgetTemplate->IsGeneratedName()) { AppendNewLineOrSetValue(FText::FromString(TEXT( "[" ) + WidgetTemplate->GetClass()->GetDisplayNameText().ToString() + TEXT( "]" ) )); } if (UE::UMG::Editor::bDisplayWidgetVariableGuids) { TSharedPtr WidgetBlueprintEditor = BlueprintEditor.Pin(); if (WidgetBlueprintEditor.IsValid()) { if (const UWidgetBlueprint* WidgetBlueprint = WidgetBlueprintEditor->GetWidgetBlueprintObj()) { static const FTextFormat VariableGUIDFormat = LOCTEXT("WidgetVariableGUIDFormat", "GUID: {0}"); const FGuid VariableGuid = WidgetBlueprint->WidgetVariableNameToGuidMap.FindRef(WidgetTemplate->GetFName()); AppendNewLineOrSetValue(FText::Format(VariableGUIDFormat, FText::FromString(VariableGuid.ToString(EGuidFormats::DigitsWithHyphens)))); } } } } return ToolTipText; } void FHierarchyWidget::GetFilterStrings(TArray& OutStrings) const { FHierarchyModel::GetFilterStrings(OutStrings); UWidget* WidgetTemplate = Item.GetTemplate(); if (WidgetTemplate && !WidgetTemplate->IsGeneratedName()) { OutStrings.Add(WidgetTemplate->GetClass()->GetName()); OutStrings.Add(WidgetTemplate->GetClass()->GetDisplayNameText().ToString()); } } const FSlateBrush* FHierarchyWidget::GetImage() const { if (Item.GetTemplate()) { return FSlateIconFinder::FindIconBrushForClass(Item.GetTemplate()->GetClass()); } return nullptr; } FSlateFontInfo FHierarchyWidget::GetFont() const { UWidget* WidgetTemplate = Item.GetTemplate(); if ( WidgetTemplate ) { if ( !WidgetTemplate->IsGeneratedName() && WidgetTemplate->bIsVariable ) { // TODO UMG Hacky move into style area return FCoreStyle::GetDefaultFontStyle("Bold", 10); } } static FName NormalFont("NormalFont"); return FCoreStyle::Get().GetFontStyle(NormalFont); } TOptional FHierarchyWidget::HandleCanAcceptDrop(const FDragDropEvent& DragDropEvent, EItemDropZone DropZone) { bool bIsFreeFromCircularReferences = true; TSharedPtr DragDropOp = DragDropEvent.GetOperation(); if (DragDropOp.IsValid()) { UWidgetBlueprint* Blueprint = BlueprintEditor.Pin()->GetWidgetBlueprintObj(); if (Blueprint) { if (UWidget* Widget = FWidgetBlueprintEditorUtils::GetWidgetTemplateFromDragDrop(Blueprint, Blueprint->WidgetTree, DragDropOp)) { bIsFreeFromCircularReferences = !HasCircularReferences(Blueprint, Widget, DragDropOp); } } } bool bIsDrop = false; return bIsFreeFromCircularReferences ? ProcessHierarchyDragDrop(DragDropEvent, DropZone, bIsDrop, BlueprintEditor.Pin(), Item) : TOptional(); } void FHierarchyWidget::HandleDragLeave(const FDragDropEvent& DragDropEvent) { TSharedPtr DecoratedDragDropOp = DragDropEvent.GetOperationAs(); if ( DecoratedDragDropOp.IsValid() ) { DecoratedDragDropOp->ResetToDefaultToolTip(); } FHierarchyModel::HandleDragLeave(DragDropEvent); } FReply FHierarchyWidget::HandleAcceptDrop(const FDragDropEvent& DragDropEvent, EItemDropZone DropZone) { bool bIsDrop = true; TOptional Zone = ProcessHierarchyDragDrop(DragDropEvent, DropZone, bIsDrop, BlueprintEditor.Pin(), Item); if (Zone.IsSet()) { TArray DragDropPreviewWidgets; DetermineDragDropPreviewWidgets(DragDropPreviewWidgets, DragDropEvent); // Move the remaining widgets into the transient package. Otherwise, they will remain outered to the WidgetTree and end up as properties in the BP class layout as a result. UWidgetBlueprint* BP = Cast(BlueprintEditor.Pin()->GetBlueprintObj()); if (BP) { for (UWidget* Widget : DragDropPreviewWidgets) { FHierarchyModel::RemovePreviewWidget(BP, Widget); } } } return Zone.IsSet() ? FReply::Handled() : FReply::Unhandled(); } bool FHierarchyWidget::OnVerifyNameTextChanged(const FText& InText, FText& OutErrorMessage) { bNameTextValid = FWidgetBlueprintEditorUtils::VerifyWidgetRename(BlueprintEditor.Pin().ToSharedRef(), Item, InText, OutErrorMessage); return bNameTextValid; } void FHierarchyWidget::OnNameTextCommited(const FText& InText, ETextCommit::Type CommitInfo) { if (CommitInfo == ETextCommit::OnEnter || bNameTextValid) { FWidgetBlueprintEditorUtils::RenameWidget(BlueprintEditor.Pin().ToSharedRef(), Item.GetTemplate()->GetFName(), InText.ToString()); } bNameTextValid = false; } void FHierarchyWidget::GetChildren(TArray< TSharedPtr >& Children) { TSharedPtr BPEd = BlueprintEditor.Pin(); // Check for named slots if ( INamedSlotInterface* NamedSlotHost = Cast(Item.GetTemplate()) ) { TArray SlotNames; NamedSlotHost->GetSlotNames(SlotNames); for ( FName& SlotName : SlotNames ) { TSharedPtr ChildItem = MakeShareable(new FNamedSlotModel(Item, SlotName, BPEd)); Children.Add(ChildItem); } } // Check if it's a panel widget that can support children if ( UPanelWidget* PanelWidget = Cast(Item.GetTemplate()) ) { for ( int32 i = 0; i < PanelWidget->GetChildrenCount(); i++ ) { UWidget* Child = PanelWidget->GetChildAt(i); if ( Child ) { TSharedPtr ChildItem = MakeShareable(new FHierarchyWidget(BPEd->GetReferenceFromTemplate(Child), BPEd)); Children.Add(ChildItem); } } } } void FHierarchyWidget::OnSelection() { TSet SelectedWidgets; SelectedWidgets.Add(Item); BlueprintEditor.Pin()->SelectWidgets(SelectedWidgets, true); } void FHierarchyWidget::OnMouseEnter() { BlueprintEditor.Pin()->SetHoveredWidget(Item); } void FHierarchyWidget::OnMouseLeave() { BlueprintEditor.Pin()->ClearHoveredWidget(); } bool FHierarchyWidget::IsHovered() const { return BlueprintEditor.Pin()->GetHoveredWidget() == Item; } void FHierarchyWidget::UpdateSelection() { const TSet& SelectedWidgets = BlueprintEditor.Pin()->GetSelectedWidgets(); bIsSelected = SelectedWidgets.Contains(Item); } bool FHierarchyWidget::CanRename() const { return !IsLockedInDesigner(); } void FHierarchyWidget::RequestBeginRename() { RenameEvent.ExecuteIfBound(); } void FHierarchyWidget::OnBeginEditing() { bEditing = true; } void FHierarchyWidget::OnEndEditing() { bEditing = false; } ////////////////////////////////////////////////////////////////////////// void SHierarchyViewItem::Construct(const FArguments& InArgs, const TSharedRef< STableViewBase >& InOwnerTableView, TSharedPtr InModel) { bHovered = false; Model = InModel; Model->RenameEvent.BindSP(this, &SHierarchyViewItem::OnRequestBeginRename); SetHover(TAttribute::CreateSP(this, &SHierarchyViewItem::ShouldAppearHovered)); STableRow< TSharedPtr >::Construct( STableRow< TSharedPtr >::FArguments() .OnCanAcceptDrop(this, &SHierarchyViewItem::HandleCanAcceptDrop) .OnAcceptDrop(this, &SHierarchyViewItem::HandleAcceptDrop) .OnDragDetected(this, &SHierarchyViewItem::HandleDragDetected) .OnDragEnter(this, &SHierarchyViewItem::HandleDragEnter) .OnDragLeave(this, &SHierarchyViewItem::HandleDragLeave) .Padding(0.0f) .Content() [ SNew(SHorizontalBox) // Widget icon + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SImage) .ColorAndOpacity(FSlateColor::UseForeground()) .Image(Model->GetImage()) .ToolTipText(Model->GetImageToolTipText()) ] // Name of the widget + SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(2, 0, 0, 0) .VAlign(VAlign_Center) [ SAssignNew(EditBox, SInlineEditableTextBlock) .Font(this, &SHierarchyViewItem::GetItemFont) .Text(this, &SHierarchyViewItem::GetItemText) .ToolTipText(Model->GetLabelToolTipText()) .HighlightText(InArgs._HighlightText) .IsReadOnly(this, &SHierarchyViewItem::IsReadOnly) .OnEnterEditingMode(this, &SHierarchyViewItem::OnBeginNameTextEdit) .OnExitEditingMode(this, &SHierarchyViewItem::OnEndNameTextEdit) .OnVerifyTextChanged(this, &SHierarchyViewItem::OnVerifyNameTextChanged) .OnTextCommitted(this, &SHierarchyViewItem::OnNameTextCommited) .IsSelected(this, &SHierarchyViewItem::IsSelectedExclusively) ] // Flow Direction Icon + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(STextBlock) // TODO Tooltip should tell you what the widget is setup to do .ToolTipText(LOCTEXT("NavigationHierarchyToolTip", "This widget overrides the navigation preference.")) .Visibility_Lambda([InModel] { return InModel->DoesWidgetOverrideNavigation() ? EVisibility::Visible : EVisibility::Collapsed; }) .ColorAndOpacity(FAppStyle::Get().GetSlateColor("Colors.Foreground")) .Font(FAppStyle::Get().GetFontStyle("FontAwesome.10")) .Text(FEditorFontGlyphs::Arrows) ] // Localization Flow Direction Icon + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(STextBlock) // TODO Tooltip should tell you what the widget is setup to do .ToolTipText(LOCTEXT("FlowDirectionHierarchyToolTip", "This widget overrides the culture/localization flow direction preference.")) .Visibility_Lambda([InModel] { return InModel->DoesWidgetOverrideFlowDirection() ? EVisibility::Visible : EVisibility::Collapsed; }) .ColorAndOpacity(FAppStyle::Get().GetSlateColor("Colors.Foreground")) .Font(FAppStyle::Get().GetFontStyle("FontAwesome.10")) .Text(FEditorFontGlyphs::Exchange) ] // Locked Icon + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SButton) .ContentPadding(FMargin(3, 1)) .ButtonStyle(FAppStyle::Get(), "HoverHintOnly") .ForegroundColor(FAppStyle::Get().GetSlateColor("Colors.Foreground")) .OnClicked(this, &SHierarchyViewItem::OnToggleLockedInDesigner) .Visibility(Model->CanControlLockedInDesigner() ? EVisibility::Visible : EVisibility::Hidden) .ToolTipText(LOCTEXT("WidgetLockedButtonToolTip", "Locks or Unlocks this widget and all children. Locking a widget prevents it from being selected in the designer view by clicking on them.\n\nHolding [Shift] will only affect this widget and no children.")) .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew(SBox) .MinDesiredWidth(12.0f) .HAlign(HAlign_Left) [ SNew(STextBlock) .Font(FAppStyle::Get().GetFontStyle("FontAwesome.10")) .Text(this, &SHierarchyViewItem::GetLockBrushForWidget) ] ] ] // Visibility icon + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SButton) .ContentPadding(FMargin(3, 1)) .ButtonStyle(FAppStyle::Get(), "HoverHintOnly") .ForegroundColor(FAppStyle::Get().GetSlateColor("Colors.Foreground")) .OnClicked(this, &SHierarchyViewItem::OnToggleVisibility) .Visibility(Model->CanControlVisibility() ? EVisibility::Visible : EVisibility::Hidden) .ToolTipText(LOCTEXT("WidgetVisibilityButtonToolTip", "Toggle Widget's Editor Visibility")) .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew(STextBlock) .Font(FAppStyle::Get().GetFontStyle("FontAwesome.10")) .Text(this, &SHierarchyViewItem::GetVisibilityBrushForWidget) ] ] ], InOwnerTableView); } SHierarchyViewItem::~SHierarchyViewItem() { Model->RenameEvent.Unbind(); } void SHierarchyViewItem::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { bHovered = true; STableRow< TSharedPtr >::OnMouseEnter(MyGeometry, MouseEvent); Model->OnMouseEnter(); } void SHierarchyViewItem::OnMouseLeave(const FPointerEvent& MouseEvent) { bHovered = false; STableRow< TSharedPtr >::OnMouseLeave(MouseEvent); Model->OnMouseLeave(); } void SHierarchyViewItem::OnBeginNameTextEdit() { Model->OnBeginEditing(); InitialText = Model->GetText(); } void SHierarchyViewItem::OnEndNameTextEdit() { Model->OnEndEditing(); } bool SHierarchyViewItem::OnVerifyNameTextChanged(const FText& InText, FText& OutErrorMessage) { return Model->OnVerifyNameTextChanged(InText, OutErrorMessage); } void SHierarchyViewItem::OnNameTextCommited(const FText& InText, ETextCommit::Type CommitInfo) { // The model can return nice names "Border_53" becomes [Border] in some cases // This check makes sure we don't rename the object internally to that nice name. // Most common case would be the user enters edit mode by accident then just moves focus away. if (InitialText.EqualToCaseIgnored(InText)) { return; } Model->OnNameTextCommited(InText, CommitInfo); } bool SHierarchyViewItem::IsReadOnly() const { return !Model->CanRename(); } void SHierarchyViewItem::OnRequestBeginRename() { TSharedPtr SafeEditBox = EditBox.Pin(); if ( SafeEditBox.IsValid() ) { SafeEditBox->EnterEditingMode(); } } FSlateFontInfo SHierarchyViewItem::GetItemFont() const { return Model->GetFont(); } FText SHierarchyViewItem::GetItemText() const { return Model->GetText(); } bool SHierarchyViewItem::ShouldAppearHovered() const { return bHovered || Model->IsHovered(); } void SHierarchyViewItem::HandleDragEnter(FDragDropEvent const& DragDropEvent) { Model->HandleDragEnter(DragDropEvent); if (DragHoverExpandTimer.IsValid()) { UnRegisterActiveTimer(DragHoverExpandTimer.ToSharedRef()); DragHoverExpandTimer.Reset(); } if (!IsItemExpanded()) { DragHoverExpandTimer = RegisterActiveTimer( 0.3f, FWidgetActiveTimerDelegate::CreateLambda([this](double InCurrentTime, float InDeltaTime) { if (!IsItemExpanded()) { ToggleExpansion(); } return EActiveTimerReturnType::Stop; })); } } void SHierarchyViewItem::HandleDragLeave(const FDragDropEvent& DragDropEvent) { Model->HandleDragLeave(DragDropEvent); if (DragHoverExpandTimer.IsValid()) { UnRegisterActiveTimer(DragHoverExpandTimer.ToSharedRef()); DragHoverExpandTimer.Reset(); } } TOptional SHierarchyViewItem::HandleCanAcceptDrop(const FDragDropEvent& DragDropEvent, EItemDropZone DropZone, TSharedPtr TargetItem) { return Model->HandleCanAcceptDrop(DragDropEvent, DropZone); } FReply SHierarchyViewItem::HandleDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { return Model->HandleDragDetected(MyGeometry, MouseEvent); } FReply SHierarchyViewItem::HandleAcceptDrop(const FDragDropEvent& DragDropEvent, EItemDropZone DropZone, TSharedPtr TargetItem) { return Model->HandleAcceptDrop(DragDropEvent, DropZone); } FReply SHierarchyViewItem::OnToggleVisibility() { Model->SetIsVisible(!Model->IsVisible()); return FReply::Handled(); } FText SHierarchyViewItem::GetVisibilityBrushForWidget() const { return Model->IsVisible() ? FEditorFontGlyphs::Eye : FEditorFontGlyphs::Eye_Slash; } FReply SHierarchyViewItem::OnToggleLockedInDesigner() { if ( Model.IsValid() ) { const bool bRecursive = FSlateApplication::Get().GetModifierKeys().IsShiftDown() ? false : true; Model->SetIsLockedInDesigner(!Model->IsLockedInDesigner(), bRecursive); } return FReply::Handled(); } FText SHierarchyViewItem::GetLockBrushForWidget() const { return Model.IsValid() && Model->IsLockedInDesigner() ? FEditorFontGlyphs::Lock : FEditorFontGlyphs::Unlock; } #undef LOCTEXT_NAMESPACE