fix(ui): port retail window control feedback

Map synthetic move and resize affordances to the exact DAT cursors, make chat top chrome movable, and replace stale primary-panel height caps with a dynamic screen-edge constraint. This keeps the retained wrapper adaptation aligned with retail Dragbar/Resizebar behavior.
This commit is contained in:
Erik 2026-07-17 09:40:08 +02:00
parent 3f3cfdac30
commit 06016014bc
16 changed files with 578 additions and 27 deletions

View file

@ -140,7 +140,9 @@ public sealed class CursorFeedbackController
CombatMode: _combatModeProvider());
var kind = ResolveKind(snapshot);
Current = new CursorFeedback(kind, ResolveCursor(root.Captured, hover), ResolveGlobalKind(snapshot));
UiCursorMedia authoredCursor = ResolveCursor(root.Captured, hover);
UiCursorMedia cursor = ResolveEffectiveCursor(kind, snapshot, authoredCursor);
Current = new CursorFeedback(kind, cursor, ResolveGlobalKind(snapshot));
return Current;
}
@ -151,7 +153,13 @@ public sealed class CursorFeedbackController
}
public CursorFeedback Resolve(CursorFeedbackSnapshot snapshot)
=> new(ResolveKind(snapshot), GlobalKind: ResolveGlobalKind(snapshot));
{
CursorFeedbackKind kind = ResolveKind(snapshot);
return new(
kind,
ResolveEffectiveCursor(kind, snapshot, authoredCursor: default),
ResolveGlobalKind(snapshot));
}
private CursorFeedbackKind ResolveKind(CursorFeedbackSnapshot snapshot)
{
@ -310,6 +318,34 @@ public sealed class CursorFeedbackController
return default;
}
private static UiCursorMedia ResolveEffectiveCursor(
CursorFeedbackKind kind,
CursorFeedbackSnapshot snapshot,
UiCursorMedia authoredCursor)
{
bool syntheticControlOwnsPointer = snapshot.ActiveResizeEdges != ResizeEdges.None
|| snapshot.HoverResizeEdges != ResizeEdges.None
|| snapshot.WindowMoveActive;
// Retail CheckCursor gives the hovered/captured Resizebar or captured
// Dragbar first refusal. Our wrapper borders are geometry, not UIElements,
// so a hovered synthetic resize edge or active synthetic control must take
// that same precedence over the content under its five-pixel hit region.
if (syntheticControlOwnsPointer
&& RetailCursorCatalog.TryGetWindowControlCursor(kind, out UiCursorMedia capturedControl))
return capturedControl;
// Ordinary imported elements retain their authored MD_Data_Cursor exactly.
if (authoredCursor.IsValid)
return authoredCursor;
// Synthetic wrapper edges and the IA-12 whole-window drag region have no
// authored child, but use retail's exact control media and hotspot.
return RetailCursorCatalog.TryGetWindowControlCursor(kind, out UiCursorMedia fallback)
? fallback
: default;
}
private static UiItemSlot? FindHoveredItemSlot(UiElement? element)
{
while (element is not null)