// Portage UE 5.5 - voir LPFixButtonPaddingCommandlet.h #include "LPFixButtonPaddingCommandlet.h" #include "AssetRegistry/AssetData.h" #include "AssetRegistry/AssetRegistryModule.h" #include "Blueprint/WidgetTree.h" #include "Components/Button.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "Misc/PackageName.h" #include "UObject/Package.h" #include "UObject/SavePackage.h" #include "WidgetBlueprint.h" DEFINE_LOG_CATEGORY_STATIC(LogLPFixButton, Log, All); namespace { // Valeurs de UE 4.27 : Engine/Source/Runtime/SlateCore/Private/Styling/CoreStyle.cpp const FMargin UE4NormalPadding(2.f, 2.f, 2.f, 2.f); const FMargin UE4PressedPadding(2.f, 3.f, 2.f, 1.f); FString Str(const FMargin& M) { return FString::Printf(TEXT("(%g,%g,%g,%g)"), M.Left, M.Top, M.Right, M.Bottom); } /** Remplace par la valeur UE4 chaque composante egale au defaut UE5. Vrai si modifie. */ bool FixMargin(FMargin& M, const FMargin& UE5, const FMargin& UE4) { const FMargin Before = M; if (M.Left == UE5.Left) { M.Left = UE4.Left; } if (M.Top == UE5.Top) { M.Top = UE4.Top; } if (M.Right == UE5.Right) { M.Right = UE4.Right; } if (M.Bottom == UE5.Bottom) { M.Bottom = UE4.Bottom; } return !(M == Before); } } ULPFixButtonPaddingCommandlet::ULPFixButtonPaddingCommandlet() { IsClient = false; IsServer = false; IsEditor = true; LogToConsole = true; } int32 ULPFixButtonPaddingCommandlet::Main(const FString& Params) { const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase); // Defaut UE5 lu sur le CDO plutot que recopie : c'est lui que recoivent les boutons 4.27. PRAGMA_DISABLE_DEPRECATION_WARNINGS const FButtonStyle& UE5Style = GetDefault()->WidgetStyle; PRAGMA_ENABLE_DEPRECATION_WARNINGS const FMargin UE5Normal = UE5Style.NormalPadding; const FMargin UE5Pressed = UE5Style.PressedPadding; UE_LOG(LogLPFixButton, Warning, TEXT("=== Marges de UButton : defaut UE5 Normal=%s Pressed=%s -> UE4 Normal=%s Pressed=%s%s ==="), *Str(UE5Normal), *Str(UE5Pressed), *Str(UE4NormalPadding), *Str(UE4PressedPadding), bDryRun ? TEXT(" [DRY RUN]") : TEXT("")); FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().SearchAllAssets(true); TArray Assets; AssetRegistryModule.Get().GetAssetsByClass(UWidgetBlueprint::StaticClass()->GetClassPathName(), Assets, true); int32 ButtonsSeen = 0; int32 ButtonsFixed = 0; int32 ButtonsKept = 0; int32 BlueprintsModified = 0; int32 SaveFailures = 0; for (const FAssetData& AssetData : Assets) { if (!AssetData.PackageName.ToString().StartsWith(TEXT("/Game/"))) { continue; } UWidgetBlueprint* Blueprint = Cast(AssetData.GetAsset()); if (!Blueprint || !Blueprint->WidgetTree) { continue; } TArray Widgets; Blueprint->WidgetTree->GetAllWidgets(Widgets); int32 FixedHere = 0; for (UWidget* Widget : Widgets) { UButton* Button = Cast(Widget); if (!Button) { continue; } ++ButtonsSeen; PRAGMA_DISABLE_DEPRECATION_WARNINGS FButtonStyle Style = Button->WidgetStyle; PRAGMA_ENABLE_DEPRECATION_WARNINGS // Composante par composante : FMargin est serialise champ par champ, une marge 4.27 // partiellement modifiee (ex. Top/Bottom seuls) a recu les autres champs du defaut UE5. const FMargin OldNormal = Style.NormalPadding; const FMargin OldPressed = Style.PressedPadding; const bool bNormal = FixMargin(Style.NormalPadding, UE5Normal, UE4NormalPadding); const bool bPressed = FixMargin(Style.PressedPadding, UE5Pressed, UE4PressedPadding); if (!bNormal && !bPressed) { ++ButtonsKept; UE_LOG(LogLPFixButton, Display, TEXT(" garde %s :: %s Normal=%s Pressed=%s"), *Blueprint->GetName(), *Button->GetName(), *Str(OldNormal), *Str(OldPressed)); continue; } UE_LOG(LogLPFixButton, Warning, TEXT(" %s :: %s Normal %s -> %s | Pressed %s -> %s"), *Blueprint->GetName(), *Button->GetName(), *Str(OldNormal), *Str(Style.NormalPadding), *Str(OldPressed), *Str(Style.PressedPadding)); ++ButtonsFixed; ++FixedHere; if (bDryRun) { continue; } Button->Modify(); Button->SetStyle(Style); } if (FixedHere == 0) { continue; } if (bDryRun) { // Liste des paquets a sauvegarder (Snapshot) avant l'application reelle. UE_LOG(LogLPFixButton, Warning, TEXT(" paquet %s (%d bouton(s))"), *Blueprint->GetOutermost()->GetName(), FixedHere); continue; } ++BlueprintsModified; FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); FKismetEditorUtilities::CompileBlueprint(Blueprint); UPackage* Package = Blueprint->GetOutermost(); FString Filename; if (!FPackageName::TryConvertLongPackageNameToFilename( Package->GetName(), Filename, FPackageName::GetAssetPackageExtension())) { ++SaveFailures; UE_LOG(LogLPFixButton, Error, TEXT(" chemin introuvable pour %s"), *Package->GetName()); continue; } ResetLoaders(Package); FSavePackageArgs SaveArgs; SaveArgs.TopLevelFlags = RF_Public | RF_Standalone; SaveArgs.SaveFlags = SAVE_None; SaveArgs.bWarnOfLongFilename = true; // GWarn et non GError : en commandlet, GError rend toute erreur de save fatale. SaveArgs.Error = GWarn; if (UPackage::SavePackage(Package, nullptr, *Filename, SaveArgs)) { UE_LOG(LogLPFixButton, Warning, TEXT(" -> %s sauvegarde (%d bouton(s))."), *Package->GetName(), FixedHere); } else { ++SaveFailures; UE_LOG(LogLPFixButton, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName()); } } UE_LOG(LogLPFixButton, Warning, TEXT("=== Termine : %d bouton(s) vu(s), %d a la marge UE5 %s, %d garde(s), %d Widget Blueprint(s) modifie(s), %d echec(s) de sauvegarde. ==="), ButtonsSeen, ButtonsFixed, bDryRun ? TEXT("(a corriger)") : TEXT("corrige(s)"), ButtonsKept, BlueprintsModified, SaveFailures); return SaveFailures > 0 ? 1 : 0; }