fix(ui): restore retail vitals and window interactions
All checks were successful
CI / linux-portable (push) Successful in 3m16s
CI / windows-gate (push) Successful in 5m41s
CI / release (push) Successful in 2m5s

This commit is contained in:
Erik 2026-08-20 13:26:35 +02:00
parent 4d84456c21
commit 1bd2b30291
36 changed files with 1462 additions and 86 deletions

View file

@ -308,6 +308,15 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
/// </summary>
public bool SuppressSelfToggle { get; set; }
/// <summary>
/// Gives the owning controller exclusive responsibility for the rendered
/// state. Automatic pointer, selection, and enabled-state transitions no
/// longer replace <see cref="ActiveState"/>; input and click delivery remain
/// unchanged. This is used by buttons such as the radar UI lock whose face
/// represents durable application state rather than momentary interaction.
/// </summary>
public bool ControllerOwnsVisualState { get; set; }
public bool Selected
{
get => _selected;
@ -509,14 +518,15 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
/// <summary>
/// Retail's SetState media rule (<c>UIElement::SetState @0x00464E70</c>
/// tail, the <c>m_media.m_num != 0</c> gate @0x004651c0): a committed
/// state replaces the playing media ONLY when its media array is
/// non-empty — an authored empty-media state keeps the PREVIOUS media
/// (why an empty <c>Normal_pressed</c> never blanks a Normal-art
/// button), while an authored File=0 draw-nothing image counts as media
/// and clears the face (#416: the roster-row bar children's base
/// state). An UNAUTHORED committed state runs retail's state-0 arm
/// against the base media array. Face segments model retail's
/// tail plus <c>MediaMachine::Update_Image @0x00465870</c>): a committed
/// state replaces the playing IMAGE only when its effective media array
/// contains image media. An empty state—or a cursor/sound/message-only
/// state—keeps the PREVIOUS image (why the toolbar inventory button's
/// inherited non-image <c>Normal_pressed</c> media never blanks its closed
/// backpack art), while an authored File=0 draw-nothing image clears the
/// face (#416: the roster-row bar children's base state). An UNAUTHORED
/// committed state runs retail's state-0 arm against the base media array.
/// Face segments model retail's
/// PassToChildren children, so each segment resolves the rule against
/// its OWN authored states. Synced lazily on the first draw after any
/// <see cref="ActiveState"/> write so every commit path (the visual
@ -553,7 +563,7 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
string current)
{
if (info.States.TryGetValue(committedId, out UiStateInfo? state))
return state.MediaCount != 0 ? committedName : current;
return HasImageMedia(info, state, committedName) ? committedName : current;
// Synthetic/test infos may carry StateMedia without States entries;
// a drawable entry for the committed name counts as authored media.
if (info.StateMedia.ContainsKey(committedName))
@ -562,10 +572,18 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
// otherwise the previous media keeps playing.
if (info.States.TryGetValue(
UiStateInfo.DirectStateId, out UiStateInfo? baseState))
return baseState.MediaCount != 0 ? "" : current;
return HasImageMedia(info, baseState, "") ? "" : current;
return info.StateMedia.ContainsKey("") ? "" : current;
}
private static bool HasImageMedia(
ElementInfo info,
UiStateInfo state,
string stateName)
// StateMedia keeps committed pre-ImageMediaCount fixtures and compact
// synthetic tests faithful for ordinary non-zero image entries.
=> state.ImageMediaCount != 0 || info.StateMedia.ContainsKey(stateName);
/// <summary>
/// Returns the File id the media rule selected for this face; 0 draws
/// nothing (an authored File=0 image reaches this as a media-state whose
@ -1037,6 +1055,9 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
private void UpdateVisualState()
{
if (ControllerOwnsVisualState)
return;
uint requested = ComputeRequestedStateId();
if (_hasCustomSelectionPair)
{