Scenario.cs 12 KB

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