#include "UEWidgets.hpp" #include "Engine_classes.hpp" std::shared_ptr g_WidgetsLogger; void ApplyPositionOffset(SDK::UUserWidget* Widget, float offset, SDK::FVector2D Alignment) { if (!Widget) return; if (Widget->Slot && Widget->Slot->IsA(SDK::UCanvasPanelSlot::StaticClass())) { auto* canvasSlot = static_cast(Widget->Slot); canvasSlot->SetPosition(SDK::FVector2D(offset, 0)); canvasSlot->SetAlignment(Alignment); } } void ApplyTransformOffset(SDK::UWidget* Widget, float OffsetX, float OffsetY) { if (!Widget) return; SDK::FWidgetTransform Transform = Widget->RenderTransform; Transform.Translation.X = OffsetX; // Horizontal shifting Transform.Translation.Y = OffsetY; // Vertical shifting (optional) Widget->SetRenderTransform(Transform); } void CenterWidget(SDK::UUserWidget* Widget, float offset, float screenWidth, float screenHeight, float targetWidth, float targetHeight, float compensation) { if (!Widget) return; float aspectRatio = screenWidth / screenHeight; float targetAspect = targetWidth / targetHeight; if (aspectRatio > targetAspect) { float scale = targetHeight / screenHeight; // horizontal scale to apply to get the same vertical size as 1080p targetWidth = (float)screenWidth * scale; } targetWidth -= offset * 2; Widget->SetDesiredSizeInViewport(SDK::FVector2D(targetWidth, targetHeight)); Widget->SetPositionInViewport(SDK::FVector2D(offset, 0.f), true); ApplyTransformOffset(Widget, compensation, 0); } float ApplyOffsetsSmart_Internal(SDK::UWidget* Widget, float Left, float Right, int Depth = 0, int MaxDepth = 1) { if (!Widget) return 0; static float leftApplied = 0; // Apply if CanvasSlot if (Widget->Slot && Widget->Slot->IsA(SDK::UCanvasPanelSlot::StaticClass())) { if (Depth <= MaxDepth) { auto* Slot = static_cast(Widget->Slot); SDK::FMargin Offsets = Slot->GetOffsets(); SDK::FAnchors Anchors = Slot->GetAnchors(); float MinX = Anchors.Minimum.X; float MaxX = Anchors.Maximum.X; // Use SetLogger first to initialize logger #ifdef MY_VERBOSE_LOGS std::string indent(Depth * 2, ' '); // 2 espaces par niveau if (g_WidgetsLogger) g_WidgetsLogger->debug("{}CanvasPanelSlot: {} {} - Slot: {} {} - Offets: {} {} {} {} - Anchors: {} {} {} {}", indent, Widget->GetName(), Widget->Class->GetName(), Slot->GetName(), Slot->Class->GetName(), Offsets.Left, Offsets.Top, Offsets.Right, Offsets.Bottom, Anchors.Minimum.X, Anchors.Minimum.Y, Anchors.Maximum.X, Anchors.Maximum.Y); #endif if (MinX == 0.f && MaxX == 1.f) return 0; // Ignore stretch if (MinX == 0.5f && MaxX == 0.5f) return 0; // Ignore centered if (MinX == 0.f && MaxX == 0.f) // Modify only pure left leftApplied = Offsets.Left = Left; else if (MinX == 1.f && MaxX == 1.f) // Modify only pure right Offsets.Left = -Right; Slot->SetOffsets(Offsets); } } // Stop to max depth if (Depth >= MaxDepth) return leftApplied; // Browse the children if (Widget->IsA(SDK::UPanelWidget::StaticClass())) { auto* Panel = static_cast(Widget); for (int i = 0; i < Panel->GetChildrenCount(); ++i) ApplyOffsetsSmart_Internal(Panel->GetChildAt(i), Left, Right, Depth + 1, MaxDepth); } if (Widget->IsA(SDK::UUserWidget::StaticClass())) { auto* UW = static_cast(Widget); if (UW->WidgetTree && UW->WidgetTree->RootWidget) ApplyOffsetsSmart_Internal(UW->WidgetTree->RootWidget, Left, Right, Depth + 1, MaxDepth); } return leftApplied; } float ApplyOffsetsSmart(SDK::UWidget* Widget, float Left, float Right, int MaxDepth) { float leftApplied = ApplyOffsetsSmart_Internal(Widget, Left, Right, 0, MaxDepth); return leftApplied; } static void ApplyOffsetsRecursive_Internal(SDK::UWidget* Widget, float Left, float Right, const std::vector& ExcludeObjects, const std::vector& ExcludeClass, int CurrentDepth, int MaxDepth) { if (!Widget || CurrentDepth > MaxDepth) return; // Apply offsets according to Slot type for (const auto& ExcludeObject : ExcludeObjects) { if (ExcludeObject == Widget) return; } std::string widgetName; std::string className; widgetName = Widget->GetName(); if (Widget->Class) className = Widget->Class->GetName(); for (const auto& pattern : ExcludeClass) { if (widgetName.find(pattern) != std::string::npos || className.find(pattern) != std::string::npos) return; } if (Widget->Slot) { if (Widget->Slot->IsA(SDK::UCanvasPanelSlot::StaticClass())) { auto* Slot = static_cast(Widget->Slot); SDK::FMargin Offsets = Slot->GetOffsets(); #ifdef MY_VERBOSE_LOGS SDK::FAnchors anchors = Slot->GetAnchors(); if (g_WidgetsLogger) g_WidgetsLogger->debug("CanvasPanelSlot: {} {} - Slot: {} {} - Offets: {} {} {} {} - Anchors: {} {} {} {}", \ Widget->GetName(), Widget->Class->GetName(), \ Slot->GetName(), Slot->Class->GetName(), \ Offsets.Left, Offsets.Top, Offsets.Right, Offsets.Bottom, \ anchors.Minimum.X, anchors.Minimum.Y, anchors.Maximum.X, anchors.Maximum.Y); #endif Offsets.Left = Left; Offsets.Right = Right; Slot->SetOffsets(Offsets); } else if (Widget->Slot->IsA(SDK::UVerticalBoxSlot::StaticClass())) { auto* Slot = static_cast(Widget->Slot); SDK::FMargin Margin = Slot->Padding; #ifdef MY_VERBOSE_LOGS if (g_WidgetsLogger) g_WidgetsLogger->debug("VerticalBox Slot: {} {} - Slot: {} {} - Margins: {} {} {} {}", \ Widget->GetName(), Widget->Class->GetName(), \ Slot->GetName(), Slot->Class->GetName(), \ Margin.Left, Margin.Top, Margin.Right, Margin.Bottom); #endif Margin.Left = Left; Margin.Right = Right; Slot->SetPadding(Margin); } } // Go deeper in children if we got a Panel if (Widget->IsA(SDK::UPanelWidget::StaticClass()) && CurrentDepth < MaxDepth) { auto* Panel = static_cast(Widget); int childrenCount = Panel->GetChildrenCount(); for (int i = 0; i < childrenCount; ++i) { ApplyOffsetsRecursive_Internal(Panel->GetChildAt(i), Left, Right, ExcludeObjects, ExcludeClass, CurrentDepth + 1, MaxDepth); } } // Go deeper if the widget is an UserWidget if (Widget->IsA(SDK::UUserWidget::StaticClass())) { auto* ChildWidget = static_cast(Widget); if (ChildWidget->WidgetTree && ChildWidget->WidgetTree->RootWidget) { ApplyOffsetsRecursive_Internal(ChildWidget->WidgetTree->RootWidget, Left, Right, ExcludeObjects, ExcludeClass, CurrentDepth + 1, MaxDepth); } } } void ApplyOffsetsRecursive(SDK::UWidget* Widget, float Left, float Right, const std::vector& ExcludeObjects, const std::vector& ExcludeClass, int MaxDepth) { ApplyOffsetsRecursive_Internal(Widget, Left, Right, ExcludeObjects, ExcludeClass, 0, MaxDepth); } static void ApplyOverlayOffsetRecursive_Internal(SDK::UWidget* Widget, float left, float right, 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 = left; Padding.Right = right; } 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), left, right, alignment, ExcludeNames, CurrentDepth + 1, MaxDepth); } } } void ApplyOverlayOffsetRecursive(SDK::UWidget* Widget, float left, float right, SDK::EHorizontalAlignment alignment, const std::vector& ExcludeNames, int MaxDepth) { ApplyOverlayOffsetRecursive_Internal(Widget, left, right, alignment, ExcludeNames, 0, MaxDepth); } // -- Tracking && dumping widgets for debugging -- void FindAndApplyCanvasRecursive(SDK::UWidget* widget, float offset, int MaxDepth, int currentDepth) { if (!widget || currentDepth > MaxDepth) return; // Applying straight if it's a CanvasPanel if (widget->IsA(SDK::UCanvasPanel::StaticClass())) { ApplyOffsetsRecursive(widget, offset, offset); } // We go deeper if 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(); } void DumpWidgetRecursive(SDK::UWidget* Widget, int Depth, int MaxDepth) { if (!Widget || Depth > MaxDepth) return; std::string indent(Depth * 2, ' '); std::string className = Widget->Class ? Widget->Class->GetName() : "UnknownClass"; std::string widgetName = Widget->GetName(); if (g_WidgetsLogger) g_WidgetsLogger->debug("{}Widget: {} [{}]", indent, widgetName, className); // Slot info si disponible if (Widget->Slot) { std::string slotClass = Widget->Slot->Class ? Widget->Slot->Class->GetName() : "UnknownSlot"; if (g_WidgetsLogger) g_WidgetsLogger->debug("{} Slot class: {}", indent, slotClass); } // Descendre dans les enfants si c'est un panel et qu'on est encore sous MaxDepth if (Depth < MaxDepth) { if (Widget->IsA(SDK::UPanelWidget::StaticClass())) { auto* panel = static_cast(Widget); int count = panel->GetChildrenCount(); for (int i = 0; i < count; ++i) { DumpWidgetRecursive(panel->GetChildAt(i), Depth + 1, MaxDepth); } } if (Widget->IsA(SDK::UUserWidget::StaticClass())) { auto* userWidget = static_cast(Widget); if (userWidget->WidgetTree && userWidget->WidgetTree->RootWidget) { DumpWidgetRecursive(userWidget->WidgetTree->RootWidget, Depth + 1, MaxDepth); } } } } void SetLogger(std::shared_ptr logger) { g_WidgetsLogger = logger; }