feat(ui): port retail component book rows
Instantiate gmSpellComponentUI category and component entries from LayoutDesc 0x21000033 instead of synthesizing catalog rows. Preserve localized category titles, authored textures and geometry, live icon/count/desired bindings, selected-object synchronization, and retail's inclusive 0..5000 desired-level contract. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
15bb61e05d
commit
a1175e6aac
13 changed files with 2395 additions and 60 deletions
188
src/AcDream.App/UI/Layout/ComponentBookTemplateFactory.cs
Normal file
188
src/AcDream.App/UI/Layout/ComponentBookTemplateFactory.cs
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
using DatReaderWriter;
|
||||
|
||||
namespace AcDream.App.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates retail's category and component listbox templates from LayoutDesc
|
||||
/// 0x21000033. This is the retained equivalent of
|
||||
/// <c>UIElement_ListBox::AddItemFromTemplateList</c> used by
|
||||
/// <c>gmSpellComponentUI::UpdateComponents @ 0x0048A910</c>.
|
||||
/// </summary>
|
||||
public sealed class ComponentBookTemplateFactory
|
||||
{
|
||||
public const uint LayoutId = 0x21000033u;
|
||||
public const uint CategoryTemplateId = 0x10000466u;
|
||||
public const uint ComponentTemplateId = 0x10000467u;
|
||||
public const uint IconId = 0x10000468u;
|
||||
public const uint NameId = 0x10000469u;
|
||||
public const uint OwnedCountId = 0x1000046Au;
|
||||
public const uint DesiredCountId = 0x1000046Bu;
|
||||
|
||||
private const uint LocalStringTableId = 0x23000001u;
|
||||
private const uint HighlightState = UiButtonStateMachine.Highlight;
|
||||
|
||||
private static readonly (string Key, string Fallback)[] CategoryStrings =
|
||||
[
|
||||
("ID_SpellComp_Category_Scarabs", "SCARABS"),
|
||||
("ID_SpellComp_Category_Herbs", "HERBS"),
|
||||
("ID_SpellComp_Category_Gems", "POWDERED GEMS"),
|
||||
("ID_SpellComp_Category_Alchemical", "ALCHEMICAL SUBSTANCES"),
|
||||
("ID_SpellComp_Category_Talismans", "TALISMANS"),
|
||||
("ID_SpellComp_Category_Tapers", "TAPERS"),
|
||||
("ID_SpellComp_Category_Peas", "PEAS"),
|
||||
];
|
||||
|
||||
private readonly ElementInfo _categoryTemplate;
|
||||
private readonly ElementInfo _componentTemplate;
|
||||
private readonly Func<uint, (uint tex, int w, int h)> _resolveSprite;
|
||||
private readonly UiDatFont? _defaultFont;
|
||||
private readonly IReadOnlyDictionary<uint, UiDatFont?> _fonts;
|
||||
private readonly string[] _categoryNames;
|
||||
|
||||
public ComponentBookTemplateFactory(
|
||||
ElementInfo categoryTemplate,
|
||||
ElementInfo componentTemplate,
|
||||
Func<uint, (uint tex, int w, int h)> resolveSprite,
|
||||
UiDatFont? defaultFont,
|
||||
IReadOnlyDictionary<uint, UiDatFont?>? fonts = null,
|
||||
IReadOnlyList<string>? categoryNames = null)
|
||||
{
|
||||
_categoryTemplate = categoryTemplate;
|
||||
_componentTemplate = componentTemplate;
|
||||
_resolveSprite = resolveSprite;
|
||||
_defaultFont = defaultFont;
|
||||
_fonts = fonts ?? new Dictionary<uint, UiDatFont?>();
|
||||
_categoryNames = categoryNames?.ToArray()
|
||||
?? CategoryStrings.Select(value => value.Fallback).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads and resolves the two template definitions while the caller holds the
|
||||
/// shared DAT lock. Fonts and localized category strings are captured eagerly,
|
||||
/// so later inventory refreshes instantiate rows without reading DAT files.
|
||||
/// </summary>
|
||||
public static ComponentBookTemplateFactory? TryLoad(
|
||||
DatCollection dats,
|
||||
Func<uint, (uint tex, int w, int h)> resolveSprite,
|
||||
UiDatFont? defaultFont,
|
||||
Func<uint, UiDatFont?>? resolveFont)
|
||||
{
|
||||
ElementInfo? category = LayoutImporter.ImportInfos(
|
||||
dats, LayoutId, CategoryTemplateId);
|
||||
ElementInfo? component = LayoutImporter.ImportInfos(
|
||||
dats, LayoutId, ComponentTemplateId);
|
||||
if (category is null || component is null)
|
||||
return null;
|
||||
|
||||
var fonts = new Dictionary<uint, UiDatFont?>();
|
||||
CaptureFonts(category, resolveFont, fonts);
|
||||
CaptureFonts(component, resolveFont, fonts);
|
||||
|
||||
var strings = new DatStringResolver(dats);
|
||||
string[] categoryNames = CategoryStrings
|
||||
.Select(value => strings.Resolve(
|
||||
LocalStringTableId,
|
||||
DatStringResolver.ComputeHash(value.Key))
|
||||
?? value.Fallback)
|
||||
.ToArray();
|
||||
|
||||
return new ComponentBookTemplateFactory(
|
||||
category, component, resolveSprite, defaultFont, fonts, categoryNames);
|
||||
}
|
||||
|
||||
public UiTemplateListSlot CreateCategoryRow(uint category)
|
||||
{
|
||||
ImportedLayout content = Build(_categoryTemplate);
|
||||
if (content.Root is not UiText title)
|
||||
throw new InvalidOperationException(
|
||||
"Retail component category template did not resolve to UIElement_Text.");
|
||||
|
||||
string label = category < _categoryNames.Length
|
||||
? _categoryNames[category]
|
||||
: "OTHER COMPONENTS";
|
||||
title.LinesProvider = () => [new UiText.Line(label, title.DefaultColor)];
|
||||
|
||||
return new UiTemplateListSlot(
|
||||
content, uint.MaxValue, content.Root is IUiDatStateful stateful
|
||||
? stateful.ActiveRetailStateId
|
||||
: UiStateInfo.DirectStateId, selectedState: null);
|
||||
}
|
||||
|
||||
public ComponentRow CreateComponentRow(
|
||||
uint componentId,
|
||||
uint iconTexture,
|
||||
string name,
|
||||
int ownedCount,
|
||||
uint desiredCount)
|
||||
{
|
||||
ImportedLayout content = Build(_componentTemplate);
|
||||
UiElement iconHost = Required(content, IconId);
|
||||
UiText nameText = Required<UiText>(content, NameId);
|
||||
UiText ownedText = Required<UiText>(content, OwnedCountId);
|
||||
UiField desiredField = Required<UiField>(content, DesiredCountId);
|
||||
|
||||
var icon = new UiTextureElement
|
||||
{
|
||||
Width = iconHost.Width,
|
||||
Height = iconHost.Height,
|
||||
Anchors = AnchorEdges.Left | AnchorEdges.Top
|
||||
| AnchorEdges.Right | AnchorEdges.Bottom,
|
||||
Texture = iconTexture,
|
||||
};
|
||||
iconHost.AddChild(icon);
|
||||
|
||||
nameText.LinesProvider = () => [new UiText.Line(name, nameText.DefaultColor)];
|
||||
ownedText.LinesProvider = () =>
|
||||
[new UiText.Line(ownedCount.ToString(System.Globalization.CultureInfo.InvariantCulture),
|
||||
ownedText.DefaultColor)];
|
||||
|
||||
// NumberInputFilter + the 0..5000 validation are installed by the controller,
|
||||
// matching gmSpellComponentUI::ListenToElementMessage @ 0x0048A420.
|
||||
desiredField.ClearOnSubmit = false;
|
||||
desiredField.RecordHistory = false;
|
||||
desiredField.SelectAllOnFocus = true;
|
||||
desiredField.CharacterFilter = char.IsAsciiDigit;
|
||||
desiredField.SetText(desiredCount.ToString(
|
||||
System.Globalization.CultureInfo.InvariantCulture));
|
||||
|
||||
uint normalState = content.Root is IUiDatStateful stateful
|
||||
? stateful.ActiveRetailStateId
|
||||
: UiStateInfo.DirectStateId;
|
||||
var slot = new UiTemplateListSlot(
|
||||
content, componentId, normalState, HighlightState);
|
||||
return new ComponentRow(slot, desiredField);
|
||||
}
|
||||
|
||||
private ImportedLayout Build(ElementInfo template)
|
||||
=> LayoutImporter.Build(
|
||||
template,
|
||||
_resolveSprite,
|
||||
_defaultFont,
|
||||
did => _fonts.TryGetValue(did, out UiDatFont? font) ? font : _defaultFont);
|
||||
|
||||
private static T Required<T>(ImportedLayout content, uint id)
|
||||
where T : UiElement
|
||||
=> content.FindElement(id) as T
|
||||
?? throw new InvalidOperationException(
|
||||
$"Retail component template element 0x{id:X8} did not resolve to {typeof(T).Name}.");
|
||||
|
||||
private static UiElement Required(ImportedLayout content, uint id)
|
||||
=> content.FindElement(id)
|
||||
?? throw new InvalidOperationException(
|
||||
$"Retail component template element 0x{id:X8} is missing.");
|
||||
|
||||
private static void CaptureFonts(
|
||||
ElementInfo info,
|
||||
Func<uint, UiDatFont?>? resolveFont,
|
||||
Dictionary<uint, UiDatFont?> fonts)
|
||||
{
|
||||
if (info.FontDid != 0u && !fonts.ContainsKey(info.FontDid))
|
||||
fonts[info.FontDid] = resolveFont?.Invoke(info.FontDid);
|
||||
foreach (ElementInfo child in info.Children)
|
||||
CaptureFonts(child, resolveFont, fonts);
|
||||
}
|
||||
|
||||
public readonly record struct ComponentRow(
|
||||
UiTemplateListSlot Slot,
|
||||
UiField DesiredField);
|
||||
}
|
||||
|
|
@ -2,9 +2,9 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Spells;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Selection;
|
||||
using AcDream.Core.Spells;
|
||||
|
||||
namespace AcDream.App.UI.Layout;
|
||||
|
|
@ -48,6 +48,7 @@ public sealed class SpellbookWindowController : IRetainedPanelController
|
|||
private readonly ClientObjectTable _objects;
|
||||
private readonly Func<uint> _playerGuid;
|
||||
private readonly IReadOnlyDictionary<uint, SpellComponentDescriptor> _components;
|
||||
private readonly SelectionState _selection;
|
||||
private readonly Func<uint, uint> _resolveSpellIcon;
|
||||
private readonly Func<uint, uint> _resolveComponentIcon;
|
||||
private readonly Func<uint, int> _spellLevel;
|
||||
|
|
@ -66,10 +67,12 @@ public sealed class SpellbookWindowController : IRetainedPanelController
|
|||
private readonly UiButton _deleteButton;
|
||||
private readonly UiItemList _spellList;
|
||||
private readonly UiItemList _componentList;
|
||||
private readonly ComponentBookTemplateFactory _componentTemplates;
|
||||
private readonly List<(UiButton Button, uint Mask)> _filters = new();
|
||||
private readonly SpellbookRowStyle _rowStyle;
|
||||
private readonly UiDatFont? _rowFont;
|
||||
private uint? _selectedSpell;
|
||||
private uint? _selectedComponent;
|
||||
private bool _spellsDirty;
|
||||
private bool _componentsDirty;
|
||||
private bool _componentEditActive;
|
||||
|
|
@ -81,6 +84,7 @@ public sealed class SpellbookWindowController : IRetainedPanelController
|
|||
ClientObjectTable objects,
|
||||
Func<uint> playerGuid,
|
||||
IReadOnlyDictionary<uint, SpellComponentDescriptor> components,
|
||||
SelectionState selection,
|
||||
Func<uint, uint> resolveSpellIcon,
|
||||
Func<uint, uint> resolveComponentIcon,
|
||||
Func<uint, int> spellLevel,
|
||||
|
|
@ -99,6 +103,7 @@ public sealed class SpellbookWindowController : IRetainedPanelController
|
|||
UiButton deleteButton,
|
||||
UiItemList spellList,
|
||||
UiItemList componentList,
|
||||
ComponentBookTemplateFactory componentTemplates,
|
||||
SpellbookRowStyle rowStyle,
|
||||
UiDatFont? rowFont)
|
||||
{
|
||||
|
|
@ -106,6 +111,7 @@ public sealed class SpellbookWindowController : IRetainedPanelController
|
|||
_objects = objects;
|
||||
_playerGuid = playerGuid;
|
||||
_components = components;
|
||||
_selection = selection;
|
||||
_resolveSpellIcon = resolveSpellIcon;
|
||||
_resolveComponentIcon = resolveComponentIcon;
|
||||
_spellLevel = spellLevel;
|
||||
|
|
@ -124,6 +130,7 @@ public sealed class SpellbookWindowController : IRetainedPanelController
|
|||
_deleteButton = deleteButton;
|
||||
_spellList = spellList;
|
||||
_componentList = componentList;
|
||||
_componentTemplates = componentTemplates;
|
||||
_rowStyle = rowStyle;
|
||||
_rowFont = rowFont;
|
||||
|
||||
|
|
@ -148,6 +155,7 @@ public sealed class SpellbookWindowController : IRetainedPanelController
|
|||
_objects.ObjectRemoved += OnObjectRemoved;
|
||||
_objects.ObjectMoved += OnObjectMoved;
|
||||
_objects.ContainerContentsReplaced += OnContainerContentsReplaced;
|
||||
_selection.Changed += OnSelectionChanged;
|
||||
ShowPage(SpellbookWindowPage.Spells);
|
||||
RebuildAll();
|
||||
}
|
||||
|
|
@ -160,6 +168,7 @@ public sealed class SpellbookWindowController : IRetainedPanelController
|
|||
ClientObjectTable objects,
|
||||
Func<uint> playerGuid,
|
||||
IReadOnlyDictionary<uint, SpellComponentDescriptor> components,
|
||||
SelectionState selection,
|
||||
Func<uint, uint> resolveSpellIcon,
|
||||
Func<uint, uint> resolveComponentIcon,
|
||||
Func<uint, int> spellLevel,
|
||||
|
|
@ -170,6 +179,7 @@ public sealed class SpellbookWindowController : IRetainedPanelController
|
|||
Action<string, Action<bool>> showConfirmation,
|
||||
Action<uint, uint> setDesiredComponent,
|
||||
Action close,
|
||||
ComponentBookTemplateFactory componentTemplates,
|
||||
SpellbookRowStyle rowStyle,
|
||||
UiDatFont? rowFont)
|
||||
{
|
||||
|
|
@ -199,13 +209,14 @@ public sealed class SpellbookWindowController : IRetainedPanelController
|
|||
}
|
||||
|
||||
return new SpellbookWindowController(
|
||||
layout, spellbook, objects, playerGuid, components,
|
||||
layout, spellbook, objects, playerGuid, components, selection,
|
||||
resolveSpellIcon, resolveComponentIcon,
|
||||
spellLevel, selectObject,
|
||||
addFavorite, sendFilter, removeSpell, showConfirmation,
|
||||
setDesiredComponent, close,
|
||||
spellPage, componentPage, spellTab, componentTab, closeButton,
|
||||
deleteButton, spellList, componentList, rowStyle, rowFont);
|
||||
deleteButton, spellList, componentList, componentTemplates,
|
||||
rowStyle, rowFont);
|
||||
}
|
||||
|
||||
public void ShowPage(SpellbookWindowPage page)
|
||||
|
|
@ -337,40 +348,29 @@ public sealed class SpellbookWindowController : IRetainedPanelController
|
|||
if (category != nextCategory)
|
||||
{
|
||||
category = nextCategory;
|
||||
_componentList.AddItem(new UiCatalogSlot
|
||||
UiTemplateListSlot categoryRow =
|
||||
_componentTemplates.CreateCategoryRow(nextCategory);
|
||||
categoryRow.Clicked = () =>
|
||||
{
|
||||
EntryId = uint.MaxValue,
|
||||
Label = ComponentCategoryName(nextCategory),
|
||||
ShowLabel = true,
|
||||
SpriteResolve = _componentList.SpriteResolve,
|
||||
});
|
||||
_selectedComponent = null;
|
||||
SyncComponentSelection();
|
||||
_selectObject(0u);
|
||||
};
|
||||
_componentList.AddItem(categoryRow);
|
||||
}
|
||||
uint desired = _spellbook.DesiredComponents.TryGetValue(componentId, out uint amount) ? amount : 0u;
|
||||
var row = new UiCatalogSlot
|
||||
{
|
||||
EntryId = componentId,
|
||||
CatalogIconTexture = _resolveComponentIcon(
|
||||
pack.IconId != 0 ? pack.IconId : descriptor?.IconId ?? 0u),
|
||||
Label = string.IsNullOrWhiteSpace(pack.Name) ? descriptor?.Name ?? $"Component {componentId}" : pack.Name,
|
||||
Detail = packGroup.Quantity.ToString(CultureInfo.InvariantCulture),
|
||||
ShowLabel = true,
|
||||
SpriteResolve = _componentList.SpriteResolve,
|
||||
};
|
||||
var desiredField = new UiField
|
||||
{
|
||||
Left = width - 47f,
|
||||
Top = 4f,
|
||||
Width = 43f,
|
||||
Height = 24f,
|
||||
MaxCharacters = 4,
|
||||
ClearOnSubmit = false,
|
||||
RecordHistory = false,
|
||||
SelectAllOnFocus = true,
|
||||
RightAligned = true,
|
||||
CharacterFilter = char.IsAsciiDigit,
|
||||
BackgroundColor = new Vector4(0f, 0f, 0f, 0.65f),
|
||||
};
|
||||
desiredField.SetText(desired.ToString(CultureInfo.InvariantCulture));
|
||||
string componentName = string.IsNullOrWhiteSpace(pack.Name)
|
||||
? descriptor?.Name ?? $"Component {componentId}"
|
||||
: pack.Name;
|
||||
ComponentBookTemplateFactory.ComponentRow row =
|
||||
_componentTemplates.CreateComponentRow(
|
||||
componentId,
|
||||
_resolveComponentIcon(
|
||||
pack.IconId != 0 ? pack.IconId : descriptor?.IconId ?? 0u),
|
||||
componentName,
|
||||
packGroup.Quantity,
|
||||
desired);
|
||||
UiField desiredField = row.DesiredField;
|
||||
desiredField.OnFocusGained = () => _componentEditActive = true;
|
||||
uint committed = desired;
|
||||
void Commit(string value)
|
||||
|
|
@ -392,25 +392,18 @@ public sealed class SpellbookWindowController : IRetainedPanelController
|
|||
Commit(value);
|
||||
_componentEditActive = false;
|
||||
};
|
||||
row.AddChild(desiredField);
|
||||
row.Clicked = () => _selectObject(pack.ObjectId);
|
||||
_componentList.AddItem(row);
|
||||
row.Slot.Clicked = () =>
|
||||
{
|
||||
_selectedComponent = componentId;
|
||||
SyncComponentSelection();
|
||||
_selectObject(pack.ObjectId);
|
||||
};
|
||||
_componentList.AddItem(row.Slot);
|
||||
}
|
||||
}
|
||||
SyncComponentSelectionFromWorld();
|
||||
}
|
||||
|
||||
private static string ComponentCategoryName(uint category) => category switch
|
||||
{
|
||||
0u => "Scarabs",
|
||||
1u => "Herbs",
|
||||
2u => "Powdered Gems",
|
||||
3u => "Alchemical Substances",
|
||||
4u => "Talismans",
|
||||
5u => "Tapers",
|
||||
6u => "Prismatic Components",
|
||||
_ => "Other Components",
|
||||
};
|
||||
|
||||
private bool IsOwnedByPlayer(ClientObject item, uint playerGuid)
|
||||
{
|
||||
if (playerGuid == 0) return item.ContainerId != 0 || item.WielderId != 0;
|
||||
|
|
@ -479,6 +472,23 @@ public sealed class SpellbookWindowController : IRetainedPanelController
|
|||
slot.Selected = slot.EntryId == _selectedSpell;
|
||||
}
|
||||
|
||||
private void SyncComponentSelection()
|
||||
{
|
||||
for (int i = 0; i < _componentList.GetNumUIItems(); i++)
|
||||
if (_componentList.GetItem(i) is UiTemplateListSlot slot)
|
||||
slot.SetSelected(slot.EntryId == _selectedComponent);
|
||||
}
|
||||
|
||||
private void SyncComponentSelectionFromWorld()
|
||||
{
|
||||
_selectedComponent = _selection.SelectedObjectId is uint selected
|
||||
&& _objects.Get(selected) is { } item
|
||||
&& IsComponent(item)
|
||||
? item.WeenieClassId
|
||||
: null;
|
||||
SyncComponentSelection();
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if (_spellsDirty)
|
||||
|
|
@ -505,6 +515,7 @@ public sealed class SpellbookWindowController : IRetainedPanelController
|
|||
}
|
||||
private void OnObjectMoved(ClientObjectMove _) => _componentsDirty = true;
|
||||
private void OnContainerContentsReplaced(uint _) => _componentsDirty = true;
|
||||
private void OnSelectionChanged(SelectionTransition _) => SyncComponentSelectionFromWorld();
|
||||
private bool IsComponent(ClientObject item)
|
||||
=> item.IsComponentPack || _components.ContainsKey(item.WeenieClassId);
|
||||
public void OnShown() => RebuildAll();
|
||||
|
|
@ -520,6 +531,7 @@ public sealed class SpellbookWindowController : IRetainedPanelController
|
|||
_objects.ObjectRemoved -= OnObjectRemoved;
|
||||
_objects.ObjectMoved -= OnObjectMoved;
|
||||
_objects.ContainerContentsReplaced -= OnContainerContentsReplaced;
|
||||
_selection.Changed -= OnSelectionChanged;
|
||||
RetailTabBinding.SetClick(_spellTab, null);
|
||||
RetailTabBinding.SetClick(_componentTab, null);
|
||||
_closeButton.OnClick = null;
|
||||
|
|
|
|||
72
src/AcDream.App/UI/Layout/UiTemplateListSlot.cs
Normal file
72
src/AcDream.App/UI/Layout/UiTemplateListSlot.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
namespace AcDream.App.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// One runtime listbox entry instantiated from an authored LayoutDesc template.
|
||||
/// Retail <c>UIElement_ListBox::AddItemFromTemplateList</c> creates exactly this
|
||||
/// ownership shape: the list owns the entry while the template owns its visuals.
|
||||
/// </summary>
|
||||
public sealed class UiTemplateListSlot : UiItemSlot
|
||||
{
|
||||
private readonly IUiDatStateful? _statefulRoot;
|
||||
private readonly uint _normalState;
|
||||
private readonly uint? _selectedState;
|
||||
private bool? _stateWasSelected;
|
||||
|
||||
public UiTemplateListSlot(
|
||||
ImportedLayout content,
|
||||
uint entryId,
|
||||
uint normalState,
|
||||
uint? selectedState)
|
||||
{
|
||||
Content = content;
|
||||
EntryId = entryId;
|
||||
_normalState = normalState;
|
||||
_selectedState = selectedState;
|
||||
_statefulRoot = content.Root as IUiDatStateful;
|
||||
|
||||
Width = content.Root.Width;
|
||||
Height = content.Root.Height;
|
||||
content.Root.Left = 0f;
|
||||
content.Root.Top = 0f;
|
||||
content.Root.Anchors = AnchorEdges.Left | AnchorEdges.Top
|
||||
| AnchorEdges.Right | AnchorEdges.Bottom;
|
||||
AddChild(content.Root);
|
||||
}
|
||||
|
||||
public ImportedLayout Content { get; }
|
||||
public uint EntryId { get; }
|
||||
public new Action? Clicked { get; set; }
|
||||
|
||||
public override bool HandlesClick => Clicked is not null;
|
||||
|
||||
public void SetSelected(bool selected)
|
||||
{
|
||||
Selected = selected;
|
||||
ApplySelectionState();
|
||||
}
|
||||
|
||||
public override bool OnEvent(in UiEvent e)
|
||||
{
|
||||
if (e.Type == UiEventType.MouseDown)
|
||||
return true;
|
||||
if (e.Type == UiEventType.Click && Clicked is not null)
|
||||
{
|
||||
Clicked();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnDraw(UiRenderContext ctx)
|
||||
=> ApplySelectionState();
|
||||
|
||||
private void ApplySelectionState()
|
||||
{
|
||||
if (_stateWasSelected == Selected)
|
||||
return;
|
||||
|
||||
_stateWasSelected = Selected;
|
||||
_statefulRoot?.TrySetRetailState(
|
||||
Selected && _selectedState.HasValue ? _selectedState.Value : _normalState);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue