Implement step 2: Update Dialog to implement IRunnable<int?>

- Changed Dialog to implement IRunnable<int?> interface
- Added Result property that returns the index of the clicked button or null if canceled
- Implemented OnIsRunningChanging to extract button result before dialog closes
- Maintained backward compatibility with legacy Canceled property
- Dialog can still inherit from Window (as per new requirement)

Co-authored-by: tig <585482+tig@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-11-22 00:07:19 +00:00
parent d8cc1e3338
commit 6df84b63fa

View File

@@ -7,13 +7,21 @@ namespace Terminal.Gui.Views;
/// scheme.
/// </summary>
/// <remarks>
/// To run the <see cref="Dialog"/> modally, create the <see cref="Dialog"/>, and pass it to
/// <see cref="IApplication.Run(Toplevel, Func{Exception, bool})"/>. This will execute the dialog until
/// it terminates via the <see cref="Application.QuitKey"/> (`Esc` by default),
/// or when one of the views or buttons added to the dialog calls
/// <see cref="Application.RequestStop"/>.
/// <para>
/// To run the <see cref="Dialog"/> modally, create the <see cref="Dialog"/>, and pass it to
/// <see cref="IApplication.Run(Toplevel, Func{Exception, bool})"/>. This will execute the dialog until
/// it terminates via the <see cref="Application.QuitKey"/> (`Esc` by default),
/// or when one of the views or buttons added to the dialog calls
/// <see cref="Application.RequestStop"/>.
/// </para>
/// <para>
/// <b>Phase 2:</b> <see cref="Dialog"/> now implements <see cref="IRunnable{TResult}"/> with
/// <c>int?</c> as the result type, returning the index of the clicked button. The <see cref="Result"/>
/// property replaces the need for manual result tracking. A result of <see langword="null"/> indicates
/// the dialog was canceled (ESC pressed, window closed without clicking a button).
/// </para>
/// </remarks>
public class Dialog : Window
public class Dialog : Window, IRunnable<int?>
{
/// <summary>
/// Initializes a new instance of the <see cref="Dialog"/> class with no <see cref="Button"/>s.
@@ -85,7 +93,13 @@ public class Dialog : Window
}
/// <summary>Gets a value indicating whether the <see cref="Dialog"/> was canceled.</summary>
/// <remarks>The default value is <see langword="true"/>.</remarks>
/// <remarks>
/// <para>The default value is <see langword="true"/>.</para>
/// <para>
/// <b>Deprecated:</b> Use <see cref="Result"/> instead. This property is maintained for backward
/// compatibility. A <see langword="null"/> <see cref="Result"/> indicates the dialog was canceled.
/// </para>
/// </remarks>
public bool Canceled
{
get { return _canceled; }
@@ -101,6 +115,21 @@ public class Dialog : Window
}
}
/// <summary>
/// Gets or sets the result data extracted when the dialog was accepted, or <see langword="null"/> if not accepted.
/// </summary>
/// <remarks>
/// <para>
/// Returns the zero-based index of the button that was clicked, or <see langword="null"/> if the
/// dialog was canceled (ESC pressed, window closed without clicking a button).
/// </para>
/// <para>
/// This property is automatically set in <see cref="OnIsRunningChanging"/> when the dialog is
/// closing. The result is extracted by finding which button has focus when the dialog stops.
/// </para>
/// </remarks>
public int? Result { get; set; }
/// <summary>
/// Defines the default border styling for <see cref="Dialog"/>. Can be configured via
/// <see cref="ConfigurationManager"/>.
@@ -168,4 +197,67 @@ public class Dialog : Window
return false;
}
#region IRunnable<int> Implementation
/// <summary>
/// Called when the dialog is about to stop running. Extracts the button result before the dialog is removed
/// from the runnable stack.
/// </summary>
/// <param name="oldIsRunning">The current value of IsRunning.</param>
/// <param name="newIsRunning">The new value of IsRunning (true = starting, false = stopping).</param>
/// <returns><see langword="true"/> to cancel; <see langword="false"/> to proceed.</returns>
/// <remarks>
/// This method is called by the IRunnable infrastructure when the dialog is stopping. It extracts
/// which button was clicked (if any) before views are disposed.
/// </remarks>
protected virtual bool OnIsRunningChanging (bool oldIsRunning, bool newIsRunning)
{
if (!newIsRunning && oldIsRunning) // Stopping
{
// Extract result BEFORE disposal - find which button has focus or was last clicked
Result = null; // Default: canceled (null = no button clicked)
for (var i = 0; i < _buttons.Count; i++)
{
if (_buttons [i].HasFocus)
{
Result = i;
_canceled = false;
break;
}
}
// If no button has focus, check if any button was the last focused view
if (Result is null && MostFocused is Button btn && _buttons.Contains (btn))
{
Result = _buttons.IndexOf (btn);
_canceled = false;
}
// Update legacy Canceled property for backward compatibility
if (Result is null)
{
_canceled = true;
}
}
else if (newIsRunning) // Starting
{
// Clear result when starting
Result = null;
_canceled = true; // Default to canceled until a button is clicked
}
// Call base implementation (Toplevel.IRunnable.RaiseIsRunningChanging)
return ((IRunnable)this).RaiseIsRunningChanging (oldIsRunning, newIsRunning);
}
// Explicitly implement IRunnable<int> to override the behavior from Toplevel's IRunnable
bool IRunnable.RaiseIsRunningChanging (bool oldIsRunning, bool newIsRunning)
{
// Call our virtual method so subclasses can override
return OnIsRunningChanging (oldIsRunning, newIsRunning);
}
#endregion
}