FlagSelector.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. #nullable enable
  2. namespace Terminal.Gui;
  3. /// <summary>
  4. /// Provides a user interface for displaying and selecting flags.
  5. /// Flags can be set from a dictionary or directly from an enum type.
  6. /// </summary>
  7. public class FlagSelector : View, IDesignable, IOrientation
  8. {
  9. /// <summary>
  10. /// Initializes a new instance of the <see cref="FlagSelector"/> class.
  11. /// </summary>
  12. public FlagSelector ()
  13. {
  14. CanFocus = true;
  15. Width = Dim.Auto (DimAutoStyle.Content);
  16. Height = Dim.Auto (DimAutoStyle.Content);
  17. // ReSharper disable once UseObjectOrCollectionInitializer
  18. _orientationHelper = new (this);
  19. _orientationHelper.Orientation = Orientation.Vertical;
  20. // Accept (Enter key or DoubleClick) - Raise Accept event - DO NOT advance state
  21. AddCommand (Command.Accept, HandleAcceptCommand);
  22. CreateSubViews ();
  23. }
  24. private bool? HandleAcceptCommand (ICommandContext? ctx) { return RaiseAccepting (ctx); }
  25. private uint _value;
  26. /// <summary>
  27. /// Gets or sets the value of the selected flags.
  28. /// </summary>
  29. public uint Value
  30. {
  31. get => _value;
  32. set
  33. {
  34. if (_value == value)
  35. {
  36. return;
  37. }
  38. _value = value;
  39. if (_value == 0)
  40. {
  41. UncheckAll ();
  42. }
  43. else
  44. {
  45. UncheckNone ();
  46. UpdateChecked ();
  47. }
  48. if (ValueEdit is { })
  49. {
  50. ValueEdit.Text = value.ToString ();
  51. }
  52. RaiseValueChanged ();
  53. }
  54. }
  55. private void RaiseValueChanged ()
  56. {
  57. OnValueChanged ();
  58. ValueChanged?.Invoke (this, new (Value));
  59. }
  60. /// <summary>
  61. /// Called when <see cref="Value"/> has changed.
  62. /// </summary>
  63. protected virtual void OnValueChanged () { }
  64. /// <summary>
  65. /// Raised when <see cref="Value"/> has changed.
  66. /// </summary>
  67. public event EventHandler<EventArgs<uint>>? ValueChanged;
  68. private FlagSelectorStyles _styles;
  69. /// <summary>
  70. /// Gets or sets the styles for the flag selector.
  71. /// </summary>
  72. public FlagSelectorStyles Styles
  73. {
  74. get => _styles;
  75. set
  76. {
  77. if (_styles == value)
  78. {
  79. return;
  80. }
  81. _styles = value;
  82. CreateSubViews ();
  83. }
  84. }
  85. /// <summary>
  86. /// Set the flags and flag names.
  87. /// </summary>
  88. /// <param name="flags"></param>
  89. public void SetFlags (IReadOnlyDictionary<uint, string> flags)
  90. {
  91. Flags = flags;
  92. CreateSubViews ();
  93. }
  94. /// <summary>
  95. /// Set the flags and flag names from an enum type.
  96. /// </summary>
  97. /// <typeparam name="TEnum">The enum type to extract flags from</typeparam>
  98. /// <remarks>
  99. /// This is a convenience method that converts an enum to a dictionary of flag values and names.
  100. /// The enum values are converted to uint values and the enum names become the display text.
  101. /// </remarks>
  102. public void SetFlags<TEnum> () where TEnum : struct, Enum
  103. {
  104. // Convert enum names and values to a dictionary
  105. Dictionary<uint, string> flagsDictionary = Enum.GetValues<TEnum> ()
  106. .ToDictionary (
  107. f => Convert.ToUInt32 (f),
  108. f => f.ToString ()
  109. );
  110. SetFlags (flagsDictionary);
  111. }
  112. /// <summary>
  113. /// Set the flags and flag names from an enum type with custom display names.
  114. /// </summary>
  115. /// <typeparam name="TEnum">The enum type to extract flags from</typeparam>
  116. /// <param name="nameSelector">A function that converts enum values to display names</param>
  117. /// <remarks>
  118. /// This is a convenience method that converts an enum to a dictionary of flag values and custom names.
  119. /// The enum values are converted to uint values and the display names are determined by the nameSelector function.
  120. /// </remarks>
  121. /// <example>
  122. /// <code>
  123. /// // Use enum values with custom display names
  124. /// var flagSelector = new FlagSelector ();
  125. /// flagSelector.SetFlags&lt;FlagSelectorStyles&gt;
  126. /// (f => f switch {
  127. /// FlagSelectorStyles.ShowNone => "Show None Value",
  128. /// FlagSelectorStyles.ShowValueEdit => "Show Value Editor",
  129. /// FlagSelectorStyles.All => "Everything",
  130. /// _ => f.ToString()
  131. /// });
  132. /// </code>
  133. /// </example>
  134. public void SetFlags<TEnum> (Func<TEnum, string> nameSelector) where TEnum : struct, Enum
  135. {
  136. // Convert enum values and custom names to a dictionary
  137. Dictionary<uint, string> flagsDictionary = Enum.GetValues<TEnum> ()
  138. .ToDictionary (
  139. f => Convert.ToUInt32 (f),
  140. nameSelector
  141. );
  142. SetFlags (flagsDictionary);
  143. }
  144. /// <summary>
  145. /// Gets the flags.
  146. /// </summary>
  147. public IReadOnlyDictionary<uint, string>? Flags { get; internal set; }
  148. private TextField? ValueEdit { get; set; }
  149. private void CreateSubViews ()
  150. {
  151. if (Flags is null)
  152. {
  153. return;
  154. }
  155. View [] subviews = SubViews.ToArray ();
  156. RemoveAll ();
  157. foreach (View v in subviews)
  158. {
  159. v.Dispose ();
  160. }
  161. if (Styles.HasFlag (FlagSelectorStyles.ShowNone) && !Flags.ContainsKey (0))
  162. {
  163. Add (CreateCheckBox ("None", 0));
  164. }
  165. for (var index = 0; index < Flags.Count; index++)
  166. {
  167. if (!Styles.HasFlag (FlagSelectorStyles.ShowNone) && Flags.ElementAt (index).Key == 0)
  168. {
  169. continue;
  170. }
  171. Add (CreateCheckBox (Flags.ElementAt (index).Value, Flags.ElementAt (index).Key));
  172. }
  173. if (Styles.HasFlag (FlagSelectorStyles.ShowValueEdit))
  174. {
  175. ValueEdit = new ()
  176. {
  177. Id = "valueEdit",
  178. CanFocus = false,
  179. Text = Value.ToString (),
  180. Width = 5,
  181. ReadOnly = true
  182. };
  183. Add (ValueEdit);
  184. }
  185. SetLayout ();
  186. return;
  187. CheckBox CreateCheckBox (string name, uint flag)
  188. {
  189. var checkbox = new CheckBox
  190. {
  191. CanFocus = false,
  192. Title = name,
  193. Id = name,
  194. Data = flag,
  195. HighlightStyle = HighlightStyle
  196. };
  197. checkbox.Selecting += (sender, args) => { RaiseSelecting (args.Context); };
  198. checkbox.CheckedStateChanged += (sender, args) =>
  199. {
  200. uint newValue = Value;
  201. if (checkbox.CheckedState == CheckState.Checked)
  202. {
  203. if ((uint)checkbox.Data == 0)
  204. {
  205. newValue = 0;
  206. }
  207. else
  208. {
  209. newValue |= flag;
  210. }
  211. }
  212. else
  213. {
  214. newValue &= ~flag;
  215. }
  216. Value = newValue;
  217. //UpdateChecked();
  218. };
  219. return checkbox;
  220. }
  221. }
  222. private void SetLayout ()
  223. {
  224. foreach (View sv in SubViews)
  225. {
  226. if (Orientation == Orientation.Vertical)
  227. {
  228. sv.X = 0;
  229. sv.Y = Pos.Align (Alignment.Start);
  230. }
  231. else
  232. {
  233. sv.X = Pos.Align (Alignment.Start);
  234. sv.Y = 0;
  235. sv.Margin!.Thickness = new (0, 0, 1, 0);
  236. }
  237. }
  238. }
  239. private void UncheckAll ()
  240. {
  241. foreach (CheckBox cb in SubViews.Where (sv => sv is CheckBox cb && cb.Title != "None").Cast<CheckBox> ())
  242. {
  243. cb.CheckedState = CheckState.UnChecked;
  244. }
  245. }
  246. private void UncheckNone ()
  247. {
  248. foreach (CheckBox cb in SubViews.Where (sv => sv is CheckBox { Title: "None" }).Cast<CheckBox> ())
  249. {
  250. cb.CheckedState = CheckState.UnChecked;
  251. }
  252. }
  253. private void UpdateChecked ()
  254. {
  255. foreach (CheckBox cb in SubViews.Where (sv => sv is CheckBox { }).Cast<CheckBox> ())
  256. {
  257. var flag = (uint)(cb.Data ?? throw new InvalidOperationException ("ComboBox.Data must be set"));
  258. // If this flag is set in Value, check the checkbox. Otherwise, uncheck it.
  259. if (flag == 0 && Value != 0)
  260. {
  261. cb.CheckedState = CheckState.UnChecked;
  262. }
  263. else
  264. {
  265. cb.CheckedState = (Value & flag) == flag ? CheckState.Checked : CheckState.UnChecked;
  266. }
  267. }
  268. }
  269. /// <inheritdoc/>
  270. protected override void OnSubViewAdded (View view) { }
  271. #region IOrientation
  272. /// <summary>
  273. /// Gets or sets the <see cref="Orientation"/> for this <see cref="RadioGroup"/>. The default is
  274. /// <see cref="Orientation.Vertical"/>.
  275. /// </summary>
  276. public Orientation Orientation
  277. {
  278. get => _orientationHelper.Orientation;
  279. set => _orientationHelper.Orientation = value;
  280. }
  281. private readonly OrientationHelper _orientationHelper;
  282. #pragma warning disable CS0067 // The event is never used
  283. /// <inheritdoc/>
  284. public event EventHandler<CancelEventArgs<Orientation>>? OrientationChanging;
  285. /// <inheritdoc/>
  286. public event EventHandler<EventArgs<Orientation>>? OrientationChanged;
  287. #pragma warning restore CS0067 // The event is never used
  288. #pragma warning restore CS0067
  289. /// <summary>Called when <see cref="Orientation"/> has changed.</summary>
  290. /// <param name="newOrientation"></param>
  291. public void OnOrientationChanged (Orientation newOrientation) { SetLayout (); }
  292. #endregion IOrientation
  293. /// <inheritdoc/>
  294. public bool EnableForDesign ()
  295. {
  296. SetFlags<FlagSelectorStyles> (
  297. f => f switch
  298. {
  299. FlagSelectorStyles.ShowNone => "Show _None Value",
  300. FlagSelectorStyles.ShowValueEdit => "Show _Value Editor",
  301. FlagSelectorStyles.All => "Show _All Flags Selector",
  302. _ => f.ToString ()
  303. });
  304. return true;
  305. }
  306. }