fix(ui): select retained items on mouse down

Port UIElement_ListBox's press-time selection ordering through the shared retained item-list contract. Inventory, loot, paperdoll, and physical shortcuts now update canonical selection before release or drag promotion, while target-mode consumption suppresses drag and release-time activation.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-24 06:17:31 +02:00
parent 2dd5cb80d2
commit 043ab10b3c
18 changed files with 292 additions and 62 deletions

View file

@ -83,6 +83,9 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
ConfigureList(_topContainer, ContainerCellSize, horizontalScroll: false, containerEmptySprite);
ConfigureList(_containerList, ContainerCellSize, horizontalScroll: true, containerEmptySprite);
ConfigureList(_contentsList, ItemCellSize, horizontalScroll: true, contentsEmptySprite);
_topContainer.PrimaryItemPressed = PressItem;
_containerList.PrimaryItemPressed = PressItem;
_contentsList.PrimaryItemPressed = PressItem;
_topContainer.ExamineItemRequested = ExamineItem;
_containerList.ExamineItemRequested = ExamineItem;
_contentsList.ExamineItemRequested = ExamineItem;
@ -325,7 +328,6 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
private void AddRootCell(uint guid)
{
UiItemSlot cell = CreateCell(_topContainer, guid, ItemDragSource.Ground);
cell.Clicked = () => HandlePrimaryClick(guid, () => Select(guid));
cell.DoubleClicked = RequestClose;
cell.IsOpenContainer = _openContainer == guid;
SetCapacity(cell, guid);
@ -335,7 +337,7 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
private void AddContainerCell(uint guid)
{
UiItemSlot cell = CreateCell(_containerList, guid, ItemDragSource.Ground);
cell.Clicked = () => HandlePrimaryClick(guid, () => OpenNestedContainer(guid));
cell.Clicked = () => OpenNestedContainer(guid);
SetCapacity(cell, guid);
_containerList.AddItem(cell);
}
@ -343,7 +345,6 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
private void AddContentsCell(uint guid)
{
UiItemSlot cell = CreateCell(_contentsList, guid, ItemDragSource.Ground);
cell.Clicked = () => HandlePrimaryClick(guid, () => Select(guid));
cell.DoubleClicked = () => _itemInteraction.ActivateItem(guid);
cell.DragAcceptSprite = 0x060011F9u;
cell.DragRejectSprite = 0x060011F8u;
@ -384,11 +385,12 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
_itemInteraction.ExamineSelectedOrEnterMode(guid);
}
private void HandlePrimaryClick(uint guid, Action fallback)
private bool PressItem(uint guid)
{
if (_itemInteraction.OfferPrimaryClick(guid) != ItemPrimaryClickResult.NotActive)
return;
fallback();
return true;
Select(guid);
return false;
}
private void ApplyIndicators()
@ -573,6 +575,9 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
_objects.Cleared -= OnObjectsCleared;
_selection.Changed -= OnSelectionChanged;
_itemInteraction.StateChanged -= OnInteractionStateChanged;
_topContainer.PrimaryItemPressed = null;
_containerList.PrimaryItemPressed = null;
_contentsList.PrimaryItemPressed = null;
_topContainer.ExamineItemRequested = null;
_containerList.ExamineItemRequested = null;
_contentsList.ExamineItemRequested = null;

View file

@ -153,9 +153,21 @@ public sealed class InventoryController : IItemListDragHandler, IRetainedPanelCo
_contentsGrid?.RegisterDragHandler(this);
_containerList?.RegisterDragHandler(this);
_topContainer?.RegisterDragHandler(this);
if (_contentsGrid is not null) _contentsGrid.ExamineItemRequested = ExamineItem;
if (_containerList is not null) _containerList.ExamineItemRequested = ExamineItem;
if (_topContainer is not null) _topContainer.ExamineItemRequested = ExamineItem;
if (_contentsGrid is not null)
{
_contentsGrid.PrimaryItemPressed = PressItem;
_contentsGrid.ExamineItemRequested = ExamineItem;
}
if (_containerList is not null)
{
_containerList.PrimaryItemPressed = PressItem;
_containerList.ExamineItemRequested = ExamineItem;
}
if (_topContainer is not null)
{
_topContainer.PrimaryItemPressed = PressSelfItem;
_topContainer.ExamineItemRequested = ExamineItem;
}
// Burden meter: vertical 11×58 bar (gmBackpackUI m_burdenMeter, retail direction 4).
_burdenMeter = layout.FindElement(BurdenMeterId) as UiMeter;
@ -432,12 +444,7 @@ public sealed class InventoryController : IItemListDragHandler, IRetainedPanelCo
dragIconTexture: _dragIconIds?.Invoke(
ItemType.Container, PlayerPackBaseIcon, 0u, 0u, 0u) ?? 0u);
main.DragAcceptSprite = 0x060011F7u; main.DragRejectSprite = 0x060011F8u;
main.Clicked = () =>
{
if (_itemInteraction?.OfferSelfPrimaryClick()
is not null and not ItemPrimaryClickResult.NotActive) return;
OpenContainer(p);
};
main.Clicked = () => OpenContainer(p);
main.DoubleClicked = () => _itemInteraction?.ActivateItem(p);
SetCapacityBar(main, p); // main-pack fullness (items / ItemsCapacity)
_topContainer.AddItem(main);
@ -480,22 +487,28 @@ public sealed class InventoryController : IItemListDragHandler, IRetainedPanelCo
cell.DoubleClicked = () => _itemInteraction?.ActivateItem(guid);
if (isContainer)
{
cell.Clicked = () => HandlePrimaryClick(guid, () => OpenContainer(guid));
cell.Clicked = () => OpenContainer(guid);
SetCapacityBar(cell, guid);
}
else
{
cell.Clicked = () => HandlePrimaryClick(guid, () => SelectItem(guid));
}
list.AddItem(cell);
}
private void HandlePrimaryClick(uint guid, Action fallback)
private bool PressItem(uint guid)
{
if (_itemInteraction?.OfferPrimaryClick(guid)
is not null and not ItemPrimaryClickResult.NotActive)
return;
fallback();
return true;
SelectItem(guid);
return false;
}
private bool PressSelfItem(uint guid)
{
if (_itemInteraction?.OfferSelfPrimaryClick()
is not null and not ItemPrimaryClickResult.NotActive)
return true;
SelectItem(guid);
return false;
}
private void AddEmptyCell(UiItemList list)
@ -931,9 +944,21 @@ public sealed class InventoryController : IItemListDragHandler, IRetainedPanelCo
_objects.ObjectUpdated -= OnObjectChanged;
_objects.Cleared -= OnObjectsCleared;
_selection.Changed -= OnSelectionChanged;
if (_contentsGrid is not null) _contentsGrid.ExamineItemRequested = null;
if (_containerList is not null) _containerList.ExamineItemRequested = null;
if (_topContainer is not null) _topContainer.ExamineItemRequested = null;
if (_contentsGrid is not null)
{
_contentsGrid.PrimaryItemPressed = null;
_contentsGrid.ExamineItemRequested = null;
}
if (_containerList is not null)
{
_containerList.PrimaryItemPressed = null;
_containerList.ExamineItemRequested = null;
}
if (_topContainer is not null)
{
_topContainer.PrimaryItemPressed = null;
_topContainer.ExamineItemRequested = null;
}
if (_itemInteraction is not null)
{
_itemInteraction.StateChanged -= OnInteractionStateChanged;

View file

@ -84,6 +84,7 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
var (element, mask, _, unlockBit) = PaperdollSlotBackgrounds.Definitions[i];
if (layout.FindElement(element) is not UiItemList list) continue;
list.RegisterDragHandler(this);
list.PrimaryItemPressed = PressItem;
list.ExamineItemRequested = ExamineItem;
list.Cell.SourceKind = ItemDragSource.Equipment;
list.Cell.SlotIndex = i; // definition position = equipped drag-payload SourceSlot
@ -91,15 +92,6 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
&& emptySlotSprites.TryGetValue(element, out uint authoredSprite)
? authoredSprite
: emptySlotSprite;
list.Cell.Clicked = () =>
{
uint itemId = list.Cell.ItemId;
if (itemId != 0 && _itemInteraction?.OfferPrimaryClick(itemId)
is not null and not ItemPrimaryClickResult.NotActive)
return;
if (itemId != 0)
_selection.Select(itemId, SelectionChangeSource.Paperdoll);
};
list.Cell.DoubleClicked = () =>
{
if (list.Cell.ItemId != 0)
@ -357,6 +349,14 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
_itemInteraction.ExamineSelectedOrEnterMode(itemId);
}
private bool PressItem(uint itemId)
{
if (_itemInteraction.OfferPrimaryClick(itemId) != ItemPrimaryClickResult.NotActive)
return true;
_selection.Select(itemId, SelectionChangeSource.Paperdoll);
return false;
}
/// <summary>Detach event handlers (idempotent).</summary>
public void Dispose()
{
@ -369,7 +369,10 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
_objects.Cleared -= OnObjectsCleared;
_selection.Changed -= OnSelectionChanged;
foreach (var (_, list) in _slots)
{
list.PrimaryItemPressed = null;
list.ExamineItemRequested = null;
}
if (_dollViewport is UiViewport doll)
doll.ClickedAt = null;
switch (_dollDragMask)

View file

@ -138,6 +138,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
if (_slots[i] is { } list)
{
WireClick(list);
list.PrimaryItemPressed = PressItem;
list.ExamineItemRequested = ExamineItem;
// B.1 drag-drop spine: this controller is the drop handler for every
// toolbar slot list; each cell knows its slot index + that it's a
@ -532,6 +533,19 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
};
}
private bool PressItem(uint itemId)
{
if (_itemInteraction?.OfferPrimaryClick(itemId)
is not null and not ItemPrimaryClickResult.NotActive)
return true;
if (_selectItem is not null)
_selectItem(itemId);
else
_selection?.Select(itemId, SelectionChangeSource.Toolbar);
return false;
}
private void ExamineItem(uint itemId)
{
if (_selectItem is not null)
@ -777,7 +791,10 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
_selection.Changed -= OnSelectionChanged;
foreach (UiItemList? list in _slots)
if (list is not null)
{
list.PrimaryItemPressed = null;
list.ExamineItemRequested = null;
}
_repo.ObjectAdded -= OnRepositoryObjectChanged;
_repo.ObjectUpdated -= OnRepositoryObjectChanged;
_repo.ObjectRemoved -= OnRepositoryObjectChanged;