diff --git a/HellionChat/PayloadHandler.cs b/HellionChat/PayloadHandler.cs index 2d3d95b..91aeb5e 100644 --- a/HellionChat/PayloadHandler.cs +++ b/HellionChat/PayloadHandler.cs @@ -49,6 +49,7 @@ internal sealed class PayloadHandler public uint LastHoverCounter; private (Chunk, Payload?)? _popup; + private object? _popupOwner; public PayloadHandler( ThemeRegistry themes, @@ -69,6 +70,10 @@ internal sealed class PayloadHandler _logger = logger; } + // Every chat surface shares this handler, so each one claims it before it + // renders. Without that the popup cannot tell whose click it belongs to. + internal object? ActiveSurface { get; set; } + internal void Draw() { DrawPopups(); @@ -84,7 +89,9 @@ internal sealed class PayloadHandler private void DrawPopups() { - if (_popup == null) + // A foreign surface finds no open popup here and would otherwise read + // that as "closed" and drop the state before the owner gets to draw. + if (_popup == null || !PopupOwnership.Owns(_popupOwner, ActiveSurface)) return; var (chunk, payload) = _popup.Value; @@ -93,6 +100,7 @@ internal sealed class PayloadHandler if (!popup.Success) { _popup = null; + _popupOwner = null; return; } @@ -934,6 +942,7 @@ internal sealed class PayloadHandler private void RightClickPayload(Chunk chunk, Payload? payload) { _popup = (chunk, payload); + _popupOwner = ActiveSurface; ImGui.OpenPopup(PopupId); } } diff --git a/HellionChat/Ui/Components/MessageList.cs b/HellionChat/Ui/Components/MessageList.cs index 4704632..89c0ffc 100644 --- a/HellionChat/Ui/Components/MessageList.cs +++ b/HellionChat/Ui/Components/MessageList.cs @@ -158,6 +158,11 @@ internal sealed class MessageList return; } + // Claim the shared handler, so a popup opened here stays with this + // window instead of being cleared by whichever surface draws first. + if (_handler is not null) + _handler.ActiveSurface = this; + // No own ImRaii.Child here — MainWindow already wraps the message // area in one. Nesting would give the window two stacked scrolls // and a runaway content-height computation. diff --git a/HellionChat/Ui/InputPreview.cs b/HellionChat/Ui/InputPreview.cs index 8de71c9..60b3c80 100644 --- a/HellionChat/Ui/InputPreview.cs +++ b/HellionChat/Ui/InputPreview.cs @@ -175,6 +175,7 @@ internal sealed class InputPreview : Window _handlerLender.ResetCounter(); var handler = _handlerLender.Borrow(); + handler.ActiveSurface = this; _chunkRenderer.DrawChunks( _previewMessage!.Content, wrap: true, diff --git a/HellionChat/Util/PopupOwnership.cs b/HellionChat/Util/PopupOwnership.cs new file mode 100644 index 0000000..cb6165d --- /dev/null +++ b/HellionChat/Util/PopupOwnership.cs @@ -0,0 +1,12 @@ +namespace HellionChat.Util; + +// Chat surfaces share one payload handler and all render it in the same frame. +// A popup belongs to the surface that opened it; the others must leave its +// state alone instead of reading "no popup here" as "popup was closed". +internal static class PopupOwnership +{ + // Reference identity on purpose -- two surfaces can compare equal without + // being the same window. + internal static bool Owns(object? owner, object? surface) => + owner is not null && ReferenceEquals(owner, surface); +}