diff --git a/demo/Directory.Build.props b/demo/Directory.Build.props index c950d6a..3a82054 100644 --- a/demo/Directory.Build.props +++ b/demo/Directory.Build.props @@ -1,6 +1,6 @@ enable - 11.0.9 + 11.0.10 diff --git a/demo/Ursa.Demo/Pages/NumPadDemo.axaml b/demo/Ursa.Demo/Pages/NumPadDemo.axaml new file mode 100644 index 0000000..dbd997a --- /dev/null +++ b/demo/Ursa.Demo/Pages/NumPadDemo.axaml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/demo/Ursa.Demo/Pages/NumPadDemo.axaml.cs b/demo/Ursa.Demo/Pages/NumPadDemo.axaml.cs new file mode 100644 index 0000000..b82a6ae --- /dev/null +++ b/demo/Ursa.Demo/Pages/NumPadDemo.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Markup.Xaml; + +namespace Ursa.Demo.Pages; + +public partial class NumPadDemo : UserControl +{ + public NumPadDemo() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs index aa9f90a..e55a00a 100644 --- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs @@ -46,6 +46,7 @@ public class MainViewViewModel : ViewModelBase MenuKeys.MenuKeyNavMenu => new NavMenuDemoViewModel(), MenuKeys.MenuKeyNumberDisplayer => new NumberDisplayerDemoViewModel(), MenuKeys.MenuKeyNumericUpDown => new NumericUpDownDemoViewModel(), + MenuKeys.MenuKeyNumPad => new NumPadDemoViewModel(), MenuKeys.MenuKeyPagination => new PaginationDemoViewModel(), MenuKeys.MenuKeyRangeSlider => new RangeSliderDemoViewModel(), MenuKeys.MenuKeyScrollToButton => new ScrollToButtonDemoViewModel(), diff --git a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs index 1fa6c8a..aed7270 100644 --- a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs @@ -33,6 +33,7 @@ public class MenuViewModel: ViewModelBase new() { MenuHeader = "Nav Menu", Key = MenuKeys.MenuKeyNavMenu, Status = "New"}, // new() { MenuHeader = "Number Displayer", Key = MenuKeys.MenuKeyNumberDisplayer, Status = "New" }, new() { MenuHeader = "Numeric UpDown", Key = MenuKeys.MenuKeyNumericUpDown }, + new() { MenuHeader = "NumPad", Key = MenuKeys.MenuKeyNumPad, Status = "New" }, new() { MenuHeader = "Pagination", Key = MenuKeys.MenuKeyPagination }, new() { MenuHeader = "RangeSlider", Key = MenuKeys.MenuKeyRangeSlider }, new() { MenuHeader = "Scroll To", Key = MenuKeys.MenuKeyScrollToButton, Status = "New" }, @@ -72,6 +73,7 @@ public static class MenuKeys public const string MenuKeyNavMenu = "NavMenu"; public const string MenuKeyNumberDisplayer = "NumberDisplayer"; public const string MenuKeyNumericUpDown = "NumericUpDown"; + public const string MenuKeyNumPad = "NumPad"; public const string MenuKeyPagination = "Pagination"; public const string MenuKeyRangeSlider = "RangeSlider"; public const string MenuKeyScrollToButton = "ScrollToButton"; diff --git a/demo/Ursa.Demo/ViewModels/NumPadDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/NumPadDemoViewModel.cs new file mode 100644 index 0000000..88fcfb1 --- /dev/null +++ b/demo/Ursa.Demo/ViewModels/NumPadDemoViewModel.cs @@ -0,0 +1,6 @@ +namespace Ursa.Demo.ViewModels; + +public class NumPadDemoViewModel +{ + +} \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Controls/NumPad.axaml b/src/Ursa.Themes.Semi/Controls/NumPad.axaml new file mode 100644 index 0000000..bc6c8e6 --- /dev/null +++ b/src/Ursa.Themes.Semi/Controls/NumPad.axaml @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml index e79e02f..a369754 100644 --- a/src/Ursa.Themes.Semi/Controls/_index.axaml +++ b/src/Ursa.Themes.Semi/Controls/_index.axaml @@ -22,6 +22,7 @@ + diff --git a/src/Ursa/Controls/Dialog/OverlayDialog.cs b/src/Ursa/Controls/Dialog/OverlayDialog.cs index 8d16e5f..7d69047 100644 --- a/src/Ursa/Controls/Dialog/OverlayDialog.cs +++ b/src/Ursa/Controls/Dialog/OverlayDialog.cs @@ -211,6 +211,12 @@ public static class OverlayDialog control.CanLightDismiss = options.CanLightDismiss; control.CanDragMove = options.CanDragMove; } - - + + internal static T? Recall(string? hostId) where T: Control + { + var host = OverlayDialogManager.GetHost(hostId); + if (host is null) return null; + var item = host.Recall(); + return item; + } } \ No newline at end of file diff --git a/src/Ursa/Controls/NumPad/NumPad.cs b/src/Ursa/Controls/NumPad/NumPad.cs new file mode 100644 index 0000000..0b0ffc3 --- /dev/null +++ b/src/Ursa/Controls/NumPad/NumPad.cs @@ -0,0 +1,109 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Interactivity; +using Irihi.Avalonia.Shared.Helpers; + +namespace Ursa.Controls; + +public class NumPad: TemplatedControl +{ + public static readonly StyledProperty TargetProperty = AvaloniaProperty.Register( + nameof(Target)); + + public InputElement? Target + { + get => GetValue(TargetProperty); + set => SetValue(TargetProperty, value); + } + + public static readonly StyledProperty NumModeProperty = AvaloniaProperty.Register( + nameof(NumMode), defaultValue: true); + + public bool NumMode + { + get => GetValue(NumModeProperty); + set => SetValue(NumModeProperty, value); + } + + public static readonly AttachedProperty AttachProperty = + AvaloniaProperty.RegisterAttached("Attach"); + + public static void SetAttach(InputElement obj, bool value) => obj.SetValue(AttachProperty, value); + public static bool GetAttach(InputElement obj) => obj.GetValue(AttachProperty); + + static NumPad() + { + AttachProperty.Changed.AddClassHandler(OnAttachNumPad); + } + + private static void OnAttachNumPad(InputElement input, AvaloniaPropertyChangedEventArgs args) + { + if (args.NewValue.Value) + { + GotFocusEvent.AddHandler(OnTargetGotFocus, input); + } + else + { + GotFocusEvent.RemoveHandler(OnTargetGotFocus, input); + } + } + + private static void OnTargetGotFocus(object sender, GotFocusEventArgs e) + { + if (sender is not InputElement) return; + var existing = OverlayDialog.Recall(null); + if (existing is not null) + { + existing.Target = sender as InputElement; + return; + } + var numPad = new NumPad() { Target = sender as InputElement }; + OverlayDialog.Show(numPad, new object(), options: new OverlayDialogOptions() { Buttons = DialogButton.None }); + } + + private static readonly Dictionary KeyInputMapping = new() + { + [Key.NumPad0] = "0", + [Key.NumPad1] = "1", + [Key.NumPad2] = "2", + [Key.NumPad3] = "3", + [Key.NumPad4] = "4", + [Key.NumPad5] = "5", + [Key.NumPad6] = "6", + [Key.NumPad7] = "7", + [Key.NumPad8] = "8", + [Key.NumPad9] = "9", + [Key.Add] = "+", + [Key.Subtract] = "-", + [Key.Multiply] = "*", + [Key.Divide] = "/", + [Key.Decimal] = ".", + }; + + public void ProcessClick(object o) + { + if (Target is null || o is not NumPadButton b) return; + var key = (b.NumMode ? b.NumKey : b.FunctionKey)?? Key.None; + if (KeyInputMapping.TryGetValue(key, out string s)) + { + Target.RaiseEvent(new TextInputEventArgs() + { + Source = this, + RoutedEvent = TextInputEvent, + Text = s, + }); + } + else + { + Target.RaiseEvent(new KeyEventArgs() + { + Source = this, + RoutedEvent = KeyDownEvent, + Key = key, + }); + } + } +} \ No newline at end of file diff --git a/src/Ursa/Controls/NumPad/NumPadButton.cs b/src/Ursa/Controls/NumPad/NumPadButton.cs new file mode 100644 index 0000000..0aa368f --- /dev/null +++ b/src/Ursa/Controls/NumPad/NumPadButton.cs @@ -0,0 +1,54 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; + +namespace Ursa.Controls; + +public class NumPadButton: RepeatButton +{ + public static readonly StyledProperty NumKeyProperty = AvaloniaProperty.Register( + nameof(NumKey)); + + public Key? NumKey + { + get => GetValue(NumKeyProperty); + set => SetValue(NumKeyProperty, value); + } + + public static readonly StyledProperty FunctionKeyProperty = AvaloniaProperty.Register( + nameof(FunctionKey)); + + public Key? FunctionKey + { + get => GetValue(FunctionKeyProperty); + set => SetValue(FunctionKeyProperty, value); + } + + public static readonly StyledProperty NumModeProperty = AvaloniaProperty.Register( + nameof(NumMode)); + + public bool NumMode + { + get => GetValue(NumModeProperty); + set => SetValue(NumModeProperty, value); + } + + public static readonly StyledProperty NumContentProperty = AvaloniaProperty.Register( + nameof(NumContent)); + + public object? NumContent + { + get => GetValue(NumContentProperty); + set => SetValue(NumContentProperty, value); + } + + public static readonly StyledProperty FunctionContentProperty = AvaloniaProperty.Register( + nameof(FunctionContent)); + + public object? FunctionContent + { + get => GetValue(FunctionContentProperty); + set => SetValue(FunctionContentProperty, value); + } + +} \ No newline at end of file diff --git a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs index 198aa64..a8dbd14 100644 --- a/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs +++ b/src/Ursa/Controls/NumericUpDown/NumericUpDownBase.cs @@ -213,9 +213,13 @@ public abstract class NumericUpDown : TemplatedControl, IClearControl _textBox?.Focus(); _textBox!.IsReadOnly = true; } - } - + + protected override void OnTextInput(TextInputEventArgs e) + { + _textBox?.RaiseEvent(e); + } + private void OnDragPanelPointerReleased(object sender, PointerReleasedEventArgs e) { _point = null; diff --git a/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Shared.cs b/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Shared.cs index da2a894..f2bdfa9 100644 --- a/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Shared.cs +++ b/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Shared.cs @@ -189,4 +189,10 @@ public partial class OverlayDialogHost: Canvas } return result; } + + internal T? Recall() + { + var element = _layers.LastOrDefault(a => a.Element.Content?.GetType() == typeof(T)); + return element?.Element.Content is T t ? t : default; + } } \ No newline at end of file