initial commit; cv 0.0.3
This commit is contained in:
173
Api/PanelWidget.hpp
Normal file
173
Api/PanelWidget.hpp
Normal file
@@ -0,0 +1,173 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "Widget.hpp"
|
||||
#include "PanelSlot.hpp"
|
||||
|
||||
namespace PalUIExtension::Api
|
||||
{
|
||||
class PanelWidget : public Widget
|
||||
{
|
||||
public:
|
||||
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.PanelWidget"); }
|
||||
static auto DebugTag() -> const TCHAR* { return STR("PanelWidget"); }
|
||||
|
||||
PanelWidget() = default;
|
||||
explicit PanelWidget(UObject* Obj)
|
||||
: Widget(Obj, ClassOf<PanelWidget>(), DebugTag()) {}
|
||||
|
||||
auto GetChildrenCount() -> std::optional<int32>
|
||||
{
|
||||
return CallForReturn<int32>(STR("GetChildrenCount"));
|
||||
}
|
||||
auto HasAnyChildren() -> std::optional<bool>
|
||||
{
|
||||
return CallForReturn<bool>(STR("HasAnyChildren"));
|
||||
}
|
||||
|
||||
auto GetChildAt(int32 Index) -> Widget
|
||||
{
|
||||
struct P { int32 Index; UObject* ReturnValue; } p{ Index, nullptr };
|
||||
auto Result = CallForReturn(STR("GetChildAt"), p, &P::ReturnValue);
|
||||
return (Result && *Result) ? Widget{ *Result } : Widget{};
|
||||
}
|
||||
|
||||
auto GetChildIndex(UObject* Content) -> std::optional<int32>
|
||||
{
|
||||
struct P { UObject* Content; int32 ReturnValue; } p{ Content, -1 };
|
||||
return CallForReturn(STR("GetChildIndex"), p, &P::ReturnValue);
|
||||
}
|
||||
|
||||
auto HasChild(UObject* Content) -> std::optional<bool>
|
||||
{
|
||||
struct P { UObject* Content; bool ReturnValue; } p{ Content, false };
|
||||
return CallForReturn(STR("HasChild"), p, &P::ReturnValue);
|
||||
}
|
||||
|
||||
auto AddChild(UObject* Content) -> PanelSlot
|
||||
{
|
||||
struct P { UObject* Content; UObject* ReturnValue; } p{ Content, nullptr };
|
||||
auto Result = CallForReturn(STR("AddChild"), p, &P::ReturnValue);
|
||||
return (Result && *Result) ? PanelSlot{ *Result } : PanelSlot{};
|
||||
}
|
||||
|
||||
auto RemoveChild(UObject* Content) -> std::optional<bool>
|
||||
{
|
||||
struct P { UObject* Content; bool ReturnValue; } p{ Content, false };
|
||||
return CallForReturn(STR("RemoveChild"), p, &P::ReturnValue);
|
||||
}
|
||||
|
||||
auto RemoveChildAt(int32 Index) -> std::optional<bool>
|
||||
{
|
||||
struct P { int32 Index; bool ReturnValue; } p{ Index, false };
|
||||
return CallForReturn(STR("RemoveChildAt"), p, &P::ReturnValue);
|
||||
}
|
||||
|
||||
auto ClearChildren() -> bool { return CallVoid(STR("ClearChildren")); }
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Deliberately NOT a wrapper around the engine's GetAllChildren.
|
||||
//
|
||||
// That UFUNCTION returns TArray<UWidget*> by value. ProcessEvent
|
||||
// constructs that array into the frame with the engine's allocator
|
||||
// and then hands you ownership of the allocation. Getting that
|
||||
// wrong is either a leak on every call or a free across a module
|
||||
// boundary, and neither shows up as a crash near the cause.
|
||||
//
|
||||
// The count + index loop is two extra ProcessEvent calls per child
|
||||
// and zero ownership questions. If you are calling this per-frame
|
||||
// over a large panel, cache the result; if you are calling it once
|
||||
// in a Construct hook, the cost is irrelevant.
|
||||
// ------------------------------------------------------------------
|
||||
auto GetAllChildren() -> std::vector<Widget>
|
||||
{
|
||||
std::vector<Widget> Children;
|
||||
|
||||
auto Count = GetChildrenCount();
|
||||
if (!Count || *Count <= 0) return Children;
|
||||
|
||||
Children.reserve(static_cast<size_t>(*Count));
|
||||
for (int32 i = 0; i < *Count; ++i)
|
||||
{
|
||||
Widget Child = GetChildAt(i);
|
||||
if (Child) Children.push_back(Child);
|
||||
}
|
||||
return Children;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Children WITHOUT ProcessEvent.
|
||||
//
|
||||
// GetAllChildren() above costs 1 + N ProcessEvent calls. That is
|
||||
// fine once in a Construct hook and not fine as the inner loop of a
|
||||
// whole-tree walk, where it becomes 1 + N per PANEL.
|
||||
//
|
||||
// UPanelWidget::Slots is a TArray<UPanelSlot*> UPROPERTY (confirmed
|
||||
// in the dump: UHTHeaderDump/UMG/Public/PanelWidget.h line 14), and
|
||||
// UPanelSlot::Content is a UPROPERTY too. So the full child list is
|
||||
// reachable as pure memory reads. Slot order is child order -- the
|
||||
// engine's own GetChildAt(i) is Slots[i]->Content.
|
||||
//
|
||||
// Nothing here is copied out of engine memory except the pointers
|
||||
// themselves, which is what makes reading the TArray safe; see the
|
||||
// TArrayRead_ comment in UmgTypes.hpp for where that line is.
|
||||
//
|
||||
// Returns false if Slots could not be read or looked implausible,
|
||||
// WITHOUT touching Out -- so a caller can fall back to
|
||||
// GetAllChildren() and know it isn't double-appending.
|
||||
// ------------------------------------------------------------------
|
||||
static constexpr int32 kMaxPlausibleChildren = 4096;
|
||||
|
||||
auto TryGetChildObjects(std::vector<UObject*>& Out) const -> bool
|
||||
{
|
||||
auto* Slots = TryPropertyPtr<TArrayRead_<UObject*>>(STR("Slots"));
|
||||
if (!Slots) return false;
|
||||
|
||||
if (!Slots->IsPlausible(kMaxPlausibleChildren))
|
||||
{
|
||||
Output::send<LogLevel::Warning>(
|
||||
STR("[{}] 'Slots' does not look like a live TArray ")
|
||||
STR("(Num={}, Max={}, Data={}) -- falling back to ProcessEvent\n"),
|
||||
m_tag, Slots->Num, Slots->Max,
|
||||
Slots->Data ? STR("set") : STR("null"));
|
||||
return false;
|
||||
}
|
||||
|
||||
Out.reserve(Out.size() + static_cast<size_t>(Slots->Num));
|
||||
for (int32 i = 0; i < Slots->Num; ++i)
|
||||
{
|
||||
UObject* Slot = Slots->Data[i];
|
||||
if (!Slot) continue;
|
||||
|
||||
// Read Content off the slot directly rather than through the
|
||||
// PanelSlot wrapper: the wrapper's IsA check would log on
|
||||
// anything unexpected, and this runs once per node of a
|
||||
// whole-tree walk.
|
||||
if (UObject* Content = RawObject{ Slot, STR("PanelSlot") }.TryObjectProperty(STR("Content")))
|
||||
{
|
||||
Out.push_back(Content);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Property read first, ProcessEvent second. Use this anywhere you
|
||||
// just want the children and don't care which route produced them.
|
||||
auto GetChildObjects() -> std::vector<UObject*>
|
||||
{
|
||||
std::vector<UObject*> Out;
|
||||
if (TryGetChildObjects(Out)) return Out;
|
||||
|
||||
for (Widget& Child : GetAllChildren())
|
||||
{
|
||||
if (UObject* O = Child.Get()) Out.push_back(O);
|
||||
}
|
||||
return Out;
|
||||
}
|
||||
|
||||
protected:
|
||||
PanelWidget(UObject* Obj, UClass* Expected, const TCHAR* Tag)
|
||||
: Widget(Obj, Expected, Tag) {}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user