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