feat(ui): centralize retail selection state
This commit is contained in:
parent
c7607f019c
commit
7983309d23
30 changed files with 591 additions and 108 deletions
|
|
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
|||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Selection;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
|
@ -56,7 +57,8 @@ public class InventoryControllerTests
|
|||
int? strength = 100, List<uint>? uses = null, List<uint>? closes = null,
|
||||
List<(uint item, uint container, int placement)>? puts = null,
|
||||
string? ownerName = null,
|
||||
Action? onClose = null)
|
||||
Action? onClose = null,
|
||||
SelectionState? selection = null)
|
||||
=> InventoryController.Bind(layout, objects, () => Player,
|
||||
iconIds: (_, _, _, _, _) => 0u,
|
||||
strength: () => strength, datFont: null,
|
||||
|
|
@ -64,7 +66,8 @@ public class InventoryControllerTests
|
|||
sendUse: uses is null ? null : g => uses.Add(g),
|
||||
sendNoLongerViewing: closes is null ? null : g => closes.Add(g),
|
||||
sendPutItemInContainer: puts is null ? null : (i, c, p) => puts.Add((i, c, p)),
|
||||
onClose: onClose);
|
||||
onClose: onClose,
|
||||
selection: selection ?? new SelectionState());
|
||||
|
||||
private static UiButton MakeButton(uint id)
|
||||
{
|
||||
|
|
@ -316,7 +319,8 @@ public class InventoryControllerTests
|
|||
{
|
||||
var (layout, grid, containers, top, _, _, _, _) = BuildLayout();
|
||||
InventoryController.Bind(layout, new ClientObjectTable(), () => Player,
|
||||
iconIds: (_, _, _, _, _) => 0u, strength: () => 100, datFont: null,
|
||||
iconIds: (_, _, _, _, _) => 0u, strength: () => 100,
|
||||
selection: new SelectionState(), datFont: null,
|
||||
contentsEmptySprite: 0x06004D20u, sideBagEmptySprite: 0x06005D9Cu, mainPackEmptySprite: 0x06005D9Cu);
|
||||
|
||||
Assert.Equal(0x06004D20u, grid.GetItem(0)!.EmptySprite); // a padded empty contents cell
|
||||
|
|
@ -414,6 +418,27 @@ public class InventoryControllerTests
|
|||
Assert.Empty(closes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClickGridItem_updatesSharedSelection_andExternalSelectionMovesSquare()
|
||||
{
|
||||
var (layout, grid, _, _, _, _, _, _) = BuildLayout();
|
||||
var objects = new ClientObjectTable();
|
||||
SeedContained(objects, 0xAu, Player, slot: 0);
|
||||
SeedContained(objects, 0xBu, Player, slot: 1);
|
||||
var selection = new SelectionState();
|
||||
Bind(layout, objects, selection: selection);
|
||||
|
||||
grid.GetItem(0)!.Clicked!();
|
||||
|
||||
Assert.Equal(0xAu, selection.SelectedObjectId);
|
||||
Assert.True(grid.GetItem(0)!.Selected);
|
||||
|
||||
selection.Select(0xBu, SelectionChangeSource.World);
|
||||
|
||||
Assert.False(grid.GetItem(0)!.Selected);
|
||||
Assert.True(grid.GetItem(1)!.Selected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TargetMode_suppressesSelectedSquare_onPendingSource()
|
||||
{
|
||||
|
|
@ -439,6 +464,7 @@ public class InventoryControllerTests
|
|||
InventoryController.Bind(layout, objects, () => Player,
|
||||
iconIds: (_, _, _, _, _) => 0u,
|
||||
strength: () => 100,
|
||||
selection: new SelectionState(),
|
||||
datFont: null,
|
||||
itemInteraction: interaction);
|
||||
grid.GetItem(0)!.Clicked!();
|
||||
|
|
@ -469,7 +495,7 @@ public class InventoryControllerTests
|
|||
(ItemType type, uint icon)? mainPackCall = null;
|
||||
InventoryController.Bind(layout, objects, () => Player,
|
||||
iconIds: (t, icon, _, _, _) => { if (icon == 0x0600127Eu) mainPackCall = (t, icon); return 0u; },
|
||||
strength: () => 100, datFont: null);
|
||||
strength: () => 100, selection: new SelectionState(), datFont: null);
|
||||
|
||||
// Retail draws a constant backpack over the Container type-underlay (IconData::RenderIcons
|
||||
// IsThePlayer branch). The backpack RenderSurface 0x0600127E is visually confirmed (2026-06-22).
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ public class InventoryFrameImportProbe
|
|||
playerGuid: static () => 0u,
|
||||
iconIds: static (_, _, _, _, _) => 0u,
|
||||
strength: static () => 100,
|
||||
selection: new AcDream.Core.Selection.SelectionState(),
|
||||
datFont: null,
|
||||
onClose: () => closes++);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
|||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Selection;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
|
@ -34,11 +35,13 @@ public class PaperdollControllerTests
|
|||
}
|
||||
|
||||
private static PaperdollController Bind(ImportedLayout layout, ClientObjectTable objects,
|
||||
List<(uint item, uint mask)>? wields = null, uint emptySlot = 0u)
|
||||
List<(uint item, uint mask)>? wields = null, uint emptySlot = 0u,
|
||||
SelectionState? selection = null)
|
||||
=> PaperdollController.Bind(layout, objects, () => Player,
|
||||
iconIds: (_, _, _, _, _) => 0x1234u,
|
||||
sendWield: wields is null ? null : (i, m) => wields.Add((i, m)),
|
||||
emptySlotSprite: emptySlot);
|
||||
emptySlotSprite: emptySlot,
|
||||
selection: selection ?? new SelectionState());
|
||||
|
||||
private static void SeedEquipped(ClientObjectTable t, uint guid, EquipMask loc)
|
||||
{
|
||||
|
|
@ -63,6 +66,21 @@ public class PaperdollControllerTests
|
|||
Assert.Equal(0u, lists[ShieldSlot].Cell.ItemId); // shield slot empty
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClickEquippedItem_updatesSharedSelection()
|
||||
{
|
||||
var (layout, lists) = BuildLayout();
|
||||
var objects = new ClientObjectTable();
|
||||
SeedEquipped(objects, 0xA01u, EquipMask.HeadWear);
|
||||
var selection = new SelectionState();
|
||||
Bind(layout, objects, selection: selection);
|
||||
|
||||
lists[HeadSlot].Cell.Clicked!();
|
||||
|
||||
Assert.Equal(0xA01u, selection.SelectedObjectId);
|
||||
Assert.True(lists[HeadSlot].Cell.Selected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Populate_matches_a_weapon_into_the_composite_slot()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||
using System.Numerics;
|
||||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using AcDream.Core.Selection;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
|
@ -65,7 +66,7 @@ public class SelectedObjectControllerTests
|
|||
|
||||
private sealed class Harness
|
||||
{
|
||||
public Action<uint?>? SelectionHandler;
|
||||
public readonly SelectionState Selection = new();
|
||||
public Action<uint, float>? HealthHandler;
|
||||
public readonly List<uint> QueryHealthCalls = new();
|
||||
|
||||
|
|
@ -75,17 +76,19 @@ public class SelectedObjectControllerTests
|
|||
public readonly Dictionary<uint, bool> HasHealthMap = new();
|
||||
public readonly Dictionary<uint, uint> StackMap = new();
|
||||
|
||||
public void FireSelection(uint? g) => SelectionHandler?.Invoke(g);
|
||||
public void FireSelection(uint? g)
|
||||
{
|
||||
if (g is { } guid)
|
||||
Selection.Select(guid, SelectionChangeSource.System);
|
||||
else
|
||||
Selection.Clear(SelectionChangeSource.System);
|
||||
}
|
||||
public void FireHealth(uint g, float pct) => HealthHandler?.Invoke(g, pct);
|
||||
|
||||
public SelectedObjectController Bind(ImportedLayout layout, UiDatFont? datFont = null)
|
||||
=> SelectedObjectController.Bind(
|
||||
layout,
|
||||
subscribeSelectionChanged: h => SelectionHandler = h,
|
||||
unsubscribeSelectionChanged: h =>
|
||||
{
|
||||
if (SelectionHandler == h) SelectionHandler = null;
|
||||
},
|
||||
selection: Selection,
|
||||
subscribeHealthChanged: h => HealthHandler = h,
|
||||
unsubscribeHealthChanged: h =>
|
||||
{
|
||||
|
|
@ -362,7 +365,6 @@ public class SelectedObjectControllerTests
|
|||
h.NameMap[0x12345678u] = "Something";
|
||||
var c = h.Bind(layout);
|
||||
|
||||
Assert.NotNull(h.SelectionHandler);
|
||||
Assert.Null(Record.Exception(() => h.FireSelection(0x12345678u)));
|
||||
Assert.Null(Record.Exception(() => h.FireHealth(0x12345678u, 0.5f)));
|
||||
Assert.Null(Record.Exception(() => c.Tick(0.5)));
|
||||
|
|
@ -425,7 +427,6 @@ public class SelectedObjectControllerTests
|
|||
h.FireSelection(0xAA09u);
|
||||
h.FireHealth(0xAA09u, 0.5f);
|
||||
|
||||
Assert.Null(h.SelectionHandler);
|
||||
Assert.Null(h.HealthHandler);
|
||||
Assert.False(healthMeterEl.Visible);
|
||||
Assert.Empty(h.QueryHealthCalls);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue