fix(ui): finish retail stack selector polish

Keep the horizontal thumb at its raw pointer position so both endpoints are reachable, honor the stack entry's authored right alignment, and preserve PublicWeenieDesc plural names from CreateObject through the object table. Port retail's singular s/es fallback when no plural was sent.

Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-11 10:22:11 +02:00
parent a20e5c68c7
commit 6005c51c4d
22 changed files with 233 additions and 36 deletions

View file

@ -405,6 +405,8 @@ public static class DatWidgetFactory
FocusFieldSprite = focusSprite,
Selectable = selectable,
OneLine = oneLine,
Centered = info.HJustify == HJustify.Center,
RightAligned = info.HJustify == HJustify.Right,
};
if (info.TryGetEffectiveInteger(0x1Eu, out int maxCharacters))
field.MaxCharacters = maxCharacters;

View file

@ -99,6 +99,7 @@ public sealed class SelectedObjectController : IRetainedPanelController
private uint? _current;
private string? _currentName;
private double _flashRemaining; // > 0 while the selection overlay is flashing
private bool _changingSplitFromSlider;
private bool _disposed;
/// <summary>White label color for the name line.</summary>
@ -166,8 +167,8 @@ public sealed class SelectedObjectController : IRetainedPanelController
{
_stackSizeSlider.Visible = false;
_stackSizeSlider.Horizontal = true;
_stackSizeSlider.ScalarValue = () => _splitQuantity.Ratio;
_stackSizeSlider.ScalarChanged = _splitQuantity.SetFromSliderRatio;
_stackSizeSlider.SetScalarPosition(_splitQuantity.Ratio);
_stackSizeSlider.ScalarChanged = OnStackSliderChanged;
}
// Attach a centered UiText child to the name element for the object name display.
@ -224,7 +225,7 @@ public sealed class SelectedObjectController : IRetainedPanelController
/// (typical host: <c>h =&gt; Combat.HealthChanged += h</c>) — drives meter visibility.</param>
/// <param name="isHealthTarget">Returns true for guids that may show a health meter
/// (proxy for retail's <c>IsPlayer() || pet_owner || ObjectIsAttackable()</c>).</param>
/// <param name="name">Returns the display name for a given guid (or null if unknown).</param>
/// <param name="name">Returns retail's NAME_APPROPRIATE display name for a guid.</param>
/// <param name="healthPercent">Returns the health fill fraction [0..1] for a given guid.</param>
/// <param name="hasHealth">Returns true if real health has been received for a guid
/// (so a re-selected, already-known target shows its bar immediately).</param>
@ -342,7 +343,26 @@ public sealed class SelectedObjectController : IRetainedPanelController
=> _splitQuantity.SetFromText(text);
private void OnSplitQuantityChanged()
=> _stackSizeEntry?.SetText(_splitQuantity.Value.ToString(System.Globalization.CultureInfo.InvariantCulture));
{
_stackSizeEntry?.SetText(_splitQuantity.Value.ToString(System.Globalization.CultureInfo.InvariantCulture));
// UIElement_Scrollbar owns its live thumb position while dragging and only
// broadcasts message 0xA. Entry/selection changes explicitly write attr 0x86.
if (!_changingSplitFromSlider)
_stackSizeSlider?.SetScalarPosition(_splitQuantity.Ratio);
}
private void OnStackSliderChanged(float position)
{
_changingSplitFromSlider = true;
try
{
_splitQuantity.SetFromSliderRatio(position);
}
finally
{
_changingSplitFromSlider = false;
}
}
private void OnObjectUpdated(ClientObject updated)
{