feat(ui): port retail radar and compass

This commit is contained in:
Erik 2026-07-10 16:14:37 +02:00
parent c4af181b92
commit 3cbe4b00a1
43 changed files with 2882 additions and 262 deletions

View file

@ -68,6 +68,10 @@ public sealed class UiRoot : UiElement
/// <summary>True when a widget holds keyboard focus (e.g. a focused chat input).</summary>
public bool WantsKeyboard => KeyboardFocus is not null;
/// <summary>Retail PlayerModule::LockUI gate. Blocks all retained-window
/// move/resize interactions without disabling their buttons or content.</summary>
public bool UiLocked { get; set; }
/// <summary>Current drag source (set between drag-begin and drop/cancel).</summary>
public UiElement? DragSource { get; private set; }
public object? DragPayload { get; private set; }
@ -79,7 +83,7 @@ public sealed class UiRoot : UiElement
{
var target = Pick(MouseX, MouseY);
var window = FindWindow(target);
return window is { Resizable: true }
return !UiLocked && window is { Resizable: true }
? HitEdges(window, MouseX, MouseY, ResizeGrip)
: ResizeEdges.None;
}
@ -90,7 +94,7 @@ public sealed class UiRoot : UiElement
{
var target = Pick(MouseX, MouseY);
var window = FindWindow(target);
if (target is null || window is not { Draggable: true })
if (UiLocked || target is null || window is not { Draggable: true })
return false;
if (HoverResizeEdges != ResizeEdges.None)
return false;
@ -141,6 +145,9 @@ public sealed class UiRoot : UiElement
/// <summary>Raised when a drag is released over no UI element.</summary>
public event Action<object /*payload*/, int /*x*/, int /*y*/>? DragReleasedOutsideUi;
/// <summary>Raised after a registered top-level window finishes moving.</summary>
public event Action<string, UiElement>? WindowMoved;
private uint _nextEventId = 0x10000001u;
public override void AddChild(UiElement child)
@ -221,8 +228,16 @@ public sealed class UiRoot : UiElement
// Window-move drag takes precedence over drag-drop / hover / fall-through.
if (_windowDragTarget is not null)
{
_windowDragTarget.Left = x - _windowDragOffX;
_windowDragTarget.Top = y - _windowDragOffY;
float left = x - _windowDragOffX;
float top = y - _windowDragOffY;
if (_windowDragTarget.ConstrainDragToParent
&& _windowDragTarget.Parent is { } parent)
{
left = Math.Clamp(left, 0f, Math.Max(0f, parent.Width - _windowDragTarget.Width));
top = Math.Clamp(top, 0f, Math.Max(0f, parent.Height - _windowDragTarget.Height));
}
_windowDragTarget.Left = left;
_windowDragTarget.Top = top;
return;
}
@ -285,7 +300,7 @@ public sealed class UiRoot : UiElement
var window = FindWindow(target);
// Retail-faithful: pressing on a window raises it above its peers.
if (window is not null) BringToFront(window);
if (btn == UiMouseButton.Left && window is not null)
if (btn == UiMouseButton.Left && window is not null && !UiLocked)
{
var edges = window.Resizable ? HitEdges(window, x, y, ResizeGrip) : ResizeEdges.None;
if (edges != ResizeEdges.None)
@ -370,8 +385,15 @@ public sealed class UiRoot : UiElement
if (_windowDragTarget is not null)
{
var movedWindow = _windowDragTarget;
_windowDragTarget = null;
ReleaseCapture();
foreach (var pair in _windows)
if (ReferenceEquals(pair.Value, movedWindow))
{
WindowMoved?.Invoke(pair.Key, movedWindow);
break;
}
return;
}