From 4e1d62b8f5d3b8ba01ec258029c84fd7148db470 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:10:32 +0000 Subject: [PATCH] =?UTF-8?q?Simplify=20IRunnable:=20Rename=20Activating?= =?UTF-8?q?=E2=86=92Starting,=20remove=20Toplevel=20complexity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: tig <585482+tig@users.noreply.github.com> --- Terminal.Gui/App/IRunnable.cs | 92 ++------ Terminal.Gui/App/RunnableEventArgs.cs | 88 +------- Terminal.Gui/ViewBase/Runnable.cs | 236 +++++++------------- Terminal.Gui/Views/Dialog.cs | 58 +---- Terminal.Gui/Views/Toplevel.cs | 277 +----------------------- Tests/UnitTests/Views/IRunnableTests.cs | 164 -------------- 6 files changed, 111 insertions(+), 804 deletions(-) delete mode 100644 Tests/UnitTests/Views/IRunnableTests.cs diff --git a/Terminal.Gui/App/IRunnable.cs b/Terminal.Gui/App/IRunnable.cs index 3c299cd82..9d97674c8 100644 --- a/Terminal.Gui/App/IRunnable.cs +++ b/Terminal.Gui/App/IRunnable.cs @@ -17,7 +17,7 @@ namespace Terminal.Gui.App; /// /// /// This interface follows the Terminal.Gui Cancellable Work Pattern (CWP) for lifecycle events -/// where cancellation makes sense (Stopping, Activating, Deactivating). +/// where cancellation makes sense (Stopping, Starting). /// /// public interface IRunnable @@ -53,60 +53,30 @@ public interface IRunnable void RaiseStoppingEvent (); /// - /// Raises the event. - /// Called by when this runnable is becoming the active session. + /// Raises the event. + /// Called by or when this runnable session is starting. /// - /// The previously active runnable being deactivated, or null if none. - /// if activation was canceled; otherwise . + /// if starting was canceled; otherwise . /// /// - /// This method implements the Cancellable Work Pattern for activation: + /// This method implements the Cancellable Work Pattern for starting: /// 1. Calls virtual method (can cancel) - /// 2. Raises event (can cancel) + /// 2. Raises event (can cancel) /// 3. If canceled, returns true - /// 4. If not canceled, calls and returns false + /// 4. If not canceled, calls and returns false /// /// - bool RaiseActivatingEvent (IRunnable? deactivated); + bool RaiseStartingEvent (); /// - /// Raises the event. - /// Called by after activation succeeds. + /// Raises the event. + /// Called by after starting succeeds. /// - /// The previously active runnable that was deactivated, or null if none. /// - /// This is the post-notification phase of activation. Implementations should raise the - /// event. + /// This is the post-notification phase of starting. Implementations should raise the + /// event. /// - void RaiseActivatedEvent (IRunnable? deactivated); - - /// - /// Raises the event. - /// Called by when switching to a new runnable. - /// - /// The newly activated runnable, or null if none. - /// if deactivation was canceled; otherwise . - /// - /// - /// This method implements the Cancellable Work Pattern for deactivation: - /// 1. Calls virtual method (can cancel) - /// 2. Raises event (can cancel) - /// 3. If canceled, returns true - /// 4. If not canceled, calls and returns false - /// - /// - bool RaiseDeactivatingEvent (IRunnable? activated); - - /// - /// Raises the event. - /// Called by after deactivation succeeds. - /// - /// The newly activated runnable, or null if none. - /// - /// This is the post-notification phase of deactivation. Implementations should raise the - /// event. - /// - void RaiseDeactivatedEvent (IRunnable? activated); + void RaiseStartedEvent (); #endregion @@ -162,40 +132,24 @@ public interface IRunnable event EventHandler? Stopped; /// - /// Raised when the runnable session is about to become active (the current session). - /// Can be canceled by setting to . + /// Raised when the runnable session is about to start running. + /// Can be canceled by setting to . /// /// - /// Subscribe to this event to prevent activation or to perform pre-activation work. + /// Subscribe to this event to prevent starting or to perform pre-start work. + /// This aligns with and . /// - event EventHandler? Activating; + event EventHandler? Starting; /// - /// Raised when the runnable session has become active. + /// Raised when the runnable session has started running. /// /// - /// This is the post-notification event in the Cancellable Work Pattern pair with . - /// Subscribe to this event for post-activation logic (e.g., setting focus). + /// This is the post-notification event in the Cancellable Work Pattern pair with . + /// Subscribe to this event for post-start logic (e.g., setting focus). + /// This aligns with and . /// - event EventHandler? Activated; - - /// - /// Raised when the runnable session is about to cease being active (another session is activating). - /// Can be canceled by setting to . - /// - /// - /// Subscribe to this event to prevent deactivation or to perform pre-deactivation work. - /// - event EventHandler? Deactivating; - - /// - /// Raised when the runnable session has ceased being active. - /// - /// - /// This is the post-notification event in the Cancellable Work Pattern pair with . - /// Subscribe to this event for cleanup or state preservation after deactivation. - /// - event EventHandler? Deactivated; + event EventHandler? Started; #endregion } diff --git a/Terminal.Gui/App/RunnableEventArgs.cs b/Terminal.Gui/App/RunnableEventArgs.cs index 3fa6a2b58..797ffe4b1 100644 --- a/Terminal.Gui/App/RunnableEventArgs.cs +++ b/Terminal.Gui/App/RunnableEventArgs.cs @@ -1,87 +1,5 @@ namespace Terminal.Gui.App; -/// -/// Event args for lifecycle events that provide information about the runnable. -/// -/// -/// Used for post-notification events that cannot be canceled: -/// and . -/// -public class RunnableEventArgs : EventArgs -{ - /// - /// Initializes a new instance of . - /// - /// The runnable involved in the event. - public RunnableEventArgs (IRunnable runnable) - { - Runnable = runnable; - } - - /// - /// Gets the runnable involved in the event. - /// - public IRunnable Runnable { get; } -} - -/// -/// Event args for event. Allows cancellation. -/// -/// -/// This event is raised when a runnable session is about to become active. It can be canceled -/// by setting to . -/// -public class RunnableActivatingEventArgs : System.ComponentModel.CancelEventArgs -{ - /// - /// Initializes a new instance of . - /// - /// The runnable that is being activated. - /// The runnable that is being deactivated, or null if none. - public RunnableActivatingEventArgs (IRunnable activating, IRunnable? deactivated) - { - Activating = activating; - Deactivated = deactivated; - } - - /// - /// Gets the runnable that is being activated. - /// - public IRunnable Activating { get; } - - /// - /// Gets the runnable that is being deactivated, or null if none. - /// - public IRunnable? Deactivated { get; } -} - -/// -/// Event args for event. Allows cancellation. -/// -/// -/// This event is raised when a runnable session is about to cease being active. It can be canceled -/// by setting to . -/// -public class RunnableDeactivatingEventArgs : System.ComponentModel.CancelEventArgs -{ - /// - /// Initializes a new instance of . - /// - /// The runnable that is being deactivated. - /// The runnable that is being activated, or null if none. - public RunnableDeactivatingEventArgs (IRunnable deactivating, IRunnable? activated) - { - Deactivating = deactivating; - Activated = activated; - } - - /// - /// Gets the runnable that is being deactivated. - /// - public IRunnable Deactivating { get; } - - /// - /// Gets the runnable that is being activated, or null if none. - /// - public IRunnable? Activated { get; } -} +// Note: IRunnable lifecycle events (Starting, Started, Stopping, Stopped, Initializing, Initialized) +// use standard EventArgs or System.ComponentModel.CancelEventArgs. +// No custom event args are needed for the simplified IRunnable interface. diff --git a/Terminal.Gui/ViewBase/Runnable.cs b/Terminal.Gui/ViewBase/Runnable.cs index 37b94b339..feb8f0e02 100644 --- a/Terminal.Gui/ViewBase/Runnable.cs +++ b/Terminal.Gui/ViewBase/Runnable.cs @@ -11,8 +11,7 @@ namespace Terminal.Gui.ViewBase; /// /// /// To customize lifecycle behavior, override the protected virtual methods: -/// , , , -/// , , . +/// , , , . /// /// public class Runnable : View, IRunnable @@ -22,6 +21,42 @@ public class Runnable : View, IRunnable #region IRunnable Implementation (RaiseXxxEvent Methods) + /// + public virtual bool RaiseStartingEvent () + { + // CWP Phase 1: Pre-notification via virtual method (can cancel) + if (OnStarting ()) + { + return true; // Starting canceled + } + + // CWP Phase 2: Event notification (can cancel) + var args = new System.ComponentModel.CancelEventArgs (); + Starting?.Invoke (this, args); + + if (args.Cancel) + { + return true; // Starting canceled + } + + // CWP Phase 3: Perform the work (mark as running) + Running = true; + + // CWP Phase 4: Post-notification via virtual method + OnStarted (); + + // CWP Phase 5: Post-notification event + Started?.Invoke (this, EventArgs.Empty); + + return false; // Starting succeeded + } + + /// + public virtual void RaiseStartedEvent () + { + Started?.Invoke (this, EventArgs.Empty); + } + /// public virtual void RaiseStoppingEvent () { @@ -50,72 +85,47 @@ public class Runnable : View, IRunnable Stopped?.Invoke (this, EventArgs.Empty); } - /// - 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 - } - - /// - public virtual void RaiseActivatedEvent (IRunnable? deactivated) - { - Activated?.Invoke (this, new RunnableEventArgs (this)); - } - - /// - 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 - } - - /// - public virtual void RaiseDeactivatedEvent (IRunnable? activated) - { - Deactivated?.Invoke (this, new RunnableEventArgs (this)); - } - #endregion #region Protected Virtual Methods (Override Pattern) + /// + /// Called before event. Override to cancel starting. + /// + /// to cancel; to proceed. + /// + /// + /// This is the first phase of the Cancellable Work Pattern for starting. + /// Default implementation returns (allow starting). + /// + /// + /// Override this method to provide custom logic for determining whether the runnable + /// should start (e.g., validating preconditions). + /// + /// + protected virtual bool OnStarting () + { + return false; // Default: allow starting + } + + /// + /// Called after session has started. Override for post-start work. + /// + /// + /// + /// This is the fourth phase of the Cancellable Work Pattern for starting. + /// At this point, is . + /// Default implementation does nothing. + /// + /// + /// Override this method to perform work that should occur after the session starts. + /// + /// + protected virtual void OnStarted () + { + // Default: do nothing + } + /// /// Called before event. Override to cancel stopping. /// @@ -153,110 +163,24 @@ public class Runnable : View, IRunnable // Default: do nothing } - /// - /// Called before event. Override to cancel activation. - /// - /// The previously active runnable being deactivated, or null if none. - /// to cancel; to proceed. - /// - /// - /// This is the first phase of the Cancellable Work Pattern for activation. - /// Default implementation returns (allow activation). - /// - /// - /// Override this method to provide custom logic for determining whether the runnable - /// should become active. - /// - /// - protected virtual bool OnActivating (IRunnable? deactivated) - { - return false; // Default: allow activation - } - - /// - /// Called after activation succeeds. Override for post-activation logic. - /// - /// The previously active runnable that was deactivated, or null if none. - /// - /// - /// This is the fourth phase of the Cancellable Work Pattern for activation. - /// Default implementation calls . - /// - /// - /// Override this method to perform work that should occur after activation - /// (e.g., setting focus, updating UI). Overrides must call base to ensure the - /// event is raised. - /// - /// - protected virtual void OnActivated (IRunnable? deactivated) - { - RaiseActivatedEvent (deactivated); - } - - /// - /// Called before event. Override to cancel deactivation. - /// - /// The newly activated runnable, or null if none. - /// to cancel; to proceed. - /// - /// - /// This is the first phase of the Cancellable Work Pattern for deactivation. - /// Default implementation returns (allow deactivation). - /// - /// - /// Override this method to provide custom logic for determining whether the runnable - /// should be deactivated (e.g., preventing switching away if unsaved changes exist). - /// - /// - protected virtual bool OnDeactivating (IRunnable? activated) - { - return false; // Default: allow deactivation - } - - /// - /// Called after deactivation succeeds. Override for post-deactivation logic. - /// - /// The newly activated runnable, or null if none. - /// - /// - /// This is the fourth phase of the Cancellable Work Pattern for deactivation. - /// Default implementation calls . - /// - /// - /// Override this method to perform work that should occur after deactivation - /// (e.g., saving state, releasing resources). Overrides must call base to ensure the - /// event is raised. - /// - /// - protected virtual void OnDeactivated (IRunnable? activated) - { - RaiseDeactivatedEvent (activated); - } - #endregion #region Events // Note: Initializing and Initialized events are inherited from View (ISupportInitialize pattern) + /// + public event EventHandler? Starting; + + /// + public event EventHandler? Started; + /// public event EventHandler? Stopping; /// public event EventHandler? Stopped; - /// - public event EventHandler? Activating; - - /// - public event EventHandler? Activated; - - /// - public event EventHandler? Deactivating; - - /// - public event EventHandler? Deactivated; - #endregion /// diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs index 6ebe0a93a..4037d7312 100644 --- a/Terminal.Gui/Views/Dialog.cs +++ b/Terminal.Gui/Views/Dialog.cs @@ -7,19 +7,13 @@ namespace Terminal.Gui.Views; /// 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 +public class Dialog : Window { /// /// Initializes a new instance of the class with no s. @@ -66,24 +60,6 @@ public class Dialog : Window, IModalRunnable _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 @@ -109,14 +85,7 @@ public class Dialog : Window, IModalRunnable } /// 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.")] + /// The default value is . public bool Canceled { get { return _canceled; } @@ -132,29 +101,6 @@ public class Dialog : Window, IModalRunnable } } - /// - /// 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 /// . diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index f3228e0e4..fedc501f4 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -17,12 +17,8 @@ namespace Terminal.Gui.Views; /// and run (e.g. s). To run a Toplevel, create the and call /// . /// -/// -/// Toplevel implements to support the runnable session lifecycle with proper event handling -/// following the Cancellable Work Pattern (CWP). -/// /// -public partial class Toplevel : View, IRunnable +public partial class Toplevel : View { /// /// Initializes a new instance of the class, @@ -96,60 +92,27 @@ public partial class Toplevel : View, IRunnable /// public bool IsLoaded { get; private set; } + // TODO: IRunnable: Re-implement as an event on IRunnable; IRunnable.Activating/Activate /// Invoked when the Toplevel active. - /// - /// - /// Obsolete: Use instead. This event is maintained for backward - /// compatibility and will be raised alongside the new event. - /// - /// - [Obsolete ("Use IRunnable.Activated instead. This event is maintained for backward compatibility.")] public event EventHandler? Activate; + // TODO: IRunnable: Re-implement as an event on IRunnable; IRunnable.Deactivating/Deactivate? /// Invoked when the Toplevel ceases to be active. - /// - /// - /// Obsolete: Use instead. This event is maintained for backward - /// compatibility and will be raised alongside the new event. - /// - /// - [Obsolete ("Use IRunnable.Deactivated instead. This event is maintained for backward compatibility.")] public event EventHandler? Deactivate; /// Invoked when the Toplevel's is closed by . - /// - /// - /// Obsolete: This event is maintained for backward compatibility. The IRunnable architecture - /// combines this functionality into the event. - /// - /// - [Obsolete ("Use IRunnable.Stopped instead. This event is maintained for backward compatibility.")] public event EventHandler? Closed; /// /// Invoked when the Toplevel's is being closed by /// . /// - /// - /// - /// Obsolete: Use instead. This event is maintained for backward - /// compatibility and will be raised alongside the new event. - /// - /// - [Obsolete ("Use IRunnable.Stopping instead. This event is maintained for backward compatibility.")] public event EventHandler? Closing; /// /// Invoked when the has begun to be loaded. A Loaded event handler /// is a good place to finalize initialization before calling Run. /// - /// - /// - /// Obsolete: Use instead. The Loaded event conceptually maps to - /// the Initialized event from the ISupportInitialize pattern, which is now part of IRunnable. - /// - /// - [Obsolete ("Use View.Initialized instead. The Loaded event maps to the Initialized event from ISupportInitialize.")] public event EventHandler? Loaded; /// @@ -181,13 +144,6 @@ public partial class Toplevel : View, IRunnable /// on this . /// /// - /// - /// - /// Obsolete: Use instead. The Ready event is similar to Initialized - /// but was fired later in the lifecycle. The IRunnable architecture consolidates these into Initialized. - /// - /// - [Obsolete ("Use View.Initialized instead. The Ready event is consolidated into the Initialized event.")] public event EventHandler? Ready; /// @@ -203,13 +159,6 @@ public partial class Toplevel : View, IRunnable /// Invoked when the Toplevel has been unloaded. A Unloaded event handler is a good place /// to dispose objects after calling . /// - /// - /// - /// Obsolete: Use instead. The Unloaded event is consolidated into - /// the Stopped event in the IRunnable architecture. - /// - /// - [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)); } @@ -254,226 +203,6 @@ public partial class Toplevel : View, IRunnable #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) - - /// - public event EventHandler? Stopping; - - /// - public event EventHandler? Stopped; - - /// - public event EventHandler? Activating; - - /// - public event EventHandler? Activated; - - /// - public event EventHandler? Deactivating; - - /// - public event EventHandler? Deactivated; - - /// - 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); - } - - /// - 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 - } - - /// - public virtual void RaiseActivatedEvent (IRunnable? deactivated) - { - Activated?.Invoke (this, new RunnableEventArgs (this)); - } - - /// - 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 - } - - /// - public virtual void RaiseDeactivatedEvent (IRunnable? activated) - { - Deactivated?.Invoke (this, new RunnableEventArgs (this)); - } - - /// - /// Called before event. Override to cancel stopping. - /// - /// to cancel; to proceed. - /// - /// - /// This is the first phase of the Cancellable Work Pattern for stopping. - /// Default implementation calls the legacy method for backward compatibility. - /// - /// - protected virtual bool OnStopping () - { - // For backward compatibility, delegate to legacy OnClosing method - var ev = new ToplevelClosingEventArgs (this); - return OnClosing (ev); - } - - /// - /// Called after session has stopped. Override for post-stop cleanup. - /// - /// - /// Default implementation does nothing. For backward compatibility, the legacy - /// event is raised by Application.End(). - /// - protected virtual void OnStopped () - { - // Default: do nothing - // Note: Legacy Closed event is raised by Application.End() - } - - /// - /// Called before event. Override to cancel activation. - /// - /// The previously active runnable being deactivated, or null if none. - /// to cancel; to proceed. - /// - /// Default implementation returns false (allow activation). For backward compatibility, - /// the legacy method is called after activation succeeds. - /// - protected virtual bool OnActivating (IRunnable? deactivated) - { - return false; // Default: allow activation - } - - /// - /// Called after activation succeeds. Override for post-activation logic. - /// - /// The previously active runnable that was deactivated, or null if none. - /// - /// Default implementation raises the event and calls the legacy - /// method for backward compatibility. - /// - 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)); - } - } - - /// - /// Called before event. Override to cancel deactivation. - /// - /// The newly activated runnable, or null if none. - /// to cancel; to proceed. - /// - /// Default implementation returns false (allow deactivation). - /// - protected virtual bool OnDeactivating (IRunnable? activated) - { - return false; // Default: allow deactivation - } - - /// - /// Called after deactivation succeeds. Override for post-deactivation logic. - /// - /// The newly activated runnable, or null if none. - /// - /// Default implementation raises the event and calls the legacy - /// method for backward compatibility. - /// - 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? diff --git a/Tests/UnitTests/Views/IRunnableTests.cs b/Tests/UnitTests/Views/IRunnableTests.cs deleted file mode 100644 index d6c9db6e3..000000000 --- a/Tests/UnitTests/Views/IRunnableTests.cs +++ /dev/null @@ -1,164 +0,0 @@ -using Xunit; -using Xunit.Abstractions; - -namespace UnitTests.ViewsTests; - -public class IRunnableTests (ITestOutputHelper output) -{ - private readonly ITestOutputHelper _output = output; - - [Fact] - public void Toplevel_Implements_IRunnable () - { - var top = new Toplevel (); - Assert.IsAssignableFrom (top); - } - - [Fact] - public void Dialog_Implements_IModalRunnable () - { - var dialog = new Dialog (); - Assert.IsAssignableFrom (dialog); - Assert.IsAssignableFrom> (dialog); - } - - [Fact] - public void Runnable_Base_Class_Works () - { - var runnable = new Runnable (); - Assert.IsAssignableFrom (runnable); - Assert.False (runnable.Running); - } - - [Fact] - [AutoInitShutdown] - public void Dialog_Result_Property_Works () - { - var dialog = new Dialog (); - var btn1 = new Button { Text = "OK" }; - var btn2 = new Button { Text = "Cancel" }; - - dialog.AddButton (btn1); - dialog.AddButton (btn2); - - // Result should be null by default - Assert.Null (dialog.Result); - - // Simulate clicking the first button by invoking Accept - btn1.InvokeCommand (Command.Accept); - - // Result should now be 0 (first button) - Assert.Equal (0, dialog.Result); - - // Reset - dialog.Result = null; - - // Simulate clicking the second button - btn2.InvokeCommand (Command.Accept); - - // Result should now be 1 (second button) - Assert.Equal (1, dialog.Result); - } - - [Fact] - [AutoInitShutdown] - public void Toplevel_Legacy_Events_Still_Work () - { - // Verify that legacy Toplevel events still fire for backward compatibility - var eventsRaised = new List (); - var top = new Toplevel (); - -#pragma warning disable CS0618 // Type or member is obsolete - top.Activate += (s, e) => eventsRaised.Add ("Activate"); -#pragma warning restore CS0618 - - var token = Application.Begin (top); - - // Legacy event should still fire - Assert.Contains ("Activate", eventsRaised); - - Application.End (token); - } - - [Fact] - public void IRunnable_Stopping_Event_Works () - { - // Test that the Stopping event can be subscribed to and fires - var top = new Toplevel (); - var stoppingFired = false; - var stoppedFired = false; - - top.Stopping += (s, e) => { stoppingFired = true; }; - top.Stopped += (s, e) => { stoppedFired = true; }; - - top.Running = true; - top.RaiseStoppingEvent (); - - Assert.True (stoppingFired); - Assert.True (stoppedFired); - Assert.False (top.Running); - } - - [Fact] - public void IRunnable_Stopping_Can_Be_Canceled () - { - // Test that the Stopping event can cancel the stop operation - var top = new Toplevel (); - var stoppingFired = false; - var stoppedFired = false; - - top.Stopping += (s, e) => - { - stoppingFired = true; - e.Cancel = true; // Cancel the stop - }; - - top.Stopped += (s, e) => { stoppedFired = true; }; - - top.Running = true; - top.RaiseStoppingEvent (); - - Assert.True (stoppingFired); - Assert.False (stoppedFired); // Should not fire because it was canceled - Assert.True (top.Running); // Should still be running - } - - [Fact] - [AutoInitShutdown] - public void IRunnable_Initialization_Events_Fire () - { - // Test that Initializing and Initialized events fire - var initializingFired = false; - var initializedFired = false; - var top = new Toplevel (); - - top.Initializing += (s, e) => { initializingFired = true; }; - top.Initialized += (s, e) => { initializedFired = true; }; - - var token = Application.Begin (top); - - Assert.True (initializingFired); - Assert.True (initializedFired); - - Application.End (token); - } - - [Fact] - public void IRunnable_Activating_And_Activated_Events_Can_Fire () - { - // Test that manually calling the activation methods works - var activatingFired = false; - var activatedFired = false; - var top = new Toplevel (); - - top.Activating += (s, e) => { activatingFired = true; }; - top.Activated += (s, e) => { activatedFired = true; }; - - // Manually trigger activation - bool canceled = top.RaiseActivatingEvent (null); - - Assert.False (canceled); - Assert.True (activatingFired); - Assert.True (activatedFired); - } -}