// Copyright Epic Games, Inc. All Rights Reserved. #include "Details/SWidgetDetailsView.h" #include "Widgets/Text/STextBlock.h" #include "Framework/Application/SlateApplication.h" #include "Widgets/Images/SImage.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SCheckBox.h" #include "IDetailsView.h" #include "IDetailKeyframeHandler.h" #include "Animation/UMGDetailKeyframeHandler.h" #include "Details/DetailWidgetExtensionHandler.h" #include "EditorClassUtils.h" #include "Customizations/UMGDetailCustomizations.h" #include "PropertyEditorModule.h" #include "Customizations/SlateBrushCustomization.h" #include "Customizations/SlateFontInfoCustomization.h" #include "Customizations/WidgetTypeCustomization.h" #include "Customizations/WidgetChildTypeCustomization.h" #include "Customizations/WidgetNavigationCustomization.h" #include "Customizations/CanvasSlotCustomization.h" #include "Customizations/HorizontalAlignmentCustomization.h" #include "Customizations/VerticalAlignmentCustomization.h" #include "Customizations/SlateChildSizeCustomization.h" #include "Customizations/TextJustifyCustomization.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Blueprint/WidgetTree.h" #include "WidgetBlueprintEditorUtils.h" #include "ScopedTransaction.h" #include "Styling/SlateIconFinder.h" #include "UMGEditorModule.h" #include "UMGEditorProjectSettings.h" #include "SUIComponentView.h" #define LOCTEXT_NAMESPACE "UMG" void SWidgetDetailsView::Construct(const FArguments& InArgs, TSharedPtr InBlueprintEditor) { BlueprintEditor = InBlueprintEditor; // Create a property view FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor"); FDetailsViewArgs DetailsViewArgs; DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea; DetailsViewArgs.bHideSelectionTip = true; DetailsViewArgs.NotifyHook = this; PropertyView = EditModule.CreateDetailView(DetailsViewArgs); // Create a handler for keyframing via the details panel if (FWidgetBlueprintEditorUtils::GetRelevantSettings(BlueprintEditor)->bEnableWidgetAnimationEditor) { TSharedRef KeyframeHandler = MakeShareable(new FUMGDetailKeyframeHandler(InBlueprintEditor)); PropertyView->SetKeyframeHandler(KeyframeHandler); } // Create a handler for property binding via the details panel TSharedRef BindingHandler = MakeShareable( new FDetailWidgetExtensionHandler( InBlueprintEditor ) ); PropertyView->SetExtensionHandler(BindingHandler); // Handle EditDefaultsOnly and EditInstanceOnly flags PropertyView->SetIsPropertyVisibleDelegate(FIsPropertyVisible::CreateSP(this, &SWidgetDetailsView::IsPropertyVisible)); // Notify us of object selection changes so we can update the package re-mapping PropertyView->SetOnObjectArrayChanged(FOnObjectArrayChanged::CreateSP(this, &SWidgetDetailsView::OnPropertyViewObjectArrayChanged)); PropertyView->SetIsPropertyEditingEnabledDelegate(FIsPropertyEditingEnabled::CreateSP(this, &SWidgetDetailsView::IsDetailsPanelEditingAllowed)); TSharedRef IsVariableCheckbox = SNew(SCheckBox) .IsChecked(this, &SWidgetDetailsView::GetIsVariable) .OnCheckStateChanged(this, &SWidgetDetailsView::HandleIsVariableChanged) .IsEnabled(this, &SWidgetDetailsView::IsDetailsPanelEditingAllowed) .Padding(FMargin(3.0f, 1.0f, 18.0f, 1.0f)) [ SNew(STextBlock) .Text(LOCTEXT("IsVariable", "Is Variable")) ]; const UWidgetEditingProjectSettings* UMGEditorProjectSettings = FWidgetBlueprintEditorUtils::GetRelevantSettings(InBlueprintEditor); const bool bEnableUIComponent = UMGEditorProjectSettings ? UMGEditorProjectSettings->bEnableUIComponentsProperty : true; // default to true ChildSlot [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 2.0f) [ SAssignNew(BorderArea, SBorder) .BorderImage(FAppStyle::GetBrush(TEXT("ToolPanel.GroupBorder"))) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .Padding(4.0f) [ SNew(SHorizontalBox) .Visibility(this, &SWidgetDetailsView::GetCategoryAreaVisibility) + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f, 0.0f, 3.0f, 0.0f) .VAlign(VAlign_Center) [ SNew(SImage) .Image(FAppStyle::GetBrush(TEXT("UMGEditor.CategoryIcon"))) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f, 0.0f, 6.0f, 0.0f) [ SNew(SBox) .WidthOverride(200.0f) .VAlign(VAlign_Center) [ SNew(SEditableTextBox) .SelectAllTextWhenFocused(true) .ToolTipText(LOCTEXT("CategoryToolTip", "Sets the category of the widget")) .HintText(LOCTEXT("Category", "Category")) .Text(this, &SWidgetDetailsView::GetCategoryText) .OnTextCommitted(this, &SWidgetDetailsView::HandleCategoryTextCommitted) ] ] ] + SVerticalBox::Slot() .AutoHeight() .Padding(4.0f) [ SNew(SHorizontalBox) .Visibility(this, &SWidgetDetailsView::GetNameAreaVisibility) + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f, 0.0f, 3.0f, 0.0f) .VAlign(VAlign_Center) [ SNew(SImage) .Image(this, &SWidgetDetailsView::GetNameIcon) .ColorAndOpacity(FSlateColor::UseForeground()) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f, 0.0f, 6.0f, 0.0f) [ SNew(SBox) .WidthOverride(200.0f) .VAlign(VAlign_Center) [ SAssignNew(NameTextBox, SEditableTextBox) .SelectAllTextWhenFocused(true) .HintText(LOCTEXT("Name", "Name")) .Text(this, &SWidgetDetailsView::GetNameText) .IsEnabled(this, &SWidgetDetailsView::IsWidgetNameFieldEnabled) .OnTextChanged(this, &SWidgetDetailsView::HandleNameTextChanged) .OnTextCommitted(this, &SWidgetDetailsView::HandleNameTextCommitted) ] ] + SHorizontalBox::Slot() .AutoWidth() [ FWidgetBlueprintEditorUtils::GetRelevantSettings(BlueprintEditor)->bEnableMakeVariable ? IsVariableCheckbox : SNullWidget::NullWidget ] + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SAssignNew(ClassLinkArea, SBox) ] ] // Only if not the user widget + SVerticalBox::Slot() .AutoHeight() .HAlign(EHorizontalAlignment::HAlign_Fill) .Padding(bEnableUIComponent ? 4.0f : 0.0f, 0.0f) [ bEnableUIComponent ? SNew(SUIComponentView, InBlueprintEditor) .IsEnabled(this, &SWidgetDetailsView::IsWidgetNameFieldEnabled) .Visibility(this, &SWidgetDetailsView::GetComponentAreaVisibility) : SNullWidget::NullWidget ] ] ] + SVerticalBox::Slot() .FillHeight(1.0f) [ PropertyView.ToSharedRef() ] ]; BlueprintEditor.Pin()->OnSelectedWidgetsChanging.AddRaw(this, &SWidgetDetailsView::OnEditorSelectionChanging); BlueprintEditor.Pin()->OnSelectedWidgetsChanged.AddRaw(this, &SWidgetDetailsView::OnEditorSelectionChanged); RegisterCustomizations(); // Refresh the selection in the details panel. OnEditorSelectionChanged(); } SWidgetDetailsView::~SWidgetDetailsView() { if ( BlueprintEditor.IsValid() ) { BlueprintEditor.Pin()->OnSelectedWidgetsChanging.RemoveAll(this); BlueprintEditor.Pin()->OnSelectedWidgetsChanged.RemoveAll(this); } // Unregister the property type layouts PropertyView->UnregisterInstancedCustomPropertyTypeLayout(TEXT("Widget")); PropertyView->UnregisterInstancedCustomPropertyTypeLayout(TEXT("WidgetChild")); PropertyView->UnregisterInstancedCustomPropertyTypeLayout(TEXT("WidgetNavigation")); PropertyView->UnregisterInstancedCustomPropertyTypeLayout(TEXT("PanelSlot")); PropertyView->UnregisterInstancedCustomPropertyTypeLayout(TEXT("EHorizontalAlignment")); PropertyView->UnregisterInstancedCustomPropertyTypeLayout(TEXT("EVerticalAlignment")); PropertyView->UnregisterInstancedCustomPropertyTypeLayout(TEXT("SlateChildSize")); PropertyView->UnregisterInstancedCustomPropertyTypeLayout(TEXT("SlateBrush")); PropertyView->UnregisterInstancedCustomPropertyTypeLayout(TEXT("SlateFontInfo")); PropertyView->UnregisterInstancedCustomPropertyTypeLayout(TEXT("ETextJustify")); } void SWidgetDetailsView::RegisterCustomizations() { check(BlueprintEditor.Pin()); TSharedRef BlueprintEditorRef = BlueprintEditor.Pin().ToSharedRef(); PropertyView->RegisterInstancedCustomPropertyLayout(UWidget::StaticClass(), FOnGetDetailCustomizationInstance::CreateStatic(&FBlueprintWidgetCustomization::MakeInstance, BlueprintEditorRef, BlueprintEditorRef->GetBlueprintObj())); PropertyView->RegisterInstancedCustomPropertyTypeLayout(TEXT("Widget"), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FWidgetTypeCustomization::MakeInstance, BlueprintEditorRef), nullptr); PropertyView->RegisterInstancedCustomPropertyTypeLayout(TEXT("WidgetChild"), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FWidgetChildTypeCustomization::MakeInstance, BlueprintEditor.Pin().ToSharedRef()), nullptr); PropertyView->RegisterInstancedCustomPropertyTypeLayout(TEXT("WidgetNavigation"), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FWidgetNavigationCustomization::MakeInstance, BlueprintEditorRef)); PropertyView->RegisterInstancedCustomPropertyTypeLayout(TEXT("PanelSlot"), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FCanvasSlotCustomization::MakeInstance, BlueprintEditorRef->GetBlueprintObj())); PropertyView->RegisterInstancedCustomPropertyTypeLayout(TEXT("EHorizontalAlignment"), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FHorizontalAlignmentCustomization::MakeInstance)); PropertyView->RegisterInstancedCustomPropertyTypeLayout(TEXT("EVerticalAlignment"), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FVerticalAlignmentCustomization::MakeInstance)); PropertyView->RegisterInstancedCustomPropertyTypeLayout(TEXT("SlateChildSize"), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FSlateChildSizeCustomization::MakeInstance)); PropertyView->RegisterInstancedCustomPropertyTypeLayout(TEXT("SlateBrush"), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FSlateBrushStructCustomization::MakeInstance, false)); PropertyView->RegisterInstancedCustomPropertyTypeLayout(TEXT("SlateFontInfo"), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FSlateFontInfoStructCustomization::MakeInstance)); PropertyView->RegisterInstancedCustomPropertyTypeLayout(TEXT("ETextJustify"), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FTextJustifyCustomization::MakeInstance)); TWeakPtr WeakBlueprintEditor = BlueprintEditor; IUMGEditorModule& UMGEditorModule = FModuleManager::LoadModuleChecked("UMGEditor"); for (const IUMGEditorModule::FCustomPropertyTypeLayout& Layout : UMGEditorModule.GetAllInstancedCustomPropertyTypeLayout()) { if (Layout.Type.GetAssetName().IsValid() && Layout.Delegate.IsBound()) { PropertyView->RegisterInstancedCustomPropertyTypeLayout(Layout.Type.GetAssetName(), FOnGetPropertyTypeCustomizationInstance::CreateLambda([LocalDelegate = Layout.Delegate, WeakBlueprintEditor] { return LocalDelegate.Execute(WeakBlueprintEditor); })); } } } void SWidgetDetailsView::OnEditorSelectionChanging() { ClearFocusIfOwned(); // We force the destruction of the currently monitored object when selection is about to change, to ensure all migrations occur // immediately. SelectedObjects.Empty(); PropertyView->SetObjects(SelectedObjects); } void SWidgetDetailsView::OnEditorSelectionChanged() { // Clear selection in the property view. SelectedObjects.Empty(); PropertyView->SetObjects(SelectedObjects); TOptional bIsWidgetSelection; // Add any selected widgets to the list of pending selected objects. const TSet& SelectedWidgets = BlueprintEditor.Pin()->GetSelectedWidgets(); for ( const FWidgetReference& WidgetRef : SelectedWidgets ) { // Edit actions will go directly to the preview widget, changes will be // propagated to the template via SWidgetDetailsView::NotifyPostChange SelectedObjects.Add(WidgetRef.GetPreview()); bIsWidgetSelection = true; } // Add any selected objects (non-widgets) to the pending selected objects. const TSet>& Selection = BlueprintEditor.Pin()->GetSelectedObjects(); for ( const TWeakObjectPtr Selected : Selection ) { if ( UObject* S = Selected.Get() ) { SelectedObjects.Add(S); bIsWidgetSelection = bIsWidgetSelection.Get(true) && Cast(S) != nullptr; } } BorderArea->SetVisibility(bIsWidgetSelection.Get(false) ? EVisibility::Visible : EVisibility::Collapsed); // If only 1 valid selected object exists, update the class link to point to the right class. if ( SelectedObjects.Num() == 1 && SelectedObjects[0].IsValid() ) { FEditorClassUtils::FSourceLinkParams SourceLinkParams; SourceLinkParams.bUseDefaultFormat = true; ClassLinkArea->SetContent(FEditorClassUtils::GetSourceLink(SelectedObjects[0]->GetClass(), SourceLinkParams)); } else { ClassLinkArea->SetContent(SNullWidget::NullWidget); } // Update the preview view to look at the current selection set. const bool bForceRefresh = false; PropertyView->SetObjects(SelectedObjects, bForceRefresh); } void SWidgetDetailsView::OnPropertyViewObjectArrayChanged(const FString& InTitle, const TArray& InObjects) { // Update the package remapping for all selected objects so that we can find the correct localization ID when editing text properties (since we edit a copy of the real data, not connected to the asset package) UBlueprint* UMGBlueprint = BlueprintEditor.Pin()->GetBlueprintObj(); if (UMGBlueprint) { UPackage* UMGPackage = UMGBlueprint->GetOutermost(); if (UMGPackage) { TMap, TWeakObjectPtr> ObjectToPackageMapping; for (const TWeakObjectPtr Object : InObjects) { ObjectToPackageMapping.Add(Object, UMGPackage); } PropertyView->SetObjectPackageOverrides(ObjectToPackageMapping); } } } void SWidgetDetailsView::ClearFocusIfOwned() { static bool bIsReentrant = false; if ( !bIsReentrant ) { bIsReentrant = true; // When the selection is changed, we may be potentially actively editing a property, // if this occurs we need need to immediately clear keyboard focus if ( FSlateApplication::Get().HasFocusedDescendants(AsShared()) ) { FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::Mouse); } bIsReentrant = false; } } bool SWidgetDetailsView::IsDetailsPanelEditingAllowed() const { return !SelectedObjects.ContainsByPredicate([](const TWeakObjectPtr WeakObj) { if (UObject* ObjPtr = WeakObj.Get()) { if (UWidget* Widget = Cast(ObjPtr)) { return Widget->IsLockedInDesigner(); } } return false; }); } bool SWidgetDetailsView::IsWidgetNameFieldEnabled() const { return IsSingleObjectSelected() && IsDetailsPanelEditingAllowed(); } bool SWidgetDetailsView::IsPropertyVisible(const FPropertyAndParent& PropertyAndParent) const { const FProperty& Property = PropertyAndParent.Property; const bool bIsEditDefaultsOnly = Property.HasAnyPropertyFlags(CPF_DisableEditOnInstance); const bool bIsEditInstanceOnly = Property.HasAnyPropertyFlags(CPF_DisableEditOnTemplate); if (bIsEditDefaultsOnly || bIsEditInstanceOnly) { // EditDefaultsOnly properties are only visible when the CDO/root is selected, EditInstanceOnly are only visible when the CDO/root is *not* selected const bool bIsCDOSelected = IsWidgetCDOSelected(); if ((bIsEditDefaultsOnly && !bIsCDOSelected) || (bIsEditInstanceOnly && bIsCDOSelected)) { return false; } } return true; } bool SWidgetDetailsView::IsWidgetCDOSelected() const { if ( SelectedObjects.Num() == 1 ) { UWidget* Widget = Cast(SelectedObjects[0].Get()); // since we're passing preview widget when selecting root (owner) widget, // this is also considered as selecting CDO so the category shows up correctly if (Widget && Widget == BlueprintEditor.Pin()->GetPreview()) { return true; } } return false; } EVisibility SWidgetDetailsView::GetNameAreaVisibility() const { return IsWidgetCDOSelected() ? EVisibility::Collapsed : EVisibility::Visible; } EVisibility SWidgetDetailsView::GetCategoryAreaVisibility() const { return IsWidgetCDOSelected() ? EVisibility::Visible : EVisibility::Collapsed; } EVisibility SWidgetDetailsView::GetComponentAreaVisibility() const { if (IsWidgetCDOSelected() || SelectedObjects.Num() > 1) { return EVisibility::Collapsed; } return EVisibility::Visible; } void SWidgetDetailsView::HandleCategoryTextCommitted(const FText& Text, ETextCommit::Type CommitType) { if ( SelectedObjects.Num() == 1 && !Text.IsEmptyOrWhitespace() ) { if ( UUserWidget* Widget = Cast(SelectedObjects[0].Get()) ) { ensureMsgf(IsWidgetCDOSelected(), TEXT("You can ony change the category if you are the preview widget.")); UUserWidget* WidgetCDO = Widget->GetClass()->GetDefaultObject(); WidgetCDO->PaletteCategory = Text; // Set the new category on the widget blueprint as well so that it's available when the blueprint isn't loaded. UWidgetBlueprint* Blueprint = BlueprintEditor.Pin()->GetWidgetBlueprintObj(); Blueprint->PaletteCategory = Text.ToString(); // Immediately force a rebuild so that all palettes update to show it in a new category. FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); // MarkBlueprintAsStructurallyModified will invalidate the selection. Reselect it. if (TSharedPtr Editor = BlueprintEditor.Pin()) { if (Editor->GetPreview()) { TSet NewSelectedObjects; NewSelectedObjects.Add(Editor->GetPreview()); Editor->SelectObjects(NewSelectedObjects); } } } } } FText SWidgetDetailsView::GetCategoryText() const { if ( SelectedObjects.Num() == 1 ) { if ( UUserWidget* Widget = Cast(SelectedObjects[0].Get()) ) { UUserWidget* WidgetCDO = Widget->GetClass()->GetDefaultObject(); FText Category = WidgetCDO->PaletteCategory; if ( Category.EqualToCaseIgnored(UUserWidget::StaticClass()->GetDefaultObject()->PaletteCategory) ) { return FText::GetEmpty(); } else { return Category; } } } return FText::GetEmpty(); } const FSlateBrush* SWidgetDetailsView::GetNameIcon() const { if (SelectedObjects.Num() == 1) { const UWidget* Widget = Cast(SelectedObjects[0].Get()); if (Widget) { const UClass* WidgetClass = Widget->GetClass(); if (WidgetClass) { return FSlateIconFinder::FindIconBrushForClass(WidgetClass); } } } return nullptr; } FText SWidgetDetailsView::GetNameText() const { if ( SelectedObjects.Num() == 1 ) { UWidget* Widget = Cast(SelectedObjects[0].Get()); if ( Widget ) { return Widget->IsGeneratedName() ? FText::FromName(Widget->GetFName()) : Widget->GetLabelText(); } } if (SelectedObjects.Num() > 1) { return LOCTEXT("MultipleWidgetsSelected", "Multiple Values"); } return FText::GetEmpty(); } bool SWidgetDetailsView::IsSingleObjectSelected() const { return SelectedObjects.Num() == 1; } void SWidgetDetailsView::HandleNameTextChanged(const FText& Text) { FText OutErrorMessage; if ( !HandleVerifyNameTextChanged(Text, OutErrorMessage) ) { NameTextBox->SetError(OutErrorMessage); } else { NameTextBox->SetError(FText::GetEmpty()); } } bool SWidgetDetailsView::HandleVerifyNameTextChanged(const FText& InText, FText& OutErrorMessage) { if ( SelectedObjects.Num() == 1 ) { UWidget* PreviewWidget = Cast(SelectedObjects[0].Get()); TSharedRef BlueprintEditorRef = BlueprintEditor.Pin().ToSharedRef(); FWidgetReference WidgetRef = BlueprintEditorRef->GetReferenceFromPreview(PreviewWidget); return FWidgetBlueprintEditorUtils::VerifyWidgetRename(BlueprintEditorRef, WidgetRef, InText, OutErrorMessage); } else { return false; } } void SWidgetDetailsView::HandleNameTextCommitted(const FText& Text, ETextCommit::Type CommitType) { static bool IsReentrant = false; if ( !IsReentrant ) { IsReentrant = true; if ( SelectedObjects.Num() == 1 ) { FText DummyText; if ( HandleVerifyNameTextChanged(Text, DummyText) ) { UWidget* Widget = Cast(SelectedObjects[0].Get()); if (Widget && !Widget->GetLabelText().EqualToCaseIgnored(Text)) { FWidgetBlueprintEditorUtils::RenameWidget(BlueprintEditor.Pin().ToSharedRef(), Widget->GetFName(), Text.ToString()); } } } IsReentrant = false; if (CommitType == ETextCommit::OnUserMovedFocus || CommitType == ETextCommit::OnCleared) { NameTextBox->SetError(FText::GetEmpty()); } } } ECheckBoxState SWidgetDetailsView::GetIsVariable() const { TOptional Result; for (const TWeakObjectPtr& Obj : SelectedObjects) { if (UWidget* Widget = Cast(Obj.Get())) { ECheckBoxState WidgetState = Widget->bIsVariable ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; if (Result.IsSet() && WidgetState != Result.GetValue()) { return ECheckBoxState::Undetermined; } Result = WidgetState; } } return Result.IsSet() ? Result.GetValue() : ECheckBoxState::Unchecked; } void SWidgetDetailsView::HandleIsVariableChanged(ECheckBoxState CheckState) { if (SelectedObjects.Num() > 0) { UWidgetBlueprint* Blueprint = BlueprintEditor.Pin()->GetWidgetBlueprintObj(); check(Blueprint); TSharedPtr BPEditor = BlueprintEditor.Pin(); check(BPEditor); TArray> WidgetToModify; for (const TWeakObjectPtr& SelectedObject : SelectedObjects) { if (UWidget* Widget = Cast(SelectedObject.Get())) { check(Blueprint->WidgetTree); FWidgetReference WidgetRef = BPEditor->GetReferenceFromTemplate(Blueprint->WidgetTree->FindWidget(Widget->GetFName())); if (WidgetRef.IsValid()) { WidgetToModify.Add(WidgetRef.GetTemplate()); WidgetToModify.Add(WidgetRef.GetPreview()); } } } if (WidgetToModify.Num() > 0) { const FScopedTransaction Transaction(LOCTEXT("VariableToggle", "Variable Toggle")); for (UWidget* Widget : WidgetToModify) { Widget->Modify(); Widget->bIsVariable = (CheckState == ECheckBoxState::Checked); } // Refresh references and flush editors FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); } } } void SWidgetDetailsView::NotifyPreChange(FEditPropertyChain* PropertyAboutToChange) { // During auto-key do not migrate values if( BlueprintEditor.Pin()->GetSequencer()->GetAutoChangeMode() == EAutoChangeMode::None ) { TSharedPtr Editor = BlueprintEditor.Pin(); const bool bIsModify = true; Editor->MigrateFromChain(nullptr, PropertyAboutToChange, bIsModify); } } void SWidgetDetailsView::NotifyPostChange(const FPropertyChangedEvent& PropertyChangedEvent, FEditPropertyChain* PropertyThatChanged) { const static FName DesignerRebuildName("DesignerRebuild"); if ( PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive && BlueprintEditor.Pin()->GetSequencer()->GetAutoChangeMode() == EAutoChangeMode::None ) { TSharedPtr Editor = BlueprintEditor.Pin(); const bool bIsModify = false; Editor->MigrateFromChain(&PropertyChangedEvent, PropertyThatChanged, bIsModify); // Any time we migrate a property value we need to mark the blueprint as structurally modified so users don't need // to recompile it manually before they see it play in game using the latest version. FBlueprintEditorUtils::MarkBlueprintAsModified(BlueprintEditor.Pin()->GetBlueprintObj()); if (PropertyChangedEvent.ChangeType != EPropertyChangeType::ValueSet) { ClearFocusIfOwned(); } } // If the property that changed is marked as "DesignerRebuild" we invalidate // the preview. if ( PropertyChangedEvent.Property->HasMetaData(DesignerRebuildName) || PropertyThatChanged->GetActiveMemberNode()->GetValue()->HasMetaData(DesignerRebuildName) ) { const bool bViewOnly = true; BlueprintEditor.Pin()->InvalidatePreview(bViewOnly); } } #undef LOCTEXT_NAMESPACE