acdream/src/AcDream.App/UI/Layout/RetailConfirmationTextInputDialogView.cs

160 lines
6 KiB
C#

namespace AcDream.App.UI.Layout;
/// <summary>
/// Retail type-5 <c>ConfirmationTextInputDialog</c> (class type
/// <c>0x15</c>, catalog root <c>0x2C</c>). Accept stores the field text under
/// property <c>0x9C</c>; reject/Escape stores the empty string. Character
/// deletion is the first consumer and performs retail's case-insensitive
/// comparison with the localized <c>DELETE</c> response in its callback.
/// </summary>
internal sealed class RetailConfirmationTextInputDialogView : IRetailDialogView
{
public const uint RootElementId = 0x2Cu;
public const uint InputElementId = 0x2Cu;
public const uint AcceptButtonId = 0x2Eu;
public const uint RejectButtonId = 0x2Fu;
public const uint PopupElementId = 0x3Du;
public const uint MessageElementId = 0x3Eu;
private readonly UiRoot _host;
private readonly RetailDialogData _data;
private readonly uint _context;
private readonly Action<uint> _closeDialog;
private readonly UiElement _popup;
private readonly UiText _message;
private readonly UiField _input;
private readonly UiButton _accept;
private readonly UiButton _reject;
private readonly float _basePopupHeight;
private readonly float _baseMessageHeight;
private bool _focusPending = true;
public RetailConfirmationTextInputDialogView(
UiRoot host,
ImportedLayout layout,
RetailDialogData data,
uint context,
Action<uint> closeDialog)
{
_host = host ?? throw new ArgumentNullException(nameof(host));
ArgumentNullException.ThrowIfNull(layout);
_data = data ?? throw new ArgumentNullException(nameof(data));
_context = context;
_closeDialog = closeDialog ?? throw new ArgumentNullException(nameof(closeDialog));
Root = layout.Root as UiDialogRoot
?? throw new ArgumentException(
"Confirmation-text-input layout root is not a UiDialogRoot.",
nameof(layout));
_popup = layout.FindElement(PopupElementId)
?? throw new ArgumentException(
"Confirmation-text-input layout is missing popup element 0x3D.",
nameof(layout));
_message = layout.FindElement(MessageElementId) as UiText
?? throw new ArgumentException(
"Confirmation-text-input layout is missing text element 0x3E.",
nameof(layout));
// The field deliberately repeats the root's numeric id. ImportedLayout
// registers descendants after ancestors, matching GetChildRecursive's
// effective result for this catalog shape.
_input = layout.FindElement(InputElementId) as UiField
?? throw new ArgumentException(
"Confirmation-text-input layout is missing input field 0x2C.",
nameof(layout));
_accept = layout.FindElement(AcceptButtonId) as UiButton
?? throw new ArgumentException(
"Confirmation-text-input layout is missing accept button 0x2E.",
nameof(layout));
_reject = layout.FindElement(RejectButtonId) as UiButton
?? throw new ArgumentException(
"Confirmation-text-input layout is missing reject button 0x2F.",
nameof(layout));
_basePopupHeight = _popup.Height;
_baseMessageHeight = _message.Height;
_popup.LayoutPolicy = null;
_popup.Anchors = AnchorEdges.None;
_message.LayoutPolicy = null;
_message.Anchors = AnchorEdges.None;
_message.Padding = 0f;
_message.Selectable = false;
_input.ClearOnSubmit = false;
_input.RecordHistory = false;
if (_data.GetString(RetailDialogProperty.TextInputAcceptLabel) is { } acceptLabel)
_accept.Label = acceptLabel;
if (_data.GetString(RetailDialogProperty.TextInputRejectLabel) is { } rejectLabel)
_reject.Label = rejectLabel;
Root.Cancel = Reject;
_accept.OnClick = Accept;
_reject.OnClick = Reject;
_input.OnSubmit = _ => Accept();
SetMessage(_data.GetString(RetailDialogProperty.Message) ?? string.Empty);
SizeAndCenter();
}
public UiDialogRoot Root { get; }
public void Tick()
{
SizeAndCenter();
if (_focusPending && Root.Parent is not null)
{
_host.SetKeyboardFocus(_input);
_focusPending = false;
}
}
public void SetPendingCount(int count)
{
// This catalog root authors no pending-count display.
}
public void DetachHandlers()
{
Root.Cancel = null;
_accept.OnClick = null;
_reject.OnClick = null;
_input.OnSubmit = null;
}
private void Accept()
{
_data.Set(RetailDialogProperty.TextInputResult, _input.Text);
_closeDialog(_context);
}
private void Reject()
{
_data.Set(RetailDialogProperty.TextInputResult, string.Empty);
_closeDialog(_context);
}
private void SetMessage(string text)
{
float maximumWidth = Math.Max(1f, _message.Width - 2f * _message.Padding);
Func<string, float> measure = _message.DatFont is { } font
? font.MeasureWidth
: static value => value.Length * 8f;
IReadOnlyList<string> wrapped = UiText.WrapWords(text, measure, maximumWidth);
var lines = new UiText.Line[wrapped.Count];
for (int i = 0; i < wrapped.Count; i++)
lines[i] = new UiText.Line(wrapped[i], _message.DefaultColor);
_message.LinesProvider = () => lines;
float lineHeight = _message.DatFont?.LineHeight ?? 16f;
_message.Height = Math.Max(_baseMessageHeight, lines.Length * lineHeight);
_popup.Height = _basePopupHeight + (_message.Height - _baseMessageHeight);
}
private void SizeAndCenter()
{
Root.Left = 0f;
Root.Top = 0f;
Root.Width = _host.Width;
Root.Height = _host.Height;
_popup.Left = MathF.Round((Root.Width - _popup.Width) * 0.5f);
_popup.Top = MathF.Round((Root.Height - _popup.Height) * 0.5f);
}
}