UICatalog.cs 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393
  1. global using Attribute = Terminal.Gui.Attribute;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Collections.ObjectModel;
  5. using System.CommandLine;
  6. using System.CommandLine.Builder;
  7. using System.CommandLine.Parsing;
  8. using System.Data;
  9. using System.Diagnostics;
  10. using System.Diagnostics.CodeAnalysis;
  11. using System.Globalization;
  12. using System.IO;
  13. using System.Linq;
  14. using System.Reflection;
  15. using System.Runtime.InteropServices;
  16. using System.Text;
  17. using System.Text.Json;
  18. using System.Text.Json.Serialization;
  19. using Terminal.Gui;
  20. using static Terminal.Gui.ConfigurationManager;
  21. using Command = Terminal.Gui.Command;
  22. using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment;
  23. #nullable enable
  24. namespace UICatalog;
  25. /// <summary>
  26. /// UI Catalog is a comprehensive sample library for Terminal.Gui. It provides a simple UI for adding to the
  27. /// catalog of scenarios.
  28. /// </summary>
  29. /// <remarks>
  30. /// <para>UI Catalog attempts to satisfy the following goals:</para>
  31. /// <para>
  32. /// <list type="number">
  33. /// <item>
  34. /// <description>Be an easy-to-use showcase for Terminal.Gui concepts and features.</description>
  35. /// </item>
  36. /// <item>
  37. /// <description>Provide sample code that illustrates how to properly implement said concepts & features.</description>
  38. /// </item>
  39. /// <item>
  40. /// <description>Make it easy for contributors to add additional samples in a structured way.</description>
  41. /// </item>
  42. /// </list>
  43. /// </para>
  44. /// </remarks>
  45. public class UICatalogApp
  46. {
  47. private static int _cachedCategoryIndex;
  48. // When a scenario is run, the main app is killed. These items
  49. // are therefore cached so that when the scenario exits the
  50. // main app UI can be restored to previous state
  51. private static int _cachedScenarioIndex;
  52. private static string? _cachedTheme = string.Empty;
  53. private static ObservableCollection<string>? _categories;
  54. [SuppressMessage ("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
  55. private static readonly FileSystemWatcher _currentDirWatcher = new ();
  56. private static ViewDiagnosticFlags _diagnosticFlags;
  57. private static string _forceDriver = string.Empty;
  58. [SuppressMessage ("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
  59. private static readonly FileSystemWatcher _homeDirWatcher = new ();
  60. private static bool _isFirstRunning = true;
  61. private static Options _options;
  62. private static ObservableCollection<Scenario>? _scenarios;
  63. // If set, holds the scenario the user selected
  64. private static Scenario? _selectedScenario;
  65. private static MenuBarItem? _themeMenuBarItem;
  66. private static MenuItem []? _themeMenuItems;
  67. private static string _topLevelColorScheme = string.Empty;
  68. [SerializableConfigurationProperty (Scope = typeof (AppScope), OmitClassName = true)]
  69. [JsonPropertyName ("UICatalog.StatusBar")]
  70. public static bool ShowStatusBar { get; set; } = true;
  71. /// <summary>
  72. /// Gets the message displayed in the About Box. `public` so it can be used from Unit tests.
  73. /// </summary>
  74. /// <returns></returns>
  75. public static string GetAboutBoxMessage ()
  76. {
  77. // NOTE: Do not use multiline verbatim strings here.
  78. // WSL gets all confused.
  79. StringBuilder msg = new ();
  80. msg.AppendLine ("UI Catalog: A comprehensive sample library for");
  81. msg.AppendLine ();
  82. msg.AppendLine ("""
  83. _______ _ _ _____ _
  84. |__ __| (_) | | / ____| (_)
  85. | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _
  86. | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | |
  87. | | __/ | | | | | | | | | | | (_| | || |__| | |_| | |
  88. |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_|
  89. """);
  90. msg.AppendLine ();
  91. msg.AppendLine ("v2 - Pre-Alpha");
  92. msg.AppendLine ();
  93. msg.AppendLine ("https://github.com/gui-cs/Terminal.Gui");
  94. return msg.ToString ();
  95. }
  96. private static void ConfigFileChanged (object sender, FileSystemEventArgs e)
  97. {
  98. if (Application.Top == null)
  99. {
  100. return;
  101. }
  102. // TODO: This is a hack. Figure out how to ensure that the file is fully written before reading it.
  103. //Thread.Sleep (500);
  104. Load ();
  105. Apply ();
  106. }
  107. private static int Main (string [] args)
  108. {
  109. Console.OutputEncoding = Encoding.Default;
  110. if (Debugger.IsAttached)
  111. {
  112. CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
  113. }
  114. _scenarios = Scenario.GetScenarios ();
  115. _categories = Scenario.GetAllCategories ();
  116. // Process command line args
  117. // "UICatalog [--driver <driver>] [--benchmark] [scenario name]"
  118. // If no driver is provided, the default driver is used.
  119. Option<string> driverOption = new Option<string> ("--driver", "The IConsoleDriver to use.").FromAmong (
  120. Application.GetDriverTypes ()
  121. .Select (d => d!.Name)
  122. .ToArray ()
  123. );
  124. driverOption.AddAlias ("-d");
  125. driverOption.AddAlias ("--d");
  126. Option<bool> benchmarkFlag = new Option<bool> ("--benchmark", "Enables benchmarking. If a Scenario is specified, just that Scenario will be benchmarked.");
  127. benchmarkFlag.AddAlias ("-b");
  128. benchmarkFlag.AddAlias ("--b");
  129. Option<uint> benchmarkTimeout = new Option<uint> ("--timeout", getDefaultValue: () => Scenario.BenchmarkTimeout, $"The maximum time in milliseconds to run a benchmark for. Default is {Scenario.BenchmarkTimeout}ms.");
  130. benchmarkTimeout.AddAlias ("-t");
  131. benchmarkTimeout.AddAlias ("--t");
  132. Option<string> resultsFile = new Option<string> ("--file", "The file to save benchmark results to. If not specified, the results will be displayed in a TableView.");
  133. resultsFile.AddAlias ("-f");
  134. resultsFile.AddAlias ("--f");
  135. Argument<string> scenarioArgument = new Argument<string> (
  136. name: "scenario",
  137. description: "The name of the Scenario to run. If not provided, the UI Catalog UI will be shown.",
  138. getDefaultValue: () => "none"
  139. ).FromAmong (
  140. _scenarios.Select (s => s.GetName ())
  141. .Append ("none")
  142. .ToArray ()
  143. );
  144. var rootCommand = new RootCommand ("A comprehensive sample library for Terminal.Gui")
  145. {
  146. scenarioArgument, benchmarkFlag, benchmarkTimeout, resultsFile, driverOption,
  147. };
  148. rootCommand.SetHandler (
  149. context =>
  150. {
  151. var options = new Options
  152. {
  153. Scenario = context.ParseResult.GetValueForArgument (scenarioArgument),
  154. Driver = context.ParseResult.GetValueForOption (driverOption) ?? string.Empty,
  155. Benchmark = context.ParseResult.GetValueForOption (benchmarkFlag),
  156. BenchmarkTimeout = context.ParseResult.GetValueForOption (benchmarkTimeout),
  157. ResultsFile = context.ParseResult.GetValueForOption (resultsFile) ?? string.Empty,
  158. /* etc. */
  159. };
  160. // See https://github.com/dotnet/command-line-api/issues/796 for the rationale behind this hackery
  161. _options = options;
  162. }
  163. );
  164. bool helpShown = false;
  165. var parser = new CommandLineBuilder (rootCommand)
  166. .UseHelp (ctx => helpShown = true)
  167. .Build ();
  168. parser.Invoke (args);
  169. if (helpShown)
  170. {
  171. return 0;
  172. }
  173. Scenario.BenchmarkTimeout = _options.BenchmarkTimeout;
  174. UICatalogMain (_options);
  175. return 0;
  176. }
  177. private static void OpenUrl (string url)
  178. {
  179. if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  180. {
  181. url = url.Replace ("&", "^&");
  182. Process.Start (new ProcessStartInfo ("cmd", $"/c start {url}") { CreateNoWindow = true });
  183. }
  184. else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux))
  185. {
  186. using var process = new Process
  187. {
  188. StartInfo = new ()
  189. {
  190. FileName = "xdg-open",
  191. Arguments = url,
  192. RedirectStandardError = true,
  193. RedirectStandardOutput = true,
  194. CreateNoWindow = true,
  195. UseShellExecute = false
  196. }
  197. };
  198. process.Start ();
  199. }
  200. else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
  201. {
  202. Process.Start ("open", url);
  203. }
  204. }
  205. /// <summary>
  206. /// Shows the UI Catalog selection UI. When the user selects a Scenario to run, the UI Catalog main app UI is
  207. /// killed and the Scenario is run as though it were Application.Top. When the Scenario exits, this function exits.
  208. /// </summary>
  209. /// <returns></returns>
  210. private static Scenario RunUICatalogTopLevel ()
  211. {
  212. // Run UI Catalog UI. When it exits, if _selectedScenario is != null then
  213. // a Scenario was selected. Otherwise, the user wants to quit UI Catalog.
  214. // If the user specified a driver on the command line then use it,
  215. // ignoring Config files.
  216. Application.Init (driverName: _forceDriver);
  217. if (_cachedTheme is null)
  218. {
  219. _cachedTheme = Themes?.Theme;
  220. }
  221. else
  222. {
  223. Themes!.Theme = _cachedTheme;
  224. Apply ();
  225. }
  226. Application.Run<UICatalogTopLevel> ().Dispose ();
  227. Application.Shutdown ();
  228. return _selectedScenario!;
  229. }
  230. private static void StartConfigFileWatcher ()
  231. {
  232. // Setup a file system watcher for `./.tui/`
  233. _currentDirWatcher.NotifyFilter = NotifyFilters.LastWrite;
  234. string assemblyLocation = Assembly.GetExecutingAssembly ().Location;
  235. string tuiDir;
  236. if (!string.IsNullOrEmpty (assemblyLocation))
  237. {
  238. var assemblyFile = new FileInfo (assemblyLocation);
  239. tuiDir = Path.Combine (assemblyFile.Directory!.FullName, ".tui");
  240. }
  241. else
  242. {
  243. tuiDir = Path.Combine (AppContext.BaseDirectory, ".tui");
  244. }
  245. if (!Directory.Exists (tuiDir))
  246. {
  247. Directory.CreateDirectory (tuiDir);
  248. }
  249. _currentDirWatcher.Path = tuiDir;
  250. _currentDirWatcher.Filter = "*config.json";
  251. // Setup a file system watcher for `~/.tui/`
  252. _homeDirWatcher.NotifyFilter = NotifyFilters.LastWrite;
  253. var f = new FileInfo (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile));
  254. tuiDir = Path.Combine (f.FullName, ".tui");
  255. if (!Directory.Exists (tuiDir))
  256. {
  257. Directory.CreateDirectory (tuiDir);
  258. }
  259. _homeDirWatcher.Path = tuiDir;
  260. _homeDirWatcher.Filter = "*config.json";
  261. _currentDirWatcher.Changed += ConfigFileChanged;
  262. //_currentDirWatcher.Created += ConfigFileChanged;
  263. _currentDirWatcher.EnableRaisingEvents = true;
  264. _homeDirWatcher.Changed += ConfigFileChanged;
  265. //_homeDirWatcher.Created += ConfigFileChanged;
  266. _homeDirWatcher.EnableRaisingEvents = true;
  267. }
  268. private static void StopConfigFileWatcher ()
  269. {
  270. _currentDirWatcher.EnableRaisingEvents = false;
  271. _currentDirWatcher.Changed -= ConfigFileChanged;
  272. _currentDirWatcher.Created -= ConfigFileChanged;
  273. _homeDirWatcher.EnableRaisingEvents = false;
  274. _homeDirWatcher.Changed -= ConfigFileChanged;
  275. _homeDirWatcher.Created -= ConfigFileChanged;
  276. }
  277. private static void UICatalogMain (Options options)
  278. {
  279. StartConfigFileWatcher ();
  280. // By setting _forceDriver we ensure that if the user has specified a driver on the command line, it will be used
  281. // regardless of what's in a config file.
  282. Application.ForceDriver = _forceDriver = options.Driver;
  283. // If a Scenario name has been provided on the commandline
  284. // run it and exit when done.
  285. if (options.Scenario != "none")
  286. {
  287. _topLevelColorScheme = "Base";
  288. int item = _scenarios!.IndexOf (
  289. _scenarios!.FirstOrDefault (
  290. s =>
  291. s.GetName ()
  292. .Equals (options.Scenario, StringComparison.OrdinalIgnoreCase)
  293. )!);
  294. _selectedScenario = (Scenario)Activator.CreateInstance (_scenarios [item].GetType ())!;
  295. var results = RunScenario (_selectedScenario, options.Benchmark);
  296. if (results is { })
  297. {
  298. Console.WriteLine (JsonSerializer.Serialize (results, new JsonSerializerOptions ()
  299. {
  300. WriteIndented = true
  301. }));
  302. }
  303. VerifyObjectsWereDisposed ();
  304. return;
  305. }
  306. // Benchmark all Scenarios
  307. if (options.Benchmark)
  308. {
  309. BenchmarkAllScenarios ();
  310. return;
  311. }
  312. while (RunUICatalogTopLevel () is { } scenario)
  313. {
  314. VerifyObjectsWereDisposed ();
  315. Themes!.Theme = _cachedTheme!;
  316. Apply ();
  317. scenario.TopLevelColorScheme = _topLevelColorScheme;
  318. #if DEBUG_IDISPOSABLE
  319. // Measure how long it takes for the app to shut down
  320. var sw = new Stopwatch ();
  321. string scenarioName = scenario.GetName ();
  322. Application.InitializedChanged += ApplicationOnInitializedChanged;
  323. #endif
  324. scenario.Main ();
  325. scenario.Dispose ();
  326. // This call to Application.Shutdown brackets the Application.Init call
  327. // made by Scenario.Init() above
  328. // TODO: Throw if shutdown was not called already
  329. Application.Shutdown ();
  330. VerifyObjectsWereDisposed ();
  331. #if DEBUG_IDISPOSABLE
  332. Application.InitializedChanged -= ApplicationOnInitializedChanged;
  333. void ApplicationOnInitializedChanged (object? sender, EventArgs<bool> e)
  334. {
  335. if (e.CurrentValue)
  336. {
  337. sw.Start ();
  338. }
  339. else
  340. {
  341. sw.Stop ();
  342. Debug.WriteLine ($"Shutdown of {scenarioName} Scenario took {sw.ElapsedMilliseconds}ms");
  343. }
  344. }
  345. #endif
  346. }
  347. StopConfigFileWatcher ();
  348. VerifyObjectsWereDisposed ();
  349. return;
  350. }
  351. private static BenchmarkResults? RunScenario (Scenario scenario, bool benchmark)
  352. {
  353. if (benchmark)
  354. {
  355. scenario.StartBenchmark ();
  356. }
  357. Application.Init (driverName: _forceDriver);
  358. scenario.TopLevelColorScheme = _topLevelColorScheme;
  359. if (benchmark)
  360. {
  361. Application.Screen = new (0, 0, 120, 40);
  362. }
  363. scenario.Main ();
  364. BenchmarkResults? results = null;
  365. if (benchmark)
  366. {
  367. results = scenario.EndBenchmark ();
  368. }
  369. scenario.Dispose ();
  370. // TODO: Throw if shutdown was not called already
  371. Application.Shutdown ();
  372. return results;
  373. }
  374. private static void BenchmarkAllScenarios ()
  375. {
  376. List<BenchmarkResults> resultsList = new List<BenchmarkResults> ();
  377. int maxScenarios = 5;
  378. foreach (var s in _scenarios!)
  379. {
  380. resultsList.Add (RunScenario (s, true)!);
  381. maxScenarios--;
  382. if (maxScenarios == 0)
  383. {
  384. // break;
  385. }
  386. }
  387. if (resultsList.Count > 0)
  388. {
  389. if (!string.IsNullOrEmpty (_options.ResultsFile))
  390. {
  391. var output = JsonSerializer.Serialize (
  392. resultsList,
  393. new JsonSerializerOptions ()
  394. {
  395. WriteIndented = true
  396. });
  397. using var file = File.CreateText (_options.ResultsFile);
  398. file.Write (output);
  399. file.Close ();
  400. return;
  401. }
  402. Application.Init ();
  403. var benchmarkWindow = new Window ()
  404. {
  405. Title = "Benchmark Results",
  406. };
  407. if (benchmarkWindow.Border is { })
  408. {
  409. benchmarkWindow.Border.Thickness = new (0, 0, 0, 0);
  410. }
  411. TableView resultsTableView = new ()
  412. {
  413. Width = Dim.Fill (),
  414. Height = Dim.Fill (),
  415. };
  416. // TableView provides many options for table headers. For simplicity we turn all
  417. // of these off. By enabling FullRowSelect and turning off headers, TableView looks just
  418. // like a ListView
  419. resultsTableView.FullRowSelect = true;
  420. resultsTableView.Style.ShowHeaders = true;
  421. resultsTableView.Style.ShowHorizontalHeaderOverline = false;
  422. resultsTableView.Style.ShowHorizontalHeaderUnderline = true;
  423. resultsTableView.Style.ShowHorizontalBottomline = false;
  424. resultsTableView.Style.ShowVerticalCellLines = true;
  425. resultsTableView.Style.ShowVerticalHeaderLines = true;
  426. /* By default TableView lays out columns at render time and only
  427. * measures y rows of data at a time. Where y is the height of the
  428. * console. This is for the following reasons:
  429. *
  430. * - Performance, when tables have a large amount of data
  431. * - Defensive, prevents a single wide cell value pushing other
  432. * columns off screen (requiring horizontal scrolling
  433. *
  434. * In the case of UICatalog here, such an approach is overkill so
  435. * we just measure all the data ourselves and set the appropriate
  436. * max widths as ColumnStyles
  437. */
  438. //int longestName = _scenarios!.Max (s => s.GetName ().Length);
  439. //resultsTableView.Style.ColumnStyles.Add (
  440. // 0,
  441. // new () { MaxWidth = longestName, MinWidth = longestName, MinAcceptableWidth = longestName }
  442. // );
  443. //resultsTableView.Style.ColumnStyles.Add (1, new () { MaxWidth = 1 });
  444. //resultsTableView.CellActivated += ScenarioView_OpenSelectedItem;
  445. // TableView typically is a grid where nav keys are biased for moving left/right.
  446. resultsTableView.KeyBindings.Remove (Key.Home);
  447. resultsTableView.KeyBindings.Add (Key.Home, Command.Start);
  448. resultsTableView.KeyBindings.Remove (Key.End);
  449. resultsTableView.KeyBindings.Add (Key.End, Command.End);
  450. // Ideally, TableView.MultiSelect = false would turn off any keybindings for
  451. // multi-select options. But it currently does not. UI Catalog uses Ctrl-A for
  452. // a shortcut to About.
  453. resultsTableView.MultiSelect = false;
  454. var dt = new DataTable ();
  455. dt.Columns.Add (new DataColumn ("Scenario", typeof (string)));
  456. dt.Columns.Add (new DataColumn ("Duration", typeof (TimeSpan)));
  457. dt.Columns.Add (new DataColumn ("Refreshed", typeof (int)));
  458. dt.Columns.Add (new DataColumn ("LaidOut", typeof (int)));
  459. dt.Columns.Add (new DataColumn ("ClearedContent", typeof (int)));
  460. dt.Columns.Add (new DataColumn ("DrawComplete", typeof (int)));
  461. dt.Columns.Add (new DataColumn ("Updated", typeof (int)));
  462. dt.Columns.Add (new DataColumn ("Iterations", typeof (int)));
  463. foreach (var r in resultsList)
  464. {
  465. dt.Rows.Add (
  466. r.Scenario,
  467. r.Duration,
  468. r.RefreshedCount,
  469. r.LaidOutCount,
  470. r.ClearedContentCount,
  471. r.DrawCompleteCount,
  472. r.UpdatedCount,
  473. r.IterationCount
  474. );
  475. }
  476. BenchmarkResults totalRow = new ()
  477. {
  478. Scenario = "TOTAL",
  479. Duration = new TimeSpan (resultsList.Sum (r => r.Duration.Ticks)),
  480. RefreshedCount = resultsList.Sum (r => r.RefreshedCount),
  481. LaidOutCount = resultsList.Sum (r => r.LaidOutCount),
  482. ClearedContentCount = resultsList.Sum (r => r.ClearedContentCount),
  483. DrawCompleteCount = resultsList.Sum (r => r.DrawCompleteCount),
  484. UpdatedCount = resultsList.Sum (r => r.UpdatedCount),
  485. IterationCount = resultsList.Sum (r => r.IterationCount),
  486. };
  487. dt.Rows.Add (
  488. totalRow.Scenario,
  489. totalRow.Duration,
  490. totalRow.RefreshedCount,
  491. totalRow.LaidOutCount,
  492. totalRow.ClearedContentCount,
  493. totalRow.DrawCompleteCount,
  494. totalRow.UpdatedCount,
  495. totalRow.IterationCount
  496. );
  497. dt.DefaultView.Sort = "Duration";
  498. DataTable sortedCopy = dt.DefaultView.ToTable ();
  499. resultsTableView.Table = new DataTableSource (sortedCopy);
  500. benchmarkWindow.Add (resultsTableView);
  501. Application.Run (benchmarkWindow);
  502. benchmarkWindow.Dispose ();
  503. Application.Shutdown ();
  504. }
  505. }
  506. private static void VerifyObjectsWereDisposed ()
  507. {
  508. #if DEBUG_IDISPOSABLE
  509. // Validate there are no outstanding Responder-based instances
  510. // after a scenario was selected to run. This proves the main UI Catalog
  511. // 'app' closed cleanly.
  512. foreach (View? inst in View.Instances)
  513. {
  514. Debug.Assert (inst.WasDisposed);
  515. }
  516. View.Instances.Clear ();
  517. // Validate there are no outstanding Application.RunState-based instances
  518. // after a scenario was selected to run. This proves the main UI Catalog
  519. // 'app' closed cleanly.
  520. foreach (RunState? inst in RunState.Instances)
  521. {
  522. Debug.Assert (inst.WasDisposed);
  523. }
  524. RunState.Instances.Clear ();
  525. #endif
  526. }
  527. /// <summary>
  528. /// This is the main UI Catalog app view. It is run fresh when the app loads (if a Scenario has not been passed on
  529. /// the command line) and each time a Scenario ends.
  530. /// </summary>
  531. public class UICatalogTopLevel : Toplevel
  532. {
  533. public ListView? CategoryList;
  534. public MenuItem? MiForce16Colors;
  535. public MenuItem? MiIsMenuBorderDisabled;
  536. public MenuItem? MiIsMouseDisabled;
  537. public MenuItem? MiUseSubMenusSingleFrame;
  538. public Shortcut? ShForce16Colors;
  539. //public Shortcut? ShDiagnostics;
  540. public Shortcut? ShVersion;
  541. // UI Catalog uses TableView for the scenario list instead of a ListView to demonstate how
  542. // TableView works. There's no real reason not to use ListView. Because we use TableView, and TableView
  543. // doesn't (currently) have CollectionNavigator support built in, we implement it here, within the app.
  544. public TableView ScenarioList;
  545. private readonly StatusBar? _statusBar;
  546. private readonly CollectionNavigator _scenarioCollectionNav = new ();
  547. public UICatalogTopLevel ()
  548. {
  549. _diagnosticFlags = Diagnostics;
  550. _themeMenuItems = CreateThemeMenuItems ();
  551. _themeMenuBarItem = new ("_Themes", _themeMenuItems!);
  552. MenuBar menuBar = new ()
  553. {
  554. Menus =
  555. [
  556. new (
  557. "_File",
  558. new MenuItem []
  559. {
  560. new (
  561. "_Quit",
  562. "Quit UI Catalog",
  563. RequestStop
  564. )
  565. }
  566. ),
  567. _themeMenuBarItem,
  568. new ("Diag_nostics", CreateDiagnosticMenuItems ()),
  569. new (
  570. "_Help",
  571. new MenuItem []
  572. {
  573. new (
  574. "_Documentation",
  575. "",
  576. () => OpenUrl ("https://gui-cs.github.io/Terminal.GuiV2Docs"),
  577. null,
  578. null,
  579. (KeyCode)Key.F1
  580. ),
  581. new (
  582. "_README",
  583. "",
  584. () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"),
  585. null,
  586. null,
  587. (KeyCode)Key.F2
  588. ),
  589. new (
  590. "_About...",
  591. "About UI Catalog",
  592. () => MessageBox.Query (
  593. title: "",
  594. message: GetAboutBoxMessage (),
  595. wrapMessage: false,
  596. buttons: "_Ok"
  597. ),
  598. null,
  599. null,
  600. (KeyCode)Key.A.WithCtrl
  601. )
  602. }
  603. )
  604. ]
  605. };
  606. _statusBar = new ()
  607. {
  608. Visible = ShowStatusBar,
  609. AlignmentModes = AlignmentModes.IgnoreFirstOrLast,
  610. CanFocus = false
  611. };
  612. _statusBar.Height = Dim.Auto (DimAutoStyle.Auto, minimumContentDim: Dim.Func (() => _statusBar.Visible ? 1 : 0), maximumContentDim: Dim.Func (() => _statusBar.Visible ? 1 : 0));
  613. ShVersion = new ()
  614. {
  615. Title = "Version Info",
  616. CanFocus = false,
  617. };
  618. var statusBarShortcut = new Shortcut
  619. {
  620. Key = Key.F10,
  621. Title = "Show/Hide Status Bar",
  622. CanFocus = false,
  623. };
  624. statusBarShortcut.Accepting += (sender, args) =>
  625. {
  626. _statusBar.Visible = !_statusBar.Visible;
  627. args.Cancel = true;
  628. };
  629. ShForce16Colors = new ()
  630. {
  631. CanFocus = false,
  632. CommandView = new CheckBox
  633. {
  634. Title = "16 color mode",
  635. CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked,
  636. CanFocus = false
  637. },
  638. HelpText = "",
  639. BindKeyToApplication = true,
  640. Key = Key.F7
  641. };
  642. ((CheckBox)ShForce16Colors.CommandView).CheckedStateChanging += (sender, args) =>
  643. {
  644. Application.Force16Colors = args.NewValue == CheckState.Checked;
  645. MiForce16Colors!.Checked = Application.Force16Colors;
  646. Application.LayoutAndDraw ();
  647. };
  648. _statusBar.Add (
  649. new Shortcut
  650. {
  651. CanFocus = false,
  652. Title = "Quit",
  653. Key = Application.QuitKey
  654. },
  655. statusBarShortcut,
  656. ShForce16Colors,
  657. //ShDiagnostics,
  658. ShVersion
  659. );
  660. // Create the Category list view. This list never changes.
  661. CategoryList = new ()
  662. {
  663. X = 0,
  664. Y = Pos.Bottom (menuBar),
  665. Width = Dim.Auto (),
  666. Height = Dim.Fill (Dim.Func (() =>
  667. {
  668. if (_statusBar.NeedsLayout)
  669. {
  670. throw new LayoutException ("DimFunc.Fn aborted because dependent View needs layout.");
  671. //_statusBar.Layout ();
  672. }
  673. return _statusBar.Frame.Height;
  674. })),
  675. AllowsMarking = false,
  676. CanFocus = true,
  677. Title = "_Categories",
  678. BorderStyle = LineStyle.Rounded,
  679. SuperViewRendersLineCanvas = true,
  680. Source = new ListWrapper<string> (_categories)
  681. };
  682. CategoryList.OpenSelectedItem += (s, a) => { ScenarioList!.SetFocus (); };
  683. CategoryList.SelectedItemChanged += CategoryView_SelectedChanged;
  684. // This enables the scrollbar by causing lazy instantiation to happen
  685. CategoryList.VerticalScrollBar.AutoShow = true;
  686. // Create the scenario list. The contents of the scenario list changes whenever the
  687. // Category list selection changes (to show just the scenarios that belong to the selected
  688. // category).
  689. ScenarioList = new ()
  690. {
  691. X = Pos.Right (CategoryList) - 1,
  692. Y = Pos.Bottom (menuBar),
  693. Width = Dim.Fill (),
  694. Height = Dim.Fill (Dim.Func (() =>
  695. {
  696. if (_statusBar.NeedsLayout)
  697. {
  698. throw new LayoutException ("DimFunc.Fn aborted because dependent View needs layout.");
  699. //_statusBar.Layout ();
  700. }
  701. return _statusBar.Frame.Height;
  702. })),
  703. //AllowsMarking = false,
  704. CanFocus = true,
  705. Title = "_Scenarios",
  706. BorderStyle = CategoryList.BorderStyle,
  707. SuperViewRendersLineCanvas = true
  708. };
  709. //ScenarioList.VerticalScrollBar.AutoHide = false;
  710. //ScenarioList.HorizontalScrollBar.AutoHide = false;
  711. // TableView provides many options for table headers. For simplicity we turn all
  712. // of these off. By enabling FullRowSelect and turning off headers, TableView looks just
  713. // like a ListView
  714. ScenarioList.FullRowSelect = true;
  715. ScenarioList.Style.ShowHeaders = false;
  716. ScenarioList.Style.ShowHorizontalHeaderOverline = false;
  717. ScenarioList.Style.ShowHorizontalHeaderUnderline = false;
  718. ScenarioList.Style.ShowHorizontalBottomline = false;
  719. ScenarioList.Style.ShowVerticalCellLines = false;
  720. ScenarioList.Style.ShowVerticalHeaderLines = false;
  721. /* By default TableView lays out columns at render time and only
  722. * measures y rows of data at a time. Where y is the height of the
  723. * console. This is for the following reasons:
  724. *
  725. * - Performance, when tables have a large amount of data
  726. * - Defensive, prevents a single wide cell value pushing other
  727. * columns off screen (requiring horizontal scrolling
  728. *
  729. * In the case of UICatalog here, such an approach is overkill so
  730. * we just measure all the data ourselves and set the appropriate
  731. * max widths as ColumnStyles
  732. */
  733. int longestName = _scenarios!.Max (s => s.GetName ().Length);
  734. ScenarioList.Style.ColumnStyles.Add (
  735. 0,
  736. new () { MaxWidth = longestName, MinWidth = longestName, MinAcceptableWidth = longestName }
  737. );
  738. ScenarioList.Style.ColumnStyles.Add (1, new () { MaxWidth = 1 });
  739. ScenarioList.CellActivated += ScenarioView_OpenSelectedItem;
  740. // TableView typically is a grid where nav keys are biased for moving left/right.
  741. ScenarioList.KeyBindings.Remove (Key.Home);
  742. ScenarioList.KeyBindings.Add (Key.Home, Command.Start);
  743. ScenarioList.KeyBindings.Remove (Key.End);
  744. ScenarioList.KeyBindings.Add (Key.End, Command.End);
  745. // Ideally, TableView.MultiSelect = false would turn off any keybindings for
  746. // multi-select options. But it currently does not. UI Catalog uses Ctrl-A for
  747. // a shortcut to About.
  748. ScenarioList.MultiSelect = false;
  749. ScenarioList.KeyBindings.Remove (Key.A.WithCtrl);
  750. Add (menuBar);
  751. Add (CategoryList);
  752. Add (ScenarioList);
  753. Add (_statusBar);
  754. Loaded += LoadedHandler;
  755. Unloaded += UnloadedHandler;
  756. // Restore previous selections
  757. CategoryList.SelectedItem = _cachedCategoryIndex;
  758. ScenarioList.SelectedRow = _cachedScenarioIndex;
  759. Applied += ConfigAppliedHandler;
  760. }
  761. public void ConfigChanged ()
  762. {
  763. if (_topLevelColorScheme == null || !Colors.ColorSchemes.ContainsKey (_topLevelColorScheme))
  764. {
  765. _topLevelColorScheme = "Base";
  766. }
  767. _cachedTheme = Themes?.Theme;
  768. _themeMenuItems = CreateThemeMenuItems ();
  769. _themeMenuBarItem!.Children = _themeMenuItems;
  770. foreach (MenuItem mi in _themeMenuItems!)
  771. {
  772. if (mi is { Parent: null })
  773. {
  774. mi.Parent = _themeMenuBarItem;
  775. }
  776. }
  777. ColorScheme = Colors.ColorSchemes [_topLevelColorScheme];
  778. MenuBar!.Menus [0].Children! [0]!.ShortcutKey = Application.QuitKey;
  779. ((Shortcut)_statusBar!.Subviews [0]).Key = Application.QuitKey;
  780. _statusBar.Visible = ShowStatusBar;
  781. MiIsMouseDisabled!.Checked = Application.IsMouseDisabled;
  782. ((CheckBox)ShForce16Colors!.CommandView!).CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked;
  783. Application.Top!.SetNeedsDraw ();
  784. }
  785. public MenuItem []? CreateThemeMenuItems ()
  786. {
  787. List<MenuItem> menuItems = CreateForce16ColorItems ().ToList ();
  788. menuItems.Add (null!);
  789. var schemeCount = 0;
  790. foreach (KeyValuePair<string, ThemeScope> theme in Themes!)
  791. {
  792. var item = new MenuItem
  793. {
  794. Title = theme.Key == "Dark" ? $"{theme.Key.Substring (0, 3)}_{theme.Key.Substring (3, 1)}" : $"_{theme.Key}",
  795. ShortcutKey = new Key ((KeyCode)((uint)KeyCode.D1 + schemeCount++))
  796. .WithCtrl
  797. };
  798. item.CheckType |= MenuItemCheckStyle.Checked;
  799. item.Checked = theme.Key == _cachedTheme; // CM.Themes.Theme;
  800. item.Action += () =>
  801. {
  802. Themes.Theme = _cachedTheme = theme.Key;
  803. Apply ();
  804. };
  805. menuItems.Add (item);
  806. }
  807. List<MenuItem> schemeMenuItems = new ();
  808. foreach (KeyValuePair<string, ColorScheme?> sc in Colors.ColorSchemes)
  809. {
  810. var item = new MenuItem { Title = $"_{sc.Key}", Data = sc.Key };
  811. item.CheckType |= MenuItemCheckStyle.Radio;
  812. item.Checked = sc.Key == _topLevelColorScheme;
  813. item.Action += () =>
  814. {
  815. _topLevelColorScheme = (string)item.Data;
  816. foreach (MenuItem schemeMenuItem in schemeMenuItems)
  817. {
  818. schemeMenuItem.Checked = (string)schemeMenuItem.Data == _topLevelColorScheme;
  819. }
  820. ColorScheme = Colors.ColorSchemes [_topLevelColorScheme];
  821. };
  822. item.ShortcutKey = ((Key)sc.Key [0].ToString ().ToLower ()).WithCtrl;
  823. schemeMenuItems.Add (item);
  824. }
  825. menuItems.Add (null!);
  826. var mbi = new MenuBarItem ("_Color Scheme for Application.Top", schemeMenuItems.ToArray ());
  827. menuItems.Add (mbi);
  828. return menuItems.ToArray ();
  829. }
  830. private void CategoryView_SelectedChanged (object? sender, ListViewItemEventArgs? e)
  831. {
  832. string item = _categories! [e!.Item];
  833. ObservableCollection<Scenario> newlist;
  834. if (e.Item == 0)
  835. {
  836. // First category is "All"
  837. newlist = _scenarios!;
  838. }
  839. else
  840. {
  841. newlist = new (_scenarios!.Where (s => s.GetCategories ().Contains (item)).ToList ());
  842. }
  843. ScenarioList.Table = new EnumerableTableSource<Scenario> (
  844. newlist,
  845. new ()
  846. {
  847. { "Name", s => s.GetName () }, { "Description", s => s.GetDescription () }
  848. }
  849. );
  850. // Create a collection of just the scenario names (the 1st column in our TableView)
  851. // for CollectionNavigator.
  852. List<object> firstColumnList = new ();
  853. for (var i = 0; i < ScenarioList.Table.Rows; i++)
  854. {
  855. firstColumnList.Add (ScenarioList.Table [i, 0]);
  856. }
  857. _scenarioCollectionNav.Collection = firstColumnList;
  858. }
  859. private void ConfigAppliedHandler (object? sender, ConfigurationManagerEventArgs? a) { ConfigChanged (); }
  860. [SuppressMessage ("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
  861. private MenuItem [] CreateDiagnosticFlagsMenuItems ()
  862. {
  863. const string OFF = "View Diagnostics: _Off";
  864. const string RULER = "View Diagnostics: _Ruler";
  865. const string THICKNESS = "View Diagnostics: _Thickness";
  866. const string HOVER = "View Diagnostics: _Hover";
  867. const string DRAWINDICATOR = "View Diagnostics: _DrawIndicator";
  868. var index = 0;
  869. List<MenuItem> menuItems = new ();
  870. foreach (Enum diag in Enum.GetValues (_diagnosticFlags.GetType ()))
  871. {
  872. var item = new MenuItem
  873. {
  874. Title = GetDiagnosticsTitle (diag), ShortcutKey = new Key (index.ToString () [0]).WithAlt
  875. };
  876. index++;
  877. item.CheckType |= MenuItemCheckStyle.Checked;
  878. if (GetDiagnosticsTitle (ViewDiagnosticFlags.Off) == item.Title)
  879. {
  880. item.Checked = !_diagnosticFlags.HasFlag (ViewDiagnosticFlags.Thickness)
  881. && !_diagnosticFlags.HasFlag (ViewDiagnosticFlags.Ruler)
  882. && !_diagnosticFlags.HasFlag (ViewDiagnosticFlags.Hover)
  883. && !_diagnosticFlags.HasFlag (ViewDiagnosticFlags.DrawIndicator);
  884. }
  885. else
  886. {
  887. item.Checked = _diagnosticFlags.HasFlag (diag);
  888. }
  889. item.Action += () =>
  890. {
  891. string t = GetDiagnosticsTitle (ViewDiagnosticFlags.Off);
  892. if (item.Title == t && item.Checked == false)
  893. {
  894. _diagnosticFlags &= ~(ViewDiagnosticFlags.Thickness | ViewDiagnosticFlags.Ruler | ViewDiagnosticFlags.Hover | ViewDiagnosticFlags.DrawIndicator);
  895. item.Checked = true;
  896. }
  897. else if (item.Title == t && item.Checked == true)
  898. {
  899. _diagnosticFlags |= ViewDiagnosticFlags.Thickness | ViewDiagnosticFlags.Ruler | ViewDiagnosticFlags.Hover | ViewDiagnosticFlags.DrawIndicator;
  900. item.Checked = false;
  901. }
  902. else
  903. {
  904. Enum f = GetDiagnosticsEnumValue (item.Title);
  905. if (_diagnosticFlags.HasFlag (f))
  906. {
  907. SetDiagnosticsFlag (f, false);
  908. }
  909. else
  910. {
  911. SetDiagnosticsFlag (f, true);
  912. }
  913. }
  914. foreach (MenuItem menuItem in menuItems)
  915. {
  916. if (menuItem.Title == t)
  917. {
  918. menuItem.Checked = !_diagnosticFlags.HasFlag (ViewDiagnosticFlags.Ruler)
  919. && !_diagnosticFlags.HasFlag (ViewDiagnosticFlags.Thickness)
  920. && !_diagnosticFlags.HasFlag (ViewDiagnosticFlags.Hover)
  921. && !_diagnosticFlags.HasFlag (ViewDiagnosticFlags.DrawIndicator);
  922. }
  923. else if (menuItem.Title != t)
  924. {
  925. menuItem.Checked = _diagnosticFlags.HasFlag (GetDiagnosticsEnumValue (menuItem.Title));
  926. }
  927. }
  928. Diagnostics = _diagnosticFlags;
  929. };
  930. menuItems.Add (item);
  931. }
  932. return menuItems.ToArray ();
  933. string GetDiagnosticsTitle (Enum diag)
  934. {
  935. return Enum.GetName (_diagnosticFlags.GetType (), diag) switch
  936. {
  937. "Off" => OFF,
  938. "Ruler" => RULER,
  939. "Thickness" => THICKNESS,
  940. "Hover" => HOVER,
  941. "DrawIndicator" => DRAWINDICATOR,
  942. _ => ""
  943. };
  944. }
  945. Enum GetDiagnosticsEnumValue (string? title)
  946. {
  947. return title switch
  948. {
  949. RULER => ViewDiagnosticFlags.Ruler,
  950. THICKNESS => ViewDiagnosticFlags.Thickness,
  951. HOVER => ViewDiagnosticFlags.Hover,
  952. DRAWINDICATOR => ViewDiagnosticFlags.DrawIndicator,
  953. _ => null!
  954. };
  955. }
  956. void SetDiagnosticsFlag (Enum diag, bool add)
  957. {
  958. switch (diag)
  959. {
  960. case ViewDiagnosticFlags.Ruler:
  961. if (add)
  962. {
  963. _diagnosticFlags |= ViewDiagnosticFlags.Ruler;
  964. }
  965. else
  966. {
  967. _diagnosticFlags &= ~ViewDiagnosticFlags.Ruler;
  968. }
  969. break;
  970. case ViewDiagnosticFlags.Thickness:
  971. if (add)
  972. {
  973. _diagnosticFlags |= ViewDiagnosticFlags.Thickness;
  974. }
  975. else
  976. {
  977. _diagnosticFlags &= ~ViewDiagnosticFlags.Thickness;
  978. }
  979. break;
  980. case ViewDiagnosticFlags.Hover:
  981. if (add)
  982. {
  983. _diagnosticFlags |= ViewDiagnosticFlags.Hover;
  984. }
  985. else
  986. {
  987. _diagnosticFlags &= ~ViewDiagnosticFlags.Hover;
  988. }
  989. break;
  990. case ViewDiagnosticFlags.DrawIndicator:
  991. if (add)
  992. {
  993. _diagnosticFlags |= ViewDiagnosticFlags.DrawIndicator;
  994. }
  995. else
  996. {
  997. _diagnosticFlags &= ~ViewDiagnosticFlags.DrawIndicator;
  998. }
  999. break;
  1000. default:
  1001. _diagnosticFlags = default (ViewDiagnosticFlags);
  1002. break;
  1003. }
  1004. }
  1005. }
  1006. private List<MenuItem []> CreateDiagnosticMenuItems ()
  1007. {
  1008. List<MenuItem []> menuItems = new ()
  1009. {
  1010. CreateDiagnosticFlagsMenuItems (),
  1011. new MenuItem [] { null! },
  1012. CreateDisabledEnabledMouseItems (),
  1013. CreateDisabledEnabledMenuBorder (),
  1014. CreateDisabledEnableUseSubMenusSingleFrame (),
  1015. CreateKeyBindingsMenuItems ()
  1016. };
  1017. return menuItems;
  1018. }
  1019. // TODO: This should be an ConfigurationManager setting
  1020. private MenuItem [] CreateDisabledEnabledMenuBorder ()
  1021. {
  1022. List<MenuItem> menuItems = new ();
  1023. MiIsMenuBorderDisabled = new () { Title = "Disable Menu _Border" };
  1024. MiIsMenuBorderDisabled.ShortcutKey =
  1025. new Key (MiIsMenuBorderDisabled!.Title!.Substring (14, 1) [0]).WithAlt.WithCtrl.NoShift;
  1026. MiIsMenuBorderDisabled.CheckType |= MenuItemCheckStyle.Checked;
  1027. MiIsMenuBorderDisabled.Action += () =>
  1028. {
  1029. MiIsMenuBorderDisabled.Checked = (bool)!MiIsMenuBorderDisabled.Checked!;
  1030. MenuBar!.MenusBorderStyle = !(bool)MiIsMenuBorderDisabled.Checked
  1031. ? LineStyle.Single
  1032. : LineStyle.None;
  1033. };
  1034. menuItems.Add (MiIsMenuBorderDisabled);
  1035. return menuItems.ToArray ();
  1036. }
  1037. private MenuItem [] CreateDisabledEnabledMouseItems ()
  1038. {
  1039. List<MenuItem> menuItems = new ();
  1040. MiIsMouseDisabled = new () { Title = "_Disable Mouse" };
  1041. MiIsMouseDisabled.ShortcutKey =
  1042. new Key (MiIsMouseDisabled!.Title!.Substring (1, 1) [0]).WithAlt.WithCtrl.NoShift;
  1043. MiIsMouseDisabled.CheckType |= MenuItemCheckStyle.Checked;
  1044. MiIsMouseDisabled.Action += () =>
  1045. {
  1046. MiIsMouseDisabled.Checked =
  1047. Application.IsMouseDisabled = (bool)!MiIsMouseDisabled.Checked!;
  1048. };
  1049. menuItems.Add (MiIsMouseDisabled);
  1050. return menuItems.ToArray ();
  1051. }
  1052. // TODO: This should be an ConfigurationManager setting
  1053. private MenuItem [] CreateDisabledEnableUseSubMenusSingleFrame ()
  1054. {
  1055. List<MenuItem> menuItems = new ();
  1056. MiUseSubMenusSingleFrame = new () { Title = "Enable _Sub-Menus Single Frame" };
  1057. MiUseSubMenusSingleFrame.ShortcutKey = KeyCode.CtrlMask
  1058. | KeyCode.AltMask
  1059. | (KeyCode)MiUseSubMenusSingleFrame!.Title!.Substring (8, 1) [
  1060. 0];
  1061. MiUseSubMenusSingleFrame.CheckType |= MenuItemCheckStyle.Checked;
  1062. MiUseSubMenusSingleFrame.Action += () =>
  1063. {
  1064. MiUseSubMenusSingleFrame.Checked = (bool)!MiUseSubMenusSingleFrame.Checked!;
  1065. MenuBar!.UseSubMenusSingleFrame = (bool)MiUseSubMenusSingleFrame.Checked;
  1066. };
  1067. menuItems.Add (MiUseSubMenusSingleFrame);
  1068. return menuItems.ToArray ();
  1069. }
  1070. private MenuItem [] CreateForce16ColorItems ()
  1071. {
  1072. List<MenuItem> menuItems = new ();
  1073. MiForce16Colors = new ()
  1074. {
  1075. Title = "Force _16 Colors",
  1076. ShortcutKey = Key.F6,
  1077. Checked = Application.Force16Colors,
  1078. CanExecute = () => Application.Driver?.SupportsTrueColor ?? false
  1079. };
  1080. MiForce16Colors.CheckType |= MenuItemCheckStyle.Checked;
  1081. MiForce16Colors.Action += () =>
  1082. {
  1083. MiForce16Colors.Checked = Application.Force16Colors = (bool)!MiForce16Colors.Checked!;
  1084. ((CheckBox)ShForce16Colors!.CommandView!).CheckedState =
  1085. Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked;
  1086. Application.LayoutAndDraw ();
  1087. };
  1088. menuItems.Add (MiForce16Colors);
  1089. return menuItems.ToArray ();
  1090. }
  1091. private MenuItem [] CreateKeyBindingsMenuItems ()
  1092. {
  1093. List<MenuItem> menuItems = new ();
  1094. var item = new MenuItem { Title = "_Key Bindings", Help = "Change which keys do what" };
  1095. item.Action += () =>
  1096. {
  1097. var dlg = new KeyBindingsDialog ();
  1098. Application.Run (dlg);
  1099. dlg.Dispose ();
  1100. };
  1101. menuItems.Add (null!);
  1102. menuItems.Add (item);
  1103. return menuItems.ToArray ();
  1104. }
  1105. private void LoadedHandler (object? sender, EventArgs? args)
  1106. {
  1107. ConfigChanged ();
  1108. MiIsMouseDisabled!.Checked = Application.IsMouseDisabled;
  1109. if (ShVersion is { })
  1110. {
  1111. ShVersion.Title = $"{RuntimeEnvironment.OperatingSystem} {RuntimeEnvironment.OperatingSystemVersion}, {Driver!.GetVersionInfo ()}";
  1112. }
  1113. if (_selectedScenario != null)
  1114. {
  1115. _selectedScenario = null;
  1116. _isFirstRunning = false;
  1117. }
  1118. if (!_isFirstRunning)
  1119. {
  1120. ScenarioList.SetFocus ();
  1121. }
  1122. if (_statusBar is { })
  1123. {
  1124. _statusBar.VisibleChanged += (s, e) =>
  1125. {
  1126. ShowStatusBar = _statusBar.Visible;
  1127. };
  1128. }
  1129. Loaded -= LoadedHandler;
  1130. CategoryList!.EnsureSelectedItemVisible ();
  1131. ScenarioList.EnsureSelectedCellIsVisible ();
  1132. }
  1133. /// <summary>Launches the selected scenario, setting the global _selectedScenario</summary>
  1134. /// <param name="e"></param>
  1135. private void ScenarioView_OpenSelectedItem (object? sender, EventArgs? e)
  1136. {
  1137. if (_selectedScenario is null)
  1138. {
  1139. // Save selected item state
  1140. _cachedCategoryIndex = CategoryList!.SelectedItem;
  1141. _cachedScenarioIndex = ScenarioList.SelectedRow;
  1142. // Create new instance of scenario (even though Scenarios contains instances)
  1143. var selectedScenarioName = (string)ScenarioList.Table [ScenarioList.SelectedRow, 0];
  1144. _selectedScenario = (Scenario)Activator.CreateInstance (
  1145. _scenarios!.FirstOrDefault (
  1146. s => s.GetName ()
  1147. == selectedScenarioName
  1148. )!
  1149. .GetType ()
  1150. )!;
  1151. // Tell the main app to stop
  1152. Application.RequestStop ();
  1153. }
  1154. }
  1155. private void UnloadedHandler (object? sender, EventArgs? args)
  1156. {
  1157. Applied -= ConfigAppliedHandler;
  1158. Unloaded -= UnloadedHandler;
  1159. Dispose ();
  1160. }
  1161. }
  1162. private struct Options
  1163. {
  1164. public string Driver;
  1165. public string Scenario;
  1166. public uint BenchmarkTimeout;
  1167. public bool Benchmark;
  1168. public string ResultsFile;
  1169. /* etc. */
  1170. }
  1171. }