Add methods to analyze widgets. Add method to manipulate overlay widgets. UCanvasPanelSlot manipulation improvement

This commit is contained in:
2026-02-24 07:09:15 +01:00
parent 22d08fa0a0
commit bba66fb4c7
2 changed files with 214 additions and 17 deletions

View File

@@ -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<spdlog::logger> 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<SDK::UCanvasPanelSlot*>(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<SDK::UPanelWidget*>(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<std::string>& 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<std::string>& 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<SDK::UPanelWidget*>(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<spdlog::logger> 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();
}

View File

@@ -1,5 +1,7 @@
#pragma once
#include "UMG_classes.hpp"
#include <spdlog/spdlog.h>
#include <unordered_map>
#include <atomic>
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<std::string, WidgetTrackInfo> g_WidgetTracker;
// Unreal Engine functions
/**
* @brief Gets the current player Pawn from the world.
@@ -34,8 +48,40 @@ void ReactivateDevConsole(std::shared_ptr<spdlog::logger> 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<std::string>& 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<spdlog::logger> logger);
void ClearWidgetTracking();