Merge pull request #148 from irihitech/numpad

Numpad
This commit is contained in:
Dong Bin
2024-03-15 23:36:05 +08:00
committed by GitHub
13 changed files with 455 additions and 5 deletions

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<AvaloniaVersion>11.0.9</AvaloniaVersion>
<AvaloniaVersion>11.0.10</AvaloniaVersion>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,36 @@
<UserControl
x:Class="Ursa.Demo.Pages.NumPadDemo"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:u="https://irihi.tech/ursa"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<StackPanel HorizontalAlignment="Left">
<TextBox
HorizontalAlignment="Stretch"
u:NumPad.Attach="True"
Watermark="Invoke NumPad" />
<TextBox
HorizontalAlignment="Stretch"
u:NumPad.Attach="True"
Watermark="Invoke NumPad" />
<TextBox
HorizontalAlignment="Stretch"
u:NumPad.Attach="True"
Watermark="Invoke NumPad" />
<u:IPv4Box Width="200" u:NumPad.Attach="True" />
<u:NumericIntUpDown
HorizontalAlignment="Stretch"
u:NumPad.Attach="True"
Watermark="Invoke NumPad" />
<Border Theme="{DynamicResource CardBorder}">
<StackPanel>
<TextBox Name="text" Width="200" />
<u:NumPad Target="{Binding #text}" />
</StackPanel>
</Border>
</StackPanel>
</UserControl>

View File

@@ -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();
}
}

View File

@@ -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(),

View File

@@ -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";

View File

@@ -0,0 +1,6 @@
namespace Ursa.Demo.ViewModels;
public class NumPadDemoViewModel
{
}

View File

