Buttons.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Text;
  5. using JetBrains.Annotations;
  6. using Terminal.Gui;
  7. namespace UICatalog.Scenarios;
  8. [ScenarioMetadata ("Buttons", "Demonstrates all sorts of Buttons.")]
  9. [ScenarioCategory ("Controls")]
  10. [ScenarioCategory ("Layout")]
  11. public class Buttons : Scenario
  12. {
  13. public override void Main ()
  14. {
  15. Window main = new ()
  16. {
  17. Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"
  18. };
  19. // Add a label & text field so we can demo IsDefault
  20. var editLabel = new Label { X = 0, Y = 0, TabStop = true, Text = "TextField (to demo IsDefault):" };
  21. main.Add (editLabel);
  22. // Add a TextField using Absolute layout.
  23. var edit = new TextField { X = 31, Width = 15, HotKey = Key.Y.WithAlt };
  24. main.Add (edit);
  25. // This is the default button (IsDefault = true); if user presses ENTER in the TextField
  26. // the scenario will quit
  27. var defaultButton = new Button { X = Pos.Center (), Y = Pos.AnchorEnd (), IsDefault = true, Text = "_Quit" };
  28. defaultButton.Accept += (s, e) => Application.RequestStop ();
  29. main.Add (defaultButton);
  30. var swapButton = new Button
  31. {
  32. X = 50,
  33. Width = 45,
  34. Height = 3,
  35. Text = "S_wap Default (Size = 45, 3)",
  36. ColorScheme = Colors.ColorSchemes ["Error"]
  37. };
  38. swapButton.Accept += (s, e) =>
  39. {
  40. defaultButton.IsDefault = !defaultButton.IsDefault;
  41. swapButton.IsDefault = !swapButton.IsDefault;
  42. };
  43. main.Add (swapButton);
  44. static void DoMessage (Button button, string txt)
  45. {
  46. button.Accept += (s, e) =>
  47. {
  48. string btnText = button.Text;
  49. MessageBox.Query ("Message", $"Did you click {txt}?", "Yes", "No");
  50. };
  51. }
  52. var colorButtonsLabel = new Label { X = 0, Y = Pos.Bottom (swapButton) + 1, Text = "Color Buttons: " };
  53. main.Add (colorButtonsLabel);
  54. View prev = colorButtonsLabel;
  55. foreach (KeyValuePair<string, ColorScheme> colorScheme in Colors.ColorSchemes)
  56. {
  57. var colorButton = new Button
  58. {
  59. X = Pos.Right (prev),
  60. Y = Pos.Y (colorButtonsLabel),
  61. Text = $"_{colorScheme.Key}",
  62. ColorScheme = colorScheme.Value,
  63. };
  64. DoMessage (colorButton, colorButton.Text);
  65. main.Add (colorButton);
  66. prev = colorButton;
  67. }
  68. Button button;
  69. main.Add (
  70. button = new ()
  71. {
  72. X = 2,
  73. Y = Pos.Bottom (colorButtonsLabel) + 1,
  74. Text =
  75. "A super l_öng Button that will probably expose a bug in clipping or wrapping of text. Will it?"
  76. }
  77. );
  78. DoMessage (button, button.Text);
  79. // Note the 'N' in 'Newline' will be the hotkey
  80. main.Add (
  81. button = new () { X = 2, Y = Pos.Bottom (button) + 1, Height = 2, Text = "a Newline\nin the button" }
  82. );
  83. button.Accept += (s, e) => MessageBox.Query ("Message", "Question?", "Yes", "No");
  84. var textChanger = new Button { X = 2, Y = Pos.Bottom (button) + 1, Text = "Te_xt Changer" };
  85. main.Add (textChanger);
  86. textChanger.Accept += (s, e) => textChanger.Text += "!";
  87. main.Add (
  88. button = new ()
  89. {
  90. X = Pos.Right (textChanger) + 2,
  91. Y = Pos.Y (textChanger),
  92. Text = "Lets see if this will move as \"Text Changer\" grows"
  93. }
  94. );
  95. var removeButton = new Button
  96. {
  97. X = 2, Y = Pos.Bottom (button) + 1,
  98. ColorScheme = Colors.ColorSchemes ["Error"], Text = "Remove this button"
  99. };
  100. main.Add (removeButton);
  101. // This in interesting test case because `moveBtn` and below are laid out relative to this one!
  102. removeButton.Accept += (s, e) =>
  103. {
  104. removeButton.Visible = false;
  105. };
  106. var computedFrame = new FrameView
  107. {
  108. X = 0,
  109. Y = Pos.Bottom (removeButton) + 1,
  110. Width = Dim.Percent (50),
  111. Height = 5,
  112. Title = "Computed Layout"
  113. };
  114. main.Add (computedFrame);
  115. // Demonstrates how changing the View.Frame property can move Views
  116. var moveBtn = new Button
  117. {
  118. X = 0,
  119. Y = Pos.Center () - 1,
  120. Width = 30,
  121. Height = 1,
  122. ColorScheme = Colors.ColorSchemes ["Error"],
  123. Text = "Move This \u263b Button v_ia Pos"
  124. };
  125. moveBtn.Accept += (s, e) =>
  126. {
  127. moveBtn.X = moveBtn.Frame.X + 5;
  128. };
  129. computedFrame.Add (moveBtn);
  130. // Demonstrates how changing the View.Frame property can SIZE Views (#583)
  131. var sizeBtn = new Button
  132. {
  133. Y = Pos.Center () + 1,
  134. X = 0,
  135. Width = 30,
  136. Height = 1,
  137. Text = "Grow This \u263a Button _via Pos",
  138. ColorScheme = Colors.ColorSchemes ["Error"],
  139. };
  140. sizeBtn.Accept += (s, e) =>
  141. {
  142. sizeBtn.Width = sizeBtn.Frame.Width + 5;
  143. };
  144. computedFrame.Add (sizeBtn);
  145. var absoluteFrame = new FrameView
  146. {
  147. X = Pos.Right (computedFrame),
  148. Y = Pos.Bottom (removeButton) + 1,
  149. Width = Dim.Fill (),
  150. Height = 5,
  151. Title = "Absolute Layout"
  152. };
  153. main.Add (absoluteFrame);
  154. // Demonstrates how changing the View.Frame property can move Views
  155. var moveBtnA = new Button { ColorScheme = Colors.ColorSchemes ["Error"], Text = "Move This Button via Frame" };
  156. moveBtnA.Accept += (s, e) =>
  157. {
  158. moveBtnA.Frame = new (
  159. moveBtnA.Frame.X + 5,
  160. moveBtnA.Frame.Y,
  161. moveBtnA.Frame.Width,
  162. moveBtnA.Frame.Height
  163. );
  164. };
  165. absoluteFrame.Add (moveBtnA);
  166. // Demonstrates how changing the View.Frame property can SIZE Views (#583)
  167. var sizeBtnA = new Button
  168. {
  169. Y = 2, ColorScheme = Colors.ColorSchemes ["Error"], Text = " ~  s  gui.cs   master ↑_10 = Сохранить"
  170. };
  171. sizeBtnA.Accept += (s, e) =>
  172. {
  173. sizeBtnA.Frame = new (
  174. sizeBtnA.Frame.X,
  175. sizeBtnA.Frame.Y,
  176. sizeBtnA.Frame.Width + 5,
  177. sizeBtnA.Frame.Height
  178. );
  179. };
  180. absoluteFrame.Add (sizeBtnA);
  181. var label = new Label
  182. {
  183. X = 2, Y = Pos.Bottom (computedFrame) + 1, Text = "Text Alignment (changes the four buttons above): "
  184. };
  185. main.Add (label);
  186. var radioGroup = new RadioGroup
  187. {
  188. X = 4,
  189. Y = Pos.Bottom (label) + 1,
  190. SelectedItem = 2,
  191. RadioLabels = new [] { "Start", "End", "Center", "Fill" }
  192. };
  193. main.Add (radioGroup);
  194. // Demo changing hotkey
  195. string MoveHotkey (string txt)
  196. {
  197. // Remove the '_'
  198. List<Rune> runes = txt.ToRuneList ();
  199. int i = runes.IndexOf ((Rune)'_');
  200. var start = "";
  201. if (i > -1)
  202. {
  203. start = StringExtensions.ToString (runes.GetRange (0, i));
  204. }
  205. txt = start + StringExtensions.ToString (runes.GetRange (i + 1, runes.Count - (i + 1)));
  206. runes = txt.ToRuneList ();
  207. // Move over one or go to start
  208. i++;
  209. if (i >= runes.Count)
  210. {
  211. i = 0;
  212. }
  213. // Slip in the '_'
  214. start = StringExtensions.ToString (runes.GetRange (0, i));
  215. return start + '_' + StringExtensions.ToString (runes.GetRange (i, runes.Count - i));
  216. }
  217. var mhkb = "Click to Change th_is Button's Hotkey";
  218. var moveHotKeyBtn = new Button
  219. {
  220. X = 2,
  221. Y = Pos.Bottom (radioGroup) + 1,
  222. Height = 1,
  223. Width = Dim.Width (computedFrame) - 2,
  224. ColorScheme = Colors.ColorSchemes ["TopLevel"],
  225. Text = mhkb
  226. };
  227. moveHotKeyBtn.Accept += (s, e) => { moveHotKeyBtn.Text = MoveHotkey (moveHotKeyBtn.Text); };
  228. main.Add (moveHotKeyBtn);
  229. var muhkb = " ~  s  gui.cs   master ↑10 = Сохранить";
  230. var moveUnicodeHotKeyBtn = new Button
  231. {
  232. X = Pos.Left (absoluteFrame) + 1,
  233. Y = Pos.Bottom (radioGroup) + 1,
  234. Height = 1,
  235. Width = Dim.Width (absoluteFrame) - 2,
  236. ColorScheme = Colors.ColorSchemes ["TopLevel"],
  237. Text = muhkb
  238. };
  239. moveUnicodeHotKeyBtn.Accept += (s, e) => { moveUnicodeHotKeyBtn.Text = MoveHotkey (moveUnicodeHotKeyBtn.Text); };
  240. main.Add (moveUnicodeHotKeyBtn);
  241. radioGroup.SelectedItemChanged += (s, args) =>
  242. {
  243. switch (args.SelectedItem)
  244. {
  245. case 0:
  246. moveBtn.TextAlignment = Alignment.Start;
  247. sizeBtn.TextAlignment = Alignment.Start;
  248. moveBtnA.TextAlignment = Alignment.Start;
  249. sizeBtnA.TextAlignment = Alignment.Start;
  250. moveHotKeyBtn.TextAlignment = Alignment.Start;
  251. moveUnicodeHotKeyBtn.TextAlignment = Alignment.Start;
  252. break;
  253. case 1:
  254. moveBtn.TextAlignment = Alignment.End;
  255. sizeBtn.TextAlignment = Alignment.End;
  256. moveBtnA.TextAlignment = Alignment.End;
  257. sizeBtnA.TextAlignment = Alignment.End;
  258. moveHotKeyBtn.TextAlignment = Alignment.End;
  259. moveUnicodeHotKeyBtn.TextAlignment = Alignment.End;
  260. break;
  261. case 2:
  262. moveBtn.TextAlignment = Alignment.Center;
  263. sizeBtn.TextAlignment = Alignment.Center;
  264. moveBtnA.TextAlignment = Alignment.Center;
  265. sizeBtnA.TextAlignment = Alignment.Center;
  266. moveHotKeyBtn.TextAlignment = Alignment.Center;
  267. moveUnicodeHotKeyBtn.TextAlignment = Alignment.Center;
  268. break;
  269. case 3:
  270. moveBtn.TextAlignment = Alignment.Fill;
  271. sizeBtn.TextAlignment = Alignment.Fill;
  272. moveBtnA.TextAlignment = Alignment.Fill;
  273. sizeBtnA.TextAlignment = Alignment.Fill;
  274. moveHotKeyBtn.TextAlignment = Alignment.Fill;
  275. moveUnicodeHotKeyBtn.TextAlignment = Alignment.Fill;
  276. break;
  277. }
  278. };
  279. label = new ()
  280. {
  281. X = 0,
  282. Y = Pos.Bottom (moveUnicodeHotKeyBtn) + 1,
  283. Title = "_Numeric Up/Down (press-and-hold):",
  284. };
  285. var numericUpDown = new NumericUpDown<int>
  286. {
  287. Value = 69,
  288. X = Pos.Right (label) + 1,
  289. Y = Pos.Top (label),
  290. Width = 5,
  291. Height = 1
  292. };
  293. numericUpDown.ValueChanged += NumericUpDown_ValueChanged;
  294. void NumericUpDown_ValueChanged (object sender, StateEventArgs<int> e) { }
  295. main.Add (label, numericUpDown);
  296. label = new ()
  297. {
  298. X = 0,
  299. Y = Pos.Bottom (numericUpDown) + 1,
  300. Title = "_No Repeat:"
  301. };
  302. var noRepeatAcceptCount = 0;
  303. var noRepeatButton = new Button
  304. {
  305. X = Pos.Right (label) + 1,
  306. Y = Pos.Top (label),
  307. Title = $"Accept Cou_nt: {noRepeatAcceptCount}",
  308. WantContinuousButtonPressed = false
  309. };
  310. noRepeatButton.Accept += (s, e) => { noRepeatButton.Title = $"Accept Cou_nt: {++noRepeatAcceptCount}"; };
  311. main.Add (label, noRepeatButton);
  312. label = new ()
  313. {
  314. X = 0,
  315. Y = Pos.Bottom (label) + 1,
  316. Title = "_Repeat (press-and-hold):"
  317. };
  318. var acceptCount = 0;
  319. var repeatButton = new Button
  320. {
  321. X = Pos.Right (label) + 1,
  322. Y = Pos.Top (label),
  323. Title = $"Accept Co_unt: {acceptCount}",
  324. WantContinuousButtonPressed = true
  325. };
  326. repeatButton.Accept += (s, e) => { repeatButton.Title = $"Accept Co_unt: {++acceptCount}"; };
  327. var enableCB = new CheckBox
  328. {
  329. X = Pos.Right (repeatButton) + 1,
  330. Y = Pos.Top (repeatButton),
  331. Title = "Enabled",
  332. Checked = true
  333. };
  334. enableCB.Toggled += (s, e) => { repeatButton.Enabled = !repeatButton.Enabled; };
  335. main.Add (label, repeatButton, enableCB);
  336. main.Ready += (s, e) => radioGroup.Refresh ();
  337. Application.Run (main);
  338. main.Dispose ();
  339. }
  340. /// <summary>
  341. /// Enables the user to increase or decrease a value by clicking on the up or down buttons.
  342. /// </summary>
  343. /// <remarks>
  344. /// Supports the following types: <see cref="int"/>, <see cref="long"/>, <see cref="float"/>, <see cref="double"/>, <see cref="decimal"/>.
  345. /// Supports only one digit of precision.
  346. /// </remarks>
  347. public class NumericUpDown<T> : View
  348. {
  349. private readonly Button _down;
  350. // TODO: Use a TextField instead of a Label
  351. private readonly View _number;
  352. private readonly Button _up;
  353. public NumericUpDown ()
  354. {
  355. Type type = typeof (T);
  356. if (!(type == typeof (int) || type == typeof (long) || type == typeof (float) || type == typeof (double) || type == typeof (decimal)))
  357. {
  358. throw new InvalidOperationException ("T must be a numeric type that supports addition and subtraction.");
  359. }
  360. Width = Dim.Auto (DimAutoStyle.Content); //Dim.Function (() => Digits + 2); // button + 3 for number + button
  361. Height = Dim.Auto (DimAutoStyle.Content);
  362. _down = new ()
  363. {
  364. Height = 1,
  365. Width = 1,
  366. NoPadding = true,
  367. NoDecorations = true,
  368. Title = $"{CM.Glyphs.DownArrow}",
  369. WantContinuousButtonPressed = true,
  370. CanFocus = false,
  371. };
  372. _number = new ()
  373. {
  374. Text = Value.ToString (),
  375. X = Pos.Right (_down),
  376. Y = Pos.Top (_down),
  377. Width = Dim.Func (() => Digits),
  378. Height = 1,
  379. TextAlignment = Alignment.Center,
  380. CanFocus = true
  381. };
  382. _up = new ()
  383. {
  384. X = Pos.AnchorEnd (),
  385. Y = Pos.Top (_number),
  386. Height = 1,
  387. Width = 1,
  388. NoPadding = true,
  389. NoDecorations = true,
  390. Title = $"{CM.Glyphs.UpArrow}",
  391. WantContinuousButtonPressed = true,
  392. CanFocus = false,
  393. };
  394. CanFocus = true;
  395. _down.Accept += OnDownButtonOnAccept;
  396. _up.Accept += OnUpButtonOnAccept;
  397. Add (_down, _number, _up);
  398. AddCommand (Command.ScrollUp, () =>
  399. {
  400. Value = (dynamic)Value + 1;
  401. _number.Text = Value.ToString ();
  402. return true;
  403. });
  404. AddCommand (Command.ScrollDown, () =>
  405. {
  406. Value = (dynamic)Value - 1;
  407. _number.Text = Value.ToString ();
  408. return true;
  409. });
  410. KeyBindings.Add (Key.CursorUp, Command.ScrollUp);
  411. KeyBindings.Add (Key.CursorDown, Command.ScrollDown);
  412. return;
  413. void OnDownButtonOnAccept (object s, CancelEventArgs e)
  414. {
  415. InvokeCommand (Command.ScrollDown);
  416. }
  417. void OnUpButtonOnAccept (object s, CancelEventArgs e)
  418. {
  419. InvokeCommand (Command.ScrollUp);
  420. }
  421. }
  422. private void _up_Enter (object sender, FocusEventArgs e)
  423. {
  424. throw new NotImplementedException ();
  425. }
  426. private T _value;
  427. /// <summary>
  428. /// The value that will be incremented or decremented.
  429. /// </summary>
  430. public T Value
  431. {
  432. get => _value;
  433. set
  434. {
  435. if (_value.Equals (value))
  436. {
  437. return;
  438. }
  439. T oldValue = value;
  440. StateEventArgs<T> args = new StateEventArgs<T> (_value, value);
  441. ValueChanging?.Invoke (this, args);
  442. if (args.Cancel)
  443. {
  444. return;
  445. }
  446. _value = value;
  447. _number.Text = _value.ToString ();
  448. ValueChanged?.Invoke (this, new (oldValue, _value));
  449. }
  450. }
  451. /// <summary>
  452. /// Fired when the value is about to change. Set <see cref="StateEventArgs{T}.Cancel"/> to true to prevent the change.
  453. /// </summary>
  454. [CanBeNull]
  455. public event EventHandler<StateEventArgs<T>> ValueChanging;
  456. /// <summary>
  457. /// Fired when the value has changed.
  458. /// </summary>
  459. [CanBeNull]
  460. public event EventHandler<StateEventArgs<T>> ValueChanged;
  461. /// <summary>
  462. /// The number of digits to display. The <see cref="View.Viewport"/> will be resized to fit this number of characters plus the buttons. The default is 3.
  463. /// </summary>
  464. public int Digits { get; set; } = 3;
  465. }
  466. }