CharacterMap.cs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. using NStack;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using Terminal.Gui;
  6. using Rune = System.Rune;
  7. namespace UICatalog {
  8. /// <summary>
  9. /// This Scenario demonstrates building a custom control (a class deriving from View) that:
  10. /// - Provides a simple "Character Map" application (like Windows' charmap.exe).
  11. /// - Helps test unicode character rendering in Terminal.Gui
  12. /// - Illustrates how to use ScrollView to do infinite scrolling
  13. /// </summary>
  14. [ScenarioMetadata (Name: "Character Map", Description: "Illustrates a custom control and Unicode")]
  15. [ScenarioCategory ("Text")]
  16. [ScenarioCategory ("Controls")]
  17. class CharacterMap : Scenario {
  18. public override void Setup ()
  19. {
  20. var charMap = new CharMap () {
  21. X = 0,
  22. Y = 0,
  23. Width = CharMap.RowWidth + 2,
  24. Height = Dim.Fill (),
  25. Start = 0x2500,
  26. ColorScheme = Colors.Dialog,
  27. CanFocus = true,
  28. };
  29. Win.Add (charMap);
  30. var label = new Label ("Jump To Unicode Block:") { X = Pos.Right (charMap) + 1, Y = Pos.Y (charMap) };
  31. Win.Add (label);
  32. (ustring radioLabel, int start, int end) CreateRadio (ustring title, int start, int end)
  33. {
  34. return ($"{title} (U+{start:x5}-{end:x5})", start, end);
  35. }
  36. var radioItems = new (ustring radioLabel, int start, int end) [] {
  37. CreateRadio("ASCII Control Characterss", 0x00, 0x1F),
  38. CreateRadio("C0 Control Characters", 0x80, 0x9f),
  39. CreateRadio("Hangul Jamo", 0x1100, 0x11ff), // This is where wide chars tend to start
  40. CreateRadio("Currency Symbols", 0x20A0, 0x20CF),
  41. CreateRadio("Letterlike Symbols", 0x2100, 0x214F),
  42. CreateRadio("Arrows", 0x2190, 0x21ff),
  43. CreateRadio("Mathematical symbols", 0x2200, 0x22ff),
  44. CreateRadio("Miscellaneous Technical", 0x2300, 0x23ff),
  45. CreateRadio("Box Drawing & Geometric Shapes", 0x2500, 0x25ff),
  46. CreateRadio("Miscellaneous Symbols", 0x2600, 0x26ff),
  47. CreateRadio("Dingbats", 0x2700, 0x27ff),
  48. CreateRadio("Braille", 0x2800, 0x28ff),
  49. CreateRadio("Miscellaneous Symbols and Arrows", 0x2b00, 0x2bff),
  50. CreateRadio("Alphabetic Presentation Forms", 0xFB00, 0xFb4f),
  51. CreateRadio("Cuneiform Numbers and Punctuation", 0x12400, 0x1240f),
  52. CreateRadio("Chess Symbols", 0x1FA00, 0x1FA0f),
  53. CreateRadio("End", CharMap.MaxCodePointVal - 16, CharMap.MaxCodePointVal),
  54. };
  55. var jumpList = new RadioGroup (radioItems.Select (t => t.radioLabel).ToArray ());
  56. jumpList.X = Pos.X (label);
  57. jumpList.Y = Pos.Bottom (label);
  58. jumpList.Width = Dim.Fill ();
  59. jumpList.SelectedItemChanged = (args) => {
  60. charMap.Start = radioItems [args.SelectedItem].start;
  61. };
  62. Win.Add (jumpList);
  63. }
  64. }
  65. class CharMap : ScrollView {
  66. /// <summary>
  67. /// Specifies the starting offset for the character map. The default is 0x2500
  68. /// which is the Box Drawing characters.
  69. /// </summary>
  70. public int Start {
  71. get => _start;
  72. set {
  73. _start = value;
  74. ContentOffset = new Point (0, _start / 16);
  75. SetNeedsDisplay ();
  76. }
  77. }
  78. int _start = 0x2500;
  79. public static int MaxCodePointVal => 0xE0FFF;
  80. // Row Header + space + (space + char + space)
  81. public static int RowHeaderWidth => $"U+{MaxCodePointVal:x5}".Length;
  82. public static int RowWidth => RowHeaderWidth + (" c".Length * 16);
  83. public CharMap ()
  84. {
  85. ContentSize = new Size (CharMap.RowWidth, MaxCodePointVal / 16);
  86. ShowVerticalScrollIndicator = true;
  87. ShowHorizontalScrollIndicator = false;
  88. LayoutComplete += (args) => {
  89. if (Bounds.Width <= RowWidth) {
  90. ShowHorizontalScrollIndicator = true;
  91. } else {
  92. ShowHorizontalScrollIndicator = false;
  93. }
  94. };
  95. DrawContent += CharMap_DrawContent;
  96. }
  97. #if true
  98. private void CharMap_DrawContent (Rect viewport)
  99. {
  100. //Rune ReplaceNonPrintables (Rune c)
  101. //{
  102. // if (c < 0x20) {
  103. // return new Rune (c + 0x2400); // U+25A1 □ WHITE SQUARE
  104. // } else {
  105. // return c;
  106. // }
  107. //}
  108. for (int header = 0; header < 16; header++) {
  109. Move (viewport.X + RowHeaderWidth + (header * 2), 0);
  110. Driver.AddStr ($" {header:x} ");
  111. }
  112. for (int row = 0, y = 0; row < viewport.Height / 2 - 1; row++, y += 2) {
  113. int val = (-viewport.Y + row) * 16;
  114. if (val < MaxCodePointVal) {
  115. var rowLabel = $"U+{val / 16:x4}x";
  116. Move (0, y + 1);
  117. Driver.AddStr (rowLabel);
  118. var prevColWasWide = false;
  119. for (int col = 0; col < 16; col++) {
  120. var rune = new Rune ((uint)((uint)(-viewport.Y + row) * 16 + col));
  121. Move (viewport.X + RowHeaderWidth + (col * 2) + (prevColWasWide ? 0 : 1), 0 + y + 1);
  122. Driver.AddRune (rune);
  123. //prevColWasWide = Rune.ColumnWidth(rune) > 1;
  124. }
  125. }
  126. }
  127. }
  128. #else
  129. public override void OnDrawContent (Rect viewport)
  130. {
  131. CharMap_DrawContent(this, viewport);
  132. base.OnDrawContent (viewport);
  133. }
  134. #endif
  135. }
  136. }