fix(ui): unify target-mode primary clicks

This commit is contained in:
Erik 2026-07-11 01:36:43 +02:00
parent b7e7ca9ee2
commit eb6229394a
12 changed files with 176 additions and 25 deletions

View file

@ -3,6 +3,14 @@ using AcDream.Core.Items;
namespace AcDream.App.UI;
/// <summary>Result of offering a primary click to active item-target mode.</summary>
public enum ItemPrimaryClickResult
{
NotActive,
ConsumedSuccess,
ConsumedRejected,
}
/// <summary>
/// Shared retail item interaction orchestrator. UI widgets route item clicks,
/// target acquisition, and drag-out drops here instead of duplicating
@ -46,6 +54,7 @@ public sealed class ItemInteractionController : IDisposable
};
private const long RetailUseThrottleMs = 200;
private const long RetailDoubleClickMs = 500;
private readonly ClientObjectTable _objects;
private readonly Func<uint> _playerGuid;
@ -66,6 +75,8 @@ public sealed class ItemInteractionController : IDisposable
private readonly InteractionState _interactionState;
private long _lastUseMs = long.MinValue / 2;
private uint _consumedPrimaryClickTarget;
private long _consumedPrimaryClickMs = long.MinValue / 2;
private int _busyCount;
private bool _disposed;
@ -142,12 +153,41 @@ public sealed class ItemInteractionController : IDisposable
&& TargetCompatible(source, targetGuid);
}
/// <summary>
/// The single target-mode interception point for world and retained item
/// clicks. A rejected target is still consumed and must never fall through
/// to selection, container opening, or ordinary item activation.
/// </summary>
public ItemPrimaryClickResult OfferPrimaryClick(uint targetGuid)
{
if (!IsTargetModeActive)
{
long now = _nowMs();
if (targetGuid != 0
&& targetGuid == _consumedPrimaryClickTarget
&& now - _consumedPrimaryClickMs <= RetailDoubleClickMs)
return ItemPrimaryClickResult.ConsumedRejected;
_consumedPrimaryClickTarget = 0;
return ItemPrimaryClickResult.NotActive;
}
bool accepted = AcquireTarget(targetGuid);
_consumedPrimaryClickTarget = targetGuid;
_consumedPrimaryClickMs = _nowMs();
return accepted
? ItemPrimaryClickResult.ConsumedSuccess
: ItemPrimaryClickResult.ConsumedRejected;
}
public ItemPrimaryClickResult OfferSelfPrimaryClick()
=> OfferPrimaryClick(_playerGuid());
public bool ActivateItem(uint itemGuid)
{
if (itemGuid == 0) return false;
if (IsTargetModeActive)
return AcquireTarget(itemGuid);
return OfferPrimaryClick(itemGuid) == ItemPrimaryClickResult.ConsumedSuccess;
var item = _objects.Get(itemGuid);
if (item is null) return false;
@ -316,6 +356,7 @@ public sealed class ItemInteractionController : IDisposable
private void EnterTargetMode(uint sourceGuid)
{
_consumedPrimaryClickTarget = 0;
_interactionState.EnterUseItemOnTarget(sourceGuid);
var name = _objects.Get(sourceGuid)?.Name;
if (!string.IsNullOrWhiteSpace(name))