AllViewsTester.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using Terminal.Gui;
  6. namespace UICatalog.Scenarios;
  7. [ScenarioMetadata ("All Views Tester", "Provides a test UI for all classes derived from View.")]
  8. [ScenarioCategory ("Layout")]
  9. [ScenarioCategory ("Tests")]
  10. [ScenarioCategory ("Top Level Windows")]
  11. public class AllViewsTester : Scenario
  12. {
  13. private readonly List<string> _dimNames = new () { "Auto", "Percent", "Fill", "Absolute" };
  14. // TODO: This is missing some
  15. private readonly List<string> _posNames = new () { "Percent", "AnchorEnd", "Center", "Absolute" };
  16. private ListView _classListView;
  17. private View _curView;
  18. private FrameView _hostPane;
  19. private RadioGroup _hRadioGroup;
  20. private TextField _hText;
  21. private int _hVal;
  22. private FrameView _leftPane;
  23. private FrameView _locationFrame;
  24. // Settings
  25. private FrameView _settingsPane;
  26. private FrameView _sizeFrame;
  27. private Dictionary<string, Type> _viewClasses;
  28. private RadioGroup _wRadioGroup;
  29. private TextField _wText;
  30. private int _wVal;
  31. private RadioGroup _xRadioGroup;
  32. private TextField _xText;
  33. private int _xVal;
  34. private RadioGroup _yRadioGroup;
  35. private TextField _yText;
  36. private int _yVal;
  37. private RadioGroup _orientation;
  38. private string _demoText = "This, that, and the other thing.";
  39. private TextView _demoTextView;
  40. public override void Main ()
  41. {
  42. // Don't create a sub-win (Scenario.Win); just use Application.Top
  43. Application.Init ();
  44. ConfigurationManager.Apply ();
  45. var app = new Window ();
  46. app.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
  47. var statusBar = new StatusBar (
  48. new StatusItem []
  49. {
  50. new (
  51. Application.QuitKey,
  52. $"{Application.QuitKey} to Quit",
  53. () => Quit ()
  54. ),
  55. new (
  56. KeyCode.F2,
  57. "~F2~ Toggle Frame Ruler",
  58. () =>
  59. {
  60. View.Diagnostics ^=
  61. ViewDiagnosticFlags.Ruler;
  62. app.SetNeedsDisplay ();
  63. }
  64. ),
  65. new (
  66. KeyCode.F3,
  67. "~F3~ Toggle Frame Padding",
  68. () =>
  69. {
  70. View.Diagnostics ^=
  71. ViewDiagnosticFlags.Padding;
  72. app.SetNeedsDisplay ();
  73. }
  74. )
  75. }
  76. );
  77. app.Add (statusBar);
  78. _viewClasses = GetAllViewClassesCollection ()
  79. .OrderBy (t => t.Name)
  80. .Select (t => new KeyValuePair<string, Type> (t.Name, t))
  81. .ToDictionary (t => t.Key, t => t.Value);
  82. _leftPane = new()
  83. {
  84. X = 0,
  85. Y = 0,
  86. Width = Dim.Auto (DimAutoStyle.Content),
  87. Height = Dim.Fill (1), // for status bar
  88. CanFocus = false,
  89. ColorScheme = Colors.ColorSchemes ["TopLevel"],
  90. Title = "Classes"
  91. };
  92. _classListView = new()
  93. {
  94. X = 0,
  95. Y = 0,
  96. Width = Dim.Auto (),
  97. Height = Dim.Fill (),
  98. AllowsMarking = false,
  99. ColorScheme = Colors.ColorSchemes ["TopLevel"],
  100. SelectedItem = 0,
  101. Source = new ListWrapper (_viewClasses.Keys.ToList ())
  102. };
  103. _classListView.OpenSelectedItem += (s, a) => { _settingsPane.SetFocus (); };
  104. _classListView.SelectedItemChanged += (s, args) =>
  105. {
  106. // Remove existing class, if any
  107. if (_curView != null)
  108. {
  109. _curView.LayoutComplete -= LayoutCompleteHandler;
  110. _hostPane.Remove (_curView);
  111. _curView.Dispose ();
  112. _curView = null;
  113. _hostPane.Clear ();
  114. }
  115. _curView = CreateClass (_viewClasses.Values.ToArray () [_classListView.SelectedItem]);
  116. };
  117. _leftPane.Add (_classListView);
  118. _settingsPane = new()
  119. {
  120. X = Pos.Right (_leftPane),
  121. Y = 0, // for menu
  122. Width = Dim.Fill (),
  123. Height = Dim.Auto (),
  124. CanFocus = false,
  125. ColorScheme = Colors.ColorSchemes ["TopLevel"],
  126. Title = "Settings"
  127. };
  128. string [] radioItems = { "_Percent(x)", "_AnchorEnd", "_Center", "A_bsolute(x)" };
  129. _locationFrame = new()
  130. {
  131. X = 0,
  132. Y = 0,
  133. Height = Dim.Auto (),
  134. Width = Dim.Auto (),
  135. Title = "Location (Pos)"
  136. };
  137. _settingsPane.Add (_locationFrame);
  138. var label = new Label { X = 0, Y = 0, Text = "X:" };
  139. _locationFrame.Add (label);
  140. _xRadioGroup = new() { X = 0, Y = Pos.Bottom (label), RadioLabels = radioItems };
  141. _xRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView);
  142. _xText = new() { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_xVal}" };
  143. _xText.Accept += (s, args) =>
  144. {
  145. try
  146. {
  147. _xVal = int.Parse (_xText.Text);
  148. DimPosChanged (_curView);
  149. }
  150. catch
  151. { }
  152. };
  153. _locationFrame.Add (_xText);
  154. _locationFrame.Add (_xRadioGroup);
  155. radioItems = new [] { "P_ercent(y)", "A_nchorEnd", "C_enter", "Absolute(_y)" };
  156. label = new() { X = Pos.Right (_xRadioGroup) + 1, Y = 0, Text = "Y:" };
  157. _locationFrame.Add (label);
  158. _yText = new() { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_yVal}" };
  159. _yText.Accept += (s, args) =>
  160. {
  161. try
  162. {
  163. _yVal = int.Parse (_yText.Text);
  164. DimPosChanged (_curView);
  165. }
  166. catch
  167. { }
  168. };
  169. _locationFrame.Add (_yText);
  170. _yRadioGroup = new() { X = Pos.X (label), Y = Pos.Bottom (label), RadioLabels = radioItems };
  171. _yRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView);
  172. _locationFrame.Add (_yRadioGroup);
  173. _sizeFrame = new()
  174. {
  175. X = Pos.Right (_locationFrame),
  176. Y = Pos.Y (_locationFrame),
  177. Height = Dim.Auto (),
  178. Width = Dim.Auto (),
  179. Title = "Size (Dim)"
  180. };
  181. radioItems = new [] { "Auto", "_Percent(width)", "_Fill(width)", "A_bsolute(width)" };
  182. label = new() { X = 0, Y = 0, Text = "Width:" };
  183. _sizeFrame.Add (label);
  184. _wRadioGroup = new() { X = 0, Y = Pos.Bottom (label), RadioLabels = radioItems };
  185. _wRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView);
  186. _wText = new() { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_wVal}" };
  187. _wText.Accept += (s, args) =>
  188. {
  189. try
  190. {
  191. switch (_wRadioGroup.SelectedItem)
  192. {
  193. case 1:
  194. _wVal = Math.Min (int.Parse (_wText.Text), 100);
  195. break;
  196. case 0:
  197. case 2:
  198. case 3:
  199. _wVal = int.Parse (_wText.Text);
  200. break;
  201. }
  202. DimPosChanged (_curView);
  203. }
  204. catch
  205. { }
  206. };
  207. _sizeFrame.Add (_wText);
  208. _sizeFrame.Add (_wRadioGroup);
  209. radioItems = new [] { "_Auto", "P_ercent(height)", "F_ill(height)", "Ab_solute(height)" };
  210. label = new() { X = Pos.Right (_wRadioGroup) + 1, Y = 0, Text = "Height:" };
  211. _sizeFrame.Add (label);
  212. _hText = new() { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_hVal}" };
  213. _hText.Accept += (s, args) =>
  214. {
  215. try
  216. {
  217. switch (_hRadioGroup.SelectedItem)
  218. {
  219. case 1:
  220. _hVal = Math.Min (int.Parse (_hText.Text), 100);
  221. break;
  222. case 0:
  223. case 2:
  224. case 3:
  225. _hVal = int.Parse (_hText.Text);
  226. break;
  227. }
  228. DimPosChanged (_curView);
  229. }
  230. catch
  231. { }
  232. };
  233. _sizeFrame.Add (_hText);
  234. _hRadioGroup = new() { X = Pos.X (label), Y = Pos.Bottom (label), RadioLabels = radioItems };
  235. _hRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView);
  236. _sizeFrame.Add (_hRadioGroup);
  237. _settingsPane.Add (_sizeFrame);
  238. label = new() { X = 0, Y = Pos.Bottom (_sizeFrame), Text = "_Orientation:" };
  239. _orientation = new()
  240. {
  241. X = Pos.Right (label) + 1,
  242. Y = Pos.Top (label),
  243. RadioLabels = new [] { "Horizontal", "Vertical" },
  244. Orientation = Orientation.Horizontal
  245. };
  246. _orientation.SelectedItemChanged += (s, selected) =>
  247. {
  248. if (_curView?.GetType ().GetProperty ("Orientation") is { } prop)
  249. {
  250. prop.GetSetMethod ()?.Invoke (_curView, new object [] { _orientation.SelectedItem });
  251. }
  252. };
  253. _settingsPane.Add (label, _orientation);
  254. label = new() { X = 0, Y = Pos.Bottom (_orientation), Text = "_Text:" };
  255. _demoTextView = new ()
  256. {
  257. X = Pos.Right (label) + 1,
  258. Y = Pos.Top (label),
  259. Width = Dim.Fill (),
  260. Height = Dim.Auto (minimumContentDim: 2),
  261. Text = _demoText
  262. };
  263. _demoTextView.ContentsChanged += (s, e) =>
  264. {
  265. _demoText = _demoTextView.Text;
  266. _curView.Text = _demoText;
  267. };
  268. _settingsPane.Add (label, _demoTextView);
  269. _hostPane = new()
  270. {
  271. X = Pos.Right (_leftPane),
  272. Y = Pos.Bottom (_settingsPane),
  273. Width = Dim.Fill (),
  274. Height = Dim.Fill (1), // + 1 for status bar
  275. ColorScheme = Colors.ColorSchemes ["Dialog"]
  276. };
  277. app.Add (_leftPane, _settingsPane, _hostPane);
  278. _curView = CreateClass (_viewClasses.First ().Value);
  279. Application.Run (app);
  280. app.Dispose ();
  281. Application.Shutdown ();
  282. }
  283. // TODO: Add Command.HotKey handler (pop a message box?)
  284. private View CreateClass (Type type)
  285. {
  286. // If we are to create a generic Type
  287. if (type.IsGenericType)
  288. {
  289. // For each of the <T> arguments
  290. List<Type> typeArguments = new ();
  291. // use <object>
  292. foreach (Type arg in type.GetGenericArguments ())
  293. {
  294. typeArguments.Add (typeof (object));
  295. }
  296. // And change what type we are instantiating from MyClass<T> to MyClass<object>
  297. type = type.MakeGenericType (typeArguments.ToArray ());
  298. }
  299. // Instantiate view
  300. var view = (View)Activator.CreateInstance (type);
  301. // Set the colorscheme to make it stand out if is null by default
  302. if (view.ColorScheme == null)
  303. {
  304. view.ColorScheme = Colors.ColorSchemes ["Base"];
  305. }
  306. // If the view supports a Text property, set it so we have something to look at
  307. if (view.GetType ().GetProperty ("Text") != null)
  308. {
  309. try
  310. {
  311. view.GetType ()
  312. .GetProperty ("Text")
  313. ?.GetSetMethod ()
  314. ?.Invoke (view, new [] { _demoText });
  315. }
  316. catch (TargetInvocationException e)
  317. {
  318. MessageBox.ErrorQuery ("Exception", e.InnerException.Message, "Ok");
  319. view = null;
  320. }
  321. }
  322. // If the view supports a Title property, set it so we have something to look at
  323. if (view != null && view.GetType ().GetProperty ("Title") != null)
  324. {
  325. if (view.GetType ().GetProperty ("Title")!.PropertyType == typeof (string))
  326. {
  327. view?.GetType ()
  328. .GetProperty ("Title")
  329. ?.GetSetMethod ()
  330. ?.Invoke (view, new [] { "Test Title" });
  331. }
  332. else
  333. {
  334. view?.GetType ()
  335. .GetProperty ("Title")
  336. ?.GetSetMethod ()
  337. ?.Invoke (view, new [] { "Test Title" });
  338. }
  339. }
  340. // If the view supports a Source property, set it so we have something to look at
  341. if (view != null && view.GetType ().GetProperty ("Source") != null && view.GetType ().GetProperty ("Source").PropertyType == typeof (IListDataSource))
  342. {
  343. var source = new ListWrapper (new List<string> { "Test Text #1", "Test Text #2", "Test Text #3" });
  344. view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source });
  345. }
  346. // If the view supports a Title property, set it so we have something to look at
  347. if (view?.GetType ().GetProperty ("Orientation") is { } prop)
  348. {
  349. _orientation.SelectedItem = (int)prop.GetGetMethod ()!.Invoke (view, null)!;
  350. _orientation.Enabled = true;
  351. }
  352. else
  353. {
  354. _orientation.Enabled = false;
  355. }
  356. view.Initialized += View_Initialized;
  357. // Add
  358. _hostPane.Add (view);
  359. _hostPane.SetNeedsDisplay ();
  360. return view;
  361. }
  362. private void DimPosChanged (View view)
  363. {
  364. if (view == null)
  365. {
  366. return;
  367. }
  368. try
  369. {
  370. view.X = _xRadioGroup.SelectedItem switch
  371. {
  372. 0 => Pos.Percent (_xVal),
  373. 1 => Pos.AnchorEnd (),
  374. 2 => Pos.Center (),
  375. 3 => Pos.Absolute (_xVal),
  376. _ => view.X
  377. };
  378. view.Y = _yRadioGroup.SelectedItem switch
  379. {
  380. 0 => Pos.Percent (_yVal),
  381. 1 => Pos.AnchorEnd (),
  382. 2 => Pos.Center (),
  383. 3 => Pos.Absolute (_yVal),
  384. _ => view.Y
  385. };
  386. view.Width = _wRadioGroup.SelectedItem switch
  387. {
  388. 0 => Dim.Auto (),
  389. 1 => Dim.Percent (_wVal),
  390. 2 => Dim.Fill (_wVal),
  391. 3 => Dim.Absolute (_wVal),
  392. _ => view.Width
  393. };
  394. view.Height = _hRadioGroup.SelectedItem switch
  395. {
  396. 0 => Dim.Auto (),
  397. 1 => Dim.Percent (_hVal),
  398. 2 => Dim.Fill (_hVal),
  399. 3 => Dim.Absolute (_hVal),
  400. _ => view.Height
  401. };
  402. }
  403. catch (Exception e)
  404. {
  405. MessageBox.ErrorQuery ("Exception", e.Message, "Ok");
  406. }
  407. if (view.Width is DimAuto)
  408. {
  409. _wText.Text = "Auto";
  410. _wText.Enabled = false;
  411. }
  412. else
  413. {
  414. _wText.Text = $"{_wVal}";
  415. _wText.Enabled = true;
  416. }
  417. if (view.Height is DimAuto)
  418. {
  419. _hText.Text = "Auto";
  420. _hText.Enabled = false;
  421. }
  422. else
  423. {
  424. _hText.Text = $"{_hVal}";
  425. _hText.Enabled = true;
  426. }
  427. UpdateTitle (view);
  428. }
  429. private List<Type> GetAllViewClassesCollection ()
  430. {
  431. List<Type> types = new ();
  432. foreach (Type type in typeof (View).Assembly.GetTypes ()
  433. .Where (
  434. myType =>
  435. myType.IsClass && !myType.IsAbstract && myType.IsPublic && myType.IsSubclassOf (typeof (View))
  436. ))
  437. {
  438. types.Add (type);
  439. }
  440. types.Add (typeof (View));
  441. return types;
  442. }
  443. private void LayoutCompleteHandler (object sender, LayoutEventArgs args)
  444. {
  445. UpdateSettings (_curView);
  446. UpdateTitle (_curView);
  447. }
  448. private void Quit () { Application.RequestStop (); }
  449. private void UpdateSettings (View view)
  450. {
  451. var x = view.X.ToString ();
  452. var y = view.Y.ToString ();
  453. _xRadioGroup.SelectedItem = _posNames.IndexOf (_posNames.Where (s => x.Contains (s)).First ());
  454. _yRadioGroup.SelectedItem = _posNames.IndexOf (_posNames.Where (s => y.Contains (s)).First ());
  455. _xText.Text = $"{view.Frame.X}";
  456. _yText.Text = $"{view.Frame.Y}";
  457. var w = view.Width.ToString ();
  458. var h = view.Height.ToString ();
  459. _wRadioGroup.SelectedItem = _dimNames.IndexOf (_dimNames.Where (s => w.Contains (s)).First ());
  460. _hRadioGroup.SelectedItem = _dimNames.IndexOf (_dimNames.Where (s => h.Contains (s)).First ());
  461. if (view.Width is DimAuto)
  462. {
  463. _wText.Text = "Auto";
  464. _wText.Enabled = false;
  465. }
  466. else
  467. {
  468. _wText.Text = "100";
  469. _wText.Enabled = true;
  470. }
  471. if (view.Height is DimAuto)
  472. {
  473. _hText.Text = "Auto";
  474. _hText.Enabled = false;
  475. }
  476. else
  477. {
  478. _hText.Text = "100";
  479. _hText.Enabled = true;
  480. }
  481. }
  482. private void UpdateTitle (View view) { _hostPane.Title = $"{view.GetType ().Name} - {view.X}, {view.Y}, {view.Width}, {view.Height}"; }
  483. private void View_Initialized (object sender, EventArgs e)
  484. {
  485. if (sender is not View view)
  486. {
  487. return;
  488. }
  489. if (view.Width is not DimAuto && (view.Width is null || view.Frame.Width == 0))
  490. {
  491. view.Width = Dim.Fill ();
  492. }
  493. if (view.Height is not DimAuto && (view.Height is null || view.Frame.Height == 0))
  494. {
  495. view.Height = Dim.Fill ();
  496. }
  497. UpdateSettings (view);
  498. UpdateTitle (view);
  499. }
  500. }