|
|
@@ -17,8 +17,12 @@ namespace Terminal.Gui.Views;
|
|
|
/// and run (e.g. <see cref="Dialog"/>s). To run a Toplevel, create the <see cref="Toplevel"/> and call
|
|
|
/// <see cref="IApplication.Run(Toplevel, Func{Exception, bool})"/>.
|
|
|
/// </para>
|
|
|
+/// <para>
|
|
|
+/// Toplevel implements <see cref="IRunnable"/> to support the runnable session lifecycle with proper event handling
|
|
|
+/// following the Cancellable Work Pattern (CWP).
|
|
|
+/// </para>
|
|
|
/// </remarks>
|
|
|
-public partial class Toplevel : View
|
|
|
+public partial class Toplevel : View, IRunnable
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// Initializes a new instance of the <see cref="Toplevel"/> class,
|
|
|
@@ -92,27 +96,60 @@ public partial class Toplevel : View
|
|
|
/// </summary>
|
|
|
public bool IsLoaded { get; private set; }
|
|
|
|
|
|
- // TODO: IRunnable: Re-implement as an event on IRunnable; IRunnable.Activating/Activate
|
|
|
/// <summary>Invoked when the Toplevel <see cref="SessionToken"/> active.</summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// <b>Obsolete:</b> Use <see cref="IRunnable.Activated"/> instead. This event is maintained for backward
|
|
|
+ /// compatibility and will be raised alongside the new <see cref="IRunnable.Activated"/> event.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
+ [Obsolete ("Use IRunnable.Activated instead. This event is maintained for backward compatibility.")]
|
|
|
public event EventHandler<ToplevelEventArgs>? Activate;
|
|
|
|
|
|
- // TODO: IRunnable: Re-implement as an event on IRunnable; IRunnable.Deactivating/Deactivate?
|
|
|
/// <summary>Invoked when the Toplevel<see cref="SessionToken"/> ceases to be active.</summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// <b>Obsolete:</b> Use <see cref="IRunnable.Deactivated"/> instead. This event is maintained for backward
|
|
|
+ /// compatibility and will be raised alongside the new <see cref="IRunnable.Deactivated"/> event.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
+ [Obsolete ("Use IRunnable.Deactivated instead. This event is maintained for backward compatibility.")]
|
|
|
public event EventHandler<ToplevelEventArgs>? Deactivate;
|
|
|
|
|
|
/// <summary>Invoked when the Toplevel's <see cref="SessionToken"/> is closed by <see cref="IApplication.End(SessionToken)"/>.</summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// <b>Obsolete:</b> This event is maintained for backward compatibility. The IRunnable architecture
|
|
|
+ /// combines this functionality into the <see cref="IRunnable.Stopped"/> event.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
+ [Obsolete ("Use IRunnable.Stopped instead. This event is maintained for backward compatibility.")]
|
|
|
public event EventHandler<ToplevelEventArgs>? Closed;
|
|
|
|
|
|
/// <summary>
|
|
|
/// Invoked when the Toplevel's <see cref="SessionToken"/> is being closed by
|
|
|
/// <see cref="IApplication.RequestStop(Toplevel)"/>.
|
|
|
/// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// <b>Obsolete:</b> Use <see cref="IRunnable.Stopping"/> instead. This event is maintained for backward
|
|
|
+ /// compatibility and will be raised alongside the new <see cref="IRunnable.Stopping"/> event.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
+ [Obsolete ("Use IRunnable.Stopping instead. This event is maintained for backward compatibility.")]
|
|
|
public event EventHandler<ToplevelClosingEventArgs>? Closing;
|
|
|
|
|
|
/// <summary>
|
|
|
/// Invoked when the <see cref="Toplevel"/> <see cref="SessionToken"/> has begun to be loaded. A Loaded event handler
|
|
|
/// is a good place to finalize initialization before calling Run.
|
|
|
/// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// <b>Obsolete:</b> Use <see cref="View.Initialized"/> instead. The Loaded event conceptually maps to
|
|
|
+ /// the Initialized event from the ISupportInitialize pattern, which is now part of IRunnable.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
+ [Obsolete ("Use View.Initialized instead. The Loaded event maps to the Initialized event from ISupportInitialize.")]
|
|
|
public event EventHandler? Loaded;
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -144,6 +181,13 @@ public partial class Toplevel : View
|
|
|
/// <see cref="IApplication.Run(Toplevel, Func{Exception, bool})"/> on this <see cref="Toplevel"/>.
|
|
|
/// </para>
|
|
|
/// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// <b>Obsolete:</b> Use <see cref="View.Initialized"/> instead. The Ready event is similar to Initialized
|
|
|
+ /// but was fired later in the lifecycle. The IRunnable architecture consolidates these into Initialized.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
+ [Obsolete ("Use View.Initialized instead. The Ready event is consolidated into the Initialized event.")]
|
|
|
public event EventHandler? Ready;
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -159,6 +203,13 @@ public partial class Toplevel : View
|
|
|
/// Invoked when the Toplevel <see cref="SessionToken"/> has been unloaded. A Unloaded event handler is a good place
|
|
|
/// to dispose objects after calling <see cref="IApplication.End(SessionToken)"/>.
|
|
|
/// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// <b>Obsolete:</b> Use <see cref="IRunnable.Stopped"/> instead. The Unloaded event is consolidated into
|
|
|
+ /// the Stopped event in the IRunnable architecture.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
+ [Obsolete ("Use IRunnable.Stopped instead. The Unloaded event is consolidated into the Stopped event.")]
|
|
|
public event EventHandler? Unloaded;
|
|
|
|
|
|
internal virtual void OnActivate (Toplevel deactivated) { Activate?.Invoke (this, new (deactivated)); }
|
|
|
@@ -203,6 +254,226 @@ public partial class Toplevel : View
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
+ #region IRunnable Implementation
|
|
|
+
|
|
|
+ // Note: Running property is already defined above in the Life Cycle region (line 86)
|
|
|
+ // Note: Initializing and Initialized events are inherited from View (ISupportInitialize pattern)
|
|
|
+
|
|
|
+ /// <inheritdoc/>
|
|
|
+ public event EventHandler<System.ComponentModel.CancelEventArgs>? Stopping;
|
|
|
+
|
|
|
+ /// <inheritdoc/>
|
|
|
+ public event EventHandler? Stopped;
|
|
|
+
|
|
|
+ /// <inheritdoc/>
|
|
|
+ public event EventHandler<RunnableActivatingEventArgs>? Activating;
|
|
|
+
|
|
|
+ /// <inheritdoc/>
|
|
|
+ public event EventHandler<RunnableEventArgs>? Activated;
|
|
|
+
|
|
|
+ /// <inheritdoc/>
|
|
|
+ public event EventHandler<RunnableDeactivatingEventArgs>? Deactivating;
|
|
|
+
|
|
|
+ /// <inheritdoc/>
|
|
|
+ public event EventHandler<RunnableEventArgs>? Deactivated;
|
|
|
+
|
|
|
+ /// <inheritdoc/>
|
|
|
+ public virtual void RaiseStoppingEvent ()
|
|
|
+ {
|
|
|
+ // CWP Phase 1: Pre-notification via virtual method (can cancel)
|
|
|
+ if (OnStopping ())
|
|
|
+ {
|
|
|
+ return; // Stopping canceled
|
|
|
+ }
|
|
|
+
|
|
|
+ // CWP Phase 2: Event notification (can cancel)
|
|
|
+ var args = new System.ComponentModel.CancelEventArgs ();
|
|
|
+ Stopping?.Invoke (this, args);
|
|
|
+
|
|
|
+ if (args.Cancel)
|
|
|
+ {
|
|
|
+ return; // Stopping canceled
|
|
|
+ }
|
|
|
+
|
|
|
+ // CWP Phase 3: Perform the work (stop the session)
|
|
|
+ Running = false;
|
|
|
+
|
|
|
+ // CWP Phase 4: Post-notification via virtual method
|
|
|
+ OnStopped ();
|
|
|
+
|
|
|
+ // CWP Phase 5: Post-notification event
|
|
|
+ Stopped?.Invoke (this, EventArgs.Empty);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <inheritdoc/>
|
|
|
+ public virtual bool RaiseActivatingEvent (IRunnable? deactivated)
|
|
|
+ {
|
|
|
+ // CWP Phase 1: Pre-notification via virtual method (can cancel)
|
|
|
+ if (OnActivating (deactivated))
|
|
|
+ {
|
|
|
+ return true; // Activation canceled
|
|
|
+ }
|
|
|
+
|
|
|
+ // CWP Phase 2: Event notification (can cancel)
|
|
|
+ var args = new RunnableActivatingEventArgs (this, deactivated);
|
|
|
+ Activating?.Invoke (this, args);
|
|
|
+
|
|
|
+ if (args.Cancel)
|
|
|
+ {
|
|
|
+ return true; // Activation canceled
|
|
|
+ }
|
|
|
+
|
|
|
+ // CWP Phase 3: Work is done by Application (setting Current)
|
|
|
+ // CWP Phase 4 & 5: Call post-notification methods
|
|
|
+ OnActivated (deactivated);
|
|
|
+
|
|
|
+ return false; // Activation succeeded
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <inheritdoc/>
|
|
|
+ public virtual void RaiseActivatedEvent (IRunnable? deactivated)
|
|
|
+ {
|
|
|
+ Activated?.Invoke (this, new RunnableEventArgs (this));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <inheritdoc/>
|
|
|
+ public virtual bool RaiseDeactivatingEvent (IRunnable? activated)
|
|
|
+ {
|
|
|
+ // CWP Phase 1: Pre-notification via virtual method (can cancel)
|
|
|
+ if (OnDeactivating (activated))
|
|
|
+ {
|
|
|
+ return true; // Deactivation canceled
|
|
|
+ }
|
|
|
+
|
|
|
+ // CWP Phase 2: Event notification (can cancel)
|
|
|
+ var args = new RunnableDeactivatingEventArgs (this, activated);
|
|
|
+ Deactivating?.Invoke (this, args);
|
|
|
+
|
|
|
+ if (args.Cancel)
|
|
|
+ {
|
|
|
+ return true; // Deactivation canceled
|
|
|
+ }
|
|
|
+
|
|
|
+ // CWP Phase 3: Work is done by Application (changing Current)
|
|
|
+ // CWP Phase 4 & 5: Call post-notification methods
|
|
|
+ OnDeactivated (activated);
|
|
|
+
|
|
|
+ return false; // Deactivation succeeded
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <inheritdoc/>
|
|
|
+ public virtual void RaiseDeactivatedEvent (IRunnable? activated)
|
|
|
+ {
|
|
|
+ Deactivated?.Invoke (this, new RunnableEventArgs (this));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Called before <see cref="Stopping"/> event. Override to cancel stopping.
|
|
|
+ /// </summary>
|
|
|
+ /// <returns><see langword="true"/> to cancel; <see langword="false"/> to proceed.</returns>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// This is the first phase of the Cancellable Work Pattern for stopping.
|
|
|
+ /// Default implementation calls the legacy <see cref="OnClosing"/> method for backward compatibility.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
+ protected virtual bool OnStopping ()
|
|
|
+ {
|
|
|
+ // For backward compatibility, delegate to legacy OnClosing method
|
|
|
+ var ev = new ToplevelClosingEventArgs (this);
|
|
|
+ return OnClosing (ev);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Called after session has stopped. Override for post-stop cleanup.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// Default implementation does nothing. For backward compatibility, the legacy <see cref="Closed"/>
|
|
|
+ /// event is raised by Application.End().
|
|
|
+ /// </remarks>
|
|
|
+ protected virtual void OnStopped ()
|
|
|
+ {
|
|
|
+ // Default: do nothing
|
|
|
+ // Note: Legacy Closed event is raised by Application.End()
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Called before <see cref="Activating"/> event. Override to cancel activation.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="deactivated">The previously active runnable being deactivated, or null if none.</param>
|
|
|
+ /// <returns><see langword="true"/> to cancel; <see langword="false"/> to proceed.</returns>
|
|
|
+ /// <remarks>
|
|
|
+ /// Default implementation returns false (allow activation). For backward compatibility,
|
|
|
+ /// the legacy <see cref="OnActivate"/> method is called after activation succeeds.
|
|
|
+ /// </remarks>
|
|
|
+ protected virtual bool OnActivating (IRunnable? deactivated)
|
|
|
+ {
|
|
|
+ return false; // Default: allow activation
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Called after activation succeeds. Override for post-activation logic.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="deactivated">The previously active runnable that was deactivated, or null if none.</param>
|
|
|
+ /// <remarks>
|
|
|
+ /// Default implementation raises the <see cref="Activated"/> event and calls the legacy
|
|
|
+ /// <see cref="OnActivate"/> method for backward compatibility.
|
|
|
+ /// </remarks>
|
|
|
+ protected virtual void OnActivated (IRunnable? deactivated)
|
|
|
+ {
|
|
|
+ RaiseActivatedEvent (deactivated);
|
|
|
+
|
|
|
+ // For backward compatibility, call legacy OnActivate if deactivated is a Toplevel
|
|
|
+ if (deactivated is Toplevel tl)
|
|
|
+ {
|
|
|
+ OnActivate (tl);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // If not a Toplevel, still raise the legacy Activate event with null
|
|
|
+ Activate?.Invoke (this, new ToplevelEventArgs (null));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Called before <see cref="Deactivating"/> event. Override to cancel deactivation.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="activated">The newly activated runnable, or null if none.</param>
|
|
|
+ /// <returns><see langword="true"/> to cancel; <see langword="false"/> to proceed.</returns>
|
|
|
+ /// <remarks>
|
|
|
+ /// Default implementation returns false (allow deactivation).
|
|
|
+ /// </remarks>
|
|
|
+ protected virtual bool OnDeactivating (IRunnable? activated)
|
|
|
+ {
|
|
|
+ return false; // Default: allow deactivation
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Called after deactivation succeeds. Override for post-deactivation logic.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="activated">The newly activated runnable, or null if none.</param>
|
|
|
+ /// <remarks>
|
|
|
+ /// Default implementation raises the <see cref="Deactivated"/> event and calls the legacy
|
|
|
+ /// <see cref="OnDeactivate"/> method for backward compatibility.
|
|
|
+ /// </remarks>
|
|
|
+ protected virtual void OnDeactivated (IRunnable? activated)
|
|
|
+ {
|
|
|
+ RaiseDeactivatedEvent (activated);
|
|
|
+
|
|
|
+ // For backward compatibility, call legacy OnDeactivate if activated is a Toplevel
|
|
|
+ if (activated is Toplevel tl)
|
|
|
+ {
|
|
|
+ OnDeactivate (tl);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // If not a Toplevel, still raise the legacy Deactivate event with null
|
|
|
+ Deactivate?.Invoke (this, new ToplevelEventArgs (null));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
#region Size / Position Management
|
|
|
|
|
|
// TODO: Make cancelable?
|