IRunnable.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. namespace Terminal.Gui.App;
  2. /// <summary>
  3. /// Non-generic base interface for runnable views. Provides common members without type parameter.
  4. /// </summary>
  5. /// <remarks>
  6. /// <para>
  7. /// This interface enables storing heterogeneous runnables in collections (e.g.,
  8. /// <see cref="IApplication.SessionStack"/>)
  9. /// while preserving type safety at usage sites via <see cref="IRunnable{TResult}"/>.
  10. /// </para>
  11. /// <para>
  12. /// Most code should use <see cref="IRunnable{TResult}"/> directly. This base interface is primarily
  13. /// for framework infrastructure (session management, stacking, etc.).
  14. /// </para>
  15. /// <para>
  16. /// A runnable view executes as a self-contained blocking session with its own lifecycle,
  17. /// event loop iteration, and focus management./>
  18. /// blocks until
  19. /// <see cref="IApplication.RequestStop()"/> is called.
  20. /// </para>
  21. /// <para>
  22. /// This interface follows the Terminal.Gui Cancellable Work Pattern (CWP) for all lifecycle events.
  23. /// </para>
  24. /// </remarks>
  25. /// <seealso cref="IRunnable{TResult}"/>
  26. /// <seealso cref="IApplication.Run(IRunnable, Func{Exception, bool})"/>
  27. public interface IRunnable
  28. {
  29. #region Result
  30. /// <summary>
  31. /// Gets or sets the result data extracted when the session was accepted, or <see langword="null"/> if not accepted.
  32. /// </summary>
  33. /// <remarks>
  34. /// <para>
  35. /// This is the non-generic version of the result property. For type-safe access, cast to
  36. /// <see cref="IRunnable{TResult}"/> or access the derived interface's <c>Result</c> property directly.
  37. /// </para>
  38. /// <para>
  39. /// Implementations should set this in the <see cref="RaiseIsRunningChanging"/> method
  40. /// (when stopping, i.e., <c>newIsRunning == false</c>) by extracting data from
  41. /// views before they are disposed.
  42. /// </para>
  43. /// <para>
  44. /// <see langword="null"/> indicates the session was stopped without accepting (ESC key, close without action).
  45. /// Non-<see langword="null"/> contains the result data.
  46. /// </para>
  47. /// </remarks>
  48. object? Result { get; set; }
  49. #endregion Result
  50. #region Running or not (added to/removed from RunnableSessionStack)
  51. /// <summary>
  52. /// Sets the application context for this runnable. Called from <see cref="IApplication.Begin(IRunnable)"/>.
  53. /// </summary>
  54. /// <param name="app"></param>
  55. void SetApp (IApplication app);
  56. /// <summary>
  57. /// Gets whether this runnable session is currently running (i.e., on the
  58. /// <see cref="IApplication.SessionStack"/>).
  59. /// </summary>
  60. /// <remarks>
  61. /// <para>
  62. /// This property returns a cached value that is updated atomically when the runnable is added to or
  63. /// removed from the session stack. The cached state ensures thread-safe access without race conditions.
  64. /// </para>
  65. /// <para>
  66. /// Returns <see langword="true"/> if this runnable is currently on the <see cref="IApplication.SessionStack"/>,
  67. /// <see langword="false"/> otherwise.
  68. /// </para>
  69. /// <para>
  70. /// Runnables are added to the stack during <see cref="IApplication.Begin(IRunnable)"/> and removed in
  71. /// <see cref="IApplication.End(SessionToken)"/>.
  72. /// </para>
  73. /// </remarks>
  74. bool IsRunning { get; }
  75. /// <summary>
  76. /// Sets the cached IsRunning state. Called by ApplicationImpl within the session stack lock.
  77. /// This method is internal to the framework and should not be called by application code.
  78. /// </summary>
  79. /// <param name="value">The new IsRunning value.</param>
  80. void SetIsRunning (bool value);
  81. /// <summary>
  82. /// Requests that this runnable session stop.
  83. /// </summary>
  84. public void RequestStop ();
  85. /// <summary>
  86. /// Called by the framework to raise the <see cref="IsRunningChanging"/> event.
  87. /// </summary>
  88. /// <param name="oldIsRunning">The current value of <see cref="IsRunning"/>.</param>
  89. /// <param name="newIsRunning">The new value of <see cref="IsRunning"/> (true = starting, false = stopping).</param>
  90. /// <returns><see langword="true"/> if the change was canceled; otherwise <see langword="false"/>.</returns>
  91. /// <remarks>
  92. /// <para>
  93. /// This method implements the Cancellable Work Pattern. It calls the protected virtual method first,
  94. /// then raises the event if not canceled.
  95. /// </para>
  96. /// <para>
  97. /// When <paramref name="newIsRunning"/> is <see langword="false"/> (stopping), this is the ideal place
  98. /// for implementations to extract <c>Result</c> from views before the runnable is removed from the stack.
  99. /// </para>
  100. /// </remarks>
  101. bool RaiseIsRunningChanging (bool oldIsRunning, bool newIsRunning);
  102. /// <summary>
  103. /// Raised when <see cref="IsRunning"/> is changing (e.g., when <see cref="IApplication.Begin(IRunnable)"/> or
  104. /// <see cref="IApplication.End(SessionToken)"/> is called).
  105. /// Can be canceled by setting `args.Cancel` to <see langword="true"/>.
  106. /// </summary>
  107. /// <remarks>
  108. /// <para>
  109. /// Subscribe to this event to participate in the runnable lifecycle before state changes occur.
  110. /// When <see cref="CancelEventArgs{T}.NewValue"/> is <see langword="false"/> (stopping),
  111. /// this is the ideal place to extract <c>Result</c> before views are disposed and to optionally
  112. /// cancel the stop operation (e.g., prompt to save changes).
  113. /// </para>
  114. /// <para>
  115. /// This event follows the Terminal.Gui Cancellable Work Pattern (CWP).
  116. /// </para>
  117. /// </remarks>
  118. event EventHandler<CancelEventArgs<bool>>? IsRunningChanging;
  119. /// <summary>
  120. /// Called by the framework to raise the <see cref="IsRunningChanged"/> event.
  121. /// </summary>
  122. /// <param name="newIsRunning">The new value of <see cref="IsRunning"/> (true = started, false = stopped).</param>
  123. /// <remarks>
  124. /// This method is called after the state change has occurred and cannot be canceled.
  125. /// </remarks>
  126. void RaiseIsRunningChangedEvent (bool newIsRunning);
  127. /// <summary>
  128. /// Raised after <see cref="IsRunning"/> has changed (after the runnable has been added to or removed from the
  129. /// <see cref="IApplication.SessionStack"/>).
  130. /// </summary>
  131. /// <remarks>
  132. /// <para>
  133. /// Subscribe to this event to perform post-state-change logic. When <see cref="EventArgs{T}.Value"/> is
  134. /// <see langword="true"/>,
  135. /// the runnable has started and is on the stack. When <see langword="false"/>, the runnable has stopped and been
  136. /// removed from the stack.
  137. /// </para>
  138. /// <para>
  139. /// This event follows the Terminal.Gui Cancellable Work Pattern (CWP).
  140. /// </para>
  141. /// </remarks>
  142. event EventHandler<EventArgs<bool>>? IsRunningChanged;
  143. #endregion Running or not (added to/removed from RunnableSessionStack)
  144. #region Modal or not (top of RunnableSessionStack or not)
  145. /// <summary>
  146. /// Gets whether this runnable session is at the top of the <see cref="IApplication.SessionStack"/> and thus
  147. /// exclusively receiving mouse and keyboard input.
  148. /// </summary>
  149. /// <remarks>
  150. /// <para>
  151. /// This property returns a cached value that is updated atomically when the runnable's modal state changes.
  152. /// The cached state ensures thread-safe access without race conditions.
  153. /// </para>
  154. /// <para>
  155. /// Returns <see langword="true"/> if this runnable is at the top of the stack (i.e., <c>this == app.TopRunnable</c>),
  156. /// <see langword="false"/> otherwise.
  157. /// </para>
  158. /// <para>
  159. /// The runnable at the top of the stack gets all mouse/keyboard input and thus is running "modally".
  160. /// </para>
  161. /// </remarks>
  162. bool IsModal { get; }
  163. /// <summary>
  164. /// Sets the cached IsModal state. Called by ApplicationImpl within the session stack lock.
  165. /// This method is internal to the framework and should not be called by application code.
  166. /// </summary>
  167. /// <param name="value">The new IsModal value.</param>
  168. void SetIsModal (bool value);
  169. /// <summary>
  170. /// Gets or sets whether a stop has been requested for this runnable session.
  171. /// </summary>
  172. bool StopRequested { get; set; }
  173. /// <summary>
  174. /// Called by the framework to raise the <see cref="IsModalChanged"/> event.
  175. /// </summary>
  176. /// <param name="newIsModal">The new value of <see cref="IsModal"/> (true = became modal/top, false = no longer modal).</param>
  177. /// <remarks>
  178. /// This method is called after the modal state change has occurred and cannot be canceled.
  179. /// </remarks>
  180. void RaiseIsModalChangedEvent (bool newIsModal);
  181. /// <summary>
  182. /// Raised after this runnable has become modal (top of stack) or ceased being modal.
  183. /// </summary>
  184. /// <remarks>
  185. /// <para>
  186. /// Subscribe to this event to perform post-activation logic (e.g., setting focus, updating UI state).
  187. /// When <see cref="EventArgs{T}.Value"/> is <see langword="true"/>, the runnable became modal (top of
  188. /// stack).
  189. /// When <see langword="false"/>, the runnable is no longer modal (another runnable is on top).
  190. /// </para>
  191. /// <para>
  192. /// This event follows the Terminal.Gui Cancellable Work Pattern (CWP).
  193. /// </para>
  194. /// </remarks>
  195. event EventHandler<EventArgs<bool>>? IsModalChanged;
  196. #endregion Modal or not (top of RunnableSessionStack or not)
  197. }
  198. /// <summary>
  199. /// Defines a view that can be run as an independent blocking session with <see cref="IApplication.Run(IRunnable, Func{Exception, bool})"/>,
  200. /// returning a typed result.
  201. /// </summary>
  202. /// <typeparam name="TResult">
  203. /// The type of result data returned when the session completes.
  204. /// Common types: <see cref="int"/> for button indices, <see cref="string"/> for file paths,
  205. /// custom types for complex form data.
  206. /// </typeparam>
  207. /// <remarks>
  208. /// <para>
  209. /// A runnable view executes as a self-contained blocking session with its own lifecycle,
  210. /// event loop iteration, and focus management. <see cref="IApplication.Run(IRunnable, Func{Exception, bool})"/> blocks until
  211. /// <see cref="IApplication.RequestStop()"/> is called.
  212. /// </para>
  213. /// <para>
  214. /// When <see cref="Result"/> is <see langword="null"/>, the session was stopped without being accepted
  215. /// (e.g., ESC key pressed, window closed). When non-<see langword="null"/>, it contains the result data
  216. /// extracted in <see cref="IRunnable.RaiseIsRunningChanging"/> (when stopping) before views are disposed.
  217. /// </para>
  218. /// <para>
  219. /// Implementing <see cref="IRunnable{TResult}"/> does not require deriving from any specific
  220. /// base class or using <see cref="ViewArrangement.Overlapped"/>. These are orthogonal concerns.
  221. /// </para>
  222. /// <para>
  223. /// This interface follows the Terminal.Gui Cancellable Work Pattern (CWP) for all lifecycle events.
  224. /// </para>
  225. /// </remarks>
  226. /// <seealso cref="IRunnable"/>
  227. /// <seealso cref="IApplication.Run(IRunnable, Func{Exception, bool})"/>
  228. public interface IRunnable<TResult> : IRunnable
  229. {
  230. /// <summary>
  231. /// Gets or sets the result data extracted when the session was accepted, or <see langword="null"/> if not accepted.
  232. /// </summary>
  233. /// <remarks>
  234. /// <para>
  235. /// Implementations should set this in the <see cref="IRunnable.RaiseIsRunningChanging"/> method
  236. /// (when stopping, i.e., <c>newIsRunning == false</c>) by extracting data from
  237. /// views before they are disposed.
  238. /// </para>
  239. /// <para>
  240. /// <see langword="null"/> indicates the session was stopped without accepting (ESC key, close without action).
  241. /// Non-<see langword="null"/> contains the type-safe result data.
  242. /// </para>
  243. /// </remarks>
  244. new TResult? Result { get; set; }
  245. }