@@ -0,0 +1,211 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="https://irihi.tech/ursa">
<!-- Add Resources Here -->
<Design.PreviewWith>
<u:NumPad></u:NumPad>
</Design.PreviewWith>
<ControlTheme x:Key="{x:Type u:NumPad}" TargetType="{x:Type u:NumPad}">
<Setter Property="Template">
<ControlTemplate TargetType="u:NumPad">
<Border>
<Border.Styles>
<Style Selector="u|NumPadButton">
<Setter Property="NumMode" Value="{Binding $parent[u:NumPad].NumMode}" />
<Setter Property="Command" Value="{Binding $parent[u:NumPad].ProcessClick}" />
<Setter Property="Focusable" Value="False" />
<Setter Property="CommandParameter" Value="{Binding $self}" />
<Setter Property="Width" Value="54" />
<Setter Property="Height" Value="54" />
<Setter Property="UseLayoutRounding" Value="False"></Setter>
<Setter Property="HorizontalAlignment" Value="Stretch"></Setter>
<Setter Property="VerticalAlignment" Value="Stretch"></Setter>
<Setter Property="Margin" Value="1" />
</Style>
</Border.Styles>
<Grid ColumnDefinitions="*,*,*,*" RowDefinitions="*,*,*,*,*" HorizontalAlignment="Left" VerticalAlignment="Top">
<ToggleButton
Grid.Row="0"
Grid.Column="0"
MinWidth="54"
MinHeight="54"
Padding="0"
Focusable="False"
FontWeight="Regular"
Margin="1"
IsChecked="{TemplateBinding NumMode,
Mode=TwoWay}">
<TextBlock>
<Run Text="Num" />
<LineBreak />
<Run Text="Lock" />
</TextBlock>
</ToggleButton>
<u:NumPadButton
Grid.Row="0"
Grid.Column="1"
FunctionContent="/"
FunctionKey="Divide"
NumContent="/"
NumKey="Divide" />
<u:NumPadButton
Grid.Row="0"
Grid.Column="2"
FunctionContent="*"
FunctionKey="Multiply"
NumContent="*"
NumKey="Multiply" />
<u:NumPadButton
Grid.Row="0"
Grid.Column="3"
FunctionContent="-"
FunctionKey="Subtract"
NumContent="-"
NumKey="Subtract" />
<u:NumPadButton
Grid.Row="1"
Grid.Column="0"
FunctionContent="Home"
FunctionKey="Home"
NumContent="7"
NumKey="NumPad7" />
<u:NumPadButton
Grid.Row="1"
Grid.Column="1"
FunctionContent="Up"
FunctionKey="Up"
NumContent="8"
NumKey="NumPad8" />
<u:NumPadButton
Grid.Row="1"
Grid.Column="2"
FunctionKey="PageUp"
NumContent="9"
NumKey="NumPad9">
<u:NumPadButton.FunctionContent>
<TextBlock><Run Text="Page"/><LineBreak/><Run Text="Up"/></TextBlock>
</u:NumPadButton.FunctionContent>
</u:NumPadButton>
<u:NumPadButton
Grid.Row="1"
Grid.RowSpan="2"
Grid.Column="3"
Height="{x:Static x:Double.NaN}"
VerticalAlignment="Stretch"
FunctionContent="+"
NumContent="+"
FunctionKey="Add"
NumKey="Add" />
<u:NumPadButton
Grid.Row="2"
Grid.Column="0"
FunctionContent="Left"
FunctionKey="Left"
NumContent="4"
NumKey="NumPad4" />
<u:NumPadButton
Grid.Row="2"
Grid.Column="1"
FunctionContent=" "
FunctionKey="None"
NumContent="5"
NumKey="NumPad5" />
<u:NumPadButton
Grid.Row="2"
Grid.Column="2"
FunctionContent="Right"
FunctionKey="Right"
NumContent="6"
NumKey="NumPad6" />
<u:NumPadButton
Grid.Row="3"
Grid.Column="0"
FunctionContent="End"
FunctionKey="End"
NumContent="1"
NumKey="NumPad1" />
<u:NumPadButton
Grid.Row="3"
Grid.Column="1"
FunctionContent="Down"
FunctionKey="Down"
NumContent="2"
NumKey="NumPad2" />
<u:NumPadButton
Grid.Row="3"
Grid.Column="2"
FunctionKey="PageDown"
NumContent="3"
NumKey="NumPad3">
<u:NumPadButton.FunctionContent>
<TextBlock><Run Text="Page"/><LineBreak/><Run Text="Down"/></TextBlock>
</u:NumPadButton.FunctionContent>
</u:NumPadButton>
<u:NumPadButton
Grid.Row="3"
Grid.RowSpan="2"
Grid.Column="3"
Height="{x:Static x:Double.NaN}"
VerticalAlignment="Stretch"
FunctionContent="Enter"
FunctionKey="Enter"
NumContent="Enter" />
<u:NumPadButton
Grid.Row="4"
Grid.Column="0"
Grid.ColumnSpan="2"
FunctionContent="Insert"
FunctionKey="Insert"
NumContent="0"
Width="{x:Static x:Double.NaN}"
NumKey="NumPad0" />
<u:NumPadButton
Grid.Row="4"
Grid.Column="2"
FunctionContent="Delete"
FunctionKey="Delete"
NumContent="."
NumKey="Decimal"/>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type u:NumPadButton}" TargetType="u:NumPadButton">
<Setter Property="Focusable" Value="False" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Background" Value="{DynamicResource ButtonDefaultBackground}" />
<Setter Property="CornerRadius" Value="3" />
<Setter Property="Template">
<ControlTemplate TargetType="u:NumPadButton">
<Border
Name="PART_Background"
Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding CornerRadius}">
<Panel>
<ContentPresenter
Name="PART_ContentPresenter"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding NumContent}"
IsVisible="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=NumMode}" />
<ContentPresenter
Name="PART_FunctionContentPresenter"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding FunctionContent}"
IsVisible="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=!NumMode}" />
</Panel>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover /template/ Border#PART_Background">
<Setter Property="Background" Value="{DynamicResource ButtonDefaultPointeroverBackground}" />
</Style>
<Style Selector="^:pressed /template/ Border#PART_Background">
<Setter Property="Background" Value="{DynamicResource ButtonDefaultPressedBackground}" />
</Style>
</ControlTheme>
</ResourceDictionary>

