From 939ce2bee9c3531f49dce93ad9bf03b7f62144e2 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sat, 13 Jan 2024 19:59:08 +0800 Subject: [PATCH 01/13] feat: initialize. --- demo/Ursa.Demo/Models/MenuKeys.cs | 1 + demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml | 14 ++ .../Pages/NumericUpDownDemo.axaml.cs | 15 ++ .../Ursa.Demo/ViewModels/MainViewViewModel.cs | 1 + demo/Ursa.Demo/ViewModels/MenuViewModel.cs | 1 + .../ViewModels/NumericUpDownDemoViewModel.cs | 8 ++ .../Controls/NumericUpDown.axaml | 12 ++ src/Ursa.Themes.Semi/Controls/_index.axaml | 1 + src/Ursa/Controls/NumericUpDown/IntUpDown.cs | 22 +++ .../NumericUpDown/NumericUpDownBase.cs | 133 ++++++++++++++++++ 10 files changed, 208 insertions(+) create mode 100644 demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml create mode 100644 demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml.cs create mode 100644 demo/Ursa.Demo/ViewModels/NumericUpDownDemoViewModel.cs create mode 100644 src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml create mode 100644 src/Ursa/Controls/NumericUpDown/IntUpDown.cs create mode 100644 src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs diff --git a/demo/Ursa.Demo/Models/MenuKeys.cs b/demo/Ursa.Demo/Models/MenuKeys.cs index 8532a7f..86d7fd5 100644 --- a/demo/Ursa.Demo/Models/MenuKeys.cs +++ b/demo/Ursa.Demo/Models/MenuKeys.cs @@ -14,6 +14,7 @@ public static class MenuKeys public const string MenuKeyKeyGestureInput = "KeyGestureInput"; public const string MenuKeyLoading = "Loading"; public const string MenuKeyNavigation = "Navigation"; + public const string MenuKeyNumericUpDown = "NumericUpDown"; public const string MenuKeyPagination = "Pagination"; public const string MenuKeyTagInput = "TagInput"; public const string MenuKeyTimeline = "Timeline"; diff --git a/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml b/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml new file mode 100644 index 0000000..a16edcd --- /dev/null +++ b/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml @@ -0,0 +1,14 @@ + + + + + diff --git a/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml.cs b/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml.cs new file mode 100644 index 0000000..4bdf06d --- /dev/null +++ b/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml.cs @@ -0,0 +1,15 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Ursa.Demo.ViewModels; + +namespace Ursa.Demo.Pages; + +public partial class NumericUpDownDemo : UserControl +{ + public NumericUpDownDemo() + { + InitializeComponent(); + DataContext = new NumericUpDownDemoViewModel(); + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs index d2c59ba..b18384f 100644 --- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs @@ -36,6 +36,7 @@ public class MainViewViewModel : ViewModelBase MenuKeys.MenuKeyKeyGestureInput => new KeyGestureInputDemoViewModel(), MenuKeys.MenuKeyLoading => new LoadingDemoViewModel(), MenuKeys.MenuKeyNavigation => new NavigationMenuDemoViewModel(), + MenuKeys.MenuKeyNumericUpDown => new NumericUpDownDemoViewModel(), MenuKeys.MenuKeyPagination => new PaginationDemoViewModel(), MenuKeys.MenuKeyTagInput => new TagInputDemoViewModel(), MenuKeys.MenuKeyTimeline => new TimelineDemoViewModel(), diff --git a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs index b2f04b0..957e943 100644 --- a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs @@ -23,6 +23,7 @@ public class MenuViewModel: ViewModelBase new() { MenuHeader = "KeyGestureInput", Key = MenuKeys.MenuKeyKeyGestureInput }, new() { MenuHeader = "Loading", Key = MenuKeys.MenuKeyLoading }, new() { MenuHeader = "Navigation", Key = MenuKeys.MenuKeyNavigation }, + new() { MenuHeader = "NumericUpDown", Key = MenuKeys.MenuKeyNumericUpDown }, new() { MenuHeader = "Pagination", Key = MenuKeys.MenuKeyPagination }, new() { MenuHeader = "TagInput", Key = MenuKeys.MenuKeyTagInput }, new() { MenuHeader = "Timeline", Key = MenuKeys.MenuKeyTimeline }, diff --git a/demo/Ursa.Demo/ViewModels/NumericUpDownDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/NumericUpDownDemoViewModel.cs new file mode 100644 index 0000000..9840cca --- /dev/null +++ b/demo/Ursa.Demo/ViewModels/NumericUpDownDemoViewModel.cs @@ -0,0 +1,8 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Ursa.Demo.ViewModels; + +public class NumericUpDownDemoViewModel: ObservableObject +{ + +} \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml b/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml new file mode 100644 index 0000000..53f60ce --- /dev/null +++ b/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml index f3b1ac5..8d9c5a1 100644 --- a/src/Ursa.Themes.Semi/Controls/_index.axaml +++ b/src/Ursa.Themes.Semi/Controls/_index.axaml @@ -12,6 +12,7 @@ + diff --git a/src/Ursa/Controls/NumericUpDown/IntUpDown.cs b/src/Ursa/Controls/NumericUpDown/IntUpDown.cs new file mode 100644 index 0000000..07ce4b6 --- /dev/null +++ b/src/Ursa/Controls/NumericUpDown/IntUpDown.cs @@ -0,0 +1,22 @@ +namespace Ursa.Controls; + +public class IntUpDown: NumericUpDownBase +{ + protected override Type StyleKeyOverride { get; } = typeof(NumericUpDown); + + static IntUpDown() + { + MaximumProperty.OverrideDefaultValue(100); + } + + protected override void Increase() + { + //throw new NotImplementedException(); + Value += Maximum; + } + + protected override void Decrease() + { + Value -= Maximum; + } +} \ No newline at end of file diff --git a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs new file mode 100644 index 0000000..564a235 --- /dev/null +++ b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs @@ -0,0 +1,133 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Primitives; + +namespace Ursa.Controls; + +[TemplatePart(PART_Spinner, typeof(ButtonSpinner))] +[TemplatePart(PART_TextBox, typeof(TextBox))] +public abstract class NumericUpDown : TemplatedControl +{ + public const string PART_Spinner = "PART_Spinner"; + public const string PART_TextBox = "PART_TextBox"; + + private Avalonia.Controls.NumericUpDown? _numericUpDown; + private ButtonSpinner? _spinner; + private TextBox? _textBox; + + public static readonly StyledProperty TextEditableProperty = AvaloniaProperty.Register( + nameof(TextEditable), defaultValue: true); + + public bool TextEditable + { + get => GetValue(TextEditableProperty); + set => SetValue(TextEditableProperty, value); + } + + public static readonly StyledProperty IsReadOnlyProperty = AvaloniaProperty.Register( + nameof(IsReadOnly)); + + public bool IsReadOnly + { + get => GetValue(IsReadOnlyProperty); + set => SetValue(IsReadOnlyProperty, value); + } + + public static readonly StyledProperty InnerLeftContentProperty = AvaloniaProperty.Register( + nameof(InnerLeftContent)); + + public object? InnerLeftContent + { + get => GetValue(InnerLeftContentProperty); + set => SetValue(InnerLeftContentProperty, value); + } + + public static readonly StyledProperty WatermarkProperty = AvaloniaProperty.Register( + nameof(Watermark)); + + public string? Watermark + { + get => GetValue(WatermarkProperty); + set => SetValue(WatermarkProperty, value); + } + + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + if(_spinner is not null) + { + _spinner.Spin -= OnSpin; + } + if(_textBox is not null) + { + _textBox.TextChanged -= OnTextChange; + } + _spinner = e.NameScope.Find(PART_Spinner); + _textBox = e.NameScope.Find(PART_TextBox); + if (_spinner is not null) + { + _spinner.Spin += OnSpin; + } + + if (_textBox is not null) + { + _textBox.TextChanged += OnTextChange; + } + + } + + private void OnTextChange(object sender, TextChangedEventArgs e) + { + + + } + + private void OnSpin(object sender, SpinEventArgs e) + { + if (e.Direction == SpinDirection.Increase) + { + Increase(); + } + else + { + Decrease(); + } + } + + protected abstract void Increase(); + protected abstract void Decrease(); +} + +public abstract class NumericUpDownBase: NumericUpDown where T: struct, IComparable +{ + public static readonly StyledProperty ValueProperty = AvaloniaProperty.Register, T>( + nameof(Value)); + + public T Value + { + get => GetValue(ValueProperty); + set => SetValue(ValueProperty, value); + } + + public static readonly StyledProperty MaximumProperty = AvaloniaProperty.Register, T>( + nameof(Maximum)); + + public T Maximum + { + get => GetValue(MaximumProperty); + set => SetValue(MaximumProperty, value); + } + + public static readonly StyledProperty MinimumProperty = AvaloniaProperty.Register, T>( + nameof(Minimum)); + + public T Minimum + { + get => GetValue(MinimumProperty); + set => SetValue(MinimumProperty, value); + } + + +} \ No newline at end of file From aea5ebb1a415684b889dccd466ad85af28988d12 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sun, 14 Jan 2024 01:49:31 +0800 Subject: [PATCH 02/13] feat: WIP --- src/Ursa/Controls/NumericUpDown/IntUpDown.cs | 36 ++++++-- .../NumericUpDown/NumericUpDownBase.cs | 83 +++++++++++++++++-- 2 files changed, 107 insertions(+), 12 deletions(-) diff --git a/src/Ursa/Controls/NumericUpDown/IntUpDown.cs b/src/Ursa/Controls/NumericUpDown/IntUpDown.cs index 07ce4b6..b951372 100644 --- a/src/Ursa/Controls/NumericUpDown/IntUpDown.cs +++ b/src/Ursa/Controls/NumericUpDown/IntUpDown.cs @@ -1,4 +1,6 @@ -namespace Ursa.Controls; +using Avalonia.Utilities; + +namespace Ursa.Controls; public class IntUpDown: NumericUpDownBase { @@ -6,17 +8,41 @@ public class IntUpDown: NumericUpDownBase static IntUpDown() { - MaximumProperty.OverrideDefaultValue(100); + MaximumProperty.OverrideDefaultValue(int.MaxValue); + StepProperty.OverrideDefaultValue(1); } protected override void Increase() { - //throw new NotImplementedException(); - Value += Maximum; + Value += Step; } protected override void Decrease() { - Value -= Maximum; + Value -= Step; + } + + protected override void UpdateTextToValue(string x) + { + if (int.TryParse(x, out var value)) + { + Value = value; + } + } + + protected override bool CommitInput() + { + // throw new NotImplementedException(); + return true; + } + + protected override void SyncTextAndValue() + { + // throw new NotImplementedException(); + } + + protected override int Clamp() + { + return MathUtilities.Clamp(Value, Maximum, Minimum); } } \ No newline at end of file diff --git a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs index 564a235..659aeaa 100644 --- a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs +++ b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs @@ -2,19 +2,26 @@ using Avalonia.Controls; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Interactivity; namespace Ursa.Controls; [TemplatePart(PART_Spinner, typeof(ButtonSpinner))] [TemplatePart(PART_TextBox, typeof(TextBox))] +[TemplatePart(PART_DragPanel, typeof(Panel))] public abstract class NumericUpDown : TemplatedControl { public const string PART_Spinner = "PART_Spinner"; public const string PART_TextBox = "PART_TextBox"; + public const string PART_DragPanel = "PART_DragPanel"; - private Avalonia.Controls.NumericUpDown? _numericUpDown; - private ButtonSpinner? _spinner; - private TextBox? _textBox; + protected internal ButtonSpinner? _spinner; + protected internal TextBox? _textBox; + protected internal Panel? _dragPanel; + + private Point? _point; + private bool _updateFromTextInput; public static readonly StyledProperty TextEditableProperty = AvaloniaProperty.Register( nameof(TextEditable), defaultValue: true); @@ -64,6 +71,13 @@ public abstract class NumericUpDown : TemplatedControl { _textBox.TextChanged -= OnTextChange; } + + if (_dragPanel is not null) + { + _dragPanel.PointerPressed -= OnDragPanelPointerPressed; + _dragPanel.PointerMoved -= OnDragPanelPointerMoved; + _dragPanel.PointerReleased -= OnDragPanelPointerReleased; + } _spinner = e.NameScope.Find(PART_Spinner); _textBox = e.NameScope.Find(PART_TextBox); if (_spinner is not null) @@ -75,13 +89,56 @@ public abstract class NumericUpDown : TemplatedControl { _textBox.TextChanged += OnTextChange; } + + if (_dragPanel is not null) + { + _dragPanel.PointerPressed+= OnDragPanelPointerPressed; + _dragPanel.PointerMoved += OnDragPanelPointerMoved; + _dragPanel.PointerReleased += OnDragPanelPointerReleased; + } } + protected override void OnLostFocus(RoutedEventArgs e) + { + CommitInput(); + base.OnLostFocus(e); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if (e.Key == Key.Enter) + { + var commitSuccess = CommitInput(); + e.Handled = !commitSuccess; + } + } + + private void OnDragPanelPointerPressed(object sender, PointerPressedEventArgs e) + { + _point = e.GetPosition(this); + } + + private void OnDragPanelPointerReleased(object sender, PointerReleasedEventArgs e) + { + _point = null; + } + + private void OnDragPanelPointerMoved(object sender, PointerEventArgs e) + { + var point = e.GetPosition(this); + var delta = point - _point; + if (delta is null) + { + return; + } + } + private void OnTextChange(object sender, TextChangedEventArgs e) { - - + _updateFromTextInput = true; + UpdateTextToValue(_textBox?.Text ?? string.Empty); + _updateFromTextInput = false; } private void OnSpin(object sender, SpinEventArgs e) @@ -98,6 +155,9 @@ public abstract class NumericUpDown : TemplatedControl protected abstract void Increase(); protected abstract void Decrease(); + protected abstract void UpdateTextToValue(string x); + protected abstract bool CommitInput(); + protected abstract void SyncTextAndValue(); } public abstract class NumericUpDownBase: NumericUpDown where T: struct, IComparable @@ -128,6 +188,15 @@ public abstract class NumericUpDownBase: NumericUpDown where T: struct, IComp get => GetValue(MinimumProperty); set => SetValue(MinimumProperty, value); } - - + + public static readonly StyledProperty StepProperty = AvaloniaProperty.Register, T>( + nameof(Step)); + + public T Step + { + get => GetValue(StepProperty); + set => SetValue(StepProperty, value); + } + + protected abstract T Clamp(); } \ No newline at end of file From 631913145b8c6ee93919a4d5e7732eebb5114c01 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sun, 14 Jan 2024 22:11:23 +0800 Subject: [PATCH 03/13] feat: finish int implementation. --- demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml | 3 +- .../Controls/NumericUpDown.axaml | 31 +- src/Ursa/Controls/NumericUpDown/IntUpDown.cs | 44 +- .../NumericUpDown/NumericUpDownBase.cs | 430 +++++++++++++++++- .../NumericUpDown/ValueChangedEventArgs.cs | 15 + 5 files changed, 470 insertions(+), 53 deletions(-) create mode 100644 src/Ursa/Controls/NumericUpDown/ValueChangedEventArgs.cs diff --git a/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml b/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml index a16edcd..6a3cfe5 100644 --- a/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml +++ b/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml @@ -9,6 +9,7 @@ d:DesignWidth="800" mc:Ignorable="d"> - + + diff --git a/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml b/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml index 53f60ce..eff46a6 100644 --- a/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml +++ b/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml @@ -3,9 +3,36 @@ xmlns:u="https://irihi.tech/ursa"> + + - - + + + + + + diff --git a/src/Ursa/Controls/NumericUpDown/IntUpDown.cs b/src/Ursa/Controls/NumericUpDown/IntUpDown.cs index b951372..6aba69b 100644 --- a/src/Ursa/Controls/NumericUpDown/IntUpDown.cs +++ b/src/Ursa/Controls/NumericUpDown/IntUpDown.cs @@ -1,48 +1,32 @@ -using Avalonia.Utilities; +using System.Globalization; +using Avalonia.Controls; +using Avalonia.Utilities; namespace Ursa.Controls; -public class IntUpDown: NumericUpDownBase +public class IntUpDown : NumericUpDownBase { protected override Type StyleKeyOverride { get; } = typeof(NumericUpDown); static IntUpDown() { MaximumProperty.OverrideDefaultValue(int.MaxValue); + MinimumProperty.OverrideDefaultValue(int.MinValue); StepProperty.OverrideDefaultValue(1); } - - protected override void Increase() + + protected override bool ParseText(string? text, out int? number) { - Value += Step; + var result = int.TryParse(text, ParsingNumberStyle, NumberFormat, out var value); + number = value; + return result; } - protected override void Decrease() - { - Value -= Step; - } - - protected override void UpdateTextToValue(string x) - { - if (int.TryParse(x, out var value)) - { - Value = value; - } - } + protected override string? ValueToString(int? value) => value?.ToString(FormatString, NumberFormat); - protected override bool CommitInput() - { - // throw new NotImplementedException(); - return true; - } + protected override int Zero => 0; - protected override void SyncTextAndValue() - { - // throw new NotImplementedException(); - } + protected override int? Add(int? a, int? b) => a + b; - protected override int Clamp() - { - return MathUtilities.Clamp(Value, Maximum, Minimum); - } + protected override int? Minus(int? a, int? b) => a - b; } \ No newline at end of file diff --git a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs index 659aeaa..1dc9a24 100644 --- a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs +++ b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs @@ -1,7 +1,10 @@ -using Avalonia; +using System.Globalization; +using System.Net.Mime; +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; +using Avalonia.Data.Converters; using Avalonia.Input; using Avalonia.Interactivity; @@ -21,7 +24,7 @@ public abstract class NumericUpDown : TemplatedControl protected internal Panel? _dragPanel; private Point? _point; - private bool _updateFromTextInput; + protected internal bool _updateFromTextInput; public static readonly StyledProperty TextEditableProperty = AvaloniaProperty.Register( nameof(TextEditable), defaultValue: true); @@ -58,8 +61,94 @@ public abstract class NumericUpDown : TemplatedControl get => GetValue(WatermarkProperty); set => SetValue(WatermarkProperty, value); } + + public static readonly StyledProperty NumberFormatProperty = AvaloniaProperty.Register( + nameof(NumberFormat), defaultValue: NumberFormatInfo.CurrentInfo); + + public NumberFormatInfo? NumberFormat + { + get => GetValue(NumberFormatProperty); + set => SetValue(NumberFormatProperty, value); + } + + public static readonly StyledProperty FormatStringProperty = AvaloniaProperty.Register( + nameof(FormatString), string.Empty); + + public string FormatString + { + get => GetValue(FormatStringProperty); + set => SetValue(FormatStringProperty, value); + } + + public static readonly StyledProperty ParsingNumberStyleProperty = AvaloniaProperty.Register( + nameof(ParsingNumberStyle)); + + public NumberStyles ParsingNumberStyle + { + get => GetValue(ParsingNumberStyleProperty); + set => SetValue(ParsingNumberStyleProperty, value); + } + + public static readonly StyledProperty TextConverterProperty = AvaloniaProperty.Register( + nameof(TextConverter)); + + public IValueConverter? TextConverter + { + get => GetValue(TextConverterProperty); + set => SetValue(TextConverterProperty, value); + } + + public static readonly StyledProperty AllowSpinProperty = AvaloniaProperty.Register( + nameof(AllowSpin), true); + + public bool AllowSpin + { + get => GetValue(AllowSpinProperty); + set => SetValue(AllowSpinProperty, value); + } + + public static readonly StyledProperty AllowMouseWheelProperty = AvaloniaProperty.Register( + nameof(AllowMouseWheel)); + + public bool AllowMouseWheel + { + get => GetValue(AllowMouseWheelProperty); + set => SetValue(AllowMouseWheelProperty, value); + } + + public event EventHandler? Spinned; - + static NumericUpDown() + { + NumberFormatProperty.Changed.AddClassHandler((o, e) => o.OnFormatChange(e)); + FormatStringProperty.Changed.AddClassHandler((o, e) => o.OnFormatChange(e)); + IsReadOnlyProperty.Changed.AddClassHandler((o,e)=>o.ChangeToSetSpinDirection(e)); + TextConverterProperty.Changed.AddClassHandler((o, e) => o.OnFormatChange(e)); + } + + protected void ChangeToSetSpinDirection(AvaloniaPropertyChangedEventArgs avaloniaPropertyChangedEventArgs, bool afterInitialization = false) + { + if (afterInitialization) + { + if (IsInitialized) + { + SetValidSpinDirection(); + } + } + else + { + SetValidSpinDirection(); + } + } + + protected virtual void OnFormatChange(AvaloniaPropertyChangedEventArgs arg) + { + if (IsInitialized) + { + SyncTextAndValue(false, null); + } + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -70,6 +159,7 @@ public abstract class NumericUpDown : TemplatedControl if(_textBox is not null) { _textBox.TextChanged -= OnTextChange; + } if (_dragPanel is not null) @@ -101,7 +191,7 @@ public abstract class NumericUpDown : TemplatedControl protected override void OnLostFocus(RoutedEventArgs e) { - CommitInput(); + CommitInput(true); base.OnLostFocus(e); } @@ -134,46 +224,65 @@ public abstract class NumericUpDown : TemplatedControl } } + [Obsolete] private void OnTextChange(object sender, TextChangedEventArgs e) { _updateFromTextInput = true; - UpdateTextToValue(_textBox?.Text ?? string.Empty); + SyncTextAndValue(); _updateFromTextInput = false; } private void OnSpin(object sender, SpinEventArgs e) { - if (e.Direction == SpinDirection.Increase) + if (AllowSpin && !IsReadOnly) { - Increase(); - } - else - { - Decrease(); + var spin = !e.UsingMouseWheel; + spin |= _textBox is { IsFocused: true }; + if (spin) + { + e.Handled = true; + var handler = Spinned; + handler?.Invoke(this, e); + if (e.Direction == SpinDirection.Increase) + { + Increase(); + } + else + { + Decrease(); + } + } } } + protected abstract void SetValidSpinDirection(); + protected abstract void Increase(); protected abstract void Decrease(); - protected abstract void UpdateTextToValue(string x); - protected abstract bool CommitInput(); - protected abstract void SyncTextAndValue(); + + protected virtual bool CommitInput(bool forceTextUpdate = false) + { + return SyncTextAndValue(true, _textBox?.Text, forceTextUpdate); + } + + protected abstract bool SyncTextAndValue(bool fromTextToValue = false, string? text = null, + bool forceTextUpdate = false); } public abstract class NumericUpDownBase: NumericUpDown where T: struct, IComparable { - public static readonly StyledProperty ValueProperty = AvaloniaProperty.Register, T>( + public static readonly StyledProperty ValueProperty = AvaloniaProperty.Register, T?>( nameof(Value)); - public T Value + public T? Value { get => GetValue(ValueProperty); set => SetValue(ValueProperty, value); } public static readonly StyledProperty MaximumProperty = AvaloniaProperty.Register, T>( - nameof(Maximum)); - + nameof(Maximum), coerce: CoerceMaximum); + public T Maximum { get => GetValue(MaximumProperty); @@ -181,13 +290,54 @@ public abstract class NumericUpDownBase: NumericUpDown where T: struct, IComp } public static readonly StyledProperty MinimumProperty = AvaloniaProperty.Register, T>( - nameof(Minimum)); + nameof(Minimum), coerce: CoerceMinimum); public T Minimum { get => GetValue(MinimumProperty); set => SetValue(MinimumProperty, value); } + + #region Max and Min Coerce + private static T CoerceMaximum(AvaloniaObject instance, T value) + { + if (instance is NumericUpDownBase n) + { + return n.CoerceMaximum(value); + } + + return value; + } + + private T CoerceMaximum(T value) + { + if (value.CompareTo(Minimum) < 0) + { + return Minimum; + } + return value; + } + + private static T CoerceMinimum(AvaloniaObject instance, T value) + { + if (instance is NumericUpDownBase n) + { + return n.CoerceMinimum(value); + } + + return value; + } + + private T CoerceMinimum(T value) + { + if (value.CompareTo(Maximum) > 0) + { + return Maximum; + } + return value; + } + + #endregion public static readonly StyledProperty StepProperty = AvaloniaProperty.Register, T>( nameof(Step)); @@ -198,5 +348,245 @@ public abstract class NumericUpDownBase: NumericUpDown where T: struct, IComp set => SetValue(StepProperty, value); } - protected abstract T Clamp(); + public static readonly StyledProperty EmptyInputValueProperty = + AvaloniaProperty.Register, T?>( + nameof(EmptyInputValue), defaultValue: null); + + public T? EmptyInputValue + { + get => GetValue(EmptyInputValueProperty); + set => SetValue(EmptyInputValueProperty, value); + } + + /// + /// Defines the event. + /// + public static readonly RoutedEvent> ValueChangedEvent = + RoutedEvent.Register>(nameof(ValueChanged), RoutingStrategies.Bubble); + + /// + /// Raised when the changes. + /// + public event EventHandler>? ValueChanged + { + add => AddHandler(ValueChangedEvent, value); + remove => RemoveHandler(ValueChangedEvent, value); + } + + static NumericUpDownBase() + { + StepProperty.Changed.AddClassHandler>((o, e) => o.ChangeToSetSpinDirection(e)); + MaximumProperty.Changed.AddClassHandler>((o, e) => o.OnConstraintChanged(e)); + MinimumProperty.Changed.AddClassHandler>((o, e) => o.OnConstraintChanged(e)); + ValueProperty.Changed.AddClassHandler>((o, e) => o.OnValueChanged(e) ); + } + + private void OnConstraintChanged(AvaloniaPropertyChangedEventArgs avaloniaPropertyChangedEventArgs) + { + if (IsInitialized) + { + SetValidSpinDirection(); + } + if (Value.HasValue) + { + SetCurrentValue(ValueProperty, Clamp(Value, Maximum, Minimum)); + } + } + + private void OnValueChanged(AvaloniaPropertyChangedEventArgs args) + { + if (IsInitialized) + { + SyncTextAndValue(false, null, true); + } + SetValidSpinDirection(); + T? oldValue = args.GetOldValue(); + T? newValue = args.GetNewValue(); + var e = new ValueChangedEventArgs(ValueChangedEvent, oldValue, newValue); + RaiseEvent(e); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + if (_textBox != null) + { + _textBox.Text = ConvertValueToText(Value); + } + } + + protected virtual T? Clamp(T? value, T max, T min) + { + if (value is null) + { + return null; + } + if (value.Value.CompareTo(max) > 0) + { + return Maximum; + } + if (value.Value.CompareTo(min) < 0) + { + return Minimum; + } + return value; + } + + protected override void SetValidSpinDirection() + { + var validDirection = ValidSpinDirections.None; + if (!IsReadOnly) + { + if (Value is null) + { + validDirection = ValidSpinDirections.Increase | ValidSpinDirections.Decrease; + } + if (Value.HasValue && Value.Value.CompareTo(Maximum) < 0) + { + validDirection |= ValidSpinDirections.Increase; + } + + if (Value.HasValue && Value.Value.CompareTo(Minimum) > 0) + { + validDirection |= ValidSpinDirections.Decrease; + } + } + if (_spinner != null) + { + _spinner.ValidSpinDirection = validDirection; + } + } + + private bool _isSyncingTextAndValue; + + protected override bool SyncTextAndValue(bool fromTextToValue = false, string? text = null, bool forceTextUpdate = false) + { + if (_isSyncingTextAndValue) return true; + _isSyncingTextAndValue = true; + var parsedTextIsValid = true; + try + { + if (fromTextToValue) + { + try + { + // TODO + var newValue = ConvertTextToValue(text); + if (EmptyInputValue is not null && newValue is null) + { + newValue = EmptyInputValue; + } + if (!Equals(newValue, Value)) + { + SetCurrentValue(ValueProperty, newValue); + } + } + catch + { + parsedTextIsValid = false; + } + } + + if (!_updateFromTextInput) + { + if (forceTextUpdate) + { + var newText = ConvertValueToText(Value); + if (_textBox!= null && !Equals(_textBox.Text, newText)) + { + _textBox.Text = newText; + } + } + } + + if (_updateFromTextInput && !parsedTextIsValid) + { + if (_spinner is not null) + { + _spinner.ValidSpinDirection = ValidSpinDirections.None; + } + } + else + { + SetValidSpinDirection(); + } + } + finally + { + _isSyncingTextAndValue = false; + } + return parsedTextIsValid; + } + + protected virtual T? ConvertTextToValue(string? text) + { + T? result; + if (string.IsNullOrWhiteSpace(text)) return null; + if (TextConverter != null) + { + var valueFromText = TextConverter.Convert(text, typeof(T?), null, CultureInfo.CurrentCulture); + return (T?)valueFromText; + } + else + { + if (!ParseText(text, out var outputValue)) + { + throw new InvalidDataException("Input string was not in a correct format."); + } + + result = outputValue; + } + return result; + } + + protected virtual string? ConvertValueToText(T? value) + { + if (TextConverter is not null) + { + return TextConverter.ConvertBack(Value, typeof(int), null, CultureInfo.CurrentCulture)?.ToString(); + } + + if (FormatString.Contains("{0")) + { + return string.Format(NumberFormat, FormatString, value); + } + + return ValueToString(Value); + } + + protected override void Increase() + { + T? value; + if (Value is not null) + { + value = Add(Value.Value, Step); + } + else + { + value = IsSet(MinimumProperty) ? Minimum : Zero; + } + SetCurrentValue(ValueProperty, Clamp(value, Maximum, Minimum)); + } + + protected override void Decrease() + { + T? value; + if (Value is not null) + { + value = Minus(Value.Value, Step); + } + else + { + value = IsSet(MaximumProperty) ? Maximum : Zero; + } + + SetCurrentValue(ValueProperty, Clamp(value, Maximum, Minimum)); + } + + protected abstract bool ParseText(string? text, out T? number); + protected abstract string? ValueToString(T? value); + protected abstract T Zero { get; } + protected abstract T? Add(T? a, T? b); + protected abstract T? Minus(T? a, T? b); + } \ No newline at end of file diff --git a/src/Ursa/Controls/NumericUpDown/ValueChangedEventArgs.cs b/src/Ursa/Controls/NumericUpDown/ValueChangedEventArgs.cs new file mode 100644 index 0000000..f5929c1 --- /dev/null +++ b/src/Ursa/Controls/NumericUpDown/ValueChangedEventArgs.cs @@ -0,0 +1,15 @@ +using Avalonia.Interactivity; + +namespace Ursa.Controls; + +public class ValueChangedEventArgs: RoutedEventArgs where T: struct, IComparable +{ + public ValueChangedEventArgs(RoutedEvent routedEvent, T? oldValue, T? newValue): base(routedEvent) + { + OldValue = oldValue; + NewValue = newValue; + } + public T? OldValue { get; } + public T? NewValue { get; } + +} \ No newline at end of file From fbdef7d758292164b20b8278ec478512794d8307 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sun, 14 Jan 2024 23:11:24 +0800 Subject: [PATCH 04/13] feat: implement drag. --- demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml | 8 ++- .../Controls/NumericUpDown.axaml | 48 +++++++++------- src/Ursa/Controls/NumericUpDown/IntUpDown.cs | 55 +++++++++++++++++++ .../NumericUpDown/NumericUpDownBase.cs | 47 +++++++++------- 4 files changed, 118 insertions(+), 40 deletions(-) diff --git a/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml b/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml index 6a3cfe5..a828016 100644 --- a/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml +++ b/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml @@ -9,7 +9,13 @@ d:DesignWidth="800" mc:Ignorable="d"> - + + + + + + + diff --git a/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml b/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml index eff46a6..ad35f14 100644 --- a/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml +++ b/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml @@ -1,7 +1,8 @@ - - + + @@ -16,21 +17,30 @@ AllowSpin="{TemplateBinding AllowSpin}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" - BorderThickness="{TemplateBinding BorderThickness}" - > - + BorderThickness="{TemplateBinding BorderThickness}"> + + + + diff --git a/src/Ursa/Controls/NumericUpDown/IntUpDown.cs b/src/Ursa/Controls/NumericUpDown/IntUpDown.cs index 6aba69b..57b066c 100644 --- a/src/Ursa/Controls/NumericUpDown/IntUpDown.cs +++ b/src/Ursa/Controls/NumericUpDown/IntUpDown.cs @@ -29,4 +29,59 @@ public class IntUpDown : NumericUpDownBase protected override int? Add(int? a, int? b) => a + b; protected override int? Minus(int? a, int? b) => a - b; +} + +public class DoubleUpDown : NumericUpDownBase +{ + protected override Type StyleKeyOverride { get; } = typeof(NumericUpDown); + + static DoubleUpDown() + { + MaximumProperty.OverrideDefaultValue(double.MaxValue); + MinimumProperty.OverrideDefaultValue(double.MinValue); + StepProperty.OverrideDefaultValue(1); + } + + protected override bool ParseText(string? text, out double? number) + { + // Weird bug + var result = double.TryParse(text, out var value); + number = value; + return result; + } + + protected override string? ValueToString(double? value) => value?.ToString(FormatString, NumberFormat); + + protected override double Zero => 0; + + protected override double? Add(double? a, double? b) => a + b; + + protected override double? Minus(double? a, double? b) => a - b; +} + +public class ByteUpDown : NumericUpDownBase +{ + protected override Type StyleKeyOverride { get; } = typeof(NumericUpDown); + + static ByteUpDown() + { + MaximumProperty.OverrideDefaultValue(byte.MaxValue); + MinimumProperty.OverrideDefaultValue(byte.MinValue); + StepProperty.OverrideDefaultValue(1); + } + + protected override bool ParseText(string? text, out byte? number) + { + var result = byte.TryParse(text, ParsingNumberStyle, NumberFormat, out var value); + number = value; + return result; + } + + protected override string? ValueToString(byte? value) => value?.ToString(FormatString, NumberFormat); + + protected override byte Zero => 0; + + protected override byte? Add(byte? a, byte? b) => (byte?) (a + b); + + protected override byte? Minus(byte? a, byte? b) => (byte?) (a - b); } \ No newline at end of file diff --git a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs index 1dc9a24..c60e205 100644 --- a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs +++ b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System.Diagnostics; +using System.Globalization; using System.Net.Mime; using Avalonia; using Avalonia.Controls; @@ -156,12 +157,6 @@ public abstract class NumericUpDown : TemplatedControl { _spinner.Spin -= OnSpin; } - if(_textBox is not null) - { - _textBox.TextChanged -= OnTextChange; - - } - if (_dragPanel is not null) { _dragPanel.PointerPressed -= OnDragPanelPointerPressed; @@ -170,16 +165,11 @@ public abstract class NumericUpDown : TemplatedControl } _spinner = e.NameScope.Find(PART_Spinner); _textBox = e.NameScope.Find(PART_TextBox); + _dragPanel = e.NameScope.Find(PART_DragPanel); if (_spinner is not null) { _spinner.Spin += OnSpin; } - - if (_textBox is not null) - { - _textBox.TextChanged += OnTextChange; - } - if (_dragPanel is not null) { _dragPanel.PointerPressed+= OnDragPanelPointerPressed; @@ -199,7 +189,7 @@ public abstract class NumericUpDown : TemplatedControl { if (e.Key == Key.Enter) { - var commitSuccess = CommitInput(); + var commitSuccess = CommitInput(true); e.Handled = !commitSuccess; } } @@ -216,22 +206,38 @@ public abstract class NumericUpDown : TemplatedControl private void OnDragPanelPointerMoved(object sender, PointerEventArgs e) { + if (TextEditable) return; + if(!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return; var point = e.GetPosition(this); var delta = point - _point; if (delta is null) { return; } + int d = GetDelta(delta.Value); + if(d > 0) + { + Increase(); + } + else if (d < 0) + { + Decrease(); + } + _point = point; } - [Obsolete] - private void OnTextChange(object sender, TextChangedEventArgs e) + private int GetDelta(Point point) { - _updateFromTextInput = true; - SyncTextAndValue(); - _updateFromTextInput = false; + bool horizontal = Math.Abs(point.X) > Math.Abs(point.Y); + var value = horizontal ? point.X : point.Y; + return value switch + { + > 0 => 1, + < 0 => -1, + _ => 0 + }; } - + private void OnSpin(object sender, SpinEventArgs e) { if (AllowSpin && !IsReadOnly) @@ -495,6 +501,7 @@ public abstract class NumericUpDownBase: NumericUpDown where T: struct, IComp if (_textBox!= null && !Equals(_textBox.Text, newText)) { _textBox.Text = newText; + _textBox.CaretIndex = newText?.Length??0; } } } From 30241f3f22f9d06feac367867c11cbb793d0c8e9 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sun, 14 Jan 2024 23:17:02 +0800 Subject: [PATCH 05/13] fix: fix focus visual. --- src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs index c60e205..0c0f67d 100644 --- a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs +++ b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs @@ -197,6 +197,7 @@ public abstract class NumericUpDown : TemplatedControl private void OnDragPanelPointerPressed(object sender, PointerPressedEventArgs e) { _point = e.GetPosition(this); + _textBox?.Focus(); } private void OnDragPanelPointerReleased(object sender, PointerReleasedEventArgs e) @@ -476,7 +477,6 @@ public abstract class NumericUpDownBase: NumericUpDown where T: struct, IComp { try { - // TODO var newValue = ConvertTextToValue(text); if (EmptyInputValue is not null && newValue is null) { From 5b7dd69f519876bc91fd90f627f43db98ce0ffca Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sun, 14 Jan 2024 23:22:42 +0800 Subject: [PATCH 06/13] fix: fix vertical direction. --- src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs index 0c0f67d..d82d6bf 100644 --- a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs +++ b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs @@ -230,7 +230,7 @@ public abstract class NumericUpDown : TemplatedControl private int GetDelta(Point point) { bool horizontal = Math.Abs(point.X) > Math.Abs(point.Y); - var value = horizontal ? point.X : point.Y; + var value = horizontal ? point.X : -point.Y; return value switch { > 0 => 1, From fecf22a5ec1c1f1d9e143913635e5b30a6646745 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sun, 14 Jan 2024 23:28:58 +0800 Subject: [PATCH 07/13] feat: add a lot of other types. --- demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml | 15 +- src/Ursa/Controls/NumericUpDown/IntUpDown.cs | 222 +++++++++++++++++-- 2 files changed, 216 insertions(+), 21 deletions(-) diff --git a/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml b/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml index a828016..5697bc3 100644 --- a/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml +++ b/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml @@ -8,14 +8,19 @@ d:DesignHeight="450" d:DesignWidth="800" mc:Ignorable="d"> - - + + + + + - + - + - + diff --git a/src/Ursa/Controls/NumericUpDown/IntUpDown.cs b/src/Ursa/Controls/NumericUpDown/IntUpDown.cs index 57b066c..874ddc8 100644 --- a/src/Ursa/Controls/NumericUpDown/IntUpDown.cs +++ b/src/Ursa/Controls/NumericUpDown/IntUpDown.cs @@ -4,15 +4,15 @@ using Avalonia.Utilities; namespace Ursa.Controls; -public class IntUpDown : NumericUpDownBase +public class NumericIntUpDown : NumericUpDownBase { protected override Type StyleKeyOverride { get; } = typeof(NumericUpDown); - static IntUpDown() + static NumericIntUpDown() { - MaximumProperty.OverrideDefaultValue(int.MaxValue); - MinimumProperty.OverrideDefaultValue(int.MinValue); - StepProperty.OverrideDefaultValue(1); + MaximumProperty.OverrideDefaultValue(int.MaxValue); + MinimumProperty.OverrideDefaultValue(int.MinValue); + StepProperty.OverrideDefaultValue(1); } protected override bool ParseText(string? text, out int? number) @@ -31,15 +31,15 @@ public class IntUpDown : NumericUpDownBase protected override int? Minus(int? a, int? b) => a - b; } -public class DoubleUpDown : NumericUpDownBase +public class NumericDoubleUpDown : NumericUpDownBase { protected override Type StyleKeyOverride { get; } = typeof(NumericUpDown); - static DoubleUpDown() + static NumericDoubleUpDown() { - MaximumProperty.OverrideDefaultValue(double.MaxValue); - MinimumProperty.OverrideDefaultValue(double.MinValue); - StepProperty.OverrideDefaultValue(1); + MaximumProperty.OverrideDefaultValue(double.MaxValue); + MinimumProperty.OverrideDefaultValue(double.MinValue); + StepProperty.OverrideDefaultValue(1); } protected override bool ParseText(string? text, out double? number) @@ -59,15 +59,15 @@ public class DoubleUpDown : NumericUpDownBase protected override double? Minus(double? a, double? b) => a - b; } -public class ByteUpDown : NumericUpDownBase +public class NumericByteUpDown : NumericUpDownBase { protected override Type StyleKeyOverride { get; } = typeof(NumericUpDown); - static ByteUpDown() + static NumericByteUpDown() { - MaximumProperty.OverrideDefaultValue(byte.MaxValue); - MinimumProperty.OverrideDefaultValue(byte.MinValue); - StepProperty.OverrideDefaultValue(1); + MaximumProperty.OverrideDefaultValue(byte.MaxValue); + MinimumProperty.OverrideDefaultValue(byte.MinValue); + StepProperty.OverrideDefaultValue(1); } protected override bool ParseText(string? text, out byte? number) @@ -84,4 +84,194 @@ public class ByteUpDown : NumericUpDownBase protected override byte? Add(byte? a, byte? b) => (byte?) (a + b); protected override byte? Minus(byte? a, byte? b) => (byte?) (a - b); -} \ No newline at end of file +} + +public class NumericSByteUpDown : NumericUpDownBase +{ + protected override Type StyleKeyOverride { get; } = typeof(NumericUpDown); + + static NumericSByteUpDown() + { + MaximumProperty.OverrideDefaultValue(sbyte.MaxValue); + MinimumProperty.OverrideDefaultValue(sbyte.MinValue); + StepProperty.OverrideDefaultValue(1); + } + + protected override bool ParseText(string? text, out sbyte? number) + { + var result = sbyte.TryParse(text, ParsingNumberStyle, NumberFormat, out var value); + number = value; + return result; + } + + protected override string? ValueToString(sbyte? value) => value?.ToString(FormatString, NumberFormat); + + protected override sbyte Zero => 0; + + protected override sbyte? Add(sbyte? a, sbyte? b) => (sbyte?) (a + b); + + protected override sbyte? Minus(sbyte? a, sbyte? b) => (sbyte?) (a - b); +} + +public class NumericShortUpDown : NumericUpDownBase +{ + protected override Type StyleKeyOverride { get; } = typeof(NumericUpDown); + + static NumericShortUpDown() + { + MaximumProperty.OverrideDefaultValue(short.MaxValue); + MinimumProperty.OverrideDefaultValue(short.MinValue); + StepProperty.OverrideDefaultValue(1); + } + + protected override bool ParseText(string? text, out short? number) + { + var result = short.TryParse(text, ParsingNumberStyle, NumberFormat, out var value); + number = value; + return result; + } + + protected override string? ValueToString(short? value) => value?.ToString(FormatString, NumberFormat); + + protected override short Zero => 0; + + protected override short? Add(short? a, short? b) => (short?) (a + b); + + protected override short? Minus(short? a, short? b) => (short?) (a - b); +} + +public class NumericUShortUpDown : NumericUpDownBase +{ + protected override Type StyleKeyOverride { get; } = typeof(NumericUpDown); + + static NumericUShortUpDown() + { + MaximumProperty.OverrideDefaultValue(ushort.MaxValue); + MinimumProperty.OverrideDefaultValue(ushort.MinValue); + StepProperty.OverrideDefaultValue(1); + } + + protected override bool ParseText(string? text, out ushort? number) + { + var result = ushort.TryParse(text, ParsingNumberStyle, NumberFormat, out var value); + number = value; + return result; + } + + protected override string? ValueToString(ushort? value) => value?.ToString(FormatString, NumberFormat); + + protected override ushort Zero => 0; + + protected override ushort? Add(ushort? a, ushort? b) => (ushort?) (a + b); + + protected override ushort? Minus(ushort? a, ushort? b) => (ushort?) (a - b); +} + +public class NumericLongUpDown : NumericUpDownBase +{ + protected override Type StyleKeyOverride { get; } = typeof(NumericUpDown); + + static NumericLongUpDown() + { + MaximumProperty.OverrideDefaultValue(long.MaxValue); + MinimumProperty.OverrideDefaultValue(long.MinValue); + StepProperty.OverrideDefaultValue(1); + } + + protected override bool ParseText(string? text, out long? number) + { + var result = long.TryParse(text, ParsingNumberStyle, NumberFormat, out var value); + number = value; + return result; + } + + protected override string? ValueToString(long? value) => value?.ToString(FormatString, NumberFormat); + + protected override long Zero => 0; + + protected override long? Add(long? a, long? b) => a + b; + + protected override long? Minus(long? a, long? b) => a - b; +} + +public class NumericULongUpDown : NumericUpDownBase +{ + protected override Type StyleKeyOverride { get; } = typeof(NumericUpDown); + + static NumericULongUpDown() + { + MaximumProperty.OverrideDefaultValue(ulong.MaxValue); + MinimumProperty.OverrideDefaultValue(ulong.MinValue); + StepProperty.OverrideDefaultValue(1); + } + + protected override bool ParseText(string? text, out ulong? number) + { + var result = ulong.TryParse(text, ParsingNumberStyle, NumberFormat, out var value); + number = value; + return result; + } + + protected override string? ValueToString(ulong? value) => value?.ToString(FormatString, NumberFormat); + + protected override ulong Zero => 0; + + protected override ulong? Add(ulong? a, ulong? b) => a + b; + + protected override ulong? Minus(ulong? a, ulong? b) => a - b; +} + +public class NumericFloatUpDown : NumericUpDownBase +{ + protected override Type StyleKeyOverride { get; } = typeof(NumericUpDown); + + static NumericFloatUpDown() + { + MaximumProperty.OverrideDefaultValue(float.MaxValue); + MinimumProperty.OverrideDefaultValue(float.MinValue); + StepProperty.OverrideDefaultValue(1); + } + + protected override bool ParseText(string? text, out float? number) + { + var result = float.TryParse(text, ParsingNumberStyle, NumberFormat, out var value); + number = value; + return result; + } + + protected override string? ValueToString(float? value) => value?.ToString(FormatString, NumberFormat); + + protected override float Zero => 0; + + protected override float? Add(float? a, float? b) => a + b; + + protected override float? Minus(float? a, float? b) => a - b; +} + +public class NumericDecimalUpDown : NumericUpDownBase +{ + protected override Type StyleKeyOverride { get; } = typeof(NumericUpDown); + + static NumericDecimalUpDown() + { + MaximumProperty.OverrideDefaultValue(decimal.MaxValue); + MinimumProperty.OverrideDefaultValue(decimal.MinValue); + StepProperty.OverrideDefaultValue(1); + } + + protected override bool ParseText(string? text, out decimal? number) + { + var result = decimal.TryParse(text, ParsingNumberStyle, NumberFormat, out var value); + number = value; + return result; + } + + protected override string? ValueToString(decimal? value) => value?.ToString(FormatString, NumberFormat); + + protected override decimal Zero => 0; + + protected override decimal? Add(decimal? a, decimal? b) => a + b; + + protected override decimal? Minus(decimal? a, decimal? b) => a - b; +} + From 0f3883ed66439c2fd1b9ae62b59cd2c4340f9641 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sun, 14 Jan 2024 23:33:36 +0800 Subject: [PATCH 08/13] feat: update property name. --- demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml | 2 +- src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml | 2 +- src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml b/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml index 5697bc3..379ac2d 100644 --- a/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml +++ b/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml @@ -21,6 +21,6 @@ - + diff --git a/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml b/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml index ad35f14..aeb4765 100644 --- a/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml +++ b/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml @@ -38,7 +38,7 @@ VerticalAlignment="Stretch" Background="Transparent" Cursor="SizeAll" - IsVisible="{TemplateBinding TextEditable, + IsVisible="{TemplateBinding IsTextEditable, Converter={x:Static BoolConverters.Not}}" /> diff --git a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs index d82d6bf..2d90ede 100644 --- a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs +++ b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs @@ -27,13 +27,13 @@ public abstract class NumericUpDown : TemplatedControl private Point? _point; protected internal bool _updateFromTextInput; - public static readonly StyledProperty TextEditableProperty = AvaloniaProperty.Register( - nameof(TextEditable), defaultValue: true); + public static readonly StyledProperty IsTextEditableProperty = AvaloniaProperty.Register( + nameof(IsTextEditable), defaultValue: true); - public bool TextEditable + public bool IsTextEditable { - get => GetValue(TextEditableProperty); - set => SetValue(TextEditableProperty, value); + get => GetValue(IsTextEditableProperty); + set => SetValue(IsTextEditableProperty, value); } public static readonly StyledProperty IsReadOnlyProperty = AvaloniaProperty.Register( @@ -207,7 +207,7 @@ public abstract class NumericUpDown : TemplatedControl private void OnDragPanelPointerMoved(object sender, PointerEventArgs e) { - if (TextEditable) return; + if (IsTextEditable) return; if(!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return; var point = e.GetPosition(this); var delta = point - _point; From 296777febb2985b1eb64fe45667a62ea6c444e8b Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sun, 14 Jan 2024 23:44:23 +0800 Subject: [PATCH 09/13] feat: make property context friendly. --- demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml | 4 +-- .../Controls/NumericUpDown.axaml | 3 +- .../NumericUpDown/NumericUpDownBase.cs | 29 +++++++++++++++---- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml b/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml index 379ac2d..33d5bf7 100644 --- a/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml +++ b/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml @@ -14,13 +14,13 @@ - + - + diff --git a/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml b/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml index aeb4765..499de51 100644 --- a/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml +++ b/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml @@ -38,8 +38,7 @@ VerticalAlignment="Stretch" Background="Transparent" Cursor="SizeAll" - IsVisible="{TemplateBinding IsTextEditable, - Converter={x:Static BoolConverters.Not}}" /> + IsVisible="{TemplateBinding AllowDrag}" /> diff --git a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs index 2d90ede..0599617 100644 --- a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs +++ b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs @@ -27,13 +27,13 @@ public abstract class NumericUpDown : TemplatedControl private Point? _point; protected internal bool _updateFromTextInput; - public static readonly StyledProperty IsTextEditableProperty = AvaloniaProperty.Register( - nameof(IsTextEditable), defaultValue: true); + public static readonly StyledProperty AllowDragProperty = AvaloniaProperty.Register( + nameof(AllowDrag), defaultValue: false); - public bool IsTextEditable + public bool AllowDrag { - get => GetValue(IsTextEditableProperty); - set => SetValue(IsTextEditableProperty, value); + get => GetValue(AllowDragProperty); + set => SetValue(AllowDragProperty, value); } public static readonly StyledProperty IsReadOnlyProperty = AvaloniaProperty.Register( @@ -183,6 +183,10 @@ public abstract class NumericUpDown : TemplatedControl { CommitInput(true); base.OnLostFocus(e); + if(AllowDrag && _dragPanel is not null) + { + _dragPanel.IsVisible = true; + } } protected override void OnKeyDown(KeyEventArgs e) @@ -192,11 +196,23 @@ public abstract class NumericUpDown : TemplatedControl var commitSuccess = CommitInput(true); e.Handled = !commitSuccess; } + + if (e.Key == Key.Escape) + { + if (AllowDrag && _dragPanel is not null) + { + _dragPanel.IsVisible = true; + } + } } private void OnDragPanelPointerPressed(object sender, PointerPressedEventArgs e) { _point = e.GetPosition(this); + if (e.ClickCount == 2 && _dragPanel is not null && AllowDrag) + { + _dragPanel.IsVisible = false; + } _textBox?.Focus(); } @@ -207,7 +223,7 @@ public abstract class NumericUpDown : TemplatedControl private void OnDragPanelPointerMoved(object sender, PointerEventArgs e) { - if (IsTextEditable) return; + if (!AllowDrag) return; if(!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return; var point = e.GetPosition(this); var delta = point - _point; @@ -420,6 +436,7 @@ public abstract class NumericUpDownBase: NumericUpDown where T: struct, IComp { _textBox.Text = ConvertValueToText(Value); } + SetValidSpinDirection(); } protected virtual T? Clamp(T? value, T max, T min) From 8230c8ec248152474f97f6955fc38e9c168682f9 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Mon, 15 Jan 2024 00:07:09 +0800 Subject: [PATCH 10/13] feat: Add Clear. --- demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml | 2 +- .../Controls/NumericUpDown.axaml | 38 +++++++++++++++++++ .../NumericUpDown/NumericUpDownBase.cs | 7 ++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml b/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml index 33d5bf7..f13dadd 100644 --- a/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml +++ b/demo/Ursa.Demo/Pages/NumericUpDownDemo.axaml @@ -14,7 +14,7 @@ - + diff --git a/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml b/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml index 499de51..3c87f03 100644 --- a/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml +++ b/src/Ursa.Themes.Semi/Controls/NumericUpDown.axaml @@ -3,6 +3,27 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:u="https://irihi.tech/ursa"> + + + + + + + + + + + + + + + @@ -39,10 +60,27 @@ Background="Transparent" Cursor="SizeAll" IsVisible="{TemplateBinding AllowDrag}" /> +