using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Terminal.Gui; namespace UICatalog; /// /// Base class for each demo/scenario. /// /// To define a new scenario: /// /// /// /// Create a new .cs file in the Scenarios directory that derives from /// . /// /// /// /// /// Annotate the derived class with a /// attribute specifying the scenario's name and description. /// /// /// /// /// Add one or more attributes to the class specifying /// which categories the scenario belongs to. If you don't specify a category the scenario will show up /// in "_All". /// /// /// /// /// Implement the override which will be called when a user selects the /// scenario to run. /// /// /// /// /// Optionally, implement the and/or overrides to /// provide a custom implementation. /// /// /// /// /// /// The UI Catalog program uses reflection to find all scenarios and adds them to the ListViews. Press ENTER to /// run the selected scenario. Press the default quit key to quit. /// /// /// /// The example below is provided in the `Scenarios` directory as a generic sample that can be copied and re-named: /// /// using Terminal.Gui; /// /// namespace UICatalog { /// [ScenarioMetadata (Name: "Generic", Description: "Generic sample - A template for creating new Scenarios")] /// [ScenarioCategory ("Controls")] /// class MyScenario : Scenario { /// public override void Setup () /// { /// // Put your scenario code here, e.g. /// Win.Add (new Button () { Text = "Press me!", /// X = Pos.Center (), /// Y = Pos.Center (), /// Clicked = () => MessageBox.Query (20, 7, "Hi", "Neat?", "Yes", "No") /// }); /// } /// } /// } /// /// public class Scenario : IDisposable { private static int _maxScenarioNameLen = 30; public string Theme = "Default"; public string TopLevelColorScheme = "Base"; private bool _disposedValue; /// /// Helper function to get the list of categories a belongs to (defined in /// ) /// /// list of category names public List GetCategories () { return ScenarioCategory.GetCategories (GetType ()); } /// Helper to get the Description (defined in ) /// public string GetDescription () { return ScenarioMetadata.GetDescription (GetType ()); } /// Helper to get the Name (defined in ) /// public string GetName () { return ScenarioMetadata.GetName (GetType ()); } /// Helper to get the and the Name (defined in ) /// public string GetQuitKeyAndName () { return $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"; } /// /// Returns a list of all instanaces defined in the project, sorted by /// . /// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class /// public static ObservableCollection GetScenarios () { List objects = new (); foreach (Type type in typeof (Scenario).Assembly.ExportedTypes .Where ( myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)) )) { var scenario = (Scenario)Activator.CreateInstance (type); objects.Add (scenario); _maxScenarioNameLen = Math.Max (_maxScenarioNameLen, scenario.GetName ().Length + 1); } return new (objects.OrderBy (s => s.GetName ()).ToList ()); } /// /// Called by UI Catalog to run the . This is the main entry point for the /// . /// /// /// /// Scenario developers are encouraged to override this method as the primary way of authoring a new /// scenario. /// /// /// The base implementation calls , , and . /// /// public virtual void Main () { } /// Gets the Scenario Name + Description with the Description padded based on the longest known Scenario name. /// public override string ToString () { return $"{GetName ().PadRight (_maxScenarioNameLen)}{GetDescription ()}"; } #region IDispose public void Dispose () { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose (true); GC.SuppressFinalize (this); } protected virtual void Dispose (bool disposing) { if (!_disposedValue) { if (disposing) { } _disposedValue = true; } } #endregion IDispose /// Returns a list of all Categories set by all of the s defined in the project. internal static ObservableCollection GetAllCategories () { List aCategories = []; aCategories = typeof (Scenario).Assembly.GetTypes () .Where ( myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario))) .Select (type => System.Attribute.GetCustomAttributes (type).ToList ()) .Aggregate ( aCategories, (current, attrs) => current .Union ( attrs.Where (a => a is ScenarioCategory) .Select (a => ((ScenarioCategory)a).Name)) .ToList ()); // Sort ObservableCollection categories = new (aCategories.OrderBy (c => c).ToList ()); // Put "All" at the top categories.Insert (0, "All Scenarios"); return categories; } /// Defines the category names used to categorize a [AttributeUsage (AttributeTargets.Class, AllowMultiple = true)] public class ScenarioCategory (string name) : System.Attribute { /// Static helper function to get the Categories given a Type /// /// list of category names public static List GetCategories (Type t) { return GetCustomAttributes (t) .ToList () .Where (a => a is ScenarioCategory) .Select (a => ((ScenarioCategory)a).Name) .ToList (); } /// Static helper function to get the Name given a Type /// /// Name of the category public static string GetName (Type t) { return ((ScenarioCategory)GetCustomAttributes (t) [0]).Name; } /// Category Name public string Name { get; set; } = name; } /// Defines the metadata (Name and Description) for a [AttributeUsage (AttributeTargets.Class)] public class ScenarioMetadata (string name, string description) : System.Attribute { /// Description public string Description { get; set; } = description; /// Static helper function to get the Description given a Type /// /// public static string GetDescription (Type t) { return ((ScenarioMetadata)GetCustomAttributes (t) [0]).Description; } /// Static helper function to get the Name given a Type /// /// public static string GetName (Type t) { return ((ScenarioMetadata)GetCustomAttributes (t) [0]).Name; } /// Name public string Name { get; set; } = name; } }