Buttons.cs 20 KB

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