CharacterMap.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. #nullable enable
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Globalization;
  5. using System.Linq;
  6. using System.Text;
  7. using Terminal.Gui;
  8. namespace UICatalog.Scenarios;
  9. /// <summary>
  10. /// This Scenario demonstrates building a custom control (a class deriving from View) that: - Provides a
  11. /// "Character Map" application (like Windows' charmap.exe). - Helps test unicode character rendering in Terminal.Gui -
  12. /// Illustrates how to do infinite scrolling
  13. /// </summary>
  14. [ScenarioMetadata ("Character Map", "Unicode viewer. Demos infinite content drawing and scrolling.")]
  15. [ScenarioCategory ("Text and Formatting")]
  16. [ScenarioCategory ("Drawing")]
  17. [ScenarioCategory ("Controls")]
  18. [ScenarioCategory ("Layout")]
  19. [ScenarioCategory ("Scrolling")]
  20. public class CharacterMap : Scenario
  21. {
  22. private Label? _errorLabel;
  23. private TableView? _categoryList;
  24. private CharMap? _charMap;
  25. // Don't create a Window, just return the top-level view
  26. public override void Main ()
  27. {
  28. Application.Init ();
  29. var top = new Window
  30. {
  31. BorderStyle = LineStyle.None
  32. };
  33. _charMap = new ()
  34. {
  35. X = 0,
  36. Y = 1,
  37. Width = Dim.Fill (Dim.Func (() => _categoryList!.Frame.Width)),
  38. Height = Dim.Fill (),
  39. // SchemeName = "Base"
  40. };
  41. top.Add (_charMap);
  42. var jumpLabel = new Label
  43. {
  44. X = Pos.Right (_charMap) + 1,
  45. Y = Pos.Y (_charMap),
  46. HotKeySpecifier = (Rune)'_',
  47. Text = "_Jump To:",
  48. //SchemeName = "Dialog"
  49. };
  50. top.Add (jumpLabel);
  51. var jumpEdit = new TextField
  52. {
  53. X = Pos.Right (jumpLabel) + 1,
  54. Y = Pos.Y (_charMap),
  55. Width = 17,
  56. Caption = "e.g. 01BE3 or ✈",
  57. //SchemeName = "Dialog"
  58. };
  59. top.Add (jumpEdit);
  60. _charMap.SelectedCodePointChanged += (sender, args) =>
  61. {
  62. if (Rune.IsValid (args.CurrentValue))
  63. {
  64. jumpEdit.Text = ((Rune)args.CurrentValue).ToString ();
  65. }
  66. else
  67. {
  68. jumpEdit.Text = $"U+{args.CurrentValue:x5}";
  69. }
  70. };
  71. _errorLabel = new ()
  72. {
  73. X = Pos.Right (jumpEdit) + 1,
  74. Y = Pos.Y (_charMap),
  75. SchemeName = "error",
  76. Text = "err",
  77. Visible = false
  78. };
  79. top.Add (_errorLabel);
  80. jumpEdit.Accepting += JumpEditOnAccept;
  81. _categoryList = new () {
  82. X = Pos.Right (_charMap),
  83. Y = Pos.Bottom (jumpLabel),
  84. Height = Dim.Fill (),
  85. //SchemeName = "Dialog"
  86. };
  87. _categoryList.FullRowSelect = true;
  88. _categoryList.MultiSelect = false;
  89. _categoryList.Style.ShowVerticalCellLines = false;
  90. _categoryList.Style.AlwaysShowHeaders = true;
  91. var isDescending = false;
  92. _categoryList.Table = CreateCategoryTable (0, isDescending);
  93. // if user clicks the mouse in TableView
  94. _categoryList.MouseClick += (s, e) =>
  95. {
  96. _categoryList.ScreenToCell (e.Position, out int? clickedCol);
  97. if (clickedCol != null && e.Flags.HasFlag (MouseFlags.Button1Clicked))
  98. {
  99. EnumerableTableSource<UnicodeRange> table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
  100. string prevSelection = table.Data.ElementAt (_categoryList.SelectedRow).Category;
  101. isDescending = !isDescending;
  102. _categoryList.Table = CreateCategoryTable (clickedCol.Value, isDescending);
  103. table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
  104. _categoryList.SelectedRow = table.Data
  105. .Select ((item, index) => new { item, index })
  106. .FirstOrDefault (x => x.item.Category == prevSelection)
  107. ?.index
  108. ?? -1;
  109. }
  110. };
  111. int longestName = UnicodeRange.Ranges.Max (r => r.Category.GetColumns ());
  112. _categoryList.Style.ColumnStyles.Add (
  113. 0,
  114. new () { MaxWidth = longestName, MinWidth = longestName, MinAcceptableWidth = longestName }
  115. );
  116. _categoryList.Style.ColumnStyles.Add (1, new () { MaxWidth = 1, MinWidth = 6 });
  117. _categoryList.Style.ColumnStyles.Add (2, new () { MaxWidth = 1, MinWidth = 6 });
  118. _categoryList.Width = _categoryList.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 4;
  119. _categoryList.SelectedCellChanged += (s, args) =>
  120. {
  121. EnumerableTableSource<UnicodeRange> table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
  122. _charMap.StartCodePoint = table.Data.ToArray () [args.NewRow].Start;
  123. jumpEdit.Text = $"U+{_charMap.SelectedCodePoint:x5}";
  124. };
  125. top.Add (_categoryList);
  126. var menu = new MenuBarv2
  127. {
  128. Menus =
  129. [
  130. new (
  131. "_File",
  132. new MenuItemv2 []
  133. {
  134. new (
  135. "_Quit",
  136. $"{Application.QuitKey}",
  137. () => Application.RequestStop ()
  138. )
  139. }
  140. ),
  141. new (
  142. "_Options",
  143. new MenuItemv2 [] { CreateMenuShowWidth () }
  144. )
  145. ]
  146. };
  147. top.Add (menu);
  148. _charMap.SelectedCodePoint = 0;
  149. _charMap.SetFocus ();
  150. Application.Run (top);
  151. top.Dispose ();
  152. Application.Shutdown ();
  153. return;
  154. void JumpEditOnAccept (object? sender, CommandEventArgs e)
  155. {
  156. if (jumpEdit.Text.Length == 0)
  157. {
  158. return;
  159. }
  160. _errorLabel.Visible = true;
  161. uint result = 0;
  162. if (jumpEdit.Text.Length == 1)
  163. {
  164. result = (uint)jumpEdit.Text.ToRunes () [0].Value;
  165. }
  166. else if (jumpEdit.Text.StartsWith ("U+", StringComparison.OrdinalIgnoreCase) || jumpEdit.Text.StartsWith ("\\u"))
  167. {
  168. try
  169. {
  170. result = uint.Parse (jumpEdit.Text [2..], NumberStyles.HexNumber);
  171. }
  172. catch (FormatException)
  173. {
  174. _errorLabel.Text = "Invalid hex value";
  175. return;
  176. }
  177. }
  178. else if (jumpEdit.Text.StartsWith ("0", StringComparison.OrdinalIgnoreCase) || jumpEdit.Text.StartsWith ("\\u"))
  179. {
  180. try
  181. {
  182. result = uint.Parse (jumpEdit.Text, NumberStyles.HexNumber);
  183. }
  184. catch (FormatException)
  185. {
  186. _errorLabel.Text = "Invalid hex value";
  187. return;
  188. }
  189. }
  190. else
  191. {
  192. try
  193. {
  194. result = uint.Parse (jumpEdit.Text, NumberStyles.Integer);
  195. }
  196. catch (FormatException)
  197. {
  198. _errorLabel.Text = "Invalid value";
  199. return;
  200. }
  201. }
  202. if (result > RuneExtensions.MaxUnicodeCodePoint)
  203. {
  204. _errorLabel.Text = "Beyond maximum codepoint";
  205. return;
  206. }
  207. _errorLabel.Visible = false;
  208. EnumerableTableSource<UnicodeRange> table = (EnumerableTableSource<UnicodeRange>)_categoryList!.Table;
  209. _categoryList.SelectedRow = table.Data
  210. .Select ((item, index) => new { item, index })
  211. .FirstOrDefault (x => x.item.Start <= result && x.item.End >= result)
  212. ?.index
  213. ?? -1;
  214. _categoryList.EnsureSelectedCellIsVisible ();
  215. // Ensure the typed glyph is selected
  216. _charMap.SelectedCodePoint = (int)result;
  217. _charMap.SetFocus ();
  218. // Cancel the event to prevent ENTER from being handled elsewhere
  219. e.Handled = true;
  220. }
  221. }
  222. private EnumerableTableSource<UnicodeRange> CreateCategoryTable (int sortByColumn, bool descending)
  223. {
  224. Func<UnicodeRange, object> orderBy;
  225. var categorySort = string.Empty;
  226. var startSort = string.Empty;
  227. var endSort = string.Empty;
  228. string sortIndicator = descending ? Glyphs.DownArrow.ToString () : Glyphs.UpArrow.ToString ();
  229. switch (sortByColumn)
  230. {
  231. case 0:
  232. orderBy = r => r.Category;
  233. categorySort = sortIndicator;
  234. break;
  235. case 1:
  236. orderBy = r => r.Start;
  237. startSort = sortIndicator;
  238. break;
  239. case 2:
  240. orderBy = r => r.End;
  241. endSort = sortIndicator;
  242. break;
  243. default:
  244. throw new ArgumentException ("Invalid column number.");
  245. }
  246. IOrderedEnumerable<UnicodeRange> sortedRanges = descending
  247. ? UnicodeRange.Ranges.OrderByDescending (orderBy)
  248. : UnicodeRange.Ranges.OrderBy (orderBy);
  249. return new (
  250. sortedRanges,
  251. new ()
  252. {
  253. { $"Category{categorySort}", s => s.Category },
  254. { $"Start{startSort}", s => $"{s.Start:x5}" },
  255. { $"End{endSort}", s => $"{s.End:x5}" }
  256. }
  257. );
  258. }
  259. private MenuItemv2 CreateMenuShowWidth ()
  260. {
  261. CheckBox cb = new ()
  262. {
  263. Title = "_Show Glyph Width",
  264. CheckedState = _charMap!.ShowGlyphWidths ? CheckState.Checked : CheckState.None
  265. };
  266. var item = new MenuItemv2 { CommandView = cb };
  267. item.Action += () =>
  268. {
  269. if (_charMap is { })
  270. {
  271. _charMap.ShowGlyphWidths = cb.CheckedState == CheckState.Checked;
  272. }
  273. };
  274. return item;
  275. }
  276. public override List<Key> GetDemoKeyStrokes ()
  277. {
  278. List<Key> keys = new ();
  279. for (var i = 0; i < 200; i++)
  280. {
  281. keys.Add (Key.CursorDown);
  282. }
  283. // Category table
  284. keys.Add (Key.Tab.WithShift);
  285. // Block elements
  286. keys.Add (Key.B);
  287. keys.Add (Key.L);
  288. keys.Add (Key.Tab);
  289. for (var i = 0; i < 200; i++)
  290. {
  291. keys.Add (Key.CursorLeft);
  292. }
  293. return keys;
  294. }
  295. }