Fix some issues with new root canvas stuff

This commit is contained in:
2026-04-22 17:02:24 -04:00
parent d985a6bc55
commit a964211cc8
5 changed files with 128 additions and 101 deletions

View File

@@ -2,13 +2,16 @@
//
// RootCanvas.h
//
// UlxRootCanvasPanel is a UCanvasPanel subclass whose
// slots (UlxRootCanvasSlot) carry input-mode configuration
// in addition to layout. The PlayerController scans these
// slots, sorted by ZOrder, to arbitrate pointer visibility,
// capture, focus, and input-component blocking for
// top-level widgets. ZOrder therefore serves double duty:
// it determines draw order AND input priority.
// Luprex provides a "window management system" for root
// widgets. In this system, all top-level widgets have to go
// into the root canvas (instead of the viewport). The
// window management system monitors the widgets within the
// root canvas and continuously updates the z-orders, the
// pointer visibility, the mouse capture mode, the keyboard
// focus, and enhanced input event routing based on hints
// and directives given by the widgets.
//
// To learn more, read Docs/Luprex-Window-Management.md
//
////////////////////////////////////////////////////////////
@@ -29,11 +32,6 @@ class UWidget;
//
// UlxRootCanvasSlot
//
// Luprex provides a "window management system" for root widgets.
// This system is documented in Docs/Keyboard-Focus-and-Input-Modes.md
// The Root Canvas Slot is how widgets ask the window management system
// to engage certain behaviors.
//
////////////////////////////////////////////////////////////
UCLASS()
class INTEGRATION_API UlxRootCanvasSlot : public UCanvasPanelSlot
@@ -60,32 +58,24 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Luprex|Input Mode")
bool EnableEnhancedInput = true;
// Knowing whether the slot is under construction helps us to work
// around some bugs in Unreal. See below.
bool SlotUnderConstruction = true;
// Reliable version of SetZOrder. There is a bug in the normal version of
// SetZOrder: it crashes if you use it during OnConstruct. We have a
// workaround, but it requires using a different getter and setter.
UFUNCTION(BlueprintCallable, Category = "Luprex|Input Mode")
void SetZOrderReliable(int32 Order);
// Sequence number assigned when this slot was last brought to front.
int32 BringToFrontCount = 0;
// Reliable version of GetZOrder. There is a bug in the normal version of
// SetZOrder: it crashes if you use it during OnConstruct. We have a
// workaround, but it requires using a different getter and setter.
UFUNCTION(BlueprintCallable, Category = "Luprex|Input Mode")
int32 GetZOrderReliable();
// Widget Z Ordering: widgets that show the pointer go on top of windows
// that don't, and within a group, widgets that have greater BringToFrontCount
// go on top. Sorting an array with this puts the top windows at the end
// of the array.
bool operator<(const UlxRootCanvasSlot &Other) const
{
if (ShowPointer != Other.ShowPointer) return !ShowPointer;
return BringToFrontCount < Other.BringToFrontCount;
}
};
////////////////////////////////////////////////////////////
//
// UlxRootCanvasPanel
//
// A UCanvasPanel that uses UlxRootCanvasSlot for its
// children instead of the plain UCanvasPanelSlot. Layout
// behavior is identical to UCanvasPanel; only the slot
// type differs.
//
////////////////////////////////////////////////////////////
UCLASS()
class INTEGRATION_API UlxRootCanvasPanel : public UCanvasPanel
@@ -94,18 +84,18 @@ class INTEGRATION_API UlxRootCanvasPanel : public UCanvasPanel
public:
// Convenience wrapper around AddChild that returns the
// derived slot type, so callers don't have to cast.
// Add a child to the canvas. Also brings the child to the front
// for the first time, ensuring that every widget has a unique
// BringToFront counter.
UFUNCTION()
UlxRootCanvasSlot* AddChildToRootCanvas(UWidget* Content);
// Find children of type UserWidget. Return them in a sorted
// order, with the highest Zorder first.
// order, with the bottom window first and the top window last.
TArray<UlxRootCanvasSlot*> GetSortedUserWidgets();
// Return the largest ZOrder across all slots, or 0 if empty.
// Used as the basis for placing new widgets on top.
int32 GetMaxZOrder() const;
// Return the highest-priority UserWidget slot, or nullptr if there are none.
UlxRootCanvasSlot* GetTopWidget();
// This function updates several window-management-related properties
// which are stored in the UserWidget and the lxRootCanvasSlot. Note that
@@ -119,6 +109,17 @@ public:
bool ShowPointer, bool BlockInput, bool EnableEnhancedInput,
bool BringToFront, UWidget *DesiredFocusWidget);
// Bring the widget to the front of the Z order within its group.
// This is implemented using the BringToFront counter.
UFUNCTION(BlueprintCallable, Category = "Luprex|Window Management",
meta = (DefaultToSelf = "Widget"))
static void BringToFront(class UUserWidget *Widget);
// Recompute and apply ZOrder values for all slots based on ShowPointer and
// BringToFrontCount. Must be called from a context where SetZOrder is safe -
// i.e. not during OnConstruct.
void UpdateZOrders();
// Fetch the UlxRootCanvasSlot for a widget that is parented to a
// UlxRootCanvasPanel. Returns nullptr via the WrongType exec pin
// if the widget isn't a root widget (no slot, or slot is not a
@@ -129,8 +130,17 @@ public:
protected:
// UPanelWidget
// We inherit most of our code from CanvasPanel. This causes the
// CanvasPanel code to allocate slots of type UlxRootCanvasSlot.
virtual UClass* GetSlotClass() const override;
// We override OnSlotAdded in order to be able to be able to
// fully finish initializing the slot before the widget
// OnConstruct method has a chance to execute.
virtual void OnSlotAdded(UPanelSlot* InSlot) override;
// Monotonic counter incremented each time any slot is brought to front.
int32 BringToFrontCounter = 0;
};