123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- #nullable enable
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Diagnostics;
- using System.Linq;
- using Terminal.Gui;
- namespace UICatalog;
- /// <summary>
- /// <para>Base class for each demo/scenario.</para>
- /// <para>
- /// To define a new scenario:
- /// <list type="number">
- /// <item>
- /// <description>
- /// Create a new <c>.cs</c> file in the <cs>Scenarios</cs> directory that derives from
- /// <see cref="Scenario"/>.
- /// </description>
- /// </item>
- /// <item>
- /// <description>
- /// Annotate the <see cref="Scenario"/> derived class with a
- /// <see cref="ScenarioMetadata"/> attribute specifying the scenario's name and description.
- /// </description>
- /// </item>
- /// <item>
- /// <description>
- /// Add one or more <see cref="ScenarioCategory"/> 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".
- /// </description>
- /// </item>
- /// <item>
- /// <description>
- /// Implement the <see cref="Main"/> override which will be called when a user selects the
- /// scenario to run.
- /// </description>
- /// </item>
- /// </list>
- /// </para>
- /// <para>
- /// 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.
- /// </para>
- /// </summary>
- /// <example>
- /// The example below is provided in the `Scenarios` directory as a generic sample that can be copied and re-named:
- /// <code>
- /// using Terminal.Gui;
- ///
- /// namespace UICatalog.Scenarios;
- ///
- /// [ScenarioMetadata ("Generic", "Generic sample - A template for creating new Scenarios")]
- /// [ScenarioCategory ("Controls")]
- /// public sealed class MyScenario : Scenario
- /// {
- /// public override void Main ()
- /// {
- /// // Init
- /// Application.Init ();
- ///
- /// // Setup - Create a top-level application window and configure it.
- /// Window appWindow = new ()
- /// {
- /// Title = GetQuitKeyAndName (),
- /// };
- ///
- /// var button = new Button { X = Pos.Center (), Y = Pos.Center (), Text = "Press me!" };
- /// button.Accept += (s, e) => MessageBox.ErrorQuery ("Error", "You pressed the button!", "Ok");
- /// appWindow.Add (button);
- ///
- /// // Run - Start the application.
- /// Application.Run (appWindow);
- /// appWindow.Dispose ();
- ///
- /// // Shutdown - Calling Application.Shutdown is required.
- /// Application.Shutdown ();
- /// }
- /// }
- /// </code>
- /// </example>
- public class Scenario : IDisposable
- {
- private static int _maxScenarioNameLen = 30;
- public string TopLevelColorScheme { get; set; } = "Base";
- public BenchmarkResults BenchmarkResults
- {
- get { return _benchmarkResults; }
- }
- private bool _disposedValue;
- /// <summary>
- /// Helper function to get the list of categories a <see cref="Scenario"/> belongs to (defined in
- /// <see cref="ScenarioCategory"/>)
- /// </summary>
- /// <returns>list of category names</returns>
- public List<string> GetCategories () { return ScenarioCategory.GetCategories (GetType ()); }
- /// <summary>Helper to get the <see cref="Scenario"/> Description (defined in <see cref="ScenarioMetadata"/>)</summary>
- /// <returns></returns>
- public string GetDescription () { return ScenarioMetadata.GetDescription (GetType ()); }
- /// <summary>Helper to get the <see cref="Scenario"/> Name (defined in <see cref="ScenarioMetadata"/>)</summary>
- /// <returns></returns>
- public string GetName () { return ScenarioMetadata.GetName (GetType ()); }
- /// <summary>
- /// Helper to get the <see cref="Application.QuitKey"/> and the <see cref="Scenario"/> Name (defined in
- /// <see cref="ScenarioMetadata"/>)
- /// </summary>
- /// <returns></returns>
- public string GetQuitKeyAndName () { return $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"; }
- /// <summary>
- /// Returns a list of all <see cref="Scenario"/> instanaces defined in the project, sorted by
- /// <see cref="ScenarioMetadata.Name"/>.
- /// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class
- /// </summary>
- public static ObservableCollection<Scenario> GetScenarios ()
- {
- List<Scenario> objects = [];
- foreach (Type type in typeof (Scenario).Assembly.ExportedTypes
- .Where (
- myType => myType is { IsClass: true, IsAbstract: false }
- && myType.IsSubclassOf (typeof (Scenario))
- ))
- {
- if (Activator.CreateInstance (type) is not Scenario { } scenario)
- {
- continue;
- }
- objects.Add (scenario);
- _maxScenarioNameLen = Math.Max (_maxScenarioNameLen, scenario.GetName ().Length + 1);
- }
- return new (objects.OrderBy (s => s.GetName ()).ToList ());
- }
- /// <summary>
- /// Called by UI Catalog to run the <see cref="Scenario"/>. This is the main entry point for the <see cref="Scenario"/>
- /// .
- /// </summary>
- public virtual void Main () { }
- private const uint MAX_NATURAL_ITERATIONS = 500; // not including needed for demo keys
- private const uint ABORT_TIMEOUT_MS = 2500;
- private const int DEMO_KEY_PACING_MS = 1; // Must be non-zero
- private readonly object _timeoutLock = new ();
- private object? _timeout;
- private Stopwatch? _stopwatch;
- private readonly BenchmarkResults _benchmarkResults = new BenchmarkResults ();
- public void StartBenchmark ()
- {
- BenchmarkResults.Scenario = GetName ();
- Application.InitializedChanged += OnApplicationOnInitializedChanged;
- }
- public BenchmarkResults EndBenchmark ()
- {
- Application.InitializedChanged -= OnApplicationOnInitializedChanged;
- lock (_timeoutLock)
- {
- if (_timeout is { })
- {
- _timeout = null;
- }
- }
- return _benchmarkResults;
- }
- private List<Key> _demoKeys;
- private int _currentDemoKey = 0;
- private void OnApplicationOnInitializedChanged (object? s, EventArgs<bool> a)
- {
- if (a.CurrentValue)
- {
- lock (_timeoutLock!)
- {
- _timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (ABORT_TIMEOUT_MS), ForceCloseCallback);
- }
- Application.Iteration += OnApplicationOnIteration;
- Application.Driver!.ClearedContents += (sender, args) => BenchmarkResults.ClearedContentCount++;
- Application.Driver!.Refreshed += (sender, args) =>
- {
- BenchmarkResults.RefreshedCount++;
- if (args.CurrentValue)
- {
- BenchmarkResults.UpdatedCount++;
- }
- };
- Application.NotifyNewRunState += OnApplicationNotifyNewRunState;
- _stopwatch = Stopwatch.StartNew ();
- }
- else
- {
- Application.NotifyNewRunState -= OnApplicationNotifyNewRunState;
- Application.Iteration -= OnApplicationOnIteration;
- BenchmarkResults.Duration = _stopwatch!.Elapsed;
- _stopwatch?.Stop ();
- }
- }
- private void OnApplicationOnIteration (object? s, IterationEventArgs a)
- {
- BenchmarkResults.IterationCount++;
- if (BenchmarkResults.IterationCount > MAX_NATURAL_ITERATIONS + (_demoKeys.Count* DEMO_KEY_PACING_MS))
- {
- Application.RequestStop ();
- }
- }
- private void OnApplicationNotifyNewRunState (object? sender, RunStateEventArgs e)
- {
- // Get a list of all subviews under Application.Top (and their subviews, etc.)
- // and subscribe to their DrawComplete event
- void SubscribeAllSubviews (View view)
- {
- view.DrawComplete += (s, a) => BenchmarkResults.DrawCompleteCount++;
- view.SubviewsLaidOut += (s, a) => BenchmarkResults.LaidOutCount++;
- foreach (View subview in view.Subviews)
- {
- SubscribeAllSubviews (subview);
- }
- }
- SubscribeAllSubviews (Application.Top!);
- _currentDemoKey = 0;
- _demoKeys = GetDemoKeyStrokes ();
- Application.AddTimeout (
- new TimeSpan (0, 0, 0, 0, DEMO_KEY_PACING_MS),
- () =>
- {
- if (_currentDemoKey >= _demoKeys.Count)
- {
- return false;
- }
- Application.RaiseKeyDownEvent (_demoKeys [_currentDemoKey++]);
- return true;
- });
- }
- // If the scenario doesn't close within the abort time, this will force it to quit
- private bool ForceCloseCallback ()
- {
- lock (_timeoutLock)
- {
- if (_timeout is { })
- {
- _timeout = null;
- }
- }
- Debug.WriteLine ($@" Failed to Quit with {Application.QuitKey} after {ABORT_TIMEOUT_MS}ms and {BenchmarkResults.IterationCount} iterations. Force quit.");
- Application.RequestStop ();
- return false;
- }
- /// <summary>Gets the Scenario Name + Description with the Description padded based on the longest known Scenario name.</summary>
- /// <returns></returns>
- 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
- /// <summary>Returns a list of all Categories set by all of the <see cref="Scenario"/>s defined in the project.</summary>
- internal static ObservableCollection<string> GetAllCategories ()
- {
- List<string> aCategories = [];
- aCategories = typeof (Scenario).Assembly.GetTypes ()
- .Where (
- myType => myType is { IsClass: true, IsAbstract: false }
- && 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<string> categories = new (aCategories.OrderBy (c => c).ToList ());
- // Put "All" at the top
- categories.Insert (0, "All Scenarios");
- return categories;
- }
- public virtual List<Key> GetDemoKeyStrokes () => new List<Key> ();
- }
|