CharacterMap.cs 5.5 KB

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