diff --git a/Examples/UICatalog/Scenarios/NumericUpDownDemo.cs b/Examples/UICatalog/Scenarios/NumericUpDownDemo.cs index 0cdffdfc3..078ef823a 100644 --- a/Examples/UICatalog/Scenarios/NumericUpDownDemo.cs +++ b/Examples/UICatalog/Scenarios/NumericUpDownDemo.cs @@ -252,7 +252,7 @@ internal class NumericUpDownEditor : View where T : notnull { X = Pos.Center (), Y = Pos.Bottom (_increment) + 1, - Increment = (dynamic)1, + Increment = NumericUpDown.TryConvert (1, out T? increment) ? increment : default, }; _numericUpDown.ValueChanged += NumericUpDownOnValueChanged; diff --git a/Examples/UICatalog/Scenarios/TextAlignmentAndDirection.cs b/Examples/UICatalog/Scenarios/TextAlignmentAndDirection.cs index d1a668dd5..dcd5bf7ca 100644 --- a/Examples/UICatalog/Scenarios/TextAlignmentAndDirection.cs +++ b/Examples/UICatalog/Scenarios/TextAlignmentAndDirection.cs @@ -410,7 +410,7 @@ public class TextAlignmentAndDirection : Scenario // Save Alignment in Data foreach (View t in multiLineLabels) { - t.Data = new { h = t.TextAlignment, v = t.VerticalTextAlignment }; + t.Data = new TextAlignmentData (t.TextAlignment, t.VerticalTextAlignment); } container.Add (txtLabelTL); @@ -594,8 +594,9 @@ public class TextAlignmentAndDirection : Scenario foreach (View t in multiLineLabels) { - t.TextAlignment = (Alignment)((dynamic)t.Data).h; - t.VerticalTextAlignment = (Alignment)((dynamic)t.Data).v; + var data = (TextAlignmentData)t.Data; + t.TextAlignment = data!.h; + t.VerticalTextAlignment = data.v; } } else @@ -607,24 +608,23 @@ public class TextAlignmentAndDirection : Scenario justifyOptions.Enabled = true; } + var data = (TextAlignmentData)t.Data; + if (TextFormatter.IsVerticalDirection (t.TextDirection)) { switch (justifyOptions.SelectedItem) { case 0: t.VerticalTextAlignment = Alignment.Fill; - t.TextAlignment = ((dynamic)t.Data).h; - + t.TextAlignment = data!.h; break; case 1: - t.VerticalTextAlignment = (Alignment)((dynamic)t.Data).v; + t.VerticalTextAlignment = data!.v; t.TextAlignment = Alignment.Fill; - break; case 2: t.VerticalTextAlignment = Alignment.Fill; t.TextAlignment = Alignment.Fill; - break; } } @@ -634,18 +634,15 @@ public class TextAlignmentAndDirection : Scenario { case 0: t.TextAlignment = Alignment.Fill; - t.VerticalTextAlignment = ((dynamic)t.Data).v; - + t.VerticalTextAlignment = data!.v; break; case 1: - t.TextAlignment = (Alignment)((dynamic)t.Data).h; + t.TextAlignment = data!.h; t.VerticalTextAlignment = Alignment.Fill; - break; case 2: t.TextAlignment = Alignment.Fill; t.VerticalTextAlignment = Alignment.Fill; - break; } } @@ -653,4 +650,10 @@ public class TextAlignmentAndDirection : Scenario } } } + + private class TextAlignmentData (Alignment h, Alignment v) + { + public Alignment h { get; } = h; + public Alignment v { get; } = v; + } } diff --git a/Terminal.Gui/Views/NumericUpDown.cs b/Terminal.Gui/Views/NumericUpDown.cs index a1099bef3..bb896db57 100644 --- a/Terminal.Gui/Views/NumericUpDown.cs +++ b/Terminal.Gui/Views/NumericUpDown.cs @@ -1,5 +1,6 @@ #nullable enable using System.ComponentModel; +using System.Numerics; namespace Terminal.Gui.Views; @@ -26,13 +27,7 @@ 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) || NumericHelper.SupportsType (type))) { throw new InvalidOperationException ("T must be a numeric type that supports addition and subtraction."); } @@ -40,8 +35,11 @@ public class NumericUpDown : View where T : notnull // `object` is supported only for AllViewsTester if (type != typeof (object)) { - Increment = (dynamic)1; - Value = (dynamic)0; + if (NumericHelper.TryGetHelper (typeof (T), out INumericHelper? helper)) + { + Increment = (T)helper!.One; + Value = (T)helper!.Zero; + } } Width = Dim.Auto (DimAutoStyle.Content); @@ -106,11 +104,10 @@ public class NumericUpDown : View where T : notnull // return true; //} - if (Value is { } && Increment is { }) + if (Value is { } v && Increment is { } i && NumericHelper.TryGetHelper (typeof (T), out INumericHelper? helper)) { - Value = (dynamic)Value + (dynamic)Increment; + Value = (T)helper!.Add (v, i); } - return true; }); @@ -129,12 +126,10 @@ public class NumericUpDown : View where T : notnull // return true; //} - if (Value is { } && Increment is { }) + if (Value is { } v && Increment is { } i && NumericHelper.TryGetHelper (typeof (T), out INumericHelper? helper)) { - Value = (dynamic)Value - (dynamic)Increment; + Value = (T)helper!.Subtract (v, i); } - - return true; }); @@ -248,7 +243,7 @@ public class NumericUpDown : View where T : notnull get => _increment; set { - if (_increment is { } && value is { } && (dynamic)_increment == (dynamic)value) + if (_increment is { } oldVal && value is { } newVal && oldVal.Equals (newVal)) { return; } @@ -267,6 +262,34 @@ public class NumericUpDown : View where T : notnull // Prevent the drawing of Text /// protected override bool OnDrawingText () { return true; } + + /// + /// Attempts to convert the specified to type . + /// + /// The type to which the value should be converted. + /// The value to convert. + /// + /// When this method returns, contains the converted value if the conversion succeeded, + /// or the default value of if the conversion failed. + /// + /// + /// true if the conversion was successful; otherwise, false. + /// + public static bool TryConvert (object value, out T? result) + { + try + { + result = (T)Convert.ChangeType (value, typeof (T)); + + return true; + } + catch + { + result = default (T); + + return false; + } + } } /// @@ -274,3 +297,45 @@ public class NumericUpDown : View where T : notnull /// public class NumericUpDown : NumericUpDown { } + +internal interface INumericHelper +{ + object One { get; } + object Zero { get; } + object Add (object a, object b); + object Subtract (object a, object b); +} + +internal static class NumericHelper +{ + private static readonly Dictionary _helpers = new (); + + static NumericHelper () + { + // Register known INumber types + Register (); + Register (); + Register (); + Register (); + Register (); + // Add more as needed + } + + private static void Register () where T : INumber + { + _helpers [typeof (T)] = new NumericHelperImpl (); + } + + public static bool TryGetHelper (Type t, out INumericHelper? helper) + => _helpers.TryGetValue (t, out helper); + + private class NumericHelperImpl : INumericHelper where T : INumber + { + public object One => T.One; + public object Zero => T.Zero; + public object Add (object a, object b) => (T)a + (T)b; + public object Subtract (object a, object b) => (T)a - (T)b; + } + + public static bool SupportsType (Type type) => _helpers.ContainsKey (type); +} diff --git a/Tests/UnitTestsParallelizable/Views/NumericUpDownTests.cs b/Tests/UnitTestsParallelizable/Views/NumericUpDownTests.cs index 571a9fab4..89e5055e7 100644 --- a/Tests/UnitTestsParallelizable/Views/NumericUpDownTests.cs +++ b/Tests/UnitTestsParallelizable/Views/NumericUpDownTests.cs @@ -1,5 +1,4 @@ using System.Globalization; -using Xunit.Abstractions; namespace Terminal.Gui.ViewsTests; @@ -102,6 +101,13 @@ public class NumericUpDownTests Assert.Null (exception); } + [Fact] + public void WhenCreatedWithValidNumberType_ShouldThrowInvalidOperationException_UnlessTheyAreRegisterAsValid () + { + Exception exception = Record.Exception (() => new NumericUpDown ()); + Assert.NotNull (exception); + } + [Fact] public void WhenCreated_ShouldHaveDefaultWidthAndHeight_int () {