#nullable enable using System.ComponentModel; namespace Terminal.Gui; /// /// Enables the user to increase or decrease a value with the mouse or keyboard. /// /// /// Supports the following types: , , , , /// . Attempting to use any other type will result in an . /// public class NumericUpDown : View where T : notnull { private readonly Button _down; // TODO: Use a TextField instead of a Label private readonly View _number; private readonly Button _up; /// /// Initializes a new instance of the class. /// /// public NumericUpDown () { 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))) { throw new InvalidOperationException ("T must be a numeric type that supports addition and subtraction."); } // `object` is supported only for AllViewsTester if (type != typeof (object)) { Increment = (dynamic)1; Value = (dynamic)0; } Width = Dim.Auto (DimAutoStyle.Content); 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.Auto (minimumContentDim: Dim.Func (() => string.Format (Format, Value).GetColumns())), Height = 1, TextAlignment = Alignment.Center, CanFocus = true, }; _up = new () { X = Pos.Right (_number), 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.Accepting += OnDownButtonOnAccept; _up.Accepting += OnUpButtonOnAccept; Add (_down, _number, _up); AddCommand ( Command.ScrollUp, (ctx) => { if (type == typeof (object)) { return false; } if (RaiseSelecting (ctx) is true) { return true; } if (Value is { } && Increment is { }) { Value = (dynamic)Value + (dynamic)Increment; } return true; }); AddCommand ( Command.ScrollDown, (ctx) => { if (type == typeof (object)) { return false; } if (RaiseSelecting (ctx) is true) { return true; } if (Value is { } && Increment is { }) { Value = (dynamic)Value - (dynamic)Increment; } return true; }); KeyBindings.Add (Key.CursorUp, Command.ScrollUp); KeyBindings.Add (Key.CursorDown, Command.ScrollDown); SetText (); return; void OnDownButtonOnAccept (object? s, CommandEventArgs e) { InvokeCommand (Command.ScrollDown); e.Cancel = true; } void OnUpButtonOnAccept (object? s, CommandEventArgs e) { InvokeCommand (Command.ScrollUp); e.Cancel = true; } } private T _value = default!; /// /// 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 /// .Cancel to . /// /// public T Value { get => _value; set { if (_value.Equals (value)) { return; } T oldValue = value; CancelEventArgs args = new (in _value, ref value); ValueChanging?.Invoke (this, args); if (args.Cancel) { return; } _value = value; SetText (); ValueChanged?.Invoke (this, new (in value)); } } /// /// Raised when the value is about to change. Set .Cancel to true to prevent the /// change. /// public event EventHandler>? ValueChanging; /// /// Raised when the value has changed. /// public event EventHandler>? ValueChanged; private string _format = "{0}"; /// /// Gets or sets the format string used to display the value. The default is "{0}". /// public string Format { get => _format; set { if (_format == value) { return; } _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; } private T? _increment; /// /// public T? Increment { get => _increment; set { if (_increment is { } && value is { } && (dynamic)_increment == (dynamic)value) { return; } _increment = value; IncrementChanged?.Invoke (this, new (value!)); } } /// /// Raised when has changed. /// public event EventHandler>? IncrementChanged; // Prevent the drawing of Text /// protected override bool OnDrawingText (Rectangle viewport) { return true; } } /// /// Enables the user to increase or decrease an by clicking on the up or down buttons. /// public class NumericUpDown : NumericUpDown { }