diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Create.h index 79a2535e..95551620 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Create.h @@ -80,17 +80,8 @@ public: { UWidget* ParentWidget = WingUtils::FindExactlyOneNamed(Parent, AllWidgets, TEXT("Widget")); if (!ParentWidget) return; + if (!WingWidgets::CheckCanBeParent(ParentWidget)) return; ParentPanel = Cast(ParentWidget); - if (!ParentPanel) - { - UWingServer::Printf(TEXT("ERROR: '%s' is not a panel widget and cannot have children\n"), *Parent); - return; - } - if (!ParentPanel->CanAddMoreChildren()) - { - UWingServer::Printf(TEXT("ERROR: '%s' already has a child and cannot accept more\n"), *Parent); - return; - } } else { diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Delete.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Delete.h new file mode 100644 index 00000000..fe9c268a --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Delete.h @@ -0,0 +1,85 @@ +#pragma once + +#include "CoreMinimal.h" +#include "WingServer.h" +#include "WingHandler.h" +#include "WingFetcher.h" +#include "WingUtils.h" +#include "WidgetBlueprint.h" +#include "Blueprint/WidgetTree.h" +#include "Components/PanelWidget.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "Widget_Delete.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UWing_Widget_Delete : public UObject, public IWingHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Path to the widget, eg /Game/Widgets/WB_Test,widget:MyButton")) + FString Widget; + + virtual FString GetDescription() const override + { + return TEXT("Delete a widget from a Widget Blueprint's widget tree. " + "The widget must not have any children."); + } + + virtual void Handle() override + { + // Walk to the widget. + WingFetcher F; + UWidget* TargetWidget = F.Walk(Widget).Cast(); + if (!TargetWidget) return; + + // Get the widget blueprint and tree from the widget's outer chain. + UWidgetBlueprint* BP = TargetWidget->GetTypedOuter(); + if (!BP) + { + UWingServer::Printf(TEXT("ERROR: Could not find owning WidgetBlueprint\n")); + return; + } + UWidgetTree* Tree = BP->WidgetTree; + + // If it's a panel, make sure it has no children. + if (UPanelWidget* Panel = Cast(TargetWidget)) + { + if (Panel->GetChildrenCount() > 0) + { + UWingServer::Printf(TEXT("ERROR: Widget '%s' has %d children. Remove them first.\n"), + *WingUtils::FormatName(TargetWidget), Panel->GetChildrenCount()); + return; + } + } + + FString WidgetName = WingUtils::FormatName(TargetWidget); + + // Remove delegate bindings that reference this widget. + FString WidgetObjName = TargetWidget->GetName(); + for (int32 i = BP->Bindings.Num() - 1; i >= 0; i--) + { + if (BP->Bindings[i].ObjectName == WidgetObjName) + BP->Bindings.RemoveAt(i); + } + + // Remove the widget from the tree. + Tree->RemoveWidget(TargetWidget); + + // Remove variable nodes from the blueprint graph if this was a variable. + if (TargetWidget->bIsVariable) + { + FBlueprintEditorUtils::RemoveVariableNodes(BP, TargetWidget->GetFName()); + } + + // Rename to transient package to avoid name conflicts with future widgets. + TargetWidget->Rename(nullptr, GetTransientPackage()); + + UWingServer::Printf(TEXT("Deleted widget '%s'\n"), *WidgetName); + } +}; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Reparent.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Reparent.h new file mode 100644 index 00000000..480fdd1c --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Reparent.h @@ -0,0 +1,82 @@ +#pragma once + +#include "CoreMinimal.h" +#include "WingServer.h" +#include "WingHandler.h" +#include "WingFetcher.h" +#include "WingUtils.h" +#include "WingWidgets.h" +#include "WidgetBlueprint.h" +#include "Blueprint/WidgetTree.h" +#include "Components/PanelWidget.h" +#include "Widget_Reparent.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UWing_Widget_Reparent : public UObject, public IWingHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Path to the widget, eg /Game/Widgets/WB_Test,widget:MyButton")) + FString Widget; + + UPROPERTY(meta=(Description="Name of the new parent widget. Must be a panel.")) + FString Parent; + + virtual FString GetDescription() const override + { + return TEXT("Move a widget to a different parent in a Widget Blueprint's widget tree."); + } + + virtual void Handle() override + { + // Walk to the widget. + WingFetcher F; + UWidget* TargetWidget = F.Walk(Widget).Cast(); + if (!TargetWidget) return; + + // Get the widget blueprint from the widget's outer chain. + UWidgetBlueprint* BP = TargetWidget->GetTypedOuter(); + if (!BP) + { + UWingServer::Printf(TEXT("ERROR: Could not find owning WidgetBlueprint\n")); + return; + } + UWidgetTree* Tree = BP->WidgetTree; + + // Get all widgets for name lookup. + TArray AllWidgets; + Tree->GetAllWidgets(AllWidgets); + + FString WidgetName = WingUtils::FormatName(TargetWidget); + + // Find the new parent and verify it's a panel with room. + UWidget* ParentWidget = WingUtils::FindExactlyOneNamed(Parent, AllWidgets, TEXT("Widget")); + if (!ParentWidget) return; + if (!WingWidgets::CheckCanBeParent(ParentWidget)) return; + UPanelWidget* ParentPanel = Cast(ParentWidget); + + // Check for circular reparenting. + for (UPanelWidget* Ancestor = ParentPanel; Ancestor != nullptr; Ancestor = Ancestor->GetParent()) + { + if (Ancestor == TargetWidget) + { + UWingServer::Printf(TEXT("ERROR: Cannot reparent '%s' under itself\n"), *WidgetName); + return; + } + } + + // Remove from current location. + Tree->RemoveWidget(TargetWidget); + + // Add to new parent. + ParentPanel->AddChild(TargetWidget); + + UWingServer::Printf(TEXT("Reparented '%s' under '%s'\n"), *WidgetName, *Parent); + } +}; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingWidgets.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingWidgets.cpp index 05c1de26..b590f227 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingWidgets.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingWidgets.cpp @@ -90,6 +90,22 @@ TArray WingWidgets::Search(const FString& Query, int32 MaxRes return Results; } +bool WingWidgets::CheckCanBeParent(UWidget* Widget) +{ + UPanelWidget* Panel = Cast(Widget); + if (!Panel) + { + UWingServer::Printf(TEXT("ERROR: '%s' is not a panel widget and cannot have children\n"), *WingUtils::FormatName(Widget)); + return false; + } + if (!Panel->CanAddMoreChildren()) + { + UWingServer::Printf(TEXT("ERROR: '%s' already has a child and cannot accept more\n"), *WingUtils::FormatName(Widget)); + return false; + } + return true; +} + void WingWidgets::PrintWidgetTree(UWidget* Widget, int32 Depth) { FString Indent = FString::ChrN(Depth * 2, TEXT(' ')); diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingWidgets.h b/Plugins/UEWingman/Source/UEWingman/Public/WingWidgets.h index a8d102ab..45f00221 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingWidgets.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingWidgets.h @@ -24,6 +24,9 @@ public: // Search for widget types whose name matches the query. TArray Search(const FString& Query, int32 MaxResults, bool Exact); + // Check if a widget can accept children. Prints error if not. + static bool CheckCanBeParent(UWidget* Widget); + // Print out a tree of widgets, just showing the names and types. static void PrintWidgetTree(UWidget* Widget, int32 Depth);