namespace Terminal.Gui.Views;
///
/// A . Supports a simple API for adding s
/// across the bottom. By default, the is centered and used the
/// scheme.
///
///
///
/// To run the modally, create the , and pass it to
/// . This will execute the dialog until
/// it terminates via the (`Esc` by default),
/// or when one of the views or buttons added to the dialog calls
/// .
///
///
/// Dialog implements with int? as the result type.
/// The property contains the index of the button that was clicked, or null if canceled.
///
///
public class Dialog : Window, IModalRunnable
{
///
/// Initializes a new instance of the class with no s.
///
///
/// By default, , , , and are
/// set
/// such that the will be centered in, and no larger than 90% of , if
/// there is one. Otherwise,
/// it will be bound by the screen dimensions.
///
public Dialog ()
{
Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped;
base.ShadowStyle = DefaultShadow;
BorderStyle = DefaultBorderStyle;
X = Pos.Center ();
Y = Pos.Center ();
Width = Dim.Auto (DimAutoStyle.Auto, Dim.Percent (DefaultMinimumWidth), Dim.Percent (90));
Height = Dim.Auto (DimAutoStyle.Auto, Dim.Percent (DefaultMinimumHeight), Dim.Percent (90));
SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Dialog);
Modal = true;
ButtonAlignment = DefaultButtonAlignment;
ButtonAlignmentModes = DefaultButtonAlignmentModes;
}
private readonly List _buttons = [];
private bool _canceled;
///
/// Adds a to the , its layout will be controlled by the
///
///
/// Button to add.
public void AddButton (Button button)
{
// Use a distinct GroupId so users can use Pos.Align for other views in the Dialog
button.X = Pos.Align (ButtonAlignment, ButtonAlignmentModes, GetHashCode ());
button.Y = Pos.AnchorEnd ();
_buttons.Add (button);
Add (button);
// Subscribe to the button's Accept command to set Result
button.Accepting += Button_Accepting;
}
private void Button_Accepting (object? sender, CommandEventArgs e)
{
// Set Result to the index of the button that was clicked
if (sender is Button button)
{
int index = _buttons.IndexOf (button);
if (index >= 0)
{
Result = index;
// For backward compatibility, set Canceled = false
Canceled = false;
}
}
}
// TODO: Update button.X = Pos.Justify when alignment changes
/// Determines how the s are aligned along the bottom of the dialog.
public Alignment ButtonAlignment { get; set; }
///
/// Gets or sets the alignment modes for the dialog's buttons.
///
public AlignmentModes ButtonAlignmentModes { get; set; }
/// Optional buttons to lay out at the bottom of the dialog.
public Button [] Buttons
{
get => _buttons.ToArray ();
init
{
foreach (Button b in value)
{
AddButton (b);
}
}
}
/// Gets a value indicating whether the was canceled.
///
/// The default value is .
///
/// Obsolete: Use instead. When is null, the dialog was canceled.
/// This property is maintained for backward compatibility.
///
///
[Obsolete ("Use Result property instead. Result == null indicates the dialog was canceled.")]
public bool Canceled
{
get { return _canceled; }
set
{
#if DEBUG_IDISPOSABLE
if (EnableDebugIDisposableAsserts && WasDisposed)
{
throw new ObjectDisposedException (GetType ().FullName);
}
#endif
_canceled = value;
}
}
///
/// Gets or sets the result of the modal dialog operation.
///
///
///
/// Contains the zero-based index of the button that was clicked to close the dialog,
/// or null if the dialog was canceled (e.g., ESC key pressed).
///
///
/// The button index corresponds to the order buttons were added via or
/// the initializer.
///
///
/// For backward compatibility with the property:
/// - == null means the dialog was canceled ( == true)
/// - != null means a button was clicked ( == false)
///
///
/// This property implements where TResult is int? .
///
///
public int? Result { get; set; }
///
/// Defines the default border styling for . Can be configured via
/// .
///
[ConfigurationProperty (Scope = typeof (ThemeScope))]
public new static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Heavy;
/// The default for .
/// This property can be set in a Theme.
[ConfigurationProperty (Scope = typeof (ThemeScope))]
public static Alignment DefaultButtonAlignment { get; set; } = Alignment.End;
/// The default for .
/// This property can be set in a Theme.
[ConfigurationProperty (Scope = typeof (ThemeScope))]
public static AlignmentModes DefaultButtonAlignmentModes { get; set; } = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems;
///
/// Defines the default minimum Dialog height, as a percentage of the container width. Can be configured via
/// .
///
[ConfigurationProperty (Scope = typeof (ThemeScope))]
public static int DefaultMinimumHeight { get; set; } = 80;
///
/// Defines the default minimum Dialog width, as a percentage of the container width. Can be configured via
/// .
///
[ConfigurationProperty (Scope = typeof (ThemeScope))]
public static int DefaultMinimumWidth { get; set; } = 80;
///
/// Gets or sets whether all s are shown with a shadow effect by default.
///
[ConfigurationProperty (Scope = typeof (ThemeScope))]
public new static ShadowStyle DefaultShadow { get; set; } = ShadowStyle.Transparent;
// Dialogs are Modal and Focus is indicated by their Border. The following code ensures the
// Text of the dialog (e.g. for a MessageBox) is always drawn using the Normal Attribute.
private bool _drawingText;
///
protected override bool OnDrawingText ()
{
_drawingText = true;
return false;
}
///
protected override void OnDrewText ()
{
_drawingText = false;
}
///
protected override bool OnGettingAttributeForRole (in VisualRole role, ref Attribute currentAttribute)
{
if (_drawingText && role is VisualRole.Focus && Border?.Thickness != Thickness.Empty)
{
currentAttribute = GetScheme ().Normal;
return true;
}
return false;
}
}