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:
parent
2dd5cb80d2
commit
043ab10b3c
18 changed files with 292 additions and 62 deletions
|
|
@ -115,8 +115,8 @@ and the final two-client portal-out/materialization observer flow are
|
||||||
user-gated. Deterministic world-lifecycle automation protects fresh login,
|
user-gated. Deterministic world-lifecycle automation protects fresh login,
|
||||||
outdoor/world-edge/dungeon travel, same-location revisit, exact graceful
|
outdoor/world-edge/dungeon travel, same-location revisit, exact graceful
|
||||||
disconnect, and uncapped fresh-process reconnect with JSON/PNG artifacts.
|
disconnect, and uncapped fresh-process reconnect with JSON/PNG artifacts.
|
||||||
Release builds; the appraisal corrective implementation passes 3,496 App
|
Release builds; the appraisal and retained-item input corrections pass 3,548
|
||||||
tests / 3 skips and 7,913 complete-solution tests / 5 skips.
|
App tests / 3 skips and 7,979 complete-solution tests / 5 skips.
|
||||||
|
|
||||||
**Current work order:** the six-slice pre-M4 world-interaction completion
|
**Current work order:** the six-slice pre-M4 world-interaction completion
|
||||||
program in `docs/plans/2026-07-23-world-interaction-completion.md`: favorite
|
program in `docs/plans/2026-07-23-world-interaction-completion.md`: favorite
|
||||||
|
|
@ -125,8 +125,8 @@ equipped-child picking, vendor browsing, and authoritative vendor
|
||||||
transactions. Slices 1 and 2 plus the adjacent shared-cooldown presentation are
|
transactions. Slices 1 and 2 plus the adjacent shared-cooldown presentation are
|
||||||
user-accepted. Slice 3's first connected gate exposed and corrected the
|
user-accepted. Slice 3's first connected gate exposed and corrected the
|
||||||
monster-response flag mismatch, non-retail shared-panel mounting, and missing
|
monster-response flag mismatch, non-retail shared-panel mounting, and missing
|
||||||
inscription transaction. Its independent floaty examination window awaits the
|
inscription transaction. The creature and common-item report corrections are
|
||||||
corrective connected gate.
|
user-accepted; press-time retained-item selection is the current visual gate.
|
||||||
|
|
||||||
**Structural prerequisite before new M4 subsystem work:** all eight
|
**Structural prerequisite before new M4 subsystem work:** all eight
|
||||||
behavior-preserving `GameWindow` decomposition slices and the automated
|
behavior-preserving `GameWindow` decomposition slices and the automated
|
||||||
|
|
|
||||||
|
|
@ -113,8 +113,8 @@ and the final two-client portal-out/materialization observer flow are
|
||||||
user-gated. Deterministic world-lifecycle automation protects fresh login,
|
user-gated. Deterministic world-lifecycle automation protects fresh login,
|
||||||
outdoor/world-edge/dungeon travel, same-location revisit, exact graceful
|
outdoor/world-edge/dungeon travel, same-location revisit, exact graceful
|
||||||
disconnect, and uncapped fresh-process reconnect with JSON/PNG artifacts.
|
disconnect, and uncapped fresh-process reconnect with JSON/PNG artifacts.
|
||||||
Release builds; the appraisal corrective implementation passes 3,496 App
|
Release builds; the appraisal and retained-item input corrections pass 3,548
|
||||||
tests / 3 skips and 7,913 complete-solution tests / 5 skips.
|
App tests / 3 skips and 7,979 complete-solution tests / 5 skips.
|
||||||
|
|
||||||
**Current work order:** the six-slice pre-M4 world-interaction completion
|
**Current work order:** the six-slice pre-M4 world-interaction completion
|
||||||
program in `docs/plans/2026-07-23-world-interaction-completion.md`: favorite
|
program in `docs/plans/2026-07-23-world-interaction-completion.md`: favorite
|
||||||
|
|
@ -123,8 +123,8 @@ equipped-child picking, vendor browsing, and authoritative vendor
|
||||||
transactions. Slices 1 and 2 plus the adjacent shared-cooldown presentation are
|
transactions. Slices 1 and 2 plus the adjacent shared-cooldown presentation are
|
||||||
user-accepted. Slice 3's first connected gate exposed and corrected the
|
user-accepted. Slice 3's first connected gate exposed and corrected the
|
||||||
monster-response flag mismatch, non-retail shared-panel mounting, and missing
|
monster-response flag mismatch, non-retail shared-panel mounting, and missing
|
||||||
inscription transaction. Its independent floaty examination window awaits the
|
inscription transaction. The creature and common-item report corrections are
|
||||||
corrective connected gate.
|
user-accepted; press-time retained-item selection is the current visual gate.
|
||||||
|
|
||||||
**Structural prerequisite before new M4 subsystem work:** all eight
|
**Structural prerequisite before new M4 subsystem work:** all eight
|
||||||
behavior-preserving `GameWindow` decomposition slices and the automated
|
behavior-preserving `GameWindow` decomposition slices and the automated
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,13 @@ The separate retained ItemList path now does the same for occupied backpack,
|
||||||
side-bag, loot-container, paperdoll-slot, and physical shortcut cells; it
|
side-bag, loot-container, paperdoll-slot, and physical shortcut cells; it
|
||||||
selects the item before entering the shared appraisal owner, and RMB movement
|
selects the item before entering the shared appraisal owner, and RMB movement
|
||||||
neither appraises nor lifts the item.
|
neither appraises nor lifts the item.
|
||||||
|
The adjacent input-latency correction ports
|
||||||
|
`UIElement_ListBox::MouseDown @ 0x0046E3A0`: physical retained items now update
|
||||||
|
canonical selection and their green frame on left-button down, before the
|
||||||
|
three-pixel drag threshold. Target mode is intercepted at that same press.
|
||||||
|
Bag opening, item use/equip, and shortcut activation remain completed
|
||||||
|
click/double-click actions, so a drag or target-consumed press cannot also
|
||||||
|
activate the item.
|
||||||
|
|
||||||
**Files:** `src/AcDream.Core.Net/Messages/AppraiseInfoParser.cs`;
|
**Files:** `src/AcDream.Core.Net/Messages/AppraiseInfoParser.cs`;
|
||||||
`src/AcDream.App/UI/RetailUiRuntime.cs`;
|
`src/AcDream.App/UI/RetailUiRuntime.cs`;
|
||||||
|
|
@ -176,6 +183,8 @@ Right-clicking a visible world object opens the same examination window;
|
||||||
right-dragging the camera does not.
|
right-dragging the camera does not.
|
||||||
Right-clicking an occupied retained item cell selects and examines it without
|
Right-clicking an occupied retained item cell selects and examines it without
|
||||||
using, equipping, looting, or dragging it.
|
using, equipping, looting, or dragging it.
|
||||||
|
Left-pressing one selects it before the button is released; holding and
|
||||||
|
dragging preserves that selection without activating the item.
|
||||||
The Black Phyntos Hive specifically reads `Value: ???`, `Burden: Unknown`,
|
The Black Phyntos Hive specifically reads `Value: ???`, `Burden: Unknown`,
|
||||||
then its description after one retail paragraph break, without the bogus
|
then its description after one retail paragraph break, without the bogus
|
||||||
255-item/255-container line.
|
255-item/255-container line.
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,10 @@ the configurable `SelectRight` action's retail release-completed gesture,
|
||||||
including drag cancellation, then reuses the canonical
|
including drag cancellation, then reuses the canonical
|
||||||
picker/selection/appraisal path. The parallel retained ItemList branch now
|
picker/selection/appraisal path. The parallel retained ItemList branch now
|
||||||
selects and appraises occupied inventory, external-container, paperdoll, and
|
selects and appraises occupied inventory, external-container, paperdoll, and
|
||||||
physical-shortcut cells through the same request owner. Slice 3's first connected gate exposed
|
physical-shortcut cells through the same request owner. Its shared physical
|
||||||
|
left-click contract now also matches retail's press-time selection: the
|
||||||
|
selection frame updates on MouseDown before drag promotion, while completed
|
||||||
|
click/double-click remains the activation boundary. Slice 3's first connected gate exposed
|
||||||
three deeper defects, now corrected: the response enum mislabeled ACE's
|
three deeper defects, now corrected: the response enum mislabeled ACE's
|
||||||
creature bit `0x0100` and dropped monster packets while retaining the busy
|
creature bit `0x0100` and dropped monster packets while retaining the busy
|
||||||
cursor; the imported 310 x 400 examination layout was incorrectly hosted as
|
cursor; the imported 310 x 400 examination layout was incorrectly hosted as
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,10 @@ instead of appraising the release point.
|
||||||
Retail's separate retained ItemList right-click branch now covers physical
|
Retail's separate retained ItemList right-click branch now covers physical
|
||||||
backpack, loot, paperdoll, and toolbar item cells through the same selection
|
backpack, loot, paperdoll, and toolbar item cells through the same selection
|
||||||
and appraisal owner.
|
and appraisal owner.
|
||||||
|
The same shared ItemList contract now ports retail's left-button-down
|
||||||
|
selection timing across inventory, loot, paperdoll, and physical shortcuts:
|
||||||
|
selection is visible before release or drag promotion, while use/open/equip
|
||||||
|
remains release-completed.
|
||||||
Slice 3's first connected gate found and corrected the real remaining defects:
|
Slice 3's first connected gate found and corrected the real remaining defects:
|
||||||
ACE creature flag `0x0100` was mislabeled as a weapon profile and left the
|
ACE creature flag `0x0100` was mislabeled as a weapon profile and left the
|
||||||
appraisal busy reference held, retail's examination UI had been mounted as a
|
appraisal busy reference held, retail's examination UI had been mounted as a
|
||||||
|
|
|
||||||
|
|
@ -222,9 +222,10 @@ present in the authored base name. Empty `AddItemInfo` calls are retained as
|
||||||
real report fragments, restoring retail's intentional blank rows after
|
real report fragments, restoring retail's intentional blank rows after
|
||||||
workmanship, before armor level, around rating/special-property blocks, and at
|
workmanship, before armor level, around rating/special-property blocks, and at
|
||||||
the later use/item-level boundaries. Focused wire, projection, object-table,
|
the later use/item-level boundaries. Focused wire, projection, object-table,
|
||||||
title, and boots-layout fixtures protect the full path. The Release solution
|
title, and boots-layout fixtures protect the full path. With the subsequent
|
||||||
build and 3,533 App tests / 3 skips plus 7,956 complete-solution tests /
|
right-click and press-time retained-item input ports, the Release solution
|
||||||
5 skips pass; the connected boots comparison is the remaining visual gate.
|
build and 3,548 App tests / 3 skips plus 7,979 complete-solution tests /
|
||||||
|
5 skips pass.
|
||||||
|
|
||||||
The world right-click follow-up ports
|
The world right-click follow-up ports
|
||||||
`UIElement_SmartBoxWrapper::MouseUp @ 0x004E5820` and the
|
`UIElement_SmartBoxWrapper::MouseUp @ 0x004E5820` and the
|
||||||
|
|
@ -241,6 +242,13 @@ The retained follow-up ports the separate
|
||||||
backpack, side-bag, loot, paperdoll-slot, or physical toolbar cell now selects
|
backpack, side-bag, loot, paperdoll-slot, or physical toolbar cell now selects
|
||||||
its item and enters that same appraisal owner on a completed right-click.
|
its item and enters that same appraisal owner on a completed right-click.
|
||||||
Right-button movement cancels the click and can never begin an item drag.
|
Right-button movement cancels the click and can never begin an item drag.
|
||||||
|
The input-latency follow-up ports `UIElement_ListBox::MouseDown @ 0x0046E3A0`
|
||||||
|
and the physical left-click branch of
|
||||||
|
`UIElement_ItemList::ListenToElementMessage @ 0x004E4D50`. Canonical selection
|
||||||
|
and the retained green frame update during left-button down, before the
|
||||||
|
three-pixel drag threshold. Target mode is offered first. Opening, using,
|
||||||
|
equipping, looting, and shortcut activation remain completed-click or
|
||||||
|
double-click actions and are suppressed when target mode consumed the press.
|
||||||
|
|
||||||
## Slice 1 — spell-bar overflow arrows
|
## Slice 1 — spell-bar overflow arrows
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,52 @@ procedural arrow. The indicator is persistent selection presentation. Retail
|
||||||
also has a separate, short-lived click confirmation owned by SmartBox; it is not
|
also has a separate, short-lived click confirmation owned by SmartBox; it is not
|
||||||
implemented in `CPhysicsPart::Draw` or `ACCWeenieObject::SetSelected`.
|
implemented in `CPhysicsPart::Draw` or `ACCWeenieObject::SetSelected`.
|
||||||
|
|
||||||
|
## Retained physical-item press, activation, and drag ordering
|
||||||
|
|
||||||
|
`UIElement_ListBox::MouseDown @ 0x0046E3A0`
|
||||||
|
|
||||||
|
```text
|
||||||
|
on left mouse down over a single-selection ListBox:
|
||||||
|
item = GetItemUnderMouse()
|
||||||
|
if item exists:
|
||||||
|
SetSelectedItem(item, notify=true)
|
||||||
|
if drag-selection is enabled:
|
||||||
|
StartDragSelect()
|
||||||
|
```
|
||||||
|
|
||||||
|
`UIElement_ItemList::ListenToElementMessage @ 0x004E4D50`
|
||||||
|
|
||||||
|
```text
|
||||||
|
on the physical-item left-button-down notice:
|
||||||
|
item = UIItem under the mouse
|
||||||
|
if a target mode is active:
|
||||||
|
HandleTargetedUseLeftClick(item)
|
||||||
|
consume the notice
|
||||||
|
else if item.itemID != 0:
|
||||||
|
if this list uses single selection:
|
||||||
|
update the list selection
|
||||||
|
SetSelectedObject(item.itemID)
|
||||||
|
|
||||||
|
on the physical-item double-click notice:
|
||||||
|
if item.itemID != 0:
|
||||||
|
UseObject(item.itemID)
|
||||||
|
```
|
||||||
|
|
||||||
|
`gmPaperDollUI::ListenToElementMessage @ 0x004A5C30` uses the same ordering
|
||||||
|
for a worn item: target mode first, otherwise set the selected object on the
|
||||||
|
left-button-down notice; right-click selects and examines.
|
||||||
|
|
||||||
|
Therefore selection is not a completed-click action. The green selection frame
|
||||||
|
appears while the left button is still held, before the three-pixel drag
|
||||||
|
threshold can promote the press into a drag. Opening a bag, using/equipping an
|
||||||
|
item, and shortcut activation remain completed click or double-click actions,
|
||||||
|
so dragging never also activates the source. A target-mode-consumed press
|
||||||
|
suppresses the rest of that physical-item click gesture.
|
||||||
|
|
||||||
|
Acdream implements that split once in `UiItemList`/`UiItemSlot`. Inventory,
|
||||||
|
external-container loot, paperdoll equipment, and physical toolbar shortcuts
|
||||||
|
only supply their selection source and release-time activation command.
|
||||||
|
|
||||||
## Right-click selection and examination
|
## Right-click selection and examination
|
||||||
|
|
||||||
`UIElement_SmartBoxWrapper::MouseUp @ 0x004E5820`
|
`UIElement_SmartBoxWrapper::MouseUp @ 0x004E5820`
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,9 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
|
||||||
ConfigureList(_topContainer, ContainerCellSize, horizontalScroll: false, containerEmptySprite);
|
ConfigureList(_topContainer, ContainerCellSize, horizontalScroll: false, containerEmptySprite);
|
||||||
ConfigureList(_containerList, ContainerCellSize, horizontalScroll: true, containerEmptySprite);
|
ConfigureList(_containerList, ContainerCellSize, horizontalScroll: true, containerEmptySprite);
|
||||||
ConfigureList(_contentsList, ItemCellSize, horizontalScroll: true, contentsEmptySprite);
|
ConfigureList(_contentsList, ItemCellSize, horizontalScroll: true, contentsEmptySprite);
|
||||||
|
_topContainer.PrimaryItemPressed = PressItem;
|
||||||
|
_containerList.PrimaryItemPressed = PressItem;
|
||||||
|
_contentsList.PrimaryItemPressed = PressItem;
|
||||||
_topContainer.ExamineItemRequested = ExamineItem;
|
_topContainer.ExamineItemRequested = ExamineItem;
|
||||||
_containerList.ExamineItemRequested = ExamineItem;
|
_containerList.ExamineItemRequested = ExamineItem;
|
||||||
_contentsList.ExamineItemRequested = ExamineItem;
|
_contentsList.ExamineItemRequested = ExamineItem;
|
||||||
|
|
@ -325,7 +328,6 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
|
||||||
private void AddRootCell(uint guid)
|
private void AddRootCell(uint guid)
|
||||||
{
|
{
|
||||||
UiItemSlot cell = CreateCell(_topContainer, guid, ItemDragSource.Ground);
|
UiItemSlot cell = CreateCell(_topContainer, guid, ItemDragSource.Ground);
|
||||||
cell.Clicked = () => HandlePrimaryClick(guid, () => Select(guid));
|
|
||||||
cell.DoubleClicked = RequestClose;
|
cell.DoubleClicked = RequestClose;
|
||||||
cell.IsOpenContainer = _openContainer == guid;
|
cell.IsOpenContainer = _openContainer == guid;
|
||||||
SetCapacity(cell, guid);
|
SetCapacity(cell, guid);
|
||||||
|
|
@ -335,7 +337,7 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
|
||||||
private void AddContainerCell(uint guid)
|
private void AddContainerCell(uint guid)
|
||||||
{
|
{
|
||||||
UiItemSlot cell = CreateCell(_containerList, guid, ItemDragSource.Ground);
|
UiItemSlot cell = CreateCell(_containerList, guid, ItemDragSource.Ground);
|
||||||
cell.Clicked = () => HandlePrimaryClick(guid, () => OpenNestedContainer(guid));
|
cell.Clicked = () => OpenNestedContainer(guid);
|
||||||
SetCapacity(cell, guid);
|
SetCapacity(cell, guid);
|
||||||
_containerList.AddItem(cell);
|
_containerList.AddItem(cell);
|
||||||
}
|
}
|
||||||
|
|
@ -343,7 +345,6 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
|
||||||
private void AddContentsCell(uint guid)
|
private void AddContentsCell(uint guid)
|
||||||
{
|
{
|
||||||
UiItemSlot cell = CreateCell(_contentsList, guid, ItemDragSource.Ground);
|
UiItemSlot cell = CreateCell(_contentsList, guid, ItemDragSource.Ground);
|
||||||
cell.Clicked = () => HandlePrimaryClick(guid, () => Select(guid));
|
|
||||||
cell.DoubleClicked = () => _itemInteraction.ActivateItem(guid);
|
cell.DoubleClicked = () => _itemInteraction.ActivateItem(guid);
|
||||||
cell.DragAcceptSprite = 0x060011F9u;
|
cell.DragAcceptSprite = 0x060011F9u;
|
||||||
cell.DragRejectSprite = 0x060011F8u;
|
cell.DragRejectSprite = 0x060011F8u;
|
||||||
|
|
@ -384,11 +385,12 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
|
||||||
_itemInteraction.ExamineSelectedOrEnterMode(guid);
|
_itemInteraction.ExamineSelectedOrEnterMode(guid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandlePrimaryClick(uint guid, Action fallback)
|
private bool PressItem(uint guid)
|
||||||
{
|
{
|
||||||
if (_itemInteraction.OfferPrimaryClick(guid) != ItemPrimaryClickResult.NotActive)
|
if (_itemInteraction.OfferPrimaryClick(guid) != ItemPrimaryClickResult.NotActive)
|
||||||
return;
|
return true;
|
||||||
fallback();
|
Select(guid);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyIndicators()
|
private void ApplyIndicators()
|
||||||
|
|
@ -573,6 +575,9 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
|
||||||
_objects.Cleared -= OnObjectsCleared;
|
_objects.Cleared -= OnObjectsCleared;
|
||||||
_selection.Changed -= OnSelectionChanged;
|
_selection.Changed -= OnSelectionChanged;
|
||||||
_itemInteraction.StateChanged -= OnInteractionStateChanged;
|
_itemInteraction.StateChanged -= OnInteractionStateChanged;
|
||||||
|
_topContainer.PrimaryItemPressed = null;
|
||||||
|
_containerList.PrimaryItemPressed = null;
|
||||||
|
_contentsList.PrimaryItemPressed = null;
|
||||||
_topContainer.ExamineItemRequested = null;
|
_topContainer.ExamineItemRequested = null;
|
||||||
_containerList.ExamineItemRequested = null;
|
_containerList.ExamineItemRequested = null;
|
||||||
_contentsList.ExamineItemRequested = null;
|
_contentsList.ExamineItemRequested = null;
|
||||||
|
|
|
||||||
|
|
@ -153,9 +153,21 @@ public sealed class InventoryController : IItemListDragHandler, IRetainedPanelCo
|
||||||
_contentsGrid?.RegisterDragHandler(this);
|
_contentsGrid?.RegisterDragHandler(this);
|
||||||
_containerList?.RegisterDragHandler(this);
|
_containerList?.RegisterDragHandler(this);
|
||||||
_topContainer?.RegisterDragHandler(this);
|
_topContainer?.RegisterDragHandler(this);
|
||||||
if (_contentsGrid is not null) _contentsGrid.ExamineItemRequested = ExamineItem;
|
if (_contentsGrid is not null)
|
||||||
if (_containerList is not null) _containerList.ExamineItemRequested = ExamineItem;
|
{
|
||||||
if (_topContainer is not null) _topContainer.ExamineItemRequested = ExamineItem;
|
_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).
|
// Burden meter: vertical 11×58 bar (gmBackpackUI m_burdenMeter, retail direction 4).
|
||||||
_burdenMeter = layout.FindElement(BurdenMeterId) as UiMeter;
|
_burdenMeter = layout.FindElement(BurdenMeterId) as UiMeter;
|
||||||
|
|
@ -432,12 +444,7 @@ public sealed class InventoryController : IItemListDragHandler, IRetainedPanelCo
|
||||||
dragIconTexture: _dragIconIds?.Invoke(
|
dragIconTexture: _dragIconIds?.Invoke(
|
||||||
ItemType.Container, PlayerPackBaseIcon, 0u, 0u, 0u) ?? 0u);
|
ItemType.Container, PlayerPackBaseIcon, 0u, 0u, 0u) ?? 0u);
|
||||||
main.DragAcceptSprite = 0x060011F7u; main.DragRejectSprite = 0x060011F8u;
|
main.DragAcceptSprite = 0x060011F7u; main.DragRejectSprite = 0x060011F8u;
|
||||||
main.Clicked = () =>
|
main.Clicked = () => OpenContainer(p);
|
||||||
{
|
|
||||||
if (_itemInteraction?.OfferSelfPrimaryClick()
|
|
||||||
is not null and not ItemPrimaryClickResult.NotActive) return;
|
|
||||||
OpenContainer(p);
|
|
||||||
};
|
|
||||||
main.DoubleClicked = () => _itemInteraction?.ActivateItem(p);
|
main.DoubleClicked = () => _itemInteraction?.ActivateItem(p);
|
||||||
SetCapacityBar(main, p); // main-pack fullness (items / ItemsCapacity)
|
SetCapacityBar(main, p); // main-pack fullness (items / ItemsCapacity)
|
||||||
_topContainer.AddItem(main);
|
_topContainer.AddItem(main);
|
||||||
|
|
@ -480,22 +487,28 @@ public sealed class InventoryController : IItemListDragHandler, IRetainedPanelCo
|
||||||
cell.DoubleClicked = () => _itemInteraction?.ActivateItem(guid);
|
cell.DoubleClicked = () => _itemInteraction?.ActivateItem(guid);
|
||||||
if (isContainer)
|
if (isContainer)
|
||||||
{
|
{
|
||||||
cell.Clicked = () => HandlePrimaryClick(guid, () => OpenContainer(guid));
|
cell.Clicked = () => OpenContainer(guid);
|
||||||
SetCapacityBar(cell, guid);
|
SetCapacityBar(cell, guid);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
cell.Clicked = () => HandlePrimaryClick(guid, () => SelectItem(guid));
|
|
||||||
}
|
|
||||||
list.AddItem(cell);
|
list.AddItem(cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandlePrimaryClick(uint guid, Action fallback)
|
private bool PressItem(uint guid)
|
||||||
{
|
{
|
||||||
if (_itemInteraction?.OfferPrimaryClick(guid)
|
if (_itemInteraction?.OfferPrimaryClick(guid)
|
||||||
is not null and not ItemPrimaryClickResult.NotActive)
|
is not null and not ItemPrimaryClickResult.NotActive)
|
||||||
return;
|
return true;
|
||||||
fallback();
|
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)
|
private void AddEmptyCell(UiItemList list)
|
||||||
|
|
@ -931,9 +944,21 @@ public sealed class InventoryController : IItemListDragHandler, IRetainedPanelCo
|
||||||
_objects.ObjectUpdated -= OnObjectChanged;
|
_objects.ObjectUpdated -= OnObjectChanged;
|
||||||
_objects.Cleared -= OnObjectsCleared;
|
_objects.Cleared -= OnObjectsCleared;
|
||||||
_selection.Changed -= OnSelectionChanged;
|
_selection.Changed -= OnSelectionChanged;
|
||||||
if (_contentsGrid is not null) _contentsGrid.ExamineItemRequested = null;
|
if (_contentsGrid is not null)
|
||||||
if (_containerList is not null) _containerList.ExamineItemRequested = null;
|
{
|
||||||
if (_topContainer is not null) _topContainer.ExamineItemRequested = 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)
|
if (_itemInteraction is not null)
|
||||||
{
|
{
|
||||||
_itemInteraction.StateChanged -= OnInteractionStateChanged;
|
_itemInteraction.StateChanged -= OnInteractionStateChanged;
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
||||||
var (element, mask, _, unlockBit) = PaperdollSlotBackgrounds.Definitions[i];
|
var (element, mask, _, unlockBit) = PaperdollSlotBackgrounds.Definitions[i];
|
||||||
if (layout.FindElement(element) is not UiItemList list) continue;
|
if (layout.FindElement(element) is not UiItemList list) continue;
|
||||||
list.RegisterDragHandler(this);
|
list.RegisterDragHandler(this);
|
||||||
|
list.PrimaryItemPressed = PressItem;
|
||||||
list.ExamineItemRequested = ExamineItem;
|
list.ExamineItemRequested = ExamineItem;
|
||||||
list.Cell.SourceKind = ItemDragSource.Equipment;
|
list.Cell.SourceKind = ItemDragSource.Equipment;
|
||||||
list.Cell.SlotIndex = i; // definition position = equipped drag-payload SourceSlot
|
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)
|
&& emptySlotSprites.TryGetValue(element, out uint authoredSprite)
|
||||||
? authoredSprite
|
? authoredSprite
|
||||||
: emptySlotSprite;
|
: 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 = () =>
|
list.Cell.DoubleClicked = () =>
|
||||||
{
|
{
|
||||||
if (list.Cell.ItemId != 0)
|
if (list.Cell.ItemId != 0)
|
||||||
|
|
@ -357,6 +349,14 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
||||||
_itemInteraction.ExamineSelectedOrEnterMode(itemId);
|
_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>
|
/// <summary>Detach event handlers (idempotent).</summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
@ -369,7 +369,10 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
||||||
_objects.Cleared -= OnObjectsCleared;
|
_objects.Cleared -= OnObjectsCleared;
|
||||||
_selection.Changed -= OnSelectionChanged;
|
_selection.Changed -= OnSelectionChanged;
|
||||||
foreach (var (_, list) in _slots)
|
foreach (var (_, list) in _slots)
|
||||||
|
{
|
||||||
|
list.PrimaryItemPressed = null;
|
||||||
list.ExamineItemRequested = null;
|
list.ExamineItemRequested = null;
|
||||||
|
}
|
||||||
if (_dollViewport is UiViewport doll)
|
if (_dollViewport is UiViewport doll)
|
||||||
doll.ClickedAt = null;
|
doll.ClickedAt = null;
|
||||||
switch (_dollDragMask)
|
switch (_dollDragMask)
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
||||||
if (_slots[i] is { } list)
|
if (_slots[i] is { } list)
|
||||||
{
|
{
|
||||||
WireClick(list);
|
WireClick(list);
|
||||||
|
list.PrimaryItemPressed = PressItem;
|
||||||
list.ExamineItemRequested = ExamineItem;
|
list.ExamineItemRequested = ExamineItem;
|
||||||
// B.1 drag-drop spine: this controller is the drop handler for every
|
// 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
|
// 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)
|
private void ExamineItem(uint itemId)
|
||||||
{
|
{
|
||||||
if (_selectItem is not null)
|
if (_selectItem is not null)
|
||||||
|
|
@ -777,7 +791,10 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
||||||
_selection.Changed -= OnSelectionChanged;
|
_selection.Changed -= OnSelectionChanged;
|
||||||
foreach (UiItemList? list in _slots)
|
foreach (UiItemList? list in _slots)
|
||||||
if (list is not null)
|
if (list is not null)
|
||||||
|
{
|
||||||
|
list.PrimaryItemPressed = null;
|
||||||
list.ExamineItemRequested = null;
|
list.ExamineItemRequested = null;
|
||||||
|
}
|
||||||
_repo.ObjectAdded -= OnRepositoryObjectChanged;
|
_repo.ObjectAdded -= OnRepositoryObjectChanged;
|
||||||
_repo.ObjectUpdated -= OnRepositoryObjectChanged;
|
_repo.ObjectUpdated -= OnRepositoryObjectChanged;
|
||||||
_repo.ObjectRemoved -= OnRepositoryObjectChanged;
|
_repo.ObjectRemoved -= OnRepositoryObjectChanged;
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,20 @@ public sealed class UiItemList : UiElement
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<object, int, int>? CatalogDropped { get; set; }
|
public Action<object, int, int>? CatalogDropped { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Physical-item left-button-down command supplied by the owning panel.
|
||||||
|
/// Return <see langword="true"/> when a target mode consumed the press so
|
||||||
|
/// the slot suppresses drag promotion and the completed Click/DoubleClick
|
||||||
|
/// gesture.
|
||||||
|
///
|
||||||
|
/// Retail references:
|
||||||
|
/// <c>UIElement_ListBox::MouseDown @ 0x0046E3A0</c> selects the item under
|
||||||
|
/// the pointer during MouseDown, before optional drag selection;
|
||||||
|
/// <c>UIElement_ItemList::ListenToElementMessage @ 0x004E4D50</c> handles
|
||||||
|
/// target mode before ordinary physical-item selection.
|
||||||
|
/// </summary>
|
||||||
|
public Func<uint, bool>? PrimaryItemPressed { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Appraisal command supplied by the owning panel controller for physical
|
/// Appraisal command supplied by the owning panel controller for physical
|
||||||
/// item cells. Retail <c>UIElement_ItemList::ListenToElementMessage
|
/// item cells. Retail <c>UIElement_ItemList::ListenToElementMessage
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,7 @@ public class UiItemSlot : UiElement
|
||||||
|
|
||||||
private bool _waiting;
|
private bool _waiting;
|
||||||
internal bool WaitingVisual => _waiting;
|
internal bool WaitingVisual => _waiting;
|
||||||
|
private bool _primaryPressConsumed;
|
||||||
|
|
||||||
/// <summary>RenderSurface id -> (GL texture, w, h). Set by the factory/controller.</summary>
|
/// <summary>RenderSurface id -> (GL texture, w, h). Set by the factory/controller.</summary>
|
||||||
public Func<uint, (uint tex, int w, int h)>? SpriteResolve { get; set; }
|
public Func<uint, (uint tex, int w, int h)>? SpriteResolve { get; set; }
|
||||||
|
|
@ -134,11 +135,14 @@ public class UiItemSlot : UiElement
|
||||||
DragIconTexture = 0;
|
DragIconTexture = 0;
|
||||||
Shortcut = null;
|
Shortcut = null;
|
||||||
_waiting = false;
|
_waiting = false;
|
||||||
|
_primaryPressConsumed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override object? GetDragPayload()
|
public override object? GetDragPayload()
|
||||||
=> ItemId != 0 ? new ItemDragPayload(ItemId, SourceKind, SlotIndex, this, Shortcut) : null;
|
=> ItemId != 0 && !_primaryPressConsumed
|
||||||
|
? new ItemDragPayload(ItemId, SourceKind, SlotIndex, this, Shortcut)
|
||||||
|
: null;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override (uint tex, int w, int h)? GetDragGhost()
|
public override (uint tex, int w, int h)? GetDragGhost()
|
||||||
|
|
@ -251,8 +255,10 @@ public class UiItemSlot : UiElement
|
||||||
|
|
||||||
// ── Events / draw ─────────────────────────────────────────────────────────
|
// ── Events / draw ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// <summary>Invoked by <see cref="OnEvent"/> when a left-button-down lands on
|
/// <summary>Invoked by <see cref="OnEvent"/> when a completed left click lands
|
||||||
/// a bound slot. Wired by <c>ToolbarController</c> to the use-item callback.</summary>
|
/// on a bound slot. Selection already occurred on MouseDown through the owning
|
||||||
|
/// <see cref="UiItemList"/>; this callback performs release-time activation
|
||||||
|
/// such as opening a bag or using a shortcut.</summary>
|
||||||
public Action? Clicked { get; set; }
|
public Action? Clicked { get; set; }
|
||||||
|
|
||||||
/// <summary>Invoked by <see cref="OnEvent"/> when a double-click lands on a bound slot.</summary>
|
/// <summary>Invoked by <see cref="OnEvent"/> when a double-click lands on a bound slot.</summary>
|
||||||
|
|
@ -267,12 +273,21 @@ public class UiItemSlot : UiElement
|
||||||
// drag (press + >3px move) does NOT also use the item. UiRoot suppresses the
|
// drag (press + >3px move) does NOT also use the item. UiRoot suppresses the
|
||||||
// post-drag Click (UiRoot.cs FinishDrag returns before the Click emit).
|
// post-drag Click (UiRoot.cs FinishDrag returns before the Click emit).
|
||||||
case UiEventType.MouseDown:
|
case UiEventType.MouseDown:
|
||||||
return true; // consume the press; no use here
|
// UIElement_ListBox::MouseDown @ 0x0046E3A0 selects immediately,
|
||||||
|
// before StartDragSelect. UIElement_ItemList's notice handler
|
||||||
|
// intercepts target mode first. Activation remains release-time,
|
||||||
|
// so a press promoted to drag never also uses the item.
|
||||||
|
_primaryPressConsumed = ItemId != 0
|
||||||
|
&& FindList() is { PrimaryItemPressed: { } pressed }
|
||||||
|
&& pressed(ItemId);
|
||||||
|
return true;
|
||||||
case UiEventType.Click:
|
case UiEventType.Click:
|
||||||
Clicked?.Invoke();
|
if (!_primaryPressConsumed)
|
||||||
|
Clicked?.Invoke();
|
||||||
return true;
|
return true;
|
||||||
case UiEventType.DoubleClick:
|
case UiEventType.DoubleClick:
|
||||||
DoubleClicked?.Invoke();
|
if (!_primaryPressConsumed)
|
||||||
|
DoubleClicked?.Invoke();
|
||||||
return true;
|
return true;
|
||||||
case UiEventType.RightClick:
|
case UiEventType.RightClick:
|
||||||
if (ItemId != 0
|
if (ItemId != 0
|
||||||
|
|
|
||||||
|
|
@ -275,6 +275,61 @@ public class DragDropSpineTests
|
||||||
Assert.True(used);
|
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]
|
[Fact]
|
||||||
public void RightClick_withoutDrag_requestsItemListAppraisal()
|
public void RightClick_withoutDrag_requestsItemListAppraisal()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -199,6 +199,20 @@ public sealed class ExternalContainerControllerTests
|
||||||
Assert.Empty(h.Pickups);
|
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]
|
[Fact]
|
||||||
public void CloseAndRangeExit_SendUseOnceWithoutNoLongerViewing()
|
public void CloseAndRangeExit_SendUseOnceWithoutNoLongerViewing()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -460,7 +460,7 @@ public class InventoryControllerTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ClickGridItem_movesSquareOnly_noWire_keepsOpenContainer()
|
public void MouseDownGridItem_movesSquareImmediately_noWire_keepsOpenContainer()
|
||||||
{
|
{
|
||||||
var (layout, grid, _, top, _, _, _, _) = BuildLayout();
|
var (layout, grid, _, top, _, _, _, _) = BuildLayout();
|
||||||
var objects = new ClientObjectTable();
|
var objects = new ClientObjectTable();
|
||||||
|
|
@ -468,7 +468,8 @@ public class InventoryControllerTests
|
||||||
var uses = new List<uint>();
|
var uses = new List<uint>();
|
||||||
Bind(layout, objects, uses: uses);
|
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(grid.GetItem(0)!.Selected); // square on the selected grid item
|
||||||
Assert.True(top.GetItem(0)!.IsOpenContainer); // main pack still the open container (unchanged)
|
Assert.True(top.GetItem(0)!.IsOpenContainer); // main pack still the open container (unchanged)
|
||||||
|
|
@ -476,7 +477,7 @@ public class InventoryControllerTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ClickGridItem_updatesSharedSelection_andExternalSelectionMovesSquare()
|
public void MouseDownGridItem_updatesSharedSelection_andExternalSelectionMovesSquare()
|
||||||
{
|
{
|
||||||
var (layout, grid, _, _, _, _, _, _) = BuildLayout();
|
var (layout, grid, _, _, _, _, _, _) = BuildLayout();
|
||||||
var objects = new ClientObjectTable();
|
var objects = new ClientObjectTable();
|
||||||
|
|
@ -485,7 +486,8 @@ public class InventoryControllerTests
|
||||||
var selection = new SelectionState();
|
var selection = new SelectionState();
|
||||||
Bind(layout, objects, selection: selection);
|
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.Equal(0xAu, selection.SelectedObjectId);
|
||||||
Assert.True(grid.GetItem(0)!.Selected);
|
Assert.True(grid.GetItem(0)!.Selected);
|
||||||
|
|
@ -555,7 +557,8 @@ public class InventoryControllerTests
|
||||||
selection: new SelectionState(),
|
selection: new SelectionState(),
|
||||||
datFont: null,
|
datFont: null,
|
||||||
itemInteraction: interaction);
|
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(grid.GetItem(0)!.Selected);
|
||||||
|
|
||||||
Assert.True(interaction.ActivateItem(0xAu));
|
Assert.True(interaction.ActivateItem(0xAu));
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ public class PaperdollControllerTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ClickEquippedItem_updatesSharedSelection()
|
public void MouseDownEquippedItem_updatesSharedSelection()
|
||||||
{
|
{
|
||||||
var (layout, lists) = BuildLayout();
|
var (layout, lists) = BuildLayout();
|
||||||
var objects = new ClientObjectTable();
|
var objects = new ClientObjectTable();
|
||||||
|
|
@ -184,7 +184,8 @@ public class PaperdollControllerTests
|
||||||
var selection = new SelectionState();
|
var selection = new SelectionState();
|
||||||
Bind(layout, objects, selection: selection);
|
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.Equal(0xA01u, selection.SelectedObjectId);
|
||||||
Assert.True(lists[HeadSlot].Cell.Selected);
|
Assert.True(lists[HeadSlot].Cell.Selected);
|
||||||
|
|
|
||||||
|
|
@ -192,11 +192,19 @@ public class ToolbarControllerTests
|
||||||
var shortcuts = new List<ShortcutEntry>
|
var shortcuts = new List<ShortcutEntry>
|
||||||
{ new(Index: 0, ObjectId: 0x5001u, SpellId: 0) };
|
{ new(Index: 0, ObjectId: 0x5001u, SpellId: 0) };
|
||||||
uint used = 0;
|
uint used = 0;
|
||||||
|
var selection = new SelectionState();
|
||||||
|
|
||||||
ToolbarController.Bind(layout, repo, () => shortcuts,
|
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.
|
// 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);
|
Assert.Equal(0x5001u, used);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue