diff --git a/demo/Ursa.Demo/Models/MenuKeys.cs b/demo/Ursa.Demo/Models/MenuKeys.cs index 56e6a49..530349b 100644 --- a/demo/Ursa.Demo/Models/MenuKeys.cs +++ b/demo/Ursa.Demo/Models/MenuKeys.cs @@ -20,6 +20,7 @@ public static class MenuKeys public const string MenuKeyMessageBox = "MessageBox"; public const string MenuKeyNavigation = "Navigation"; public const string MenuKeyNavMenu = "NavMenu"; + public const string MenuKeyNumberDisplayer = "NumberDisplayer"; public const string MenuKeyNumericUpDown = "NumericUpDown"; public const string MenuKeyPagination = "Pagination"; public const string MenuKeyRangeSlider = "RangeSlider"; diff --git a/demo/Ursa.Demo/Pages/NumberDisplayerDemo.axaml b/demo/Ursa.Demo/Pages/NumberDisplayerDemo.axaml new file mode 100644 index 0000000..0a7ec19 --- /dev/null +++ b/demo/Ursa.Demo/Pages/NumberDisplayerDemo.axaml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/demo/Ursa.Demo/Pages/NumberDisplayerDemo.axaml.cs b/demo/Ursa.Demo/Pages/NumberDisplayerDemo.axaml.cs new file mode 100644 index 0000000..35df3ef --- /dev/null +++ b/demo/Ursa.Demo/Pages/NumberDisplayerDemo.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Ursa.Demo.Pages; + +public partial class NumberDisplayerDemo : UserControl +{ + public NumberDisplayerDemo() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs index 43166bd..d8c0cb4 100644 --- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs @@ -42,6 +42,7 @@ public class MainViewViewModel : ViewModelBase MenuKeys.MenuKeyMessageBox => new MessageBoxDemoViewModel(), MenuKeys.MenuKeyNavigation => new NavigationMenuDemoViewModel(), MenuKeys.MenuKeyNavMenu => new NavMenuDemoViewModel(), + MenuKeys.MenuKeyNumberDisplayer => new NumberDisplayerDemoViewModel(), MenuKeys.MenuKeyNumericUpDown => new NumericUpDownDemoViewModel(), MenuKeys.MenuKeyPagination => new PaginationDemoViewModel(), MenuKeys.MenuKeyRangeSlider => new RangeSliderDemoViewModel(), diff --git a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs index 6f6da25..c24db24 100644 --- a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs @@ -29,7 +29,8 @@ public class MenuViewModel: ViewModelBase new() { MenuHeader = "Message Box", Key = MenuKeys.MenuKeyMessageBox, Status = "New" }, new() { MenuHeader = "Navigation", Key = MenuKeys.MenuKeyNavigation, Status = "WIP" }, new() { MenuHeader = "Nav Menu", Key = MenuKeys.MenuKeyNavMenu, Status = "WIP"}, - new() { MenuHeader = "NumericUpDown", Key = MenuKeys.MenuKeyNumericUpDown, Status = "New" }, + new() { MenuHeader = "Number Displayer", Key = MenuKeys.MenuKeyNumberDisplayer, Status = "New" }, + new() { MenuHeader = "Numeric UpDown", Key = MenuKeys.MenuKeyNumericUpDown, Status = "New" }, new() { MenuHeader = "Pagination", Key = MenuKeys.MenuKeyPagination }, new() { MenuHeader = "RangeSlider", Key = MenuKeys.MenuKeyRangeSlider, Status = "New"}, new() { MenuHeader = "Selection List", Key = MenuKeys.MenuKeySelectionList, Status = "New" }, diff --git a/demo/Ursa.Demo/ViewModels/NumberDisplayerDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/NumberDisplayerDemoViewModel.cs new file mode 100644 index 0000000..0032e47 --- /dev/null +++ b/demo/Ursa.Demo/ViewModels/NumberDisplayerDemoViewModel.cs @@ -0,0 +1,26 @@ +using System; +using System.Windows.Input; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; + +namespace Ursa.Demo.ViewModels; + +public partial class NumberDisplayerDemoViewModel: ObservableObject +{ + [ObservableProperty] private int _value; + [ObservableProperty] private double _doubleValue; + public ICommand IncreaseCommand { get; } + public NumberDisplayerDemoViewModel() + { + IncreaseCommand = new RelayCommand(OnChange); + Value = 0; + DoubleValue = 0d; + } + + private void OnChange() + { + Random r = new Random(); + Value = r.Next(int.MaxValue); + DoubleValue = r.NextDouble() * 100000; + } +} \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Controls/NumberDisplayer.axaml b/src/Ursa.Themes.Semi/Controls/NumberDisplayer.axaml new file mode 100644 index 0000000..b5f5d8e --- /dev/null +++ b/src/Ursa.Themes.Semi/Controls/NumberDisplayer.axaml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml index 0b2dae5..dee8aee 100644 --- a/src/Ursa.Themes.Semi/Controls/_index.axaml +++ b/src/Ursa.Themes.Semi/Controls/_index.axaml @@ -20,6 +20,7 @@ + diff --git a/src/Ursa/Controls/NumberDisplayer/NumberDisplayer.cs b/src/Ursa/Controls/NumberDisplayer/NumberDisplayer.cs new file mode 100644 index 0000000..181e955 --- /dev/null +++ b/src/Ursa/Controls/NumberDisplayer/NumberDisplayer.cs @@ -0,0 +1,119 @@ +using Avalonia; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Data; + +namespace Ursa.Controls; + +public abstract class NumberDisplayerBase : TemplatedControl +{ + public static readonly DirectProperty InternalTextProperty = AvaloniaProperty.RegisterDirect( + nameof(InternalText), o => o.InternalText, (o, v) => o.InternalText = v); + private string _internalText; + public string InternalText + { + get => _internalText; + set => SetAndRaise(InternalTextProperty, ref _internalText, value); + } + + public static readonly StyledProperty DurationProperty = AvaloniaProperty.Register( + nameof(Duration)); + + public TimeSpan Duration + { + get => GetValue(DurationProperty); + set => SetValue(DurationProperty, value); + } + + public static readonly StyledProperty StringFormatProperty = AvaloniaProperty.Register( + nameof(StringFormat)); + + public string StringFormat + { + get => GetValue(StringFormatProperty); + set => SetValue(StringFormatProperty, value); + } +} + +public abstract class NumberDisplayer: NumberDisplayerBase +{ + public static readonly StyledProperty ValueProperty = AvaloniaProperty.Register, T?>( + nameof(Value), defaultBindingMode:BindingMode.TwoWay); + + public T? Value + { + get => GetValue(ValueProperty); + set => SetValue(ValueProperty, value); + } + + public static readonly StyledProperty InternalValueProperty = AvaloniaProperty.Register, T?>( + nameof(InternalValue)); + + public T? InternalValue + { + get => GetValue(InternalValueProperty); + set => SetValue(InternalValueProperty, value); + } + + static NumberDisplayer() + { + ValueProperty.Changed.AddClassHandler, T?>((item, args) => + { + item.InternalValue = args.NewValue.Value; + }); + InternalValueProperty.Changed.AddClassHandler, T?>((item, args) => + { + item.InternalText = args.NewValue.Value is null ? string.Empty : item.GetString(args.NewValue.Value); + }); + DurationProperty.Changed.AddClassHandler, TimeSpan>((item, args) =>item.OnDurationChanged(args)); + } + + private void OnDurationChanged(AvaloniaPropertyChangedEventArgs args) + { + this.Transitions ??= new Transitions(); + this.Transitions?.Clear(); + this.Transitions?.Add(GetTransition(args.NewValue.Value)); + } + + protected abstract ITransition GetTransition(TimeSpan duration); + protected abstract string GetString(T? value); +} + +public class Int32Displayer : NumberDisplayer +{ + protected override Type StyleKeyOverride { get; } = typeof(NumberDisplayerBase); + + protected override ITransition GetTransition(TimeSpan duration) + { + return new IntegerTransition() + { + Property = InternalValueProperty, + Duration = duration + }; + } + + protected override string GetString(int value) + { + return value.ToString(StringFormat); + } +} + +public class DoubleDisplayer : NumberDisplayer +{ + protected override Type StyleKeyOverride { get; } = typeof(NumberDisplayerBase); + + protected override ITransition GetTransition(TimeSpan duration) + { + return new DoubleTransition() + { + Property = InternalValueProperty, + Duration = duration + }; + } + + protected override string GetString(double value) + { + return value.ToString(StringFormat); + } +}