feat(ui): centralize retail selection state
This commit is contained in:
parent
c7607f019c
commit
7983309d23
30 changed files with 591 additions and 108 deletions
106
src/AcDream.Core/Selection/SelectionState.cs
Normal file
106
src/AcDream.Core/Selection/SelectionState.cs
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
using AcDream.Plugin.Abstractions;
|
||||
|
||||
namespace AcDream.Core.Selection;
|
||||
|
||||
public enum SelectionChangeSource
|
||||
{
|
||||
System,
|
||||
World,
|
||||
Radar,
|
||||
Inventory,
|
||||
Paperdoll,
|
||||
Toolbar,
|
||||
Keyboard,
|
||||
Plugin,
|
||||
}
|
||||
|
||||
public enum SelectionChangeReason
|
||||
{
|
||||
Selected,
|
||||
Cleared,
|
||||
SelectedObjectRemoved,
|
||||
PreviousSelection,
|
||||
}
|
||||
|
||||
public readonly record struct SelectionTransition(
|
||||
uint? PreviousObjectId,
|
||||
uint? SelectedObjectId,
|
||||
SelectionChangeSource Source,
|
||||
SelectionChangeReason Reason);
|
||||
|
||||
/// <summary>
|
||||
/// Single owner for the client's selected object. This ports the state behavior
|
||||
/// of retail <c>ACCWeenieObject::SetSelectedObject @ 0x0058C2E0</c>: changes are
|
||||
/// deduplicated, the old selection becomes the previous selection, and plugin/UI
|
||||
/// listeners receive one transition after state is committed.
|
||||
/// </summary>
|
||||
public sealed class SelectionState : ISelectionService
|
||||
{
|
||||
private event Action<SelectionChangedEvent>? PluginChanged;
|
||||
|
||||
public uint? SelectedObjectId { get; private set; }
|
||||
public uint? PreviousObjectId { get; private set; }
|
||||
public uint? PreviousValidObjectId { get; private set; }
|
||||
|
||||
public event Action<SelectionTransition>? Changed;
|
||||
|
||||
event Action<SelectionChangedEvent> ISelectionService.Changed
|
||||
{
|
||||
add => PluginChanged += value;
|
||||
remove => PluginChanged -= value;
|
||||
}
|
||||
|
||||
public bool Select(
|
||||
uint objectId,
|
||||
SelectionChangeSource source,
|
||||
SelectionChangeReason reason = SelectionChangeReason.Selected)
|
||||
=> objectId == 0
|
||||
? Clear(source)
|
||||
: Set(objectId, source, reason);
|
||||
|
||||
public bool Clear(
|
||||
SelectionChangeSource source,
|
||||
SelectionChangeReason reason = SelectionChangeReason.Cleared)
|
||||
=> Set(null, source, reason);
|
||||
|
||||
public bool SelectPrevious(SelectionChangeSource source = SelectionChangeSource.Keyboard)
|
||||
=> PreviousObjectId is uint previous
|
||||
&& Set(previous, source, SelectionChangeReason.PreviousSelection);
|
||||
|
||||
bool ISelectionService.Select(uint objectId)
|
||||
=> Select(objectId, SelectionChangeSource.Plugin);
|
||||
|
||||
bool ISelectionService.Clear()
|
||||
=> Clear(SelectionChangeSource.Plugin);
|
||||
|
||||
private bool Set(
|
||||
uint? selectedObjectId,
|
||||
SelectionChangeSource source,
|
||||
SelectionChangeReason reason)
|
||||
{
|
||||
if (SelectedObjectId == selectedObjectId)
|
||||
return false;
|
||||
|
||||
uint? old = SelectedObjectId;
|
||||
SelectedObjectId = selectedObjectId;
|
||||
PreviousObjectId = old;
|
||||
if (old is not null)
|
||||
PreviousValidObjectId = old;
|
||||
|
||||
var transition = new SelectionTransition(old, selectedObjectId, source, reason);
|
||||
Changed?.Invoke(transition);
|
||||
DispatchPluginChanged(new SelectionChangedEvent(old, selectedObjectId));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void DispatchPluginChanged(SelectionChangedEvent change)
|
||||
{
|
||||
Action<SelectionChangedEvent>? listeners = PluginChanged;
|
||||
if (listeners is null) return;
|
||||
foreach (Action<SelectionChangedEvent> listener in listeners.GetInvocationList())
|
||||
{
|
||||
try { listener(change); }
|
||||
catch { /* plugin exceptions never escape into client state changes */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue