From 17e14aba6394cd3894aa8bcea503f30680377730 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 4 Aug 2024 15:49:34 -0600 Subject: [PATCH 1/7] Initial commit. --- Terminal.Gui/Views/NumericUpDown.cs | 178 ++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 Terminal.Gui/Views/NumericUpDown.cs diff --git a/Terminal.Gui/Views/NumericUpDown.cs b/Terminal.Gui/Views/NumericUpDown.cs new file mode 100644 index 000000000..67650f0de --- /dev/null +++ b/Terminal.Gui/Views/NumericUpDown.cs @@ -0,0 +1,178 @@ +using System.ComponentModel; +using Terminal.Gui; + +/// +/// Enables the user to increase or decrease a value by clicking on the up or down buttons. +/// +/// +/// Supports the following types: , , , , +/// . +/// Supports only one digit of precision. +/// +public class NumericUpDown : View +{ + private readonly Button _down; + + // TODO: Use a TextField instead of a Label + private readonly View _number; + private readonly Button _up; + + public NumericUpDown () + { + Type type = typeof (T); + + if (!(type == typeof (int) || type == typeof (long) || type == typeof (float) || type == typeof (double) || type == typeof (decimal))) + { + // Support object for AllViewsTester + if (type != typeof (object)) + { + throw new InvalidOperationException ("T must be a numeric type that supports addition and subtraction."); + } + } + + Width = Dim.Auto (DimAutoStyle.Content); //Dim.Function (() => Digits + 2); // button + 3 for number + button + Height = Dim.Auto (DimAutoStyle.Content); + + _down = new () + { + Height = 1, + Width = 1, + NoPadding = true, + NoDecorations = true, + Title = $"{Glyphs.DownArrow}", + WantContinuousButtonPressed = true, + CanFocus = false, + ShadowStyle = ShadowStyle.None + }; + + _number = new () + { + Text = Value?.ToString () ?? "Err", + X = Pos.Right (_down), + Y = Pos.Top (_down), + Width = Dim.Func (() => Digits), + Height = 1, + TextAlignment = Alignment.Center, + CanFocus = true + }; + + _up = new () + { + X = Pos.AnchorEnd (), + Y = Pos.Top (_number), + Height = 1, + Width = 1, + NoPadding = true, + NoDecorations = true, + Title = $"{Glyphs.UpArrow}", + WantContinuousButtonPressed = true, + CanFocus = false, + ShadowStyle = ShadowStyle.None + }; + + CanFocus = true; + + _down.Accept += OnDownButtonOnAccept; + _up.Accept += OnUpButtonOnAccept; + + Add (_down, _number, _up); + + AddCommand ( + Command.ScrollUp, + () => + { + if (type == typeof (object)) + { + return false; + } + + Value = (dynamic)Value + 1; + _number.Text = Value?.ToString () ?? string.Empty; + + return true; + }); + + AddCommand ( + Command.ScrollDown, + () => + { + if (type == typeof (object)) + { + return false; + } + + Value = (dynamic)Value - 1; + _number.Text = Value.ToString () ?? string.Empty; + + return true; + }); + + KeyBindings.Add (Key.CursorUp, Command.ScrollUp); + KeyBindings.Add (Key.CursorDown, Command.ScrollDown); + + return; + + void OnDownButtonOnAccept (object s, HandledEventArgs e) { InvokeCommand (Command.ScrollDown); } + + void OnUpButtonOnAccept (object s, HandledEventArgs e) { InvokeCommand (Command.ScrollUp); } + } + + private void _up_Enter (object sender, FocusEventArgs e) { throw new NotImplementedException (); } + + private T _value; + + /// + /// The value that will be incremented or decremented. + /// + public T Value + { + get => _value; + set + { + if (_value.Equals (value)) + { + return; + } + + T oldValue = value; + CancelEventArgs args = new (ref _value, ref value); + ValueChanging?.Invoke (this, args); + + if (args.Cancel) + { + return; + } + + _value = value; + _number.Text = _value.ToString (); + ValueChanged?.Invoke (this, new (ref _value)); + } + } + + /// + /// Fired when the value is about to change. Set to true to prevent the change. + /// + [CanBeNull] + public event EventHandler> ValueChanging; + + /// + /// Fired when the value has changed. + /// + [CanBeNull] + public event EventHandler> ValueChanged; + + /// + /// The number of digits to display. The will be resized to fit this number of characters + /// plus the buttons. The default is 3. + /// + public int Digits { get; set; } = 3; +} + + +/// +/// Enables the user to increase or decrease an by clicking on the up or down buttons. +/// +public class NumericUpDown : NumericUpDown +{ + +} From cebf1773af29fd09c972fabd5e9c0fa75ee25d0d Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 4 Aug 2024 17:10:00 -0600 Subject: [PATCH 2/7] Added other types --- Terminal.Gui/Views/NumericUpDown.cs | 113 +++++++++++++++-- UICatalog/Scenarios/AdornmentEditor.cs | 8 +- UICatalog/Scenarios/Buttons.cs | 154 ----------------------- UICatalog/Scenarios/ContentScrolling.cs | 4 +- UICatalog/Scenarios/PosAlignDemo.cs | 2 +- UICatalog/Scenarios/Sliders.cs | 2 +- UnitTests/Views/NumericUpDownTests.cs | 160 ++++++++++++++++++++++++ 7 files changed, 272 insertions(+), 171 deletions(-) create mode 100644 UnitTests/Views/NumericUpDownTests.cs diff --git a/Terminal.Gui/Views/NumericUpDown.cs b/Terminal.Gui/Views/NumericUpDown.cs index 67650f0de..496872546 100644 --- a/Terminal.Gui/Views/NumericUpDown.cs +++ b/Terminal.Gui/Views/NumericUpDown.cs @@ -1,3 +1,4 @@ +#nullable enable using System.ComponentModel; using Terminal.Gui; @@ -5,7 +6,7 @@ using Terminal.Gui; /// Enables the user to increase or decrease a value by clicking on the up or down buttons. /// /// -/// Supports the following types: , , , , +/// Supports the following types: , , , , /// . /// Supports only one digit of precision. /// @@ -21,7 +22,7 @@ public class NumericUpDown : View { Type type = typeof (T); - if (!(type == typeof (int) || type == typeof (long) || type == typeof (float) || type == typeof (double) || type == typeof (decimal))) + if (!(type == typeof (int) || type == typeof (long) || type == typeof (double) || type == typeof (float) || type == typeof (double) || type == typeof (decimal))) { // Support object for AllViewsTester if (type != typeof (object)) @@ -30,6 +31,45 @@ public class NumericUpDown : View } } + switch (typeof (T)) + { + case { } i when i == typeof (int): + Minimum = (dynamic)int.MinValue; + Maximum = (dynamic)int.MaxValue; + Increment = (dynamic)1; + + break; + + case { } i when i == typeof (long): + Minimum = (dynamic)long.MinValue; + Maximum = (dynamic)long.MaxValue; + Increment = (dynamic)1; + + break; + + case { } i when i == typeof (double): + Minimum = (dynamic)double.MinValue; + Maximum = (dynamic)double.MaxValue; + Increment = (dynamic)1; + + break; + + case { } i when i == typeof (float): + Minimum = (dynamic)float.MinValue; + Maximum = (dynamic)float.MaxValue; + Increment = (dynamic)1; + + break; + + + case { } i when i == typeof (decimal): + Minimum = (dynamic)decimal.MinValue; + Maximum = (dynamic)decimal.MaxValue; + Increment = (dynamic)1; + + break; + } + Width = Dim.Auto (DimAutoStyle.Content); //Dim.Function (() => Digits + 2); // button + 3 for number + button Height = Dim.Auto (DimAutoStyle.Content); @@ -86,8 +126,11 @@ public class NumericUpDown : View return false; } - Value = (dynamic)Value + 1; - _number.Text = Value?.ToString () ?? string.Empty; + if (Value is { }) + { + Value = (dynamic)Value + Increment; + _number.Text = Value?.ToString () ?? string.Empty; + } return true; }); @@ -101,8 +144,11 @@ public class NumericUpDown : View return false; } - Value = (dynamic)Value - 1; - _number.Text = Value.ToString () ?? string.Empty; + if (Value is { }) + { + Value = (dynamic)Value - Increment; + _number.Text = Value.ToString () ?? string.Empty; + } return true; }); @@ -112,9 +158,15 @@ public class NumericUpDown : View return; - void OnDownButtonOnAccept (object s, HandledEventArgs e) { InvokeCommand (Command.ScrollDown); } + void OnDownButtonOnAccept (object s, HandledEventArgs e) + { + InvokeCommand (Command.ScrollDown); + } - void OnUpButtonOnAccept (object s, HandledEventArgs e) { InvokeCommand (Command.ScrollUp); } + void OnUpButtonOnAccept (object s, HandledEventArgs e) + { + InvokeCommand (Command.ScrollUp); + } } private void _up_Enter (object sender, FocusEventArgs e) { throw new NotImplementedException (); } @@ -143,8 +195,18 @@ public class NumericUpDown : View return; } + if (Comparer.Default.Compare (value, Minimum) < 0) + { + value = Minimum; + } + + if (Comparer.Default.Compare (value, Maximum) > 0) + { + value = Maximum; + } + _value = value; - _number.Text = _value.ToString (); + _number.Text = _value?.ToString () ?? string.Empty; ValueChanged?.Invoke (this, new (ref _value)); } } @@ -166,6 +228,39 @@ public class NumericUpDown : View /// plus the buttons. The default is 3. /// public int Digits { get; set; } = 3; + + private T _minimum; + + /// + /// + /// + public T Minimum + { + get { return _minimum; } + set { _minimum = value; } + } + + private T _maximum; + + /// + /// + /// + public T Maximum + { + get { return _maximum; } + set { _maximum = value; } + } + + private T _increment; + + /// + /// + /// + public T Increment + { + get { return _increment; } + set { _increment = value; } + } } diff --git a/UICatalog/Scenarios/AdornmentEditor.cs b/UICatalog/Scenarios/AdornmentEditor.cs index e97d25ada..ba99f9e84 100644 --- a/UICatalog/Scenarios/AdornmentEditor.cs +++ b/UICatalog/Scenarios/AdornmentEditor.cs @@ -78,10 +78,10 @@ public class AdornmentEditor : View AdornmentChanged?.Invoke (this, EventArgs.Empty); } - private Buttons.NumericUpDown _topEdit; - private Buttons.NumericUpDown _leftEdit; - private Buttons.NumericUpDown _bottomEdit; - private Buttons.NumericUpDown _rightEdit; + private NumericUpDown _topEdit; + private NumericUpDown _leftEdit; + private NumericUpDown _bottomEdit; + private NumericUpDown _rightEdit; public AdornmentEditor () { diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs index aab815a96..807f98dd3 100644 --- a/UICatalog/Scenarios/Buttons.cs +++ b/UICatalog/Scenarios/Buttons.cs @@ -395,159 +395,5 @@ public class Buttons : Scenario main.Dispose (); Application.Shutdown (); } - - /// - /// Enables the user to increase or decrease a value by clicking on the up or down buttons. - /// - /// - /// Supports the following types: , , , , . - /// Supports only one digit of precision. - /// - public class NumericUpDown : View - { - private readonly Button _down; - // TODO: Use a TextField instead of a Label - private readonly View _number; - private readonly Button _up; - - public NumericUpDown () - { - Type type = typeof (T); - if (!(type == typeof (int) || type == typeof (long) || type == typeof (float) || type == typeof (double) || type == typeof (decimal))) - { - throw new InvalidOperationException ("T must be a numeric type that supports addition and subtraction."); - } - - Width = Dim.Auto (DimAutoStyle.Content); //Dim.Function (() => Digits + 2); // button + 3 for number + button - Height = Dim.Auto (DimAutoStyle.Content); - - _down = new () - { - Height = 1, - Width = 1, - NoPadding = true, - NoDecorations = true, - Title = $"{CM.Glyphs.DownArrow}", - WantContinuousButtonPressed = true, - CanFocus = false, - ShadowStyle = ShadowStyle.None, - }; - - _number = new () - { - Text = Value.ToString (), - X = Pos.Right (_down), - Y = Pos.Top (_down), - Width = Dim.Func (() => Digits), - Height = 1, - TextAlignment = Alignment.Center, - CanFocus = true - }; - - _up = new () - { - X = Pos.AnchorEnd (), - Y = Pos.Top (_number), - Height = 1, - Width = 1, - NoPadding = true, - NoDecorations = true, - Title = $"{CM.Glyphs.UpArrow}", - WantContinuousButtonPressed = true, - CanFocus = false, - ShadowStyle = ShadowStyle.None, - }; - - CanFocus = true; - - _down.Accept += OnDownButtonOnAccept; - _up.Accept += OnUpButtonOnAccept; - - Add (_down, _number, _up); - - - AddCommand (Command.ScrollUp, () => - { - Value = (dynamic)Value + 1; - _number.Text = Value.ToString (); - - return true; - }); - AddCommand (Command.ScrollDown, () => - { - Value = (dynamic)Value - 1; - _number.Text = Value.ToString (); - - return true; - }); - - KeyBindings.Add (Key.CursorUp, Command.ScrollUp); - KeyBindings.Add (Key.CursorDown, Command.ScrollDown); - - return; - - void OnDownButtonOnAccept (object s, HandledEventArgs e) - { - InvokeCommand (Command.ScrollDown); - } - - void OnUpButtonOnAccept (object s, HandledEventArgs e) - { - InvokeCommand (Command.ScrollUp); - } - } - - private void _up_Enter (object sender, FocusEventArgs e) - { - throw new NotImplementedException (); - } - - private T _value; - - /// - /// The value that will be incremented or decremented. - /// - public T Value - { - get => _value; - set - { - if (_value.Equals (value)) - { - return; - } - - T oldValue = value; - CancelEventArgs args = new (ref _value, ref value); - ValueChanging?.Invoke (this, args); - - if (args.Cancel) - { - return; - } - - _value = value; - _number.Text = _value.ToString (); - ValueChanged?.Invoke (this, new (ref _value)); - } - } - - /// - /// Fired when the value is about to change. Set to true to prevent the change. - /// - [CanBeNull] - public event EventHandler> ValueChanging; - - /// - /// Fired when the value has changed. - /// - [CanBeNull] - public event EventHandler> ValueChanged; - - /// - /// The number of digits to display. The will be resized to fit this number of characters plus the buttons. The default is 3. - /// - public int Digits { get; set; } = 3; - } } diff --git a/UICatalog/Scenarios/ContentScrolling.cs b/UICatalog/Scenarios/ContentScrolling.cs index 5d4a1a3c2..9e4416658 100644 --- a/UICatalog/Scenarios/ContentScrolling.cs +++ b/UICatalog/Scenarios/ContentScrolling.cs @@ -229,7 +229,7 @@ public class ContentScrolling : Scenario Y = Pos.Bottom (cbAllowYGreaterThanContentHeight) }; - Buttons.NumericUpDown contentSizeWidth = new Buttons.NumericUpDown + NumericUpDown contentSizeWidth = new NumericUpDown { Value = view.GetContentSize ().Width, X = Pos.Right (labelContentSize) + 1, @@ -256,7 +256,7 @@ public class ContentScrolling : Scenario Y = Pos.Top (labelContentSize) }; - Buttons.NumericUpDown contentSizeHeight = new Buttons.NumericUpDown + NumericUpDown contentSizeHeight = new NumericUpDown { Value = view.GetContentSize ().Height, X = Pos.Right (labelComma) + 1, diff --git a/UICatalog/Scenarios/PosAlignDemo.cs b/UICatalog/Scenarios/PosAlignDemo.cs index d7ae5146e..03e90b806 100644 --- a/UICatalog/Scenarios/PosAlignDemo.cs +++ b/UICatalog/Scenarios/PosAlignDemo.cs @@ -236,7 +236,7 @@ public sealed class PosAlignDemo : Scenario } ]; - Buttons.NumericUpDown addedViewsUpDown = new() + NumericUpDown addedViewsUpDown = new() { Width = 9, Title = "Added", diff --git a/UICatalog/Scenarios/Sliders.cs b/UICatalog/Scenarios/Sliders.cs index c65d28093..bf81264fe 100644 --- a/UICatalog/Scenarios/Sliders.cs +++ b/UICatalog/Scenarios/Sliders.cs @@ -407,7 +407,7 @@ public class Sliders : Scenario Text = "Min _Inner Spacing:", }; - Buttons.NumericUpDown innerSpacingUpDown = new () + NumericUpDown innerSpacingUpDown = new () { X = Pos.Right (label) + 1 }; diff --git a/UnitTests/Views/NumericUpDownTests.cs b/UnitTests/Views/NumericUpDownTests.cs new file mode 100644 index 000000000..12c66993c --- /dev/null +++ b/UnitTests/Views/NumericUpDownTests.cs @@ -0,0 +1,160 @@ +using System.ComponentModel; +using Xunit.Abstractions; + +namespace Terminal.Gui.ViewsTests; + +public class NumericUpDownTests (ITestOutputHelper _output) +{ + + [Fact] + public void WhenCreated_ShouldHaveDefaultValues_int () + { + NumericUpDown numericUpDown = new (); + + Assert.Equal (0, numericUpDown.Value); + Assert.Equal (int.MinValue, numericUpDown.Minimum); + Assert.Equal (int.MaxValue, numericUpDown.Maximum); + Assert.Equal (1, numericUpDown.Increment); + } + + [Fact] + public void WhenCreated_ShouldHaveDefaultValues_long () + { + NumericUpDown numericUpDown = new (); + + Assert.Equal (0, numericUpDown.Value); + Assert.Equal (long.MinValue, numericUpDown.Minimum); + Assert.Equal (long.MaxValue, numericUpDown.Maximum); + Assert.Equal (1, numericUpDown.Increment); + } + + [Fact] + public void WhenCreated_ShouldHaveDefaultValues_float () + { + NumericUpDown numericUpDown = new (); + + Assert.Equal (0F, numericUpDown.Value); + Assert.Equal (float.MinValue, numericUpDown.Minimum); + Assert.Equal (float.MaxValue, numericUpDown.Maximum); + Assert.Equal (1.0F, numericUpDown.Increment); + } + + [Fact] + public void WhenCreated_ShouldHaveDefaultValues_double () + { + NumericUpDown numericUpDown = new (); + + Assert.Equal (0F, numericUpDown.Value); + Assert.Equal (double.MinValue, numericUpDown.Minimum); + Assert.Equal (double.MaxValue, numericUpDown.Maximum); + Assert.Equal (1.0F, numericUpDown.Increment); + } + + [Fact] + public void WhenCreated_ShouldHaveDefaultValues_decimal () + { + NumericUpDown numericUpDown = new (); + + Assert.Equal (0, numericUpDown.Value); + Assert.Equal (decimal.MinValue, numericUpDown.Minimum); + Assert.Equal (decimal.MaxValue, numericUpDown.Maximum); + Assert.Equal (1, numericUpDown.Increment); + } + + [Fact] + public void WhenCreatedWithCustomValues_ShouldHaveCustomValues_int () + { + var numericUpDown = new NumericUpDown () + { + Value = 10, + Minimum = 5, + Maximum = 15, + Increment = 2 + }; + + Assert.Equal (10, numericUpDown.Value); + Assert.Equal (5, numericUpDown.Minimum); + Assert.Equal (15, numericUpDown.Maximum); + Assert.Equal (2, numericUpDown.Increment); + } + + [Fact] + public void WhenCreatedWithCustomValues_ShouldHaveCustomValues_float () + { + var numericUpDown = new NumericUpDown () + { + Value = 10.5F, + Minimum = 5.5F, + Maximum = 15.5F, + Increment = 2.5F + }; + + Assert.Equal (10.5F, numericUpDown.Value); + Assert.Equal (5.5F, numericUpDown.Minimum); + Assert.Equal (15.5F, numericUpDown.Maximum); + Assert.Equal (2.5F, numericUpDown.Increment); + } + + [Fact] + public void NumericUpDown_WhenCreatedWithInvalidType_ShouldThrowInvalidOperationException () + { + Assert.Throws (() => new NumericUpDown ()); + } + + [Fact] + public void NumericUpDown_WhenCreatedWithInvalidTypeObject_ShouldNotThrowInvalidOperationException () + { + var numericUpDown = new NumericUpDown (); + + Assert.Equal (0, numericUpDown.Value); + Assert.Equal (0, numericUpDown.Minimum); + Assert.Equal (100, numericUpDown.Maximum); + Assert.Equal (1, numericUpDown.Increment); + } + + [Fact] + public void NumericUpDown_WhenCreatedWithInvalidTypeObjectAndCustomValues_ShouldHaveCustomValues () + { + var numericUpDown = new NumericUpDown () + { + Value = 10, + Minimum = 5, + Maximum = 15, + Increment = 2 + }; + + Assert.Equal (10, numericUpDown.Value); + Assert.Equal (5, numericUpDown.Minimum); + Assert.Equal (15, numericUpDown.Maximum); + Assert.Equal (2, numericUpDown.Increment); + } + + [Fact] + public void NumericUpDown_WhenCreated_ShouldHaveDefaultWidthAndHeight_int () + { + var numericUpDown = new NumericUpDown (); + numericUpDown.SetRelativeLayout(Application.Screen.Size); + + Assert.Equal (5, numericUpDown.Frame.Width); + Assert.Equal (1, numericUpDown.Frame.Height); + } + + [Fact] + public void NumericUpDown_WhenCreated_ShouldHaveDefaultWidthAndHeight_float () + { + var numericUpDown = new NumericUpDown (); + numericUpDown.SetRelativeLayout (Application.Screen.Size); + + Assert.Equal (5, numericUpDown.Frame.Width); + Assert.Equal (1, numericUpDown.Frame.Height); + } + + [Fact] + public void NumericUpDown_WhenCreated_ShouldHaveDefaultUpDownButtons () + { + var numericUpDown = new NumericUpDown (); + + // Assert.Equal (1, numericUpDown + } + +} \ No newline at end of file From de10e22b220a3e22360a542d1cfce642cb3750fc Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 5 Aug 2024 12:41:37 -0600 Subject: [PATCH 3/7] Code cleanup --- Terminal.Gui/Views/NumericUpDown.cs | 45 +++++++++++++++-------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/Terminal.Gui/Views/NumericUpDown.cs b/Terminal.Gui/Views/NumericUpDown.cs index c1a46e972..44b43c0c2 100644 --- a/Terminal.Gui/Views/NumericUpDown.cs +++ b/Terminal.Gui/Views/NumericUpDown.cs @@ -1,16 +1,14 @@ #nullable enable using System.ComponentModel; -using Microsoft.CodeAnalysis.Operations; namespace Terminal.Gui; /// -/// Enables the user to increase or decrease a value by clicking on the up or down buttons. +/// Enables the user to increase or decrease a value with the mouse or keyboard. /// /// /// Supports the following types: , , , , -/// . -/// Supports only one digit of precision. +/// . Attempting to use any other type will result in an . /// public class NumericUpDown : View where T : notnull { @@ -28,19 +26,26 @@ public class NumericUpDown : View where T : notnull { Type type = typeof (T); - if (!(type == typeof (object) || type == typeof (int) || type == typeof (long) || type == typeof (double) || type == typeof (float) || type == typeof (double) || type == typeof (decimal))) + if (!(type == typeof (object) + || type == typeof (int) + || type == typeof (long) + || type == typeof (double) + || type == typeof (float) + || type == typeof (double) + || type == typeof (decimal))) { throw new InvalidOperationException ("T must be a numeric type that supports addition and subtraction."); } Increment = (dynamic)1; + // `object` is supported only for AllViewsTester if (type != typeof (object)) { Value = (dynamic)0; } - Width = Dim.Auto (DimAutoStyle.Content); //Dim.Function (() => Digits + 2); // button + 3 for number + button + Width = Dim.Auto (DimAutoStyle.Content); Height = Dim.Auto (DimAutoStyle.Content); _down = new () @@ -128,22 +133,23 @@ public class NumericUpDown : View where T : notnull return; - void OnDownButtonOnAccept (object? s, HandledEventArgs e) - { - InvokeCommand (Command.ScrollDown); - } + void OnDownButtonOnAccept (object? s, HandledEventArgs e) { InvokeCommand (Command.ScrollDown); } - void OnUpButtonOnAccept (object? s, HandledEventArgs e) - { - InvokeCommand (Command.ScrollUp); - } + void OnUpButtonOnAccept (object? s, HandledEventArgs e) { InvokeCommand (Command.ScrollUp); } } private T _value = default!; /// - /// The value that will be incremented or decremented. + /// Gets or sets the value that will be incremented or decremented. /// + /// + /// + /// and events are raised when the value changes. + /// The event can be canceled the change setting to + /// . + /// + /// public T Value { get => _value; @@ -196,7 +202,8 @@ public class NumericUpDown : View where T : notnull } /// - /// Raised when the value is about to change. Set to true to prevent the change. + /// Raised when the value is about to change. Set to true to prevent the + /// change. /// public event EventHandler>? ValueChanging; @@ -206,16 +213,12 @@ public class NumericUpDown : View where T : notnull public event EventHandler>? ValueChanged; /// - /// /// public T Increment { get; set; } } - /// /// Enables the user to increase or decrease an by clicking on the up or down buttons. /// public class NumericUpDown : NumericUpDown -{ - -} +{ } From c0fc83bd78af4e4e3f53f167c86514149b15b05d Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 5 Aug 2024 17:44:48 -0600 Subject: [PATCH 4/7] Added Scenario and expanded API --- Terminal.Gui/Views/NumericUpDown.cs | 53 ++++- UICatalog/Scenarios/Buttons.cs | 18 +- UICatalog/Scenarios/NumericUpDownDemo.cs | 291 +++++++++++++++++++++++ UnitTests/Views/NumericUpDownTests.cs | 29 +++ 4 files changed, 370 insertions(+), 21 deletions(-) create mode 100644 UICatalog/Scenarios/NumericUpDownDemo.cs diff --git a/Terminal.Gui/Views/NumericUpDown.cs b/Terminal.Gui/Views/NumericUpDown.cs index 44b43c0c2..c36534172 100644 --- a/Terminal.Gui/Views/NumericUpDown.cs +++ b/Terminal.Gui/Views/NumericUpDown.cs @@ -146,7 +146,8 @@ public class NumericUpDown : View where T : notnull /// /// /// and events are raised when the value changes. - /// The event can be canceled the change setting to + /// The event can be canceled the change setting + /// to /// . /// /// @@ -175,6 +176,17 @@ public class NumericUpDown : View where T : notnull } } + /// + /// Raised when the value is about to change. Set to true to prevent the + /// change. + /// + public event EventHandler>? ValueChanging; + + /// + /// Raised when the value has changed. + /// + public event EventHandler>? ValueChanged; + private string _format = "{0}"; /// @@ -191,30 +203,47 @@ public class NumericUpDown : View where T : notnull } _format = value; + + FormatChanged?.Invoke (this, new (value)); SetText (); } } + /// + /// Raised when has changed. + /// + public event EventHandler>? FormatChanged; + private void SetText () { _number.Text = string.Format (Format, _value); Text = _number.Text; } - /// - /// Raised when the value is about to change. Set to true to prevent the - /// change. - /// - public event EventHandler>? ValueChanging; - - /// - /// Raised when the value has changed. - /// - public event EventHandler>? ValueChanged; + private T _increment; /// /// - public T Increment { get; set; } + public T Increment + { + get => _increment; + set + { + if (_increment == (dynamic)value) + { + return; + } + + _increment = value; + + IncrementChanged?.Invoke (this, new (value)); + } + } + + /// + /// Raised when has changed. + /// + public event EventHandler>? IncrementChanged; } /// diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs index 64da37ca2..8edcdf42b 100644 --- a/UICatalog/Scenarios/Buttons.cs +++ b/UICatalog/Scenarios/Buttons.cs @@ -388,16 +388,16 @@ public class Buttons : Scenario enableCB.Toggle += (s, e) => { repeatButton.Enabled = !repeatButton.Enabled; }; main.Add (label, repeatButton, enableCB); - //var decNumericUpDown = new NumericUpDown - //{ - // Value = 42.11m, - // Increment = 11.31m, - // Format = "{0:0%}", - // X = 0, - // Y = Pos.Bottom (enableCB) + 1, - //}; + var decNumericUpDown = new NumericUpDown + { + Value = 911, + Increment = 1, + Format = "{0:X}", + X = 0, + Y = Pos.Bottom (enableCB) + 1, + }; - //main.Add (decNumericUpDown); + main.Add (decNumericUpDown); main.Ready += (s, e) => { diff --git a/UICatalog/Scenarios/NumericUpDownDemo.cs b/UICatalog/Scenarios/NumericUpDownDemo.cs new file mode 100644 index 000000000..b514a041a --- /dev/null +++ b/UICatalog/Scenarios/NumericUpDownDemo.cs @@ -0,0 +1,291 @@ +#nullable enable +using System; +using Terminal.Gui; + +namespace UICatalog.Scenarios; + +[ScenarioMetadata ("NumericUpDown", "Demonstrates the NumericUpDown View")] +[ScenarioCategory ("Controls")] +public class NumericUpDownDemo : Scenario +{ + public override void Main () + { + Application.Init (); + + Window app = new () + { + Title = GetQuitKeyAndName (), + TabStop = TabBehavior.TabGroup + }; + + var editor = new AdornmentsEditor + { + X = 0, + Y = 0, + AutoSelectViewToEdit = true, + TabStop = TabBehavior.NoStop + }; + app.Add (editor); + + NumericUpDownEditor intEditor = new () + { + X = Pos.Right (editor), + Y = 0, + Title = "int", + }; + + app.Add (intEditor); + + NumericUpDownEditor floatEditor = new () + { + X = Pos.Right (intEditor), + Y = 0, + Title = "float", + }; + app.Add (floatEditor); + + app.Initialized += AppInitialized; + + void AppInitialized (object? sender, EventArgs e) + { + floatEditor!.NumericUpDown!.Increment = 0.1F; + floatEditor!.NumericUpDown!.Format = "{0:0.0}"; + + } + + Application.Run (app); + app.Dispose (); + Application.Shutdown (); + + } + +} + +internal class NumericUpDownEditor : View where T : notnull +{ + private NumericUpDown? _numericUpDown; + + internal NumericUpDown? NumericUpDown + { + get => _numericUpDown; + set + { + if (value == _numericUpDown) + { + return; + } + _numericUpDown = value; + + if (_numericUpDown is { } && _value is { }) + { + _value.Text = _numericUpDown.Text; + } + } + } + + private TextField? _value; + private TextField? _format; + private TextField? _increment; + + internal NumericUpDownEditor () + { + _numericUpDown = null; + Title = "NumericUpDownEditor"; + BorderStyle = LineStyle.Single; + Width = Dim.Auto (DimAutoStyle.Content); + Height = Dim.Auto (DimAutoStyle.Content); + TabStop = TabBehavior.TabGroup; + + Initialized += NumericUpDownEditorInitialized; + + return; + + void NumericUpDownEditorInitialized (object? sender, EventArgs e) + { + Label label = new () + { + Title = "_Value: ", + Width = 12, + }; + label.TextFormatter.Alignment = Alignment.End; + _value = new () + { + X = Pos.Right (label), + Y = Pos.Top (label), + Width = 8, + Title = "Value", + }; + _value.Accept += ValuedOnAccept; + + void ValuedOnAccept (object? sender, EventArgs e) + { + if (_numericUpDown is null) + { + return; + } + + try + { + if (string.IsNullOrEmpty (_value.Text)) + { + // Handle empty or null text if needed + _numericUpDown.Value = default!; + } + else + { + // Parse _value.Text and then convert to type T + _numericUpDown.Value = (T)Convert.ChangeType (_value.Text, typeof (T)); + } + + _value.ColorScheme = SuperView.ColorScheme; + + } + catch (System.FormatException) + { + _value.ColorScheme = Colors.ColorSchemes ["Error"]; + } + catch (InvalidCastException) + { + _value.ColorScheme = Colors.ColorSchemes ["Error"]; + } + finally + { + } + + } + Add (label, _value); + + label = new () + { + Y = Pos.Bottom (_value), + Width = 12, + Title = "_Format: ", + }; + label.TextFormatter.Alignment = Alignment.End; + + _format = new () + { + X = Pos.Right (label), + Y = Pos.Top (label), + Title = "Format", + Width = Dim.Width (_value), + }; + _format.Accept += FormatOnAccept; + + void FormatOnAccept (object? o, EventArgs eventArgs) + { + if (_numericUpDown is null) + { + return; + } + + try + { + // Test format to ensure it's valid + _ = string.Format (_format.Text, _value); + _numericUpDown.Format = _format.Text; + + _format.ColorScheme = SuperView.ColorScheme; + + } + catch (System.FormatException) + { + _format.ColorScheme = Colors.ColorSchemes ["Error"]; + } + catch (InvalidCastException) + { + _format.ColorScheme = Colors.ColorSchemes ["Error"]; + } + finally + { + } + } + + Add (label, _format); + + label = new () + { + Y = Pos.Bottom (_format), + Width = 12, + Title = "_Increment: ", + }; + label.TextFormatter.Alignment = Alignment.End; + _increment = new () + { + X = Pos.Right (label), + Y = Pos.Top (label), + Title = "Increment", + Width = Dim.Width (_value), + }; + + _increment.Accept += IncrementOnAccept; + + void IncrementOnAccept (object? o, EventArgs eventArgs) + { + if (_numericUpDown is null) + { + return; + } + + try + { + if (string.IsNullOrEmpty (_value.Text)) + { + // Handle empty or null text if needed + _numericUpDown.Increment = default!; + } + else + { + // Parse _value.Text and then convert to type T + _numericUpDown.Increment = (T)Convert.ChangeType (_increment.Text, typeof (T)); + } + + _increment.ColorScheme = SuperView.ColorScheme; + + } + catch (System.FormatException) + { + _increment.ColorScheme = Colors.ColorSchemes ["Error"]; + } + catch (InvalidCastException) + { + _increment.ColorScheme = Colors.ColorSchemes ["Error"]; + } + finally + { + } + } + + Add (label, _increment); + + _numericUpDown = new () + { + X = Pos.Center (), + Y = Pos.Bottom (_increment) + 1, + Increment = (dynamic)1, + }; + + _numericUpDown.ValueChanged += NumericUpDownOnValueChanged; + + void NumericUpDownOnValueChanged (object? o, EventArgs eventArgs) + { + _value.Text = _numericUpDown.Text; + } + + _numericUpDown.IncrementChanged += NumericUpDownOnIncrementChanged; + + void NumericUpDownOnIncrementChanged (object? o, EventArgs eventArgs) + { + _increment.Text = _numericUpDown.Increment.ToString (); + } + + Add (_numericUpDown); + + _value.Text = _numericUpDown.Text; + _format.Text = _numericUpDown.Format; + _increment.Text = _numericUpDown.Increment.ToString (); + } + } + + +} diff --git a/UnitTests/Views/NumericUpDownTests.cs b/UnitTests/Views/NumericUpDownTests.cs index 3f9ab4051..103371f19 100644 --- a/UnitTests/Views/NumericUpDownTests.cs +++ b/UnitTests/Views/NumericUpDownTests.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Xunit.Abstractions; namespace Terminal.Gui.ViewsTests; @@ -184,12 +185,39 @@ public class NumericUpDownTests (ITestOutputHelper _output) [InlineData (.75F, "{0:0%}", "75%")] public void Format_decimal (float value, string format, string expectedText) { + CultureInfo currentCulture = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; + NumericUpDown numericUpDown = new NumericUpDown (); numericUpDown.Format = format; numericUpDown.Value = value; Assert.Equal (expectedText, numericUpDown.Text); + + CultureInfo.CurrentCulture = currentCulture; + } + + [Theory] + [InlineData (0, "{0}", "0")] + [InlineData (11, "{0}", "11")] + [InlineData (-1, "{0}", "-1")] + [InlineData (911, "{0:X}", "38F")] + [InlineData (911, "0x{0:X04}", "0x038F")] + + public void Format_int (int value, string format, string expectedText) + { + CultureInfo currentCulture = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; + + NumericUpDown numericUpDown = new NumericUpDown (); + + numericUpDown.Format = format; + numericUpDown.Value = value; + + Assert.Equal (expectedText, numericUpDown.Text); + + CultureInfo.CurrentCulture = currentCulture; } [Fact] @@ -211,4 +239,5 @@ public class NumericUpDownTests (ITestOutputHelper _output) Assert.Equal (-1, numericUpDown.Value); } + } From b6db4a327c4dd060a12bbedd57a434a016f95fb0 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 5 Aug 2024 17:48:14 -0600 Subject: [PATCH 5/7] Fixed warning --- Terminal.Gui/Views/NumericUpDown.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/Views/NumericUpDown.cs b/Terminal.Gui/Views/NumericUpDown.cs index c36534172..d704c213e 100644 --- a/Terminal.Gui/Views/NumericUpDown.cs +++ b/Terminal.Gui/Views/NumericUpDown.cs @@ -147,8 +147,7 @@ public class NumericUpDown : View where T : notnull /// /// and events are raised when the value changes. /// The event can be canceled the change setting - /// to - /// . + /// .Cancel to . /// /// public T Value @@ -177,7 +176,7 @@ public class NumericUpDown : View where T : notnull } /// - /// Raised when the value is about to change. Set to true to prevent the + /// Raised when the value is about to change. Set .Cancel to true to prevent the /// change. /// public event EventHandler>? ValueChanging; @@ -229,7 +228,7 @@ public class NumericUpDown : View where T : notnull get => _increment; set { - if (_increment == (dynamic)value) + if ((dynamic)_increment == (dynamic)value) { return; } From 5dd3db92cb364c756d3af920ee8998ec6c260d71 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 5 Aug 2024 17:49:48 -0600 Subject: [PATCH 6/7] Fixed linux/mac failure --- Terminal.Gui/Views/NumericUpDown.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/NumericUpDown.cs b/Terminal.Gui/Views/NumericUpDown.cs index d704c213e..279cfd6cf 100644 --- a/Terminal.Gui/Views/NumericUpDown.cs +++ b/Terminal.Gui/Views/NumericUpDown.cs @@ -37,11 +37,10 @@ public class NumericUpDown : View where T : notnull throw new InvalidOperationException ("T must be a numeric type that supports addition and subtraction."); } - Increment = (dynamic)1; - // `object` is supported only for AllViewsTester if (type != typeof (object)) { + Increment = (dynamic)1; Value = (dynamic)0; } From 12df9beebe0275e02a027872396f913131b72486 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 5 Aug 2024 18:03:08 -0600 Subject: [PATCH 7/7] Fixed linux/mac test failure --- UnitTests/Views/NumericUpDownTests.cs | 37 ++++++++++++--------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/UnitTests/Views/NumericUpDownTests.cs b/UnitTests/Views/NumericUpDownTests.cs index 103371f19..916053001 100644 --- a/UnitTests/Views/NumericUpDownTests.cs +++ b/UnitTests/Views/NumericUpDownTests.cs @@ -53,7 +53,7 @@ public class NumericUpDownTests (ITestOutputHelper _output) [Fact] public void WhenCreatedWithCustomValues_ShouldHaveCustomValues_int () { - NumericUpDown numericUpDown = new NumericUpDown + NumericUpDown numericUpDown = new() { Value = 10, Increment = 2 @@ -66,7 +66,7 @@ public class NumericUpDownTests (ITestOutputHelper _output) [Fact] public void WhenCreatedWithCustomValues_ShouldHaveCustomValues_float () { - NumericUpDown numericUpDown = new NumericUpDown + NumericUpDown numericUpDown = new() { Value = 10.5F, Increment = 2.5F @@ -79,7 +79,7 @@ public class NumericUpDownTests (ITestOutputHelper _output) [Fact] public void WhenCreatedWithCustomValues_ShouldHaveCustomValues_decimal () { - NumericUpDown numericUpDown = new NumericUpDown + NumericUpDown numericUpDown = new () { Value = 10.5m, Increment = 2.5m @@ -98,16 +98,13 @@ public class NumericUpDownTests (ITestOutputHelper _output) [Fact] public void WhenCreatedWithInvalidTypeObject_ShouldNotThrowInvalidOperationException () { - NumericUpDown numericUpDown = new NumericUpDown (); - - Assert.Null (numericUpDown.Value); - Assert.Equal (1, numericUpDown.Increment); + NumericUpDown numericUpDown = new (); } [Fact] public void WhenCreated_ShouldHaveDefaultWidthAndHeight_int () { - NumericUpDown numericUpDown = new NumericUpDown (); + NumericUpDown numericUpDown = new (); numericUpDown.SetRelativeLayout (Application.Screen.Size); Assert.Equal (3, numericUpDown.Frame.Width); @@ -117,7 +114,7 @@ public class NumericUpDownTests (ITestOutputHelper _output) [Fact] public void WhenCreated_ShouldHaveDefaultWidthAndHeight_float () { - NumericUpDown numericUpDown = new NumericUpDown (); + NumericUpDown numericUpDown = new (); numericUpDown.SetRelativeLayout (Application.Screen.Size); Assert.Equal (3, numericUpDown.Frame.Width); @@ -127,7 +124,7 @@ public class NumericUpDownTests (ITestOutputHelper _output) [Fact] public void WhenCreated_ShouldHaveDefaultWidthAndHeight_double () { - NumericUpDown numericUpDown = new NumericUpDown (); + NumericUpDown numericUpDown = new (); numericUpDown.SetRelativeLayout (Application.Screen.Size); Assert.Equal (3, numericUpDown.Frame.Width); @@ -137,7 +134,7 @@ public class NumericUpDownTests (ITestOutputHelper _output) [Fact] public void WhenCreated_ShouldHaveDefaultWidthAndHeight_long () { - NumericUpDown numericUpDown = new NumericUpDown (); + NumericUpDown numericUpDown = new (); numericUpDown.SetRelativeLayout (Application.Screen.Size); Assert.Equal (3, numericUpDown.Frame.Width); @@ -147,7 +144,7 @@ public class NumericUpDownTests (ITestOutputHelper _output) [Fact] public void WhenCreated_ShouldHaveDefaultWidthAndHeight_decimal () { - NumericUpDown numericUpDown = new NumericUpDown (); + NumericUpDown numericUpDown = new (); numericUpDown.SetRelativeLayout (Application.Screen.Size); Assert.Equal (3, numericUpDown.Frame.Width); @@ -157,7 +154,7 @@ public class NumericUpDownTests (ITestOutputHelper _output) [Fact] public void WhenCreated_Text_Should_Be_Correct_int () { - NumericUpDown numericUpDown = new NumericUpDown (); + NumericUpDown numericUpDown = new (); Assert.Equal ("0", numericUpDown.Text); } @@ -165,7 +162,7 @@ public class NumericUpDownTests (ITestOutputHelper _output) [Fact] public void WhenCreated_Text_Should_Be_Correct_float () { - NumericUpDown numericUpDown = new NumericUpDown (); + NumericUpDown numericUpDown = new (); Assert.Equal ("0", numericUpDown.Text); } @@ -173,7 +170,7 @@ public class NumericUpDownTests (ITestOutputHelper _output) [Fact] public void Format_Default () { - NumericUpDown numericUpDown = new NumericUpDown (); + NumericUpDown numericUpDown = new (); Assert.Equal ("{0}", numericUpDown.Format); } @@ -188,7 +185,7 @@ public class NumericUpDownTests (ITestOutputHelper _output) CultureInfo currentCulture = CultureInfo.CurrentCulture; CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; - NumericUpDown numericUpDown = new NumericUpDown (); + NumericUpDown numericUpDown = new (); numericUpDown.Format = format; numericUpDown.Value = value; @@ -204,13 +201,12 @@ public class NumericUpDownTests (ITestOutputHelper _output) [InlineData (-1, "{0}", "-1")] [InlineData (911, "{0:X}", "38F")] [InlineData (911, "0x{0:X04}", "0x038F")] - public void Format_int (int value, string format, string expectedText) { CultureInfo currentCulture = CultureInfo.CurrentCulture; CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; - NumericUpDown numericUpDown = new NumericUpDown (); + NumericUpDown numericUpDown = new (); numericUpDown.Format = format; numericUpDown.Value = value; @@ -223,7 +219,7 @@ public class NumericUpDownTests (ITestOutputHelper _output) [Fact] public void KeDown_CursorUp_Increments () { - NumericUpDown numericUpDown = new NumericUpDown (); + NumericUpDown numericUpDown = new (); numericUpDown.NewKeyDownEvent (Key.CursorUp); @@ -233,11 +229,10 @@ public class NumericUpDownTests (ITestOutputHelper _output) [Fact] public void KeyDown_CursorDown_Decrements () { - NumericUpDown numericUpDown = new NumericUpDown (); + NumericUpDown numericUpDown = new (); numericUpDown.NewKeyDownEvent (Key.CursorDown); Assert.Equal (-1, numericUpDown.Value); } - }