RadioGroup.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. using NStack;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. namespace Terminal.Gui {
  6. /// <summary>
  7. /// <see cref="RadioGroup"/> shows a group of radio labels, only one of those can be selected at a given time
  8. /// </summary>
  9. public class RadioGroup : View {
  10. int selected, cursor;
  11. void Init(Rect rect, ustring [] radioLabels, int selected)
  12. {
  13. if (radioLabels == null) {
  14. this.radioLabels = new List<ustring>();
  15. } else {
  16. this.radioLabels = radioLabels.ToList ();
  17. }
  18. this.selected = selected;
  19. SetWidthHeight (this.radioLabels);
  20. CanFocus = true;
  21. }
  22. /// <summary>
  23. /// Initializes a new instance of the <see cref="RadioGroup"/> class using <see cref="LayoutStyle.Computed"/> layout.
  24. /// </summary>
  25. public RadioGroup () : this (radioLabels: new ustring [] { }) { }
  26. /// <summary>
  27. /// Initializes a new instance of the <see cref="RadioGroup"/> class using <see cref="LayoutStyle.Computed"/> layout.
  28. /// </summary>
  29. /// <param name="radioLabels">The radio labels; an array of strings that can contain hotkeys using an underscore before the letter.</param>
  30. /// <param name="selected">The index of the item to be selected, the value is clamped to the number of items.</param>
  31. public RadioGroup (ustring [] radioLabels, int selected = 0) : base ()
  32. {
  33. Init (Rect.Empty, radioLabels, selected);
  34. }
  35. /// <summary>
  36. /// Initializes a new instance of the <see cref="RadioGroup"/> class using <see cref="LayoutStyle.Absolute"/> layout.
  37. /// </summary>
  38. /// <param name="rect">Boundaries for the radio group.</param>
  39. /// <param name="radioLabels">The radio labels; an array of strings that can contain hotkeys using an underscore before the letter.</param>
  40. /// <param name="selected">The index of item to be selected, the value is clamped to the number of items.</param>
  41. public RadioGroup (Rect rect, ustring [] radioLabels, int selected = 0) : base (rect)
  42. {
  43. Init (rect, radioLabels, selected);
  44. }
  45. /// <summary>
  46. /// Initializes a new instance of the <see cref="RadioGroup"/> class using <see cref="LayoutStyle.Absolute"/> layout.
  47. /// The <see cref="View"/> frame is computed from the provided radio labels.
  48. /// </summary>
  49. /// <param name="x">The x coordinate.</param>
  50. /// <param name="y">The y coordinate.</param>
  51. /// <param name="radioLabels">The radio labels; an array of strings that can contain hotkeys using an underscore before the letter.</param>
  52. /// <param name="selected">The item to be selected, the value is clamped to the number of items.</param>
  53. public RadioGroup (int x, int y, ustring [] radioLabels, int selected = 0) :
  54. this (MakeRect (x, y, radioLabels != null ? radioLabels.ToList() : null), radioLabels, selected) { }
  55. /// <summary>
  56. /// The location of the cursor in the <see cref="RadioGroup"/>
  57. /// </summary>
  58. public int Cursor {
  59. get => cursor;
  60. set {
  61. if (cursor < 0 || cursor >= radioLabels.Count)
  62. return;
  63. cursor = value;
  64. SetNeedsDisplay ();
  65. }
  66. }
  67. void SetWidthHeight (List<ustring> radioLabels)
  68. {
  69. var r = MakeRect(0, 0, radioLabels);
  70. if (LayoutStyle == LayoutStyle.Computed) {
  71. Width = r.Width;
  72. Height = radioLabels.Count;
  73. } else {
  74. Frame = new Rect (Frame.Location, new Size (r.Width, radioLabels.Count));
  75. }
  76. }
  77. static Rect MakeRect (int x, int y, List<ustring> radioLabels)
  78. {
  79. int width = 0;
  80. if (radioLabels == null) {
  81. return new Rect (x, y, width, 0);
  82. }
  83. foreach (var s in radioLabels)
  84. width = Math.Max (s.Length + 3, width);
  85. return new Rect (x, y, width, radioLabels.Count);
  86. }
  87. List<ustring> radioLabels = new List<ustring> ();
  88. /// <summary>
  89. /// The radio labels to display
  90. /// </summary>
  91. /// <value>The radio labels.</value>
  92. public ustring [] RadioLabels {
  93. get => radioLabels.ToArray();
  94. set {
  95. var prevCount = radioLabels.Count;
  96. radioLabels = value.ToList ();
  97. if (prevCount != radioLabels.Count) {
  98. SetWidthHeight (radioLabels);
  99. }
  100. Selected = 0;
  101. cursor = 0;
  102. SetNeedsDisplay ();
  103. }
  104. }
  105. //// Redraws the RadioGroup
  106. //void Update(List<ustring> newRadioLabels)
  107. //{
  108. // for (int i = 0; i < radioLabels.Count; i++) {
  109. // Move(0, i);
  110. // Driver.SetAttribute(ColorScheme.Normal);
  111. // Driver.AddStr(ustring.Make(new string (' ', radioLabels[i].Length + 4)));
  112. // }
  113. // if (newRadioLabels.Count != radioLabels.Count) {
  114. // SetWidthHeight(newRadioLabels);
  115. // }
  116. //}
  117. ///<inheritdoc/>
  118. public override void Redraw (Rect bounds)
  119. {
  120. Driver.SetAttribute (ColorScheme.Normal);
  121. Clear ();
  122. for (int i = 0; i < radioLabels.Count; i++) {
  123. Move (0, i);
  124. Driver.SetAttribute (ColorScheme.Normal);
  125. Driver.AddStr (ustring.Make(new Rune[] { (i == selected ? Driver.Selected : Driver.UnSelected), ' '}));
  126. DrawHotString (radioLabels [i], HasFocus && i == cursor, ColorScheme);
  127. }
  128. base.Redraw (bounds);
  129. }
  130. ///<inheritdoc/>
  131. public override void PositionCursor ()
  132. {
  133. Move (0, cursor);
  134. }
  135. /// <summary>
  136. /// Invoked when the selected radio label has changed. The passed <c>int</c> indicates the newly selected item.
  137. /// </summary>
  138. public Action<int> SelectedItemChanged;
  139. /// <summary>
  140. /// The currently selected item from the list of radio labels
  141. /// </summary>
  142. /// <value>The selected.</value>
  143. public int Selected {
  144. get => selected;
  145. set {
  146. selected = value;
  147. SelectedItemChanged?.Invoke (selected);
  148. SetNeedsDisplay ();
  149. }
  150. }
  151. ///<inheritdoc/>
  152. public override bool ProcessColdKey (KeyEvent kb)
  153. {
  154. var key = kb.KeyValue;
  155. if (key < Char.MaxValue && Char.IsLetterOrDigit ((char)key)) {
  156. int i = 0;
  157. key = Char.ToUpper ((char)key);
  158. foreach (var l in radioLabels) {
  159. bool nextIsHot = false;
  160. foreach (var c in l) {
  161. if (c == '_')
  162. nextIsHot = true;
  163. else {
  164. if (nextIsHot && c == key) {
  165. Selected = i;
  166. cursor = i;
  167. if (!HasFocus)
  168. SuperView.SetFocus (this);
  169. return true;
  170. }
  171. nextIsHot = false;
  172. }
  173. }
  174. i++;
  175. }
  176. }
  177. return false;
  178. }
  179. ///<inheritdoc/>
  180. public override bool ProcessKey (KeyEvent kb)
  181. {
  182. switch (kb.Key) {
  183. case Key.CursorUp:
  184. if (cursor > 0) {
  185. cursor--;
  186. SetNeedsDisplay ();
  187. return true;
  188. }
  189. break;
  190. case Key.CursorDown:
  191. if (cursor + 1 < radioLabels.Count) {
  192. cursor++;
  193. SetNeedsDisplay ();
  194. return true;
  195. }
  196. break;
  197. case Key.Space:
  198. Selected = cursor;
  199. return true;
  200. }
  201. return base.ProcessKey (kb);
  202. }
  203. ///<inheritdoc/>
  204. public override bool MouseEvent (MouseEvent me)
  205. {
  206. if (!me.Flags.HasFlag (MouseFlags.Button1Clicked))
  207. return false;
  208. SuperView.SetFocus (this);
  209. if (me.Y < radioLabels.Count) {
  210. cursor = Selected = me.Y;
  211. SetNeedsDisplay ();
  212. }
  213. return true;
  214. }
  215. }
  216. }