Scenario.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  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>Helper to get the <see cref="Application.QuitKey"/> and the <see cref="Scenario"/> Name (defined in <see cref="ScenarioMetadata"/>)</summary>
  91. /// <returns></returns>
  92. public string GetQuitKeyAndName () { return $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"; }
  93. /// <summary>
  94. /// Returns a list of all <see cref="Scenario"/> instanaces defined in the project, sorted by
  95. /// <see cref="ScenarioMetadata.Name"/>.
  96. /// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class
  97. /// </summary>
  98. public static ObservableCollection<Scenario> GetScenarios ()
  99. {
  100. List<Scenario> objects = new ();
  101. foreach (Type type in typeof (Scenario).Assembly.ExportedTypes
  102. .Where (
  103. myType => myType.IsClass
  104. && !myType.IsAbstract
  105. && myType.IsSubclassOf (typeof (Scenario))
  106. ))
  107. {
  108. var scenario = (Scenario)Activator.CreateInstance (type);
  109. objects.Add (scenario);
  110. _maxScenarioNameLen = Math.Max (_maxScenarioNameLen, scenario.GetName ().Length + 1);
  111. }
  112. return new (objects.OrderBy (s => s.GetName ()).ToList ());
  113. }
  114. /// <summary>
  115. /// Called by UI Catalog to run the <see cref="Scenario"/>. This is the main entry point for the <see cref="Scenario"/>
  116. /// .
  117. /// </summary>
  118. /// <remarks>
  119. /// <para>
  120. /// Scenario developers are encouraged to override this method as the primary way of authoring a new
  121. /// scenario.
  122. /// </para>
  123. /// <para>
  124. /// The base implementation calls <see cref="Init"/>, <see cref="Setup"/>, and <see cref="Run"/>.
  125. /// </para>
  126. /// </remarks>
  127. public virtual void Main ()
  128. {
  129. }
  130. /// <summary>Gets the Scenario Name + Description with the Description padded based on the longest known Scenario name.</summary>
  131. /// <returns></returns>
  132. public override string ToString () { return $"{GetName ().PadRight (_maxScenarioNameLen)}{GetDescription ()}"; }
  133. #region IDispose
  134. public void Dispose ()
  135. {
  136. // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
  137. Dispose (true);
  138. GC.SuppressFinalize (this);
  139. }
  140. protected virtual void Dispose (bool disposing)
  141. {
  142. if (!_disposedValue)
  143. {
  144. if (disposing)
  145. {
  146. }
  147. _disposedValue = true;
  148. }
  149. }
  150. #endregion IDispose
  151. /// <summary>Returns a list of all Categories set by all of the <see cref="Scenario"/>s defined in the project.</summary>
  152. internal static ObservableCollection<string> GetAllCategories ()
  153. {
  154. List<string> aCategories = [];
  155. aCategories = typeof (Scenario).Assembly.GetTypes ()
  156. .Where (
  157. myType => myType.IsClass
  158. && !myType.IsAbstract
  159. && myType.IsSubclassOf (typeof (Scenario)))
  160. .Select (type => System.Attribute.GetCustomAttributes (type).ToList ())
  161. .Aggregate (
  162. aCategories,
  163. (current, attrs) => current
  164. .Union (
  165. attrs.Where (a => a is ScenarioCategory)
  166. .Select (a => ((ScenarioCategory)a).Name))
  167. .ToList ());
  168. // Sort
  169. ObservableCollection<string> categories = new (aCategories.OrderBy (c => c).ToList ());
  170. // Put "All" at the top
  171. categories.Insert (0, "All Scenarios");
  172. return categories;
  173. }
  174. /// <summary>Defines the category names used to categorize a <see cref="Scenario"/></summary>
  175. [AttributeUsage (AttributeTargets.Class, AllowMultiple = true)]
  176. public class ScenarioCategory (string name) : System.Attribute
  177. {
  178. /// <summary>Static helper function to get the <see cref="Scenario"/> Categories given a Type</summary>
  179. /// <param name="t"></param>
  180. /// <returns>list of category names</returns>
  181. public static List<string> GetCategories (Type t)
  182. {
  183. return GetCustomAttributes (t)
  184. .ToList ()
  185. .Where (a => a is ScenarioCategory)
  186. .Select (a => ((ScenarioCategory)a).Name)
  187. .ToList ();
  188. }
  189. /// <summary>Static helper function to get the <see cref="Scenario"/> Name given a Type</summary>
  190. /// <param name="t"></param>
  191. /// <returns>Name of the category</returns>
  192. public static string GetName (Type t) { return ((ScenarioCategory)GetCustomAttributes (t) [0]).Name; }
  193. /// <summary>Category Name</summary>
  194. public string Name { get; set; } = name;
  195. }
  196. /// <summary>Defines the metadata (Name and Description) for a <see cref="Scenario"/></summary>
  197. [AttributeUsage (AttributeTargets.Class)]
  198. public class ScenarioMetadata (string name, string description) : System.Attribute
  199. {
  200. /// <summary><see cref="Scenario"/> Description</summary>
  201. public string Description { get; set; } = description;
  202. /// <summary>Static helper function to get the <see cref="Scenario"/> Description given a Type</summary>
  203. /// <param name="t"></param>
  204. /// <returns></returns>
  205. public static string GetDescription (Type t) { return ((ScenarioMetadata)GetCustomAttributes (t) [0]).Description; }
  206. /// <summary>Static helper function to get the <see cref="Scenario"/> Name given a Type</summary>
  207. /// <param name="t"></param>
  208. /// <returns></returns>
  209. public static string GetName (Type t) { return ((ScenarioMetadata)GetCustomAttributes (t) [0]).Name; }
  210. /// <summary><see cref="Scenario"/> Name</summary>
  211. public string Name { get; set; } = name;
  212. }
  213. }