2024-02-01 12:56:40 +00:00
using Content.Shared.Administration ;
using Robust.Client.AutoGenerated ;
using Robust.Client.UserInterface.Controls ;
using Robust.Client.UserInterface.XAML ;
2024-02-27 14:58:54 +03:00
using Robust.Shared.Sandboxing ;
using Robust.Client.UserInterface ;
using System.Linq ;
2024-02-01 12:56:40 +00:00
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 ;
2024-02-27 14:58:54 +03:00
private List < ( string , Func < string > ) > _promptLines ;
2024-02-29 23:16:53 +03:00
private delegate ( Control , Func < string > ) ControlConstructor ( Func < string , bool > verifier , string name , QuickDialogEntry entry , bool last , object? value ) ; // are you feeling it now mr krabs?
2024-02-01 12:56:40 +00:00
/// <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 ] ;
2024-02-29 23:16:53 +03:00
var value = entry . Value ;
2024-02-01 12:56:40 +00:00
var box = new BoxContainer ( ) ;
box . AddChild ( new Label ( ) { Text = entry . Prompt , HorizontalExpand = true , SizeFlagsStretchRatio = 0.5f } ) ;
2024-02-27 14:58:54 +03:00
//var edit = new LineEdit() { HorizontalExpand = true };
// walkthrough:
// second item in this tuple is a verifier function for controls who have that option
// third item is a backup name in case we have not been provided with one from the server
2024-02-29 23:16:53 +03:00
// first item is a function that takes the other two, the QuickDialogEntry for it, a bool of whether it's the last control in the window, the default value for the control
2024-02-27 14:58:54 +03:00
// and returns another tuple:
// item 1 is the control itself
// item 2 is a Func<string>, a """generic""" function that returns whatever user has done with the control
( ControlConstructor , Func < string , bool > ? , string ) notapairanymore = entry . Type switch
2024-02-01 12:56:40 +00:00
{
2024-02-27 14:58:54 +03:00
QuickDialogEntryType . Integer = > ( SetupLineEditNumber , VerifyInt , "integer" ) ,
QuickDialogEntryType . Float = > ( SetupLineEditNumber , VerifyFloat , "float" ) ,
QuickDialogEntryType . ShortText = > ( SetupLineEdit , VerifyShortText , "short-text" ) ,
QuickDialogEntryType . LongText = > ( SetupLineEdit , VerifyLongText , "long-text" ) ,
QuickDialogEntryType . Hex16 = > ( SetupLineEditHex , VerifyHex16 , "hex16" ) ,
QuickDialogEntryType . Boolean = > ( SetupCheckBox , null , "boolean" ) ,
QuickDialogEntryType . Void = > ( SetupVoid , null , "void" ) ,
2024-02-01 12:56:40 +00:00
_ = > throw new ArgumentOutOfRangeException ( )
} ;
2024-02-27 14:58:54 +03:00
var ( setup , valid , name ) = notapairanymore ;
2024-02-01 12:56:40 +00:00
2024-02-29 23:16:53 +03:00
// try use placeholder from the caller, fall back to the generic one for whatever type is being validated.
var ( control , returner ) = setup ( valid ! , entry . Placeholder ? ? Loc . GetString ( $"quick-dialog-ui-{name}" ) , entry , i = = entries . Count - 1 , value ) ; // ARE YOU FEELING IT NOW MR KRABS?
// yes, valid can be null
// yes, i am just going to ignore that
// go fuck yourself
2024-02-27 14:58:54 +03:00
_promptLines . Add ( ( entry . FieldId , returner ) ) ;
2024-02-01 12:56:40 +00:00
2024-02-27 14:58:54 +03:00
box . AddChild ( control ) ;
2024-02-01 12:56:40 +00:00
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 > ( ) ;
2024-02-27 14:58:54 +03:00
foreach ( var ( field , returner ) in _promptLines )
2024-02-01 12:56:40 +00:00
{
2024-02-27 14:58:54 +03:00
results [ field ] = returner ( ) ;
2024-02-01 12:56:40 +00:00
}
_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 ;
}
2024-02-27 14:58:54 +03:00
private bool VerifyHex16 ( string input )
{
return input . Length < = 4 & & int . TryParse ( input , System . Globalization . NumberStyles . HexNumber , null , out int _ ) ;
}
2024-02-29 23:16:53 +03:00
private ( Control , Func < string > ) SetupLineEdit ( Func < string , bool > valid , string name , QuickDialogEntry entry , bool last , object? value ) // oh shit i'm feeling it
2024-02-27 14:58:54 +03:00
{
var edit = new LineEdit ( ) { HorizontalExpand = true } ;
edit . IsValid + = valid ;
2024-02-29 23:16:53 +03:00
if ( value ! = null )
edit . Text = value . ToString ( ) ? ? "" ;
2024-02-27 14:58:54 +03:00
edit . PlaceHolder = name ;
2024-02-29 23:16:53 +03:00
2024-02-27 14:58:54 +03:00
// Last text box gets enter confirmation.
// Only the last so you don't accidentally confirm early.
if ( last )
edit . OnTextEntered + = _ = > Confirm ( ) ;
return ( edit , ( ) = > { return edit . Text ; } ) ;
}
2024-02-29 23:16:53 +03:00
private ( Control , Func < string > ) SetupLineEditNumber ( Func < string , bool > valid , string name , QuickDialogEntry entry , bool last , object? value )
2024-02-27 14:58:54 +03:00
{
2024-02-29 23:16:53 +03:00
var ( control , returner ) = SetupLineEdit ( valid , name , entry , last , value ) ;
2024-02-27 14:58:54 +03:00
var le = ( LineEdit ) control ;
return ( control , ( ) = > le . Text . Length > 0 ? le . Text : "0" ) ; // Otherwise you'll get kicked for malformed data
}
2024-02-29 23:16:53 +03:00
private ( Control , Func < string > ) SetupLineEditHex ( Func < string , bool > valid , string name , QuickDialogEntry entry , bool last , object? value )
2024-02-27 14:58:54 +03:00
{
2024-02-29 23:16:53 +03:00
var ( control , returner ) = SetupLineEditNumber ( valid , name , entry , last , value ) ;
var le = ( LineEdit ) control ;
if ( value is int )
{
le . Text = ( ( int ) value ) . ToString ( "X" ) ;
}
le . OnTextChanged + = e = > { e . Control . Text = e . Text . ToUpper ( ) ; } ;
2024-02-27 14:58:54 +03:00
return ( control , returner ) ;
}
2024-02-29 23:16:53 +03:00
private ( Control , Func < string > ) SetupCheckBox ( Func < string , bool > _ , string name , QuickDialogEntry entry , bool last , object? value )
2024-02-27 14:58:54 +03:00
{
var check = new CheckBox ( ) { HorizontalExpand = true , HorizontalAlignment = HAlignment . Right } ;
check . Text = name ;
2024-02-29 23:16:53 +03:00
value ? ? = false ;
check . Pressed = ( bool ) value ;
2024-02-27 14:58:54 +03:00
return ( check , ( ) = > { return check . Pressed ? "true" : "false" ; } ) ;
}
2024-02-29 23:16:53 +03:00
private ( Control , Func < string > ) SetupVoid ( Func < string , bool > _ , string __ , QuickDialogEntry ___ , bool ____ , object? _____ )
2024-02-27 14:58:54 +03:00
{
var control = new Control ( ) ;
control . Visible = false ;
return ( control , ( ) = > "" ) ;
}
2024-02-01 12:56:40 +00:00
#endregion
}