Scenario.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Linq;
  5. using Terminal.Gui;
  6. namespace UICatalog;
  7. /// <summary>
  8. /// <para>Base class for each demo/scenario.</para>
  9. /// <para>
  10. /// To define a new scenario:
  11. /// <list type="number">
  12. /// <item>
  13. /// <description>
  14. /// Create a new <c>.cs</c> file in the <cs>Scenarios</cs> directory that derives from
  15. /// <see cref="Scenario"/>.
  16. /// </description>
  17. /// </item>
  18. /// <item>
  19. /// <description>
  20. /// Annotate the <see cref="Scenario"/> derived class with a
  21. /// <see cref="Scenario.ScenarioMetadata"/> attribute specifying the scenario's name and description.
  22. /// </description>
  23. /// </item>
  24. /// <item>
  25. /// <description>
  26. /// Add one or more <see cref="Scenario.ScenarioCategory"/> attributes to the class specifying
  27. /// which categories the scenario belongs to. If you don't specify a category the scenario will show up
  28. /// in "_All".
  29. /// </description>
  30. /// </item>
  31. /// <item>
  32. /// <description>
  33. /// Implement the <see cref="Setup"/> override which will be called when a user selects the
  34. /// scenario to run.
  35. /// </description>
  36. /// </item>
  37. /// <item>
  38. /// <description>
  39. /// Optionally, implement the <see cref="Init()"/> and/or <see cref="Run"/> overrides to
  40. /// provide a custom implementation.
  41. /// </description>
  42. /// </item>
  43. /// </list>
  44. /// </para>
  45. /// <para>
  46. /// The UI Catalog program uses reflection to find all scenarios and adds them to the ListViews. Press ENTER to
  47. /// run the selected scenario. Press the default quit key to quit.
  48. /// </para>
  49. /// </summary>
  50. /// <example>
  51. /// The example below is provided in the `Scenarios` directory as a generic sample that can be copied and re-named:
  52. /// <code>
  53. /// using Terminal.Gui;
  54. ///
  55. /// namespace UICatalog {
  56. /// [ScenarioMetadata (Name: "Generic", Description: "Generic sample - A template for creating new Scenarios")]
  57. /// [ScenarioCategory ("Controls")]
  58. /// class MyScenario : Scenario {
  59. /// public override void Setup ()
  60. /// {
  61. /// // Put your scenario code here, e.g.
  62. /// Win.Add (new Button () { Text = "Press me!",
  63. /// X = Pos.Center (),
  64. /// Y = Pos.Center (),
  65. /// Clicked = () => MessageBox.Query (20, 7, "Hi", "Neat?", "Yes", "No")
  66. /// });
  67. /// }
  68. /// }
  69. /// }
  70. /// </code>
  71. /// </example>
  72. public class Scenario : IDisposable
  73. {
  74. private static int _maxScenarioNameLen = 30;
  75. public string Theme = "Default";
  76. public string TopLevelColorScheme = "Base";
  77. private bool _disposedValue;
  78. /// <summary>
  79. /// Helper function to get the list of categories a <see cref="Scenario"/> belongs to (defined in
  80. /// <see cref="ScenarioCategory"/>)
  81. /// </summary>
  82. /// <returns>list of category names</returns>
  83. public List<string> GetCategories () { return ScenarioCategory.GetCategories (GetType ()); }
  84. /// <summary>Helper to get the <see cref="Scenario"/> Description (defined in <see cref="ScenarioMetadata"/>)</summary>
  85. /// <returns></returns>
  86. public string GetDescription () { return ScenarioMetadata.GetDescription (GetType ()); }
  87. /// <summary>Helper to get the <see cref="Scenario"/> Name (defined in <see cref="ScenarioMetadata"/>)</summary>
  88. /// <returns></returns>
  89. public string GetName () { return ScenarioMetadata.GetName (GetType ()); }
  90. /// <summary>
  91. /// Returns a list of all <see cref="Scenario"/> instanaces defined in the project, sorted by
  92. /// <see cref="ScenarioMetadata.Name"/>.
  93. /// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class
  94. /// </summary>
  95. public static ObservableCollection<Scenario> GetScenarios ()
  96. {
  97. List<Scenario> objects = new ();
  98. foreach (Type type in typeof (Scenario).Assembly.ExportedTypes
  99. .Where (
  100. myType => myType.IsClass
  101. && !myType.IsAbstract
  102. && myType.IsSubclassOf (typeof (Scenario))
  103. ))
  104. {
  105. var scenario = (Scenario)Activator.CreateInstance (type);
  106. objects.Add (scenario);
  107. _maxScenarioNameLen = Math.Max (_maxScenarioNameLen, scenario.GetName ().Length + 1);
  108. }
  109. return new (objects.OrderBy (s => s.GetName ()).ToList ());
  110. }
  111. /// <summary>
  112. /// Called by UI Catalog to run the <see cref="Scenario"/>. This is the main entry point for the <see cref="Scenario"/>
  113. /// .
  114. /// </summary>
  115. /// <remarks>
  116. /// <para>
  117. /// Scenario developers are encouraged to override this method as the primary way of authoring a new
  118. /// scenario.
  119. /// </para>
  120. /// <para>
  121. /// The base implementation calls <see cref="Init"/>, <see cref="Setup"/>, and <see cref="Run"/>.
  122. /// </para>
  123. /// </remarks>
  124. public virtual void Main ()
  125. {
  126. Init ();
  127. Setup ();
  128. Run ();
  129. }
  130. /// <summary>
  131. /// Helper that calls <see cref="Application.Init"/> and creates the default <see cref="Terminal.Gui.Window"/>
  132. /// implementation with a frame and label
  133. /// showing the name of the <see cref="Scenario"/> and logic to exit back to the Scenario picker UI. Override
  134. /// <see cref="Init"/> to provide any <see cref="Terminal.Gui.Toplevel"/> behavior needed.
  135. /// </summary>
  136. /// <remarks>
  137. /// <para>
  138. /// The base implementation calls <see cref="Application.Init"/> and creates a <see cref="Window"/> for
  139. /// <see cref="Win"/> and adds it to <see cref="Application.Top"/>.
  140. /// </para>
  141. /// <para>
  142. /// Overrides that do not call the base.<see cref="Run"/>, must call <see cref="Application.Init"/> before
  143. /// creating any views or calling other Terminal.Gui APIs.
  144. /// </para>
  145. /// </remarks>
  146. [ObsoleteAttribute ("This method is obsolete and will be removed in v2. Use Main instead.", false)]
  147. public virtual void Init ()
  148. {
  149. Application.Init ();
  150. ConfigurationManager.Themes.Theme = Theme;
  151. ConfigurationManager.Apply ();
  152. Top = new ();
  153. Win = new ()
  154. {
  155. Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
  156. X = 0,
  157. Y = 0,
  158. Width = Dim.Fill (),
  159. Height = Dim.Fill (),
  160. ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]
  161. };
  162. Top.Add (Win);
  163. }
  164. /// <summary>
  165. /// Runs the <see cref="Scenario"/>. Override to start the <see cref="Scenario"/> using a <see cref="Toplevel"/>
  166. /// different than `Top`.
  167. /// </summary>
  168. /// <remarks>
  169. /// Overrides that do not call the base.<see cref="Run"/>, must call <see cref="Application.Shutdown"/> before
  170. /// returning.
  171. /// </remarks>
  172. [ObsoleteAttribute ("This method is obsolete and will be removed in v2. Use Main instead.", false)]
  173. public virtual void Run ()
  174. {
  175. // Must explicitly call Application.Shutdown method to shutdown.
  176. Application.Run (Top);
  177. }
  178. /// <summary>Override this to implement the <see cref="Scenario"/> setup logic (create controls, etc...).</summary>
  179. /// <remarks>This is typically the best place to put scenario logic code.</remarks>
  180. [ObsoleteAttribute ("This method is obsolete and will be removed in v2. Use Main instead.", false)]
  181. public virtual void Setup () { }
  182. /// <summary>
  183. /// The Toplevel for the <see cref="Scenario"/>. This should be set to <see cref="Terminal.Gui.Application.Top"/>.
  184. /// </summary>
  185. //[ObsoleteAttribute ("This property is obsolete and will be removed in v2. Use Main instead.", false)]
  186. public Toplevel Top { get; set; }
  187. /// <summary>Gets the Scenario Name + Description with the Description padded based on the longest known Scenario name.</summary>
  188. /// <returns></returns>
  189. public override string ToString () { return $"{GetName ().PadRight (_maxScenarioNameLen)}{GetDescription ()}"; }
  190. /// <summary>
  191. /// The Window for the <see cref="Scenario"/>. This should be set to <see cref="Terminal.Gui.Application.Top"/> in
  192. /// most cases.
  193. /// </summary>
  194. //[ObsoleteAttribute ("This property is obsolete and will be removed in v2. Use Main instead.", false)]
  195. public Window Win { get; set; }
  196. #region IDispose
  197. public void Dispose ()
  198. {
  199. // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
  200. Dispose (true);
  201. GC.SuppressFinalize (this);
  202. }
  203. protected virtual void Dispose (bool disposing)
  204. {
  205. if (!_disposedValue)
  206. {
  207. if (disposing)
  208. {
  209. Top?.Dispose ();
  210. Win?.Dispose ();
  211. }
  212. _disposedValue = true;
  213. }
  214. }
  215. #endregion IDispose
  216. /// <summary>Returns a list of all Categories set by all of the <see cref="Scenario"/>s defined in the project.</summary>
  217. internal static ObservableCollection<string> GetAllCategories ()
  218. {
  219. List<string> aCategories = [];
  220. aCategories = typeof (Scenario).Assembly.GetTypes ()
  221. .Where (
  222. myType => myType.IsClass
  223. && !myType.IsAbstract
  224. && myType.IsSubclassOf (typeof (Scenario)))
  225. .Select (type => System.Attribute.GetCustomAttributes (type).ToList ())
  226. .Aggregate (
  227. aCategories,
  228. (current, attrs) => current
  229. .Union (
  230. attrs.Where (a => a is ScenarioCategory)
  231. .Select (a => ((ScenarioCategory)a).Name))
  232. .ToList ());
  233. // Sort
  234. ObservableCollection<string> categories = new (aCategories.OrderBy (c => c).ToList ());
  235. // Put "All" at the top
  236. categories.Insert (0, "All Scenarios");
  237. return categories;
  238. }
  239. /// <summary>Defines the category names used to categorize a <see cref="Scenario"/></summary>
  240. [AttributeUsage (AttributeTargets.Class, AllowMultiple = true)]
  241. public class ScenarioCategory (string Name) : System.Attribute
  242. {
  243. /// <summary>Static helper function to get the <see cref="Scenario"/> Categories given a Type</summary>
  244. /// <param name="t"></param>
  245. /// <returns>list of category names</returns>
  246. public static List<string> GetCategories (Type t)
  247. {
  248. return GetCustomAttributes (t)
  249. .ToList ()
  250. .Where (a => a is ScenarioCategory)
  251. .Select (a => ((ScenarioCategory)a).Name)
  252. .ToList ();
  253. }
  254. /// <summary>Static helper function to get the <see cref="Scenario"/> Name given a Type</summary>
  255. /// <param name="t"></param>
  256. /// <returns>Name of the category</returns>
  257. public static string GetName (Type t) { return ((ScenarioCategory)GetCustomAttributes (t) [0]).Name; }
  258. /// <summary>Category Name</summary>
  259. public string Name { get; set; } = Name;
  260. }
  261. /// <summary>Defines the metadata (Name and Description) for a <see cref="Scenario"/></summary>
  262. [AttributeUsage (AttributeTargets.Class)]
  263. public class ScenarioMetadata (string Name, string Description) : System.Attribute
  264. {
  265. /// <summary><see cref="Scenario"/> Description</summary>
  266. public string Description { get; set; } = Description;
  267. /// <summary>Static helper function to get the <see cref="Scenario"/> Description given a Type</summary>
  268. /// <param name="t"></param>
  269. /// <returns></returns>
  270. public static string GetDescription (Type t) { return ((ScenarioMetadata)GetCustomAttributes (t) [0]).Description; }
  271. /// <summary>Static helper function to get the <see cref="Scenario"/> Name given a Type</summary>
  272. /// <param name="t"></param>
  273. /// <returns></returns>
  274. public static string GetName (Type t) { return ((ScenarioMetadata)GetCustomAttributes (t) [0]).Name; }
  275. /// <summary><see cref="Scenario"/> Name</summary>
  276. public string Name { get; set; } = Name;
  277. }
  278. }