feat(ui): drive toolbar use state from selection

Port the retail selected-object availability predicate into Core and project it through the shared interaction owner. The imported hand now follows canonical selection/object notices, keeps weapon and targeted-tool activation on the existing wield/use cursor paths, and ghosts empty or explicitly unusable selections per the connected UX requirement.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-23 08:07:47 +02:00
parent 0134122c28
commit 60387668d0
11 changed files with 269 additions and 16 deletions

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using AcDream.Core.Combat;
using AcDream.Core.Items;
using AcDream.Core.Net.Messages;
using AcDream.Core.Selection;
namespace AcDream.App.UI.Layout;
@ -73,6 +74,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
private readonly ItemInteractionController? _itemInteraction;
private readonly Action<uint>? _selectItem;
private readonly Func<uint> _selectedObjectId;
private readonly SelectionState? _selection;
private readonly Func<uint>? _playerGuid;
private readonly Action<uint, uint, int>? _sendPutItemInContainer;
private uint _ammoObjectId;
@ -106,6 +108,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
Action? toggleCombat = null,
Action<uint>? selectItem = null,
Func<uint>? selectedObjectId = null,
SelectionState? selection = null,
Func<uint>? playerGuid = null,
Action<uint, uint, int>? sendPutItemInContainer = null,
UiDatFont? ammoFont = null,
@ -125,6 +128,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
_sendRemoveShortcut = sendRemoveShortcut;
_selectItem = selectItem;
_selectedObjectId = selectedObjectId ?? (() => 0u);
_selection = selection;
_playerGuid = playerGuid;
_sendPutItemInContainer = sendPutItemInContainer;
@ -196,6 +200,8 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
// Wire live combat-mode changes if a CombatState was provided.
if (_combatState is not null)
_combatState.CombatModeChanged += SetCombatMode;
if (_selection is not null)
_selection.Changed += OnSelectionChanged;
// D.5.4: the table now holds ALL objects (creatures, NPCs, etc.), so filter
// to our 18 shortcut guids — else every creature spawn in a busy zone
@ -206,6 +212,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
repo.ObjectMoved += OnRepositoryObjectMoved;
repo.Cleared += OnRepositoryCleared;
RefreshAmmo();
RefreshUseButton();
}
private void OnRepositoryObjectChanged(ClientObject obj)
@ -214,6 +221,8 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
RefreshAmmo();
if (IsShortcutGuid(obj.ObjectId))
Populate();
if (obj.ObjectId == _selectedObjectId())
RefreshUseButton();
}
private void OnRepositoryObjectMoved(ClientObjectMove move)
@ -234,6 +243,23 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
{
RefreshAmmo();
Populate();
RefreshUseButton();
}
private void OnSelectionChanged(SelectionTransition _)
=> RefreshUseButton();
private void RefreshUseButton()
{
if (_useButton is null)
return;
// Retail normally leaves state 1 active when selectedID == 0 so the
// click can enter generic TARGET_MODE_USE. acdream intentionally
// ghosts that empty state per the connected UX requirement; every
// selected-object classification below is the exact retail predicate.
_useButton.Enabled =
_itemInteraction?.IsToolbarUseEnabled(_selectedObjectId()) == true;
}
private bool IsAmmoRelated(ClientObject obj)
@ -330,6 +356,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
Action? toggleCombat = null,
Action<uint>? selectItem = null,
Func<uint>? selectedObjectId = null,
SelectionState? selection = null,
Func<uint>? playerGuid = null,
Action<uint, uint, int>? sendPutItemInContainer = null,
UiDatFont? ammoFont = null,
@ -338,7 +365,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
var c = new ToolbarController(layout, repo, shortcuts, iconIds, useItem, combatState,
regularDigits, ghostedDigits, emptyDigits, itemInteraction,
sendAddShortcut, sendRemoveShortcut, toggleCombat, selectItem,
selectedObjectId, playerGuid, sendPutItemInContainer, ammoFont,
selectedObjectId, selection, playerGuid, sendPutItemInContainer, ammoFont,
dragIconIds);
c.Populate();
return c;
@ -736,6 +763,8 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
if (_combatState is not null)
_combatState.CombatModeChanged -= SetCombatMode;
if (_selection is not null)
_selection.Changed -= OnSelectionChanged;
_repo.ObjectAdded -= OnRepositoryObjectChanged;
_repo.ObjectUpdated -= OnRepositoryObjectChanged;
_repo.ObjectRemoved -= OnRepositoryObjectChanged;