fix #210: stabilize mouse-up capture ownership
Pin each mouse-up to its original captured target before callbacks can hide a window or transfer capture. Classify double-clicks before dispatch, preserve replacement capture owners, and cover both re-entrant paths so jump/combat visibility changes cannot crash the UI root. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
db03b4bda8
commit
e74efc5c06
3 changed files with 106 additions and 19 deletions
|
|
@ -528,7 +528,7 @@ public sealed class UiRoot : UiElement
|
|||
return;
|
||||
}
|
||||
|
||||
if (Captured is not null)
|
||||
if (Captured is { } target)
|
||||
{
|
||||
int rawType = btn switch
|
||||
{
|
||||
|
|
@ -538,46 +538,53 @@ public sealed class UiRoot : UiElement
|
|||
_ => UiEventType.MouseUp,
|
||||
};
|
||||
|
||||
var sp = Captured.ScreenPosition;
|
||||
var raw = new UiEvent(Captured.EventId, Captured, rawType,
|
||||
// Event callbacks may synchronously hide/remove a window or transfer
|
||||
// pointer capture. Keep the mouse-down target stable for this complete
|
||||
// mouse-up transaction instead of rereading the mutable global owner.
|
||||
var sp = target.ScreenPosition;
|
||||
var raw = new UiEvent(target.EventId, target, rawType,
|
||||
Data0: (int)flags,
|
||||
Data1: (int)(x - sp.X), Data2: (int)(y - sp.Y));
|
||||
BubbleEvent(Captured, in raw);
|
||||
BubbleEvent(target, in raw);
|
||||
|
||||
// If left-up over the same element that received the down, emit Click.
|
||||
if (btn == UiMouseButton.Left && ContainsAbsolute(Captured, x, y))
|
||||
if (btn == UiMouseButton.Left && ContainsAbsolute(target, x, y))
|
||||
{
|
||||
var click = new UiEvent(Captured.EventId, Captured, UiEventType.Click,
|
||||
Data0: (int)flags,
|
||||
Data1: (int)(x - sp.X), Data2: (int)(y - sp.Y));
|
||||
BubbleEvent(Captured, in click);
|
||||
|
||||
long now = _nowMs != 0 ? _nowMs : Environment.TickCount64;
|
||||
bool isDoubleClick =
|
||||
ReferenceEquals(Captured, _lastClickTarget)
|
||||
ReferenceEquals(target, _lastClickTarget)
|
||||
&& now - _lastClickMs <= DoubleClickDelayMs
|
||||
&& Math.Abs(x - _lastClickX) <= DragDistanceThreshold
|
||||
&& Math.Abs(y - _lastClickY) <= DragDistanceThreshold;
|
||||
|
||||
var click = new UiEvent(target.EventId, target, UiEventType.Click,
|
||||
Data0: (int)flags,
|
||||
Data1: (int)(x - sp.X), Data2: (int)(y - sp.Y));
|
||||
BubbleEvent(target, in click);
|
||||
|
||||
if (isDoubleClick)
|
||||
{
|
||||
var dbl = new UiEvent(Captured.EventId, Captured, UiEventType.DoubleClick,
|
||||
var dbl = new UiEvent(target.EventId, target, UiEventType.DoubleClick,
|
||||
Data0: (int)flags,
|
||||
Data1: (int)(x - sp.X), Data2: (int)(y - sp.Y));
|
||||
BubbleEvent(Captured, in dbl);
|
||||
BubbleEvent(target, in dbl);
|
||||
}
|
||||
_lastClickTarget = Captured;
|
||||
_lastClickTarget = target;
|
||||
_lastClickMs = now;
|
||||
_lastClickX = x;
|
||||
_lastClickY = y;
|
||||
}
|
||||
else if (btn == UiMouseButton.Right && ContainsAbsolute(Captured, x, y))
|
||||
else if (btn == UiMouseButton.Right && ContainsAbsolute(target, x, y))
|
||||
{
|
||||
var click = new UiEvent(Captured.EventId, Captured, UiEventType.RightClick,
|
||||
var click = new UiEvent(target.EventId, target, UiEventType.RightClick,
|
||||
Data0: (int)flags);
|
||||
BubbleEvent(Captured, in click);
|
||||
BubbleEvent(target, in click);
|
||||
}
|
||||
|
||||
ReleaseCapture();
|
||||
// A callback may have moved capture to a newly opened modal/widget.
|
||||
// Release only the capture that this mouse-up is completing.
|
||||
if (ReferenceEquals(Captured, target))
|
||||
ReleaseCapture();
|
||||
_dragCandidate = false;
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue