Browse Source

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 <[email protected]>
copilot-swe-agent[bot] 3 weeks ago
parent
commit
6df84b63fa
1 changed files with 99 additions and 7 deletions
  1. 99 7
      Terminal.Gui/Views/Dialog.cs

+ 99 - 7
Terminal.Gui/Views/Dialog.cs

@@ -7,13 +7,21 @@ namespace Terminal.Gui.Views;
 ///     scheme.
 ///     scheme.
 /// </summary>
 /// </summary>
 /// <remarks>
 /// <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>
 /// </remarks>
-public class Dialog : Window
+public class Dialog : Window, IRunnable<int?>
 {
 {
     /// <summary>
     /// <summary>
     ///     Initializes a new instance of the <see cref="Dialog"/> class with no <see cref="Button"/>s.
     ///     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>
     /// <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
     public bool Canceled
     {
     {
         get { return _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>
     /// <summary>
     ///     Defines the default border styling for <see cref="Dialog"/>. Can be configured via
     ///     Defines the default border styling for <see cref="Dialog"/>. Can be configured via
     ///     <see cref="ConfigurationManager"/>.
     ///     <see cref="ConfigurationManager"/>.
@@ -168,4 +197,67 @@ public class Dialog : Window
 
 
         return false;
         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
 }
 }