Runnable.cs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. namespace Terminal.Gui.ViewBase;
  2. /// <summary>
  3. /// Base implementation of <see cref="IRunnable"/> for views that can be run as sessions.
  4. /// </summary>
  5. /// <remarks>
  6. /// <para>
  7. /// Views can derive from this class or implement <see cref="IRunnable"/> directly.
  8. /// This base class provides a complete reference implementation of the <see cref="IRunnable"/>
  9. /// interface following Terminal.Gui's Cancellable Work Pattern.
  10. /// </para>
  11. /// <para>
  12. /// To customize lifecycle behavior, override the protected virtual methods:
  13. /// <see cref="OnStopping"/>, <see cref="OnStopped"/>, <see cref="OnActivating"/>,
  14. /// <see cref="OnActivated"/>, <see cref="OnDeactivating"/>, <see cref="OnDeactivated"/>.
  15. /// </para>
  16. /// </remarks>
  17. public class Runnable : View, IRunnable
  18. {
  19. /// <inheritdoc/>
  20. public bool Running { get; set; }
  21. #region IRunnable Implementation (RaiseXxxEvent Methods)
  22. /// <inheritdoc/>
  23. public virtual void RaiseStoppingEvent ()
  24. {
  25. // CWP Phase 1: Pre-notification via virtual method (can cancel)
  26. if (OnStopping ())
  27. {
  28. return; // Stopping canceled
  29. }
  30. // CWP Phase 2: Event notification (can cancel)
  31. var args = new System.ComponentModel.CancelEventArgs ();
  32. Stopping?.Invoke (this, args);
  33. if (args.Cancel)
  34. {
  35. return; // Stopping canceled
  36. }
  37. // CWP Phase 3: Perform the work (stop the session)
  38. Running = false;
  39. // CWP Phase 4: Post-notification via virtual method
  40. OnStopped ();
  41. // CWP Phase 5: Post-notification event
  42. Stopped?.Invoke (this, EventArgs.Empty);
  43. }
  44. /// <inheritdoc/>
  45. public virtual bool RaiseActivatingEvent (IRunnable? deactivated)
  46. {
  47. // CWP Phase 1: Pre-notification via virtual method (can cancel)
  48. if (OnActivating (deactivated))
  49. {
  50. return true; // Activation canceled
  51. }
  52. // CWP Phase 2: Event notification (can cancel)
  53. var args = new RunnableActivatingEventArgs (this, deactivated);
  54. Activating?.Invoke (this, args);
  55. if (args.Cancel)
  56. {
  57. return true; // Activation canceled
  58. }
  59. // CWP Phase 3: Work is done by Application (setting Current)
  60. // CWP Phase 4 & 5: Call post-notification methods
  61. OnActivated (deactivated);
  62. return false; // Activation succeeded
  63. }
  64. /// <inheritdoc/>
  65. public virtual void RaiseActivatedEvent (IRunnable? deactivated)
  66. {
  67. Activated?.Invoke (this, new RunnableEventArgs (this));
  68. }
  69. /// <inheritdoc/>
  70. public virtual bool RaiseDeactivatingEvent (IRunnable? activated)
  71. {
  72. // CWP Phase 1: Pre-notification via virtual method (can cancel)
  73. if (OnDeactivating (activated))
  74. {
  75. return true; // Deactivation canceled
  76. }
  77. // CWP Phase 2: Event notification (can cancel)
  78. var args = new RunnableDeactivatingEventArgs (this, activated);
  79. Deactivating?.Invoke (this, args);
  80. if (args.Cancel)
  81. {
  82. return true; // Deactivation canceled
  83. }
  84. // CWP Phase 3: Work is done by Application (changing Current)
  85. // CWP Phase 4 & 5: Call post-notification methods
  86. OnDeactivated (activated);
  87. return false; // Deactivation succeeded
  88. }
  89. /// <inheritdoc/>
  90. public virtual void RaiseDeactivatedEvent (IRunnable? activated)
  91. {
  92. Deactivated?.Invoke (this, new RunnableEventArgs (this));
  93. }
  94. #endregion
  95. #region Protected Virtual Methods (Override Pattern)
  96. /// <summary>
  97. /// Called before <see cref="Stopping"/> event. Override to cancel stopping.
  98. /// </summary>
  99. /// <returns><see langword="true"/> to cancel; <see langword="false"/> to proceed.</returns>
  100. /// <remarks>
  101. /// <para>
  102. /// This is the first phase of the Cancellable Work Pattern for stopping.
  103. /// Default implementation returns <see langword="false"/> (allow stopping).
  104. /// </para>
  105. /// <para>
  106. /// Override this method to provide custom logic for determining whether the runnable
  107. /// should stop (e.g., prompting the user to save changes).
  108. /// </para>
  109. /// </remarks>
  110. protected virtual bool OnStopping ()
  111. {
  112. return false; // Default: allow stopping
  113. }
  114. /// <summary>
  115. /// Called after session has stopped. Override for post-stop cleanup.
  116. /// </summary>
  117. /// <remarks>
  118. /// <para>
  119. /// This is the fourth phase of the Cancellable Work Pattern for stopping.
  120. /// At this point, <see cref="Running"/> is <see langword="false"/>.
  121. /// Default implementation does nothing.
  122. /// </para>
  123. /// <para>
  124. /// Override this method to perform cleanup work that should occur after the session stops.
  125. /// </para>
  126. /// </remarks>
  127. protected virtual void OnStopped ()
  128. {
  129. // Default: do nothing
  130. }
  131. /// <summary>
  132. /// Called before <see cref="Activating"/> event. Override to cancel activation.
  133. /// </summary>
  134. /// <param name="deactivated">The previously active runnable being deactivated, or null if none.</param>
  135. /// <returns><see langword="true"/> to cancel; <see langword="false"/> to proceed.</returns>
  136. /// <remarks>
  137. /// <para>
  138. /// This is the first phase of the Cancellable Work Pattern for activation.
  139. /// Default implementation returns <see langword="false"/> (allow activation).
  140. /// </para>
  141. /// <para>
  142. /// Override this method to provide custom logic for determining whether the runnable
  143. /// should become active.
  144. /// </para>
  145. /// </remarks>
  146. protected virtual bool OnActivating (IRunnable? deactivated)
  147. {
  148. return false; // Default: allow activation
  149. }
  150. /// <summary>
  151. /// Called after activation succeeds. Override for post-activation logic.
  152. /// </summary>
  153. /// <param name="deactivated">The previously active runnable that was deactivated, or null if none.</param>
  154. /// <remarks>
  155. /// <para>
  156. /// This is the fourth phase of the Cancellable Work Pattern for activation.
  157. /// Default implementation calls <see cref="RaiseActivatedEvent"/>.
  158. /// </para>
  159. /// <para>
  160. /// Override this method to perform work that should occur after activation
  161. /// (e.g., setting focus, updating UI). Overrides must call base to ensure the
  162. /// <see cref="Activated"/> event is raised.
  163. /// </para>
  164. /// </remarks>
  165. protected virtual void OnActivated (IRunnable? deactivated)
  166. {
  167. RaiseActivatedEvent (deactivated);
  168. }
  169. /// <summary>
  170. /// Called before <see cref="Deactivating"/> event. Override to cancel deactivation.
  171. /// </summary>
  172. /// <param name="activated">The newly activated runnable, or null if none.</param>
  173. /// <returns><see langword="true"/> to cancel; <see langword="false"/> to proceed.</returns>
  174. /// <remarks>
  175. /// <para>
  176. /// This is the first phase of the Cancellable Work Pattern for deactivation.
  177. /// Default implementation returns <see langword="false"/> (allow deactivation).
  178. /// </para>
  179. /// <para>
  180. /// Override this method to provide custom logic for determining whether the runnable
  181. /// should be deactivated (e.g., preventing switching away if unsaved changes exist).
  182. /// </para>
  183. /// </remarks>
  184. protected virtual bool OnDeactivating (IRunnable? activated)
  185. {
  186. return false; // Default: allow deactivation
  187. }
  188. /// <summary>
  189. /// Called after deactivation succeeds. Override for post-deactivation logic.
  190. /// </summary>
  191. /// <param name="activated">The newly activated runnable, or null if none.</param>
  192. /// <remarks>
  193. /// <para>
  194. /// This is the fourth phase of the Cancellable Work Pattern for deactivation.
  195. /// Default implementation calls <see cref="RaiseDeactivatedEvent"/>.
  196. /// </para>
  197. /// <para>
  198. /// Override this method to perform work that should occur after deactivation
  199. /// (e.g., saving state, releasing resources). Overrides must call base to ensure the
  200. /// <see cref="Deactivated"/> event is raised.
  201. /// </para>
  202. /// </remarks>
  203. protected virtual void OnDeactivated (IRunnable? activated)
  204. {
  205. RaiseDeactivatedEvent (activated);
  206. }
  207. #endregion
  208. #region Events
  209. // Note: Initializing and Initialized events are inherited from View (ISupportInitialize pattern)
  210. /// <inheritdoc/>
  211. public event EventHandler<System.ComponentModel.CancelEventArgs>? Stopping;
  212. /// <inheritdoc/>
  213. public event EventHandler? Stopped;
  214. /// <inheritdoc/>
  215. public event EventHandler<RunnableActivatingEventArgs>? Activating;
  216. /// <inheritdoc/>
  217. public event EventHandler<RunnableEventArgs>? Activated;
  218. /// <inheritdoc/>
  219. public event EventHandler<RunnableDeactivatingEventArgs>? Deactivating;
  220. /// <inheritdoc/>
  221. public event EventHandler<RunnableEventArgs>? Deactivated;
  222. #endregion
  223. /// <summary>
  224. /// Stops and closes this runnable session.
  225. /// </summary>
  226. /// <remarks>
  227. /// <para>
  228. /// This method calls <see cref="RaiseStoppingEvent"/> to initiate the stopping process.
  229. /// The Application infrastructure will update this once IApplication supports IRunnable directly.
  230. /// </para>
  231. /// </remarks>
  232. public virtual void RequestStop ()
  233. {
  234. // TODO: Phase 3 - Update Application.RequestStop to accept IRunnable
  235. // For now, directly call RaiseStoppingEvent which follows CWP
  236. RaiseStoppingEvent ();
  237. }
  238. }