View File

@@ -22,6 +22,7 @@
<ResourceInclude Source="MessageBox.axaml" />
<ResourceInclude Source="NavMenu.axaml" />
<ResourceInclude Source="NumericUpDown.axaml" />
<ResourceInclude Source="NumPad.axaml" />
<ResourceInclude Source="NumberDisplayer.axaml" />
<ResourceInclude Source="Pagination.axaml" />
<ResourceInclude Source="RangeSlider.axaml" />

View File

@@ -211,6 +211,12 @@ public static class OverlayDialog
control.CanLightDismiss = options.CanLightDismiss;
control.CanDragMove = options.CanDragMove;
}
internal static T? Recall<T>(string? hostId) where T: Control
{
var host = OverlayDialogManager.GetHost(hostId);
if (host is null) return null;
var item = host.Recall<T>();
return item;
}
}

View File

@@ -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<InputElement?> TargetProperty = AvaloniaProperty.Register<NumPad, InputElement?>(
nameof(Target));
public InputElement? Target
{
get => GetValue(TargetProperty);
set => SetValue(TargetProperty, value);
}
public static readonly StyledProperty<bool> NumModeProperty = AvaloniaProperty.Register<NumPad, bool>(
nameof(NumMode), defaultValue: true);
public bool NumMode
{
get => GetValue(NumModeProperty);
set => SetValue(NumModeProperty, value);
}
public static readonly AttachedProperty<bool> AttachProperty =
AvaloniaProperty.RegisterAttached<NumPad, InputElement, bool>("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<InputElement, bool>(OnAttachNumPad);
}
private static void OnAttachNumPad(InputElement input, AvaloniaPropertyChangedEventArgs<bool> 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<NumPad>(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<Key, string> 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,
});
}
}
}

View File

@@ -0,0 +1,54 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
namespace Ursa.Controls;
public class NumPadButton: RepeatButton
{
public static readonly StyledProperty<Key?> NumKeyProperty = AvaloniaProperty.Register<NumPadButton, Key?>(
nameof(NumKey));
public Key? NumKey
{
get => GetValue(NumKeyProperty);
set => SetValue(NumKeyProperty, value);
}
public static readonly StyledProperty<Key?> FunctionKeyProperty = AvaloniaProperty.Register<NumPadButton, Key?>(
nameof(FunctionKey));
public Key? FunctionKey
{
get => GetValue(FunctionKeyProperty);
set => SetValue(FunctionKeyProperty, value);
}
public static readonly StyledProperty<bool> NumModeProperty = AvaloniaProperty.Register<NumPadButton, bool>(
nameof(NumMode));
public bool NumMode
{
get => GetValue(NumModeProperty);
set => SetValue(NumModeProperty, value);
}
public static readonly StyledProperty<object?> NumContentProperty = AvaloniaProperty.Register<NumPadButton, object?>(
nameof(NumContent));
public object? NumContent
{
get => GetValue(NumContentProperty);
set => SetValue(NumContentProperty, value);
}
public static readonly StyledProperty<object?> FunctionContentProperty = AvaloniaProperty.Register<NumPadButton, object?>(
nameof(FunctionContent));
public object? FunctionContent
{
get => GetValue(FunctionContentProperty);
set => SetValue(FunctionContentProperty, value);
}
}

View File

@@ -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;

View File

@@ -189,4 +189,10 @@ public partial class OverlayDialogHost: Canvas
}
return result;
}
internal T? Recall<T>()
{
var element = _layers.LastOrDefault(a => a.Element.Content?.GetType() == typeof(T));
return element?.Element.Content is T t ? t : default;
}
}