From bba66fb4c763ae47d5cf73b4b2ee896de424966f Mon Sep 17 00:00:00 2001 From: Emmanuel AYME Date: Tue, 24 Feb 2026 07:09:15 +0100 Subject: [PATCH] Add methods to analyze widgets. Add method to manipulate overlay widgets. UCanvasPanelSlot manipulation improvement --- libs/UEngine/UETools.cpp | 181 +++++++++++++++++++++++++++++++++++---- libs/UEngine/UETools.hpp | 50 ++++++++++- 2 files changed, 214 insertions(+), 17 deletions(-) diff --git a/libs/UEngine/UETools.cpp b/libs/UEngine/UETools.cpp index 89497c9..4759611 100644 --- a/libs/UEngine/UETools.cpp +++ b/libs/UEngine/UETools.cpp @@ -1,6 +1,5 @@ #include "UETools.hpp" #include "Engine_classes.hpp" -#include "UMG_classes.hpp" SDK::APawn* GetPawnFromWorld(SDK::UWorld* world) { if (!world) world = SDK::UWorld::GetWorld(); @@ -63,22 +62,174 @@ void ReactivateDevConsole(std::shared_ptr logger) { }).detach(); } -void ApplyOffsetsRecursive(SDK::UWidget* widget, float left, float right) { - if (widget && widget->Slot && widget->Slot->IsA(SDK::UCanvasPanelSlot::StaticClass())) { - auto* slot = (SDK::UCanvasPanelSlot*)widget->Slot; - if (!slot) return; - SDK::FMargin offsets = slot->GetOffsets(); +static void ApplyCanvasOffsetsRecursive_Internal(SDK::UWidget* Widget, float Left, float Right, int CurrentDepth, int MaxDepth) { + if (!Widget || CurrentDepth > MaxDepth) return; - if (offsets.Left != left || offsets.Right != right) { - offsets.Left = left; - offsets.Right = right; - slot->SetOffsets(offsets); + // Apply offsets if CanvasPanelSlot + if (Widget->Slot && Widget->Slot->IsA(SDK::UCanvasPanelSlot::StaticClass())) { + auto* Slot = static_cast(Widget->Slot); + if (!Slot) return; + SDK::FMargin Offsets = Slot->GetOffsets(); + + if (Offsets.Left != Left || Offsets.Right != Right) { + Offsets.Left = Left; + Offsets.Right = Right; + Slot->SetOffsets(Offsets); } } - // Go down if UPanelWidget - if (widget && widget->IsA(SDK::UPanelWidget::StaticClass())) { - if (auto* panel = (SDK::UPanelWidget*)widget) - for (int i = 0; i < panel->GetChildrenCount(); ++i) - ApplyOffsetsRecursive(panel->GetChildAt(i), left, right); + // Go deeper if PanelWidget + if (Widget->IsA(SDK::UPanelWidget::StaticClass()) && CurrentDepth < MaxDepth) { + auto* Panel = static_cast(Widget); + int childrenCount = Panel->GetChildrenCount(); + for (int i = 0; i < childrenCount; ++i) { + ApplyCanvasOffsetsRecursive_Internal( Panel->GetChildAt(i), Left, Right, CurrentDepth + 1, MaxDepth); + } } +} + +void ApplyOffsetsRecursive(SDK::UWidget* Widget, float Left, float Right, int MaxDepth) { + ApplyCanvasOffsetsRecursive_Internal( Widget, Left, Right, 0, MaxDepth); +} + +static void ApplyOverlayOffsetRecursive_Internal(SDK::UWidget* Widget, float Offset, SDK::EHorizontalAlignment alignment, + const std::vector& ExcludeNames, int CurrentDepth, int MaxDepth) { + if (!Widget || CurrentDepth > MaxDepth) return; + + auto IsExcluded = [&](SDK::UWidget* widget) -> bool { + for (const auto& name : ExcludeNames) { + if (widget->GetName().contains(name)) + return true; + } + return false; + }; + + auto AdjustOverlaySlot = [&](SDK::UOverlaySlot* Slot) { + if (!Slot || IsExcluded(Widget)) return; + + SDK::FMargin Padding = Slot->Padding; + + if (Slot->HorizontalAlignment == alignment) { + Padding.Left = Offset; + Padding.Right = Offset; + } + + Slot->SetPadding(Padding); + }; + + if (Widget->Slot && Widget->Slot->IsA(SDK::UOverlaySlot::StaticClass())) + AdjustOverlaySlot((SDK::UOverlaySlot*)Widget->Slot); + + if (Widget->IsA(SDK::UPanelWidget::StaticClass()) && CurrentDepth < MaxDepth) { + SDK::UPanelWidget* Panel = (SDK::UPanelWidget*)Widget; + int childrenCount = Panel->GetChildrenCount(); + for (int i = 0; i < childrenCount; ++i) { + ApplyOverlayOffsetRecursive_Internal(Panel->GetChildAt(i), Offset, alignment, ExcludeNames, CurrentDepth + 1, MaxDepth); + } + } +} + +void ApplyOverlayOffsetRecursive(SDK::UWidget* Widget, float Offset, SDK::EHorizontalAlignment alignment, + const std::vector& ExcludeNames, int MaxDepth) { + ApplyOverlayOffsetRecursive_Internal(Widget, Offset, alignment, ExcludeNames, 0, MaxDepth); +} + +void FindAndApplyCanvasRecursive(SDK::UWidget* widget, float offset, int MaxDepth, int currentDepth) { + if (!widget || currentDepth > MaxDepth) return; + + // Si c'est un CanvasPanel, on applique directement + if (widget->IsA(SDK::UCanvasPanel::StaticClass())) { + ApplyOffsetsRecursive(widget, offset, offset); + } + + // Si c'est un panel, on descend dans les enfants + if (widget->IsA(SDK::UPanelWidget::StaticClass())) { + auto* panel = static_cast(widget); + int childrenCount = panel->GetChildrenCount(); + for (int i = 0; i < childrenCount; ++i) { + FindAndApplyCanvasRecursive(panel->GetChildAt(i), offset, MaxDepth, currentDepth + 1); + } + } +} + +void TrackWidgetConstruct(SDK::UUserWidget* widget) { + if (!widget || !widget->Class) return; + + std::string className = widget->Class->GetName(); + auto& info = g_WidgetTracker[className]; + + info.ClassName = className; + info.ConstructCount++; + + if (info.ConstructCount == 1) + info.FirstSeen = std::chrono::steady_clock::now(); + + if (widget->IsInViewport()) + info.WasInViewport = true; + + if (widget->Outer && widget->Outer->Class) + info.OuterClass = widget->Outer->Class->GetName(); + + if (widget->WidgetTree && widget->WidgetTree->RootWidget) { + auto* root = widget->WidgetTree->RootWidget; + if (root && root->Class) + info.RootWidgetClass = root->Class->GetName(); + } +} + +void TrackWidgetDestruct(SDK::UUserWidget* widget) { + if (!widget || !widget->Class) return; + + std::string className = widget->Class->GetName(); + + if (g_WidgetTracker.contains(className)) + g_WidgetTracker[className].DestructCount++; +} + +static std::string ClassifyWidget(const WidgetTrackInfo& info) { + bool persistent = info.ConstructCount > info.DestructCount; + + if (info.WasInViewport && persistent && info.OuterClass.find("PlayerController") != std::string::npos) + return "HUD Candidate"; + + if (info.WasInViewport && (info.ClassName.find("Menu") != std::string::npos)) + return "Menu UI"; + + if (info.WasInViewport && (info.ClassName.find("Settings") != std::string::npos)) + return "Settings UI"; + + if (info.WasInViewport && (info.ClassName.find("Save") != std::string::npos || + info.ClassName.find("Load") != std::string::npos)) + return "Save/Load UI"; + + if (!persistent) + return "Temporary Widget"; + + return "Unclassified"; +} + +void DumpUIAnalysis(std::shared_ptr logger) { + if (!logger) return; + + logger->info("========== UI ANALYSIS =========="); + + for (auto& [name, info] : g_WidgetTracker) { + std::string type = ClassifyWidget(info); + bool persistent = info.ConstructCount > info.DestructCount; + + logger->info("Class: {}", name); + logger->info(" Type : {}", type); + logger->info(" Root Type : {}", info.RootWidgetClass.empty() ? "Unknown" : info.RootWidgetClass); + logger->info(" Construct : {}", info.ConstructCount); + logger->info(" Destruct : {}", info.DestructCount); + logger->info(" Persistent : {}", persistent ? "Yes" : "No"); + logger->info(" InViewport : {}", info.WasInViewport ? "Yes" : "No"); + logger->info(" Outer : {}", info.OuterClass.empty() ? "Unknown" : info.OuterClass); + logger->info(" "); + } + + logger->info("================================="); +} + +void ClearWidgetTracking() { + g_WidgetTracker.clear(); } \ No newline at end of file diff --git a/libs/UEngine/UETools.hpp b/libs/UEngine/UETools.hpp index 756a59e..b26778d 100644 --- a/libs/UEngine/UETools.hpp +++ b/libs/UEngine/UETools.hpp @@ -1,5 +1,7 @@ #pragma once +#include "UMG_classes.hpp" #include +#include #include inline std::atomic_bool g_Console_Enabled { false }; @@ -18,6 +20,18 @@ namespace SDK { class ULocalPlayer; class APlayerController; } + +struct WidgetTrackInfo { + std::string ClassName; + std::string RootWidgetClass; + std::string OuterClass; + bool WasInViewport = false; + int ConstructCount = 0; + int DestructCount = 0; + std::chrono::steady_clock::time_point FirstSeen; +}; +static std::unordered_map g_WidgetTracker; + // Unreal Engine functions /** * @brief Gets the current player Pawn from the world. @@ -34,8 +48,40 @@ void ReactivateDevConsole(std::shared_ptr logger); /** * @brief Apply offsets recursively to UVCanvasPanelSlot. - * @param widget type of UUserWidget* (pointer). + * @param widget UUserWidget* pointer. * @param left offset. * @param right offset. */ -void ApplyOffsetsRecursive(SDK::UWidget* widget, float left, float right = 0); +void ApplyOffsetsRecursive(SDK::UWidget* widget, float left, float right = 0, int MaxDepth = INT_MAX); + +/** + * @brief Apply offsets recursively to Overlay. + * @param widget UUserWidget* pointer. + * @param Offset (left and right offsets). + * @param alignment widget (left, right, center, fill ...) + * @param ExcludeNames excluded class widgets in depth check + * @param MaxDepth Max depth to go through root component + */ +void ApplyOverlayOffsetRecursive(SDK::UWidget* Widget, float Offset, SDK::EHorizontalAlignment alignment, + const std::vector& ExcludeNames = {}, int MaxDepth = INT_MAX); + +void FindAndApplyCanvasRecursive(SDK::UWidget* widget, float offset, int MaxDepth = INT_MAX, int currentDepth = 0); + +/** + * @brief Tracks potential widgets candidates for HUD & UI at construction + * @param widget UUserWidget* pointer. + */ +void TrackWidgetConstruct(SDK::UUserWidget* widget); + +/** + * @brief Tracks potential widgets candidates for HUD & UI at destruction + * @param widget UUserWidget* pointer. + */ +void TrackWidgetDestruct(SDK::UUserWidget* widget); + +/** + * @brief Dump and log previously searched UI & HUD wisgets + * @param logger the log object. + */ +void DumpUIAnalysis(std::shared_ptr logger); +void ClearWidgetTracking(); \ No newline at end of file