diff --git a/Content/Testing/WB_Test.uasset b/Content/Testing/WB_Test.uasset index 7695b51d..1f441356 100644 --- a/Content/Testing/WB_Test.uasset +++ b/Content/Testing/WB_Test.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f46523098874b027d89403b58a1423db483d42fbb8bc6a92b286083bb4839f4d -size 30689 +oid sha256:919bde37fb2ca9fd98676cc66bd873e94f53b5e1b769253d86d167c3df5b800b +size 34126 diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Create.h new file mode 100644 index 00000000..79a2535e --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_Create.h @@ -0,0 +1,134 @@ +#pragma once + +#include "CoreMinimal.h" +#include "WingServer.h" +#include "WingHandler.h" +#include "WingFetcher.h" +#include "WingWidgets.h" +#include "WingUtils.h" +#include "WidgetBlueprint.h" +#include "Blueprint/WidgetTree.h" +#include "Blueprint/UserWidget.h" +#include "Components/PanelWidget.h" +#include "Widget_Create.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UWing_Widget_Create : public UObject, public IWingHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Widget blueprint to add the widget to")) + FString Blueprint; + + UPROPERTY(meta=(Description="Widget type, from Widget_SearchTypes")) + FString Type; + + UPROPERTY(meta=(Description="Name for the new widget")) + FString Name; + + UPROPERTY(meta=(Optional, Description="Parent widget name. If omitted, sets as root.")) + FString Parent; + + UPROPERTY(meta=(Optional, Description="Whether to expose the widget as a variable in the blueprint (default false)")) + bool IsVariable = false; + + virtual FString GetDescription() const override + { + return TEXT("Add a widget to a Widget Blueprint's widget tree. " + "Use Widget_SearchTypes to find available widget types."); + } + + virtual void Handle() override + { + // Fetch the widget blueprint. + WingFetcher F; + UWidgetBlueprint* BP = F.Walk(Blueprint).Cast(); + if (!BP) return; + UWidgetTree* Tree = BP->WidgetTree; + + // Resolve the widget type. + WingWidgets WidgetMenu; + TArray TypeResults = WidgetMenu.Search(Type, 2, true); + if (!WingUtils::CheckExactlyOneNamed(TypeResults.Num(), TEXT("Widget type"), Type)) return; + + // Load the widget's class. + UClass* WidgetClass = TypeResults[0].Class.LoadSynchronous(); + if (!WidgetClass) + { + UWingServer::Printf(TEXT("ERROR: Failed to load widget class for '%s'\n"), *Type); + return; + } + + // Validate the proposed name. + FString UnsanitizedName = WingUtils::CheckProposedName(Name); + if (UnsanitizedName.IsEmpty()) return; + + // Check that the name is unique among existing widgets. + TArray AllWidgets; + Tree->GetAllWidgets(AllWidgets); + if (!WingUtils::FindExactlyNoneNamed(Name, AllWidgets, TEXT("Widget"))) return; + + // If a parent is specified, find it and verify it's a panel. + UPanelWidget* ParentPanel = nullptr; + if (!Parent.IsEmpty()) + { + UWidget* ParentWidget = WingUtils::FindExactlyOneNamed(Parent, AllWidgets, TEXT("Widget")); + if (!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 + { + if (Tree->RootWidget != nullptr) + { + UWingServer::Printf(TEXT("ERROR: Widget tree already has a root widget. Specify a Parent.\n")); + return; + } + } + + // Create the widget. + UWidget* NewWidget; + FName WidgetFName(*UnsanitizedName); + if (WidgetClass->IsChildOf(UUserWidget::StaticClass())) + NewWidget = CreateWidget(Tree, WidgetClass, WidgetFName); + else + NewWidget = NewObject(Tree, WidgetClass, WidgetFName, RF_Transactional); + + if (!NewWidget) + { + UWingServer::Printf(TEXT("ERROR: Failed to create widget\n")); + return; + } + + // Initialize defaults and set variable flag. + NewWidget->CreatedFromPalette(); + NewWidget->bIsVariable = IsVariable; + + // Add to the tree. + if (ParentPanel) + { + ParentPanel->AddChild(NewWidget); + } + else + { + Tree->RootWidget = NewWidget; + } + + UWingServer::Printf(TEXT("Created widget '%s' of type '%s'\n"), *Name, *Type); + } +}; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_SearchTypes.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_SearchTypes.h new file mode 100644 index 00000000..5040c6bd --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Widget_SearchTypes.h @@ -0,0 +1,50 @@ +#pragma once + +#include "CoreMinimal.h" +#include "WingServer.h" +#include "WingHandler.h" +#include "WingWidgets.h" +#include "Widget_SearchTypes.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UWing_Widget_SearchTypes : public UObject, public IWingHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Query string, can contain *")) + FString Query; + + UPROPERTY(meta=(Optional, Description="Maximum number of results (default 50)")) + int32 MaxResults = 50; + + virtual FString GetDescription() const override + { + return TEXT("Search for widget types that can be added to a Widget Blueprint. " + "Returns names for use with Widget_Create."); + } + + virtual void Handle() override + { + WingWidgets Widgets; + TArray Results = Widgets.Search(Query, MaxResults, false); + for (const WingWidgets::Type& Entry : Results) + { + UWingServer::Printf(TEXT("%s\n"), *Entry.MenuName); + } + + if (Results.Num() == 0) + { + UWingServer::Print(TEXT("No matching widget types found.\n")); + } + else if (Results.Num() >= MaxResults) + { + UWingServer::Printf(TEXT("WARNING: Reached limit of %d results. You may specify MaxResults.\n"), MaxResults); + } + } +}; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingWidgets.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingWidgets.cpp index 005a530e..05c1de26 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingWidgets.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingWidgets.cpp @@ -2,9 +2,93 @@ #include "WingServer.h" #include "WingUtils.h" #include "Blueprint/WidgetTree.h" +#include "WidgetBlueprint.h" #include "Components/Widget.h" #include "Components/PanelWidget.h" #include "Components/PanelSlot.h" +#include "Blueprint/UserWidget.h" +#include "UObject/UObjectIterator.h" +#include "AssetRegistry/AssetData.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetRegistry/IAssetRegistry.h" + +FString WingWidgets::WidgetMenuString(UClass* WidgetClass) +{ + if (UObject* GeneratedBy = WidgetClass->ClassGeneratedBy) + return GeneratedBy->GetPathName(); + UWidget* CDO = WidgetClass->GetDefaultObject(); + FString Category = CDO->GetPaletteCategory().ToString(); + FString Name = WidgetClass->GetName(); + return WingUtils::StandardizeMenuItem(Category + TEXT("|") + Name); +} + +FString WingWidgets::WidgetMenuString(const FAssetData& Data) +{ + return Data.GetObjectPathString(); +} + +WingWidgets::WingWidgets() +{ + TSortedMap Sorted; + + // Collect loaded native widget classes. + for (TObjectIterator It; It; ++It) + { + UClass* Class = *It; + if (!Class->IsChildOf(UWidget::StaticClass())) continue; + if (Class->HasAnyClassFlags(CLASS_Abstract | CLASS_Deprecated | CLASS_NewerVersionExists | CLASS_Hidden)) continue; + if (Class->HasAnyFlags(RF_Transient) && Class->HasAnyClassFlags(CLASS_CompiledFromBlueprint)) continue; + + FString MenuName = WidgetMenuString(Class); + Sorted.Add(MenuName, { MenuName, Class }); + } + + // Collect unloaded Widget Blueprint assets from the AssetRegistry. + IAssetRegistry& Registry = *IAssetRegistry::Get(); + TArray AssetResults; + Registry.GetAssetsByClass(UWidgetBlueprint::StaticClass()->GetClassPathName(), AssetResults, true); + for (const FAssetData& Data : AssetResults) + { + if (Data.IsAssetLoaded()) continue; + + FString GeneratedClassPath; + if (!Data.GetTagValue(FBlueprintTags::GeneratedClassPath, GeneratedClassPath)) continue; + + FString MenuName = WidgetMenuString(Data); + Type Entry; + Entry.MenuName = MenuName; + Entry.Class = TSoftClassPtr(FSoftClassPath(GeneratedClassPath)); + Sorted.Add(MenuName, Entry); + } + + // Flatten into the array. + AllWidgets.Reserve(Sorted.Num()); + for (const auto& Pair : Sorted) + { + AllWidgets.Add(Pair.Value); + } +} + +TArray WingWidgets::Search(const FString& Query, int32 MaxResults, bool Exact) +{ + FString ExtQuery = FString::Printf(TEXT("*%s*"), *Query.Replace(TEXT(" "), TEXT("*"))); + TArray Results; + for (const Type& Entry : AllWidgets) + { + if (Results.Num() >= MaxResults) break; + if (Exact) + { + if (Entry.MenuName.Equals(Query, ESearchCase::IgnoreCase)) + Results.Add(Entry); + } + else + { + if (Entry.MenuName.MatchesWildcard(ExtQuery, ESearchCase::IgnoreCase)) + Results.Add(Entry); + } + } + return Results; +} void WingWidgets::PrintWidgetTree(UWidget* Widget, int32 Depth) { diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingWidgets.h b/Plugins/UEWingman/Source/UEWingman/Public/WingWidgets.h index a4587126..a8d102ab 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingWidgets.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingWidgets.h @@ -1,13 +1,33 @@ #pragma once #include "CoreMinimal.h" +#include "Components/Widget.h" class UWidgetTree; -class UWidget; +struct FAssetData; + // Utility functions for widget blueprint manipulation. class WingWidgets { public: + struct Type + { + FString MenuName; + TSoftClassPtr Class; + }; + + TArray AllWidgets; + + WingWidgets(); + + // Search for widget types whose name matches the query. + TArray Search(const FString& Query, int32 MaxResults, bool Exact); + + // Print out a tree of widgets, just showing the names and types. static void PrintWidgetTree(UWidget* Widget, int32 Depth); + +private: + static FString WidgetMenuString(UClass* WidgetClass); + static FString WidgetMenuString(const FAssetData &Data); };