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