make dialog window not evil (#24677)

* add Placeholder and make default buttons flags consistent w old behaviour

* DialogWindow ops

* make QuickDialog use DialogWindow

* Update Content.Client/UserInterface/Controls/DialogWindow.xaml

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
deltanedas
2024-02-01 12:56:40 +00:00
committed by GitHub
parent b932d94ded
commit 6b03aaaec7
4 changed files with 176 additions and 146 deletions

View File

@@ -1,13 +1,8 @@
using System.Linq;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Shared.Administration; using Content.Shared.Administration;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.Administration; namespace Content.Client.Administration;
// mfw they ported input() from BYOND
/// <summary> /// <summary>
/// This handles the client portion of quick dialogs. /// This handles the client portion of quick dialogs.
/// </summary> /// </summary>
@@ -21,149 +16,22 @@ public sealed class QuickDialogSystem : EntitySystem
private void OpenDialog(QuickDialogOpenEvent ev) private void OpenDialog(QuickDialogOpenEvent ev)
{ {
var window = new FancyWindow() var ok = (ev.Buttons & QuickDialogButtonFlag.OkButton) != 0;
{ var cancel = (ev.Buttons & QuickDialogButtonFlag.CancelButton) != 0;
Title = ev.Title var window = new DialogWindow(ev.Title, ev.Prompts, ok: ok, cancel: cancel);
};
var entryContainer = new BoxContainer() window.OnConfirmed += responses =>
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
Margin = new Thickness(8),
};
var promptsDict = new Dictionary<string, LineEdit>();
for (var index = 0; index < ev.Prompts.Count; index++)
{
var entry = ev.Prompts[index];
var entryBox = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal
};
entryBox.AddChild(new Label { Text = entry.Prompt, HorizontalExpand = true, SizeFlagsStretchRatio = 0.5f });
var edit = new LineEdit() { HorizontalExpand = true };
entryBox.AddChild(edit);
switch (entry.Type)
{
case QuickDialogEntryType.Integer:
edit.IsValid += VerifyInt;
edit.PlaceHolder = Loc.GetString("quick-dialog-ui-integer");
break;
case QuickDialogEntryType.Float:
edit.IsValid += VerifyFloat;
edit.PlaceHolder = Loc.GetString("quick-dialog-ui-float");
break;
case QuickDialogEntryType.ShortText:
edit.IsValid += VerifyShortText;
edit.PlaceHolder = Loc.GetString("quick-dialog-ui-short-text");
break;
case QuickDialogEntryType.LongText:
edit.IsValid += VerifyLongText;
edit.PlaceHolder = Loc.GetString("quick-dialog-ui-long-text");
break;
default:
throw new ArgumentOutOfRangeException();
}
promptsDict.Add(entry.FieldId, edit);
entryContainer.AddChild(entryBox);
if (index == ev.Prompts.Count - 1)
{
// Last text box gets enter confirmation.
// Only the last so you don't accidentally confirm early.
edit.OnTextEntered += _ => Confirm();
}
}
var buttonsBox = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
HorizontalAlignment = Control.HAlignment.Center,
};
var alreadyReplied = false;
if ((ev.Buttons & QuickDialogButtonFlag.OkButton) != 0)
{
var okButton = new Button()
{
Text = Loc.GetString("quick-dialog-ui-ok"),
};
okButton.OnPressed += _ => Confirm();
buttonsBox.AddChild(okButton);
}
if ((ev.Buttons & QuickDialogButtonFlag.OkButton) != 0)
{
var cancelButton = new Button()
{
Text = Loc.GetString("quick-dialog-ui-cancel"),
};
cancelButton.OnPressed += _ =>
{
RaiseNetworkEvent(new QuickDialogResponseEvent(ev.DialogId,
new(),
QuickDialogButtonFlag.CancelButton));
alreadyReplied = true;
window.Close();
};
buttonsBox.AddChild(cancelButton);
}
window.OnClose += () =>
{
if (!alreadyReplied)
{
RaiseNetworkEvent(new QuickDialogResponseEvent(ev.DialogId,
new(),
QuickDialogButtonFlag.CancelButton));
}
};
entryContainer.AddChild(buttonsBox);
window.ContentsContainer.AddChild(entryContainer);
window.MinWidth *= 2; // Just double it.
window.OpenCentered();
return;
void Confirm()
{ {
RaiseNetworkEvent(new QuickDialogResponseEvent(ev.DialogId, RaiseNetworkEvent(new QuickDialogResponseEvent(ev.DialogId,
promptsDict.Select(x => (x.Key, x.Value.Text)).ToDictionary(x => x.Key, x => x.Text), responses,
QuickDialogButtonFlag.OkButton)); QuickDialogButtonFlag.OkButton));
alreadyReplied = true; };
window.Close();
}
}
private bool VerifyInt(string input) window.OnCancelled += () =>
{ {
return int.TryParse(input, out var _); RaiseNetworkEvent(new QuickDialogResponseEvent(ev.DialogId,
} new(),
QuickDialogButtonFlag.CancelButton));
private bool VerifyFloat(string input) };
{
return float.TryParse(input, out var _);
}
private bool VerifyShortText(string input)
{
return input.Length <= 100;
}
private bool VerifyLongText(string input)
{
return input.Length <= 2000;
} }
} }

View File

@@ -0,0 +1,9 @@
<controls:FancyWindow xmlns="https://spacestation14.io" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
<BoxContainer Orientation="Vertical" Margin="8">
<BoxContainer Name="Prompts" Orientation="Vertical"/> <!-- Populated in constructor -->
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center">
<Button Name="OkButton" Text="{Loc 'quick-dialog-ui-ok'}"/>
<Button Name="CancelButton" Text="{Loc 'quick-dialog-ui-cancel'}"/>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@@ -0,0 +1,147 @@
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.UserInterface.Controls;
// mfw they ported input() from BYOND
/// <summary>
/// Client-side dialog with multiple prompts.
/// Used by admin tools quick dialog system among other things.
/// </summary>
[GenerateTypedNameReferences]
public sealed partial class DialogWindow : FancyWindow
{
/// <summary>
/// Action for when the ok button is pressed or the last field has enter pressed.
/// Results maps prompt FieldIds to the LineEdit's text contents.
/// </summary>
public Action<Dictionary<string, string>>? OnConfirmed;
/// <summary>
/// Action for when the cancel button is pressed or the window is closed.
/// </summary>
public Action? OnCancelled;
/// <summary>
/// Used to ensure that only one output action is invoked.
/// E.g. Pressing cancel will invoke then close the window, but OnClose will not invoke.
/// </summary>
private bool _finished;
private List<(string, LineEdit)> _promptLines;
/// <summary>
/// Create and open a new dialog with some prompts.
/// </summary>
/// <param name="title">String to use for the window title.</param>
/// <param name="entries">Quick dialog entries to create prompts with.</param>
/// <param name="ok">Whether to have an Ok button.</param>
/// <param name="cancel">Whether to have a Cancel button. Closing the window will still cancel it.</param>
/// <remarks>
/// Won't do anything on its own, you need to handle or network with <see cref="OnConfirmed"/> and <see cref="OnCancelled"/>.
/// </remarks>
public DialogWindow(string title, List<QuickDialogEntry> entries, bool ok = true, bool cancel = true)
{
RobustXamlLoader.Load(this);
Title = title;
OkButton.Visible = ok;
CancelButton.Visible = cancel;
_promptLines = new(entries.Count);
for (int i = 0; i < entries.Count; i++)
{
var entry = entries[i];
var box = new BoxContainer();
box.AddChild(new Label() { Text = entry.Prompt, HorizontalExpand = true, SizeFlagsStretchRatio = 0.5f });
var edit = new LineEdit() { HorizontalExpand = true };
(Func<string, bool>, string) pair = entry.Type switch
{
QuickDialogEntryType.Integer => (VerifyInt, "integer"),
QuickDialogEntryType.Float => (VerifyFloat, "float"),
QuickDialogEntryType.ShortText => (VerifyShortText, "short-text"),
QuickDialogEntryType.LongText => (VerifyLongText, "long-text"),
_ => throw new ArgumentOutOfRangeException()
};
var (valid, name) = pair;
edit.IsValid += valid;
// try use placeholder from the caller, fall back to the generic one for whatever type is being validated.
edit.PlaceHolder = entry.Placeholder ?? Loc.GetString($"quick-dialog-ui-{name}");
// Last text box gets enter confirmation.
// Only the last so you don't accidentally confirm early.
if (i == entries.Count - 1)
edit.OnTextEntered += _ => Confirm();
_promptLines.Add((entry.FieldId, edit));
box.AddChild(edit);
Prompts.AddChild(box);
}
OkButton.OnPressed += _ => Confirm();
CancelButton.OnPressed += _ =>
{
_finished = true;
OnCancelled?.Invoke();
Close();
};
OnClose += () =>
{
if (!_finished)
OnCancelled?.Invoke();
};
MinWidth *= 2; // Just double it.
OpenCentered();
}
private void Confirm()
{
var results = new Dictionary<string, string>();
foreach (var (field, edit) in _promptLines)
{
results[field] = edit.Text;
}
_finished = true;
OnConfirmed?.Invoke(results);
Close();
}
#region Input validation
private bool VerifyInt(string input)
{
return int.TryParse(input, out var _);
}
private bool VerifyFloat(string input)
{
return float.TryParse(input, out var _);
}
private bool VerifyShortText(string input)
{
return input.Length <= 100;
}
private bool VerifyLongText(string input)
{
return input.Length <= 2000;
}
#endregion
}

View File

@@ -26,7 +26,7 @@ public sealed class QuickDialogOpenEvent : EntityEventArgs
/// <summary> /// <summary>
/// The buttons presented for the user. /// The buttons presented for the user.
/// </summary> /// </summary>
public QuickDialogButtonFlag Buttons = QuickDialogButtonFlag.OkButton; public QuickDialogButtonFlag Buttons = QuickDialogButtonFlag.OkButton | QuickDialogButtonFlag.CancelButton;
public QuickDialogOpenEvent(string title, List<QuickDialogEntry> prompts, int dialogId, QuickDialogButtonFlag buttons) public QuickDialogOpenEvent(string title, List<QuickDialogEntry> prompts, int dialogId, QuickDialogButtonFlag buttons)
{ {
@@ -87,11 +87,17 @@ public sealed class QuickDialogEntry
/// </summary> /// </summary>
public string Prompt; public string Prompt;
public QuickDialogEntry(string fieldId, QuickDialogEntryType type, string prompt) /// <summary>
/// String to replace the type-specific placeholder with.
/// </summary>
public string? Placeholder;
public QuickDialogEntry(string fieldId, QuickDialogEntryType type, string prompt, string? placeholder = null)
{ {
FieldId = fieldId; FieldId = fieldId;
Type = type; Type = type;
Prompt = prompt; Prompt = prompt;
Placeholder = placeholder;
} }
} }