fix(ui): unify target-mode primary clicks
This commit is contained in:
parent
b7e7ca9ee2
commit
eb6229394a
12 changed files with 176 additions and 25 deletions
|
|
@ -12069,11 +12069,9 @@ public sealed class GameWindow : IDisposable
|
|||
|
||||
if (picked is uint guid)
|
||||
{
|
||||
if (_itemInteractionController?.IsTargetModeActive == true)
|
||||
{
|
||||
_itemInteractionController.AcquireTarget(guid);
|
||||
if (_itemInteractionController?.OfferPrimaryClick(guid)
|
||||
is not null and not AcDream.App.UI.ItemPrimaryClickResult.NotActive)
|
||||
return;
|
||||
}
|
||||
|
||||
_selection.Select(guid, AcDream.Core.Selection.SelectionChangeSource.World);
|
||||
string label = DescribeLiveEntity(guid);
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -295,7 +295,8 @@ public sealed class InventoryController : IItemListDragHandler, IRetainedPanelCo
|
|||
main.DragAcceptSprite = 0x060011F7u; main.DragRejectSprite = 0x060011F8u;
|
||||
main.Clicked = () =>
|
||||
{
|
||||
if (_itemInteraction?.AcquireSelfTarget() == true) return;
|
||||
if (_itemInteraction?.OfferSelfPrimaryClick()
|
||||
is not null and not ItemPrimaryClickResult.NotActive) return;
|
||||
OpenContainer(p);
|
||||
};
|
||||
main.DoubleClicked = () => _itemInteraction?.ActivateItem(p);
|
||||
|
|
@ -332,11 +333,26 @@ public sealed class InventoryController : IItemListDragHandler, IRetainedPanelCo
|
|||
cell.DragAcceptSprite = 0x060011F7u; // green insert arrow (export-confirmed)
|
||||
cell.DragRejectSprite = 0x060011F8u; // red circle
|
||||
cell.DoubleClicked = () => _itemInteraction?.ActivateItem(guid);
|
||||
if (isContainer) { cell.Clicked = () => OpenContainer(guid); SetCapacityBar(cell, guid); }
|
||||
else cell.Clicked = () => SelectItem(guid);
|
||||
if (isContainer)
|
||||
{
|
||||
cell.Clicked = () => HandlePrimaryClick(guid, () => OpenContainer(guid));
|
||||
SetCapacityBar(cell, guid);
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.Clicked = () => HandlePrimaryClick(guid, () => SelectItem(guid));
|
||||
}
|
||||
list.AddItem(cell);
|
||||
}
|
||||
|
||||
private void HandlePrimaryClick(uint guid, Action fallback)
|
||||
{
|
||||
if (_itemInteraction?.OfferPrimaryClick(guid)
|
||||
is not null and not ItemPrimaryClickResult.NotActive)
|
||||
return;
|
||||
fallback();
|
||||
}
|
||||
|
||||
private static void AddEmptyCell(UiItemList list)
|
||||
{
|
||||
var cell = new UiItemSlot { SpriteResolve = list.SpriteResolve, SlotIndex = list.GetNumUIItems() };
|
||||
|
|
|
|||
|
|
@ -106,13 +106,12 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
|||
list.Cell.EmptySprite = emptySlotSprite; // visible empty-slot frame so every position is seen + usable. The live
|
||||
list.Cell.Clicked = () =>
|
||||
{
|
||||
if (_itemInteraction?.IsTargetModeActive == true)
|
||||
{
|
||||
_itemInteraction.AcquireSelfTarget();
|
||||
uint itemId = list.Cell.ItemId;
|
||||
if (itemId != 0 && _itemInteraction?.OfferPrimaryClick(itemId)
|
||||
is not null and not ItemPrimaryClickResult.NotActive)
|
||||
return;
|
||||
}
|
||||
if (list.Cell.ItemId != 0)
|
||||
_selection.Select(list.Cell.ItemId, SelectionChangeSource.Paperdoll);
|
||||
if (itemId != 0)
|
||||
_selection.Select(itemId, SelectionChangeSource.Paperdoll);
|
||||
};
|
||||
list.Cell.DoubleClicked = () =>
|
||||
{
|
||||
|
|
@ -144,7 +143,7 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
|||
// four-arrows — retail resolves target cursors off the SmartBox world
|
||||
// object only; UI hover is always pending. 2026-07-03 visual gate.)
|
||||
if (_dollViewport is UiViewport doll)
|
||||
doll.Clicked = () => { _itemInteraction?.AcquireSelfTarget(); };
|
||||
doll.Clicked = () => { _itemInteraction?.OfferSelfPrimaryClick(); };
|
||||
|
||||
var slotsBtnEl = layout.FindElement(0x100005BEu);
|
||||
if (slotsBtnEl is UiButton slotsBtn)
|
||||
|
|
|
|||
|
|
@ -257,7 +257,13 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
RadarController controller = RadarController.Bind(
|
||||
layout,
|
||||
_bindings.Radar.Snapshot,
|
||||
guid => _bindings.Radar.Selection.Select(guid, SelectionChangeSource.Radar),
|
||||
guid =>
|
||||
{
|
||||
if (_bindings.Toolbar.ItemInteraction.OfferPrimaryClick(guid)
|
||||
!= ItemPrimaryClickResult.NotActive)
|
||||
return;
|
||||
_bindings.Radar.Selection.Select(guid, SelectionChangeSource.Radar);
|
||||
},
|
||||
setUiLocked: _bindings.Radar.SetUiLocked,
|
||||
datFont: _bindings.Assets.DefaultFont);
|
||||
RetailWindowFrame.Mount(Host.Root, radarRoot, _bindings.Assets.ResolveSprite,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue