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

@ -275,6 +275,61 @@ public class DragDropSpineTests
Assert.True(used);
}
[Fact]
public void PhysicalItemPress_selectsBeforeRelease_thenClickActivates()
{
var (root, list, cell) = RootWithBoundSlot(0x5001u);
var selected = new List<uint>();
bool used = false;
list.PrimaryItemPressed = item =>
{
selected.Add(item);
return false;
};
cell.Clicked = () => used = true;
root.OnMouseDown(UiMouseButton.Left, 10, 10);
Assert.Equal(new uint[] { 0x5001u }, selected);
Assert.False(used);
root.OnMouseUp(UiMouseButton.Left, 10, 10);
Assert.True(used);
}
[Fact]
public void ConsumedPhysicalItemPress_suppressesCompletedActivation()
{
var (root, list, cell) = RootWithBoundSlot(0x5001u);
bool used = false;
bool doubleUsed = false;
list.PrimaryItemPressed = _ => true;
cell.Clicked = () => used = true;
cell.DoubleClicked = () => doubleUsed = true;
root.OnMouseDown(UiMouseButton.Left, 10, 10);
root.OnMouseUp(UiMouseButton.Left, 10, 10);
root.OnMouseDown(UiMouseButton.Left, 10, 10);
root.OnMouseUp(UiMouseButton.Left, 10, 10);
Assert.False(used);
Assert.False(doubleUsed);
}
[Fact]
public void ConsumedPhysicalItemPress_cannotPromoteIntoDrag()
{
var (root, list, _) = RootWithBoundSlot(0x5001u);
list.PrimaryItemPressed = _ => true;
root.OnMouseDown(UiMouseButton.Left, 10, 10);
root.OnMouseMove(20, 10);
Assert.Null(root.DragSource);
Assert.Null(root.DragPayload);
}
[Fact]
public void RightClick_withoutDrag_requestsItemListAppraisal()
{

View file

@ -199,6 +199,20 @@ public sealed class ExternalContainerControllerTests
Assert.Empty(h.Pickups);
}
[Fact]
public void MouseDownLoot_selectsBeforeCompletedClick()
{
using var h = new Harness();
h.Open(Chest, new ContainerContentEntry(Item, 0u));
UiItemSlot cell = h.Contents.GetItem(0)!;
cell.OnEvent(new UiEvent(0u, cell, UiEventType.MouseDown));
Assert.Equal(Item, h.Selection.SelectedObjectId);
Assert.True(cell.Selected);
Assert.Empty(h.Pickups);
}
[Fact]
public void CloseAndRangeExit_SendUseOnceWithoutNoLongerViewing()
{

View file

@ -460,7 +460,7 @@ public class InventoryControllerTests
}
[Fact]
public void ClickGridItem_movesSquareOnly_noWire_keepsOpenContainer()
public void MouseDownGridItem_movesSquareImmediately_noWire_keepsOpenContainer()
{
var (layout, grid, _, top, _, _, _, _) = BuildLayout();
var objects = new ClientObjectTable();
@ -468,7 +468,8 @@ public class InventoryControllerTests
var uses = new List<uint>();
Bind(layout, objects, uses: uses);
grid.GetItem(0)!.Clicked!(); // select the loose item
UiItemSlot item = grid.GetItem(0)!;
item.OnEvent(new UiEvent(0u, item, UiEventType.MouseDown));
Assert.True(grid.GetItem(0)!.Selected); // square on the selected grid item
Assert.True(top.GetItem(0)!.IsOpenContainer); // main pack still the open container (unchanged)
@ -476,7 +477,7 @@ public class InventoryControllerTests
}
[Fact]
public void ClickGridItem_updatesSharedSelection_andExternalSelectionMovesSquare()
public void MouseDownGridItem_updatesSharedSelection_andExternalSelectionMovesSquare()
{
var (layout, grid, _, _, _, _, _, _) = BuildLayout();
var objects = new ClientObjectTable();
@ -485,7 +486,8 @@ public class InventoryControllerTests
var selection = new SelectionState();
Bind(layout, objects, selection: selection);
grid.GetItem(0)!.Clicked!();
UiItemSlot item = grid.GetItem(0)!;
item.OnEvent(new UiEvent(0u, item, UiEventType.MouseDown));
Assert.Equal(0xAu, selection.SelectedObjectId);
Assert.True(grid.GetItem(0)!.Selected);
@ -555,7 +557,8 @@ public class InventoryControllerTests
selection: new SelectionState(),
datFont: null,
itemInteraction: interaction);
grid.GetItem(0)!.Clicked!();
UiItemSlot item = grid.GetItem(0)!;
item.OnEvent(new UiEvent(0u, item, UiEventType.MouseDown));
Assert.True(grid.GetItem(0)!.Selected);
Assert.True(interaction.ActivateItem(0xAu));

View file

@ -176,7 +176,7 @@ public class PaperdollControllerTests
}
[Fact]
public void ClickEquippedItem_updatesSharedSelection()
public void MouseDownEquippedItem_updatesSharedSelection()
{
var (layout, lists) = BuildLayout();
var objects = new ClientObjectTable();
@ -184,7 +184,8 @@ public class PaperdollControllerTests
var selection = new SelectionState();
Bind(layout, objects, selection: selection);
lists[HeadSlot].Cell.Clicked!();
UiItemSlot cell = lists[HeadSlot].Cell;
cell.OnEvent(new UiEvent(0u, cell, UiEventType.MouseDown));
Assert.Equal(0xA01u, selection.SelectedObjectId);
Assert.True(lists[HeadSlot].Cell.Selected);

View file

@ -192,11 +192,19 @@ public class ToolbarControllerTests
var shortcuts = new List<ShortcutEntry>
{ new(Index: 0, ObjectId: 0x5001u, SpellId: 0) };
uint used = 0;
var selection = new SelectionState();
ToolbarController.Bind(layout, repo, () => shortcuts,
iconIds: (_,_,_,_,_) => 0x77u, useItem: g => used = g);
iconIds: (_,_,_,_,_) => 0x77u,
useItem: g => used = g,
selection: selection);
UiItemSlot cell = slots[Row1[0]].Cell;
cell.OnEvent(new UiEvent(0u, cell, UiEventType.MouseDown));
Assert.Equal(0x5001u, selection.SelectedObjectId);
Assert.Equal(0u, used);
// Use now fires on Click (mouse-up), not MouseDown — drag/click disambiguation.
slots[Row1[0]].Cell.OnEvent(new UiEvent(0u, null, UiEventType.Click));
cell.OnEvent(new UiEvent(0u, cell, UiEventType.Click));
Assert.Equal(0x5001u, used);
}