From e44fbd5fc52ba1cfeb21faac6a43815e526a7a9d Mon Sep 17 00:00:00 2001 From: rabbitism Date: Mon, 20 Feb 2023 22:43:04 +0800 Subject: [PATCH 01/15] feat: Initialize IPv4Box --- src/Ursa/Controls/IPv4Box.cs | 85 ++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/Ursa/Controls/IPv4Box.cs diff --git a/src/Ursa/Controls/IPv4Box.cs b/src/Ursa/Controls/IPv4Box.cs new file mode 100644 index 0000000..1890c45 --- /dev/null +++ b/src/Ursa/Controls/IPv4Box.cs @@ -0,0 +1,85 @@ +using System.Net; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Input; + +namespace Ursa.Controls; + +[TemplatePart(PART_FirstTextPresenter, typeof(TextPresenter))] +[TemplatePart(PART_SecondTextPresenter, typeof(TextPresenter))] +[TemplatePart(PART_ThirdTextPresenter, typeof(TextPresenter))] +[TemplatePart(PART_FourthTextPresenter, typeof(TextPresenter))] +[TemplatePart(PART_PortNumericInput, typeof(NumericUpDown))] +public class IPv4Box: TemplatedControl +{ + public const string PART_FirstTextPresenter = "PART_FirstTextPresenter"; + public const string PART_SecondTextPresenter = "PART_SecondTextPresenter"; + public const string PART_ThirdTextPresenter = "PART_ThirdTextPresenter"; + public const string PART_FourthTextPresenter = "PART_FourthTextPresenter"; + public const string PART_PortNumericInput = "PART_PortNumericInput"; + private TextPresenter? _firstTextPresenter; + private TextPresenter? _secondTextPresenter; + private TextPresenter? _thirdTextPresenter; + private TextPresenter? _fourthTextPresenter; + private NumericUpDown? _portNumericInput; + + public static readonly StyledProperty IPAddressProperty = AvaloniaProperty.Register( + nameof(IPAddress)); + + public IPAddress IPAddress + { + get => GetValue(IPAddressProperty); + set => SetValue(IPAddressProperty, value); + } + + public static readonly StyledProperty PortProperty = AvaloniaProperty.Register( + nameof(Port)); + + public int Port + { + get => GetValue(PortProperty); + set => SetValue(PortProperty, value); + } + + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + _firstTextPresenter = e.NameScope.Find(PART_FirstTextPresenter); + _secondTextPresenter = e.NameScope.Find(PART_SecondTextPresenter); + _thirdTextPresenter = e.NameScope.Find(PART_ThirdTextPresenter); + _fourthTextPresenter = e.NameScope.Find(PART_FourthTextPresenter); + _portNumericInput = e.NameScope.Find(PART_PortNumericInput); + var box = new TextBox(); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if (e.Key == Key.Left) + { + if ((_firstTextPresenter?.IsFocused??false) && _firstTextPresenter.SelectionStart == 0) + { + _secondTextPresenter?.Focus(); + } + } + else if (e.Key == Key.Right) + { + if ((_firstTextPresenter?.IsFocused ?? false) && _firstTextPresenter?.SelectionEnd == 2) + { + _firstTextPresenter.Focus(); + } + } + else if (e.Key == Key.Tab) + { + + } + else if (!(e.Key is >= Key.D0 and <= Key.D9 || e.Key is >= Key.NumPad0 and <= Key.NumPad9)) + { + return; + } + base.OnKeyDown(e); + } +} \ No newline at end of file From f53076ae43e8fccb1d0d6c7e471049fdfef766b4 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Wed, 22 Feb 2023 14:47:58 +0800 Subject: [PATCH 02/15] commit: temporarily use TextBox --- demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml | 15 ++++ demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml.cs | 18 +++++ demo/Ursa.Demo/Views/MainWindow.axaml | 3 + src/Ursa.Themes.Semi/Controls/IPv4Box.axaml | 57 +++++++++++++ src/Ursa.Themes.Semi/Controls/_index.axaml | 1 + src/Ursa/Controls/IPv4Box.cs | 90 +++++++++++++++------ 6 files changed, 158 insertions(+), 26 deletions(-) create mode 100644 demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml create mode 100644 demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml.cs create mode 100644 src/Ursa.Themes.Semi/Controls/IPv4Box.axaml diff --git a/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml new file mode 100644 index 0000000..bfc5970 --- /dev/null +++ b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml @@ -0,0 +1,15 @@ + + + + + + diff --git a/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml.cs b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml.cs new file mode 100644 index 0000000..4c7d175 --- /dev/null +++ b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml.cs @@ -0,0 +1,18 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Ursa.Demo.Pages; + +public partial class IPv4BoxDemo : UserControl +{ + public IPv4BoxDemo() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/Views/MainWindow.axaml b/demo/Ursa.Demo/Views/MainWindow.axaml index a5c4284..613691a 100644 --- a/demo/Ursa.Demo/Views/MainWindow.axaml +++ b/demo/Ursa.Demo/Views/MainWindow.axaml @@ -29,6 +29,9 @@ + + + diff --git a/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml b/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml new file mode 100644 index 0000000..4165994 --- /dev/null +++ b/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml index 8b9ac73..f0db1d0 100644 --- a/src/Ursa.Themes.Semi/Controls/_index.axaml +++ b/src/Ursa.Themes.Semi/Controls/_index.axaml @@ -3,5 +3,6 @@ + diff --git a/src/Ursa/Controls/IPv4Box.cs b/src/Ursa/Controls/IPv4Box.cs index 1890c45..fc933f2 100644 --- a/src/Ursa/Controls/IPv4Box.cs +++ b/src/Ursa/Controls/IPv4Box.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Net; using Avalonia; using Avalonia.Controls; @@ -5,27 +6,37 @@ using Avalonia.Controls.Metadata; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Input; +using Avalonia.Interactivity; namespace Ursa.Controls; -[TemplatePart(PART_FirstTextPresenter, typeof(TextPresenter))] -[TemplatePart(PART_SecondTextPresenter, typeof(TextPresenter))] -[TemplatePart(PART_ThirdTextPresenter, typeof(TextPresenter))] -[TemplatePart(PART_FourthTextPresenter, typeof(TextPresenter))] -[TemplatePart(PART_PortNumericInput, typeof(NumericUpDown))] +[TemplatePart(PART_FirstTextBox, typeof(TextBox))] +[TemplatePart(PART_SecondTextBox, typeof(TextBox))] +[TemplatePart(PART_ThirdTextBox, typeof(TextBox))] +[TemplatePart(PART_FourthTextBox, typeof(TextBox))] +[TemplatePart(PART_PortTextBox, typeof(TextBox))] public class IPv4Box: TemplatedControl { - public const string PART_FirstTextPresenter = "PART_FirstTextPresenter"; - public const string PART_SecondTextPresenter = "PART_SecondTextPresenter"; - public const string PART_ThirdTextPresenter = "PART_ThirdTextPresenter"; - public const string PART_FourthTextPresenter = "PART_FourthTextPresenter"; - public const string PART_PortNumericInput = "PART_PortNumericInput"; - private TextPresenter? _firstTextPresenter; - private TextPresenter? _secondTextPresenter; - private TextPresenter? _thirdTextPresenter; - private TextPresenter? _fourthTextPresenter; - private NumericUpDown? _portNumericInput; + public const string PART_FirstTextBox = "PART_FirstTextPresenter"; + public const string PART_SecondTextBox = "PART_SecondTextPresenter"; + public const string PART_ThirdTextBox = "PART_ThirdTextPresenter"; + public const string PART_FourthTextBox = "PART_FourthTextPresenter"; + public const string PART_PortTextBox = "PART_PortNumericInput"; + private TextBox? _firstText; + private TextBox? _secondText; + private TextBox? _thirdText; + private TextBox? _fourthText; + private TextBox? _portText; + public static readonly StyledProperty ShowPortProperty = AvaloniaProperty.Register( + nameof(ShowPort)); + + public bool ShowPort + { + get => GetValue(ShowPortProperty); + set => SetValue(ShowPortProperty, value); + } + public static readonly StyledProperty IPAddressProperty = AvaloniaProperty.Register( nameof(IPAddress)); @@ -34,7 +45,7 @@ public class IPv4Box: TemplatedControl get => GetValue(IPAddressProperty); set => SetValue(IPAddressProperty, value); } - + public static readonly StyledProperty PortProperty = AvaloniaProperty.Register( nameof(Port)); @@ -44,32 +55,50 @@ public class IPv4Box: TemplatedControl set => SetValue(PortProperty, value); } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - _firstTextPresenter = e.NameScope.Find(PART_FirstTextPresenter); - _secondTextPresenter = e.NameScope.Find(PART_SecondTextPresenter); - _thirdTextPresenter = e.NameScope.Find(PART_ThirdTextPresenter); - _fourthTextPresenter = e.NameScope.Find(PART_FourthTextPresenter); - _portNumericInput = e.NameScope.Find(PART_PortNumericInput); - var box = new TextBox(); + _firstText = e.NameScope.Find(PART_FirstTextBox); + _secondText = e.NameScope.Find(PART_SecondTextBox); + _thirdText = e.NameScope.Find(PART_ThirdTextBox); + _fourthText = e.NameScope.Find(PART_FourthTextBox); + _portText = e.NameScope.Find(PART_PortTextBox); + + _firstText.LostFocus += OnTextLostFocus; + } + + private void OnTextKeyDown(object sender, KeyEventArgs args) + { + if (args.Key == Key.Right) + { + _secondText?.Focus(); + } + } + + private void OnTextLostFocus(object? sender, RoutedEventArgs args) + { + if (sender?.Equals(_firstText)??false) + { + Debug.WriteLine("FIRST"); + } } protected override void OnKeyDown(KeyEventArgs e) { if (e.Key == Key.Left) { - if ((_firstTextPresenter?.IsFocused??false) && _firstTextPresenter.SelectionStart == 0) + if ((_firstText?.IsFocused??false) && _firstText.SelectionStart == 0) { - _secondTextPresenter?.Focus(); + _secondText?.Focus(); } } else if (e.Key == Key.Right) { - if ((_firstTextPresenter?.IsFocused ?? false) && _firstTextPresenter?.SelectionEnd == 2) + if ((_firstText?.IsFocused ?? false) && _firstText?.SelectionEnd == 2) { - _firstTextPresenter.Focus(); + _firstText.Focus(); } } else if (e.Key == Key.Tab) @@ -82,4 +111,13 @@ public class IPv4Box: TemplatedControl } base.OnKeyDown(e); } + + protected override void OnTextInput(TextInputEventArgs e) + { + if (_firstText != null) + { + _firstText.Text = e.Text; + } + base.OnTextInput(e); + } } \ No newline at end of file From 5dfbd6dd3cc12e3a2db804a515a60d5419e3ae19 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Fri, 24 Feb 2023 12:34:43 +0800 Subject: [PATCH 03/15] feat: move back to textbox, add caret handling on click and focus. --- demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml | 5 +- demo/Ursa.Demo/Ursa.Demo.csproj | 2 +- src/Ursa.Themes.Semi/Controls/IPv4Box.axaml | 75 ++++++++--- src/Ursa/Controls/IPv4Box.cs | 142 +++++++++++--------- src/Ursa/Ursa.csproj | 4 + 5 files changed, 141 insertions(+), 87 deletions(-) diff --git a/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml index bfc5970..2da5968 100644 --- a/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml +++ b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml @@ -8,8 +8,9 @@ d:DesignHeight="450" d:DesignWidth="800" mc:Ignorable="d"> - + - + + diff --git a/demo/Ursa.Demo/Ursa.Demo.csproj b/demo/Ursa.Demo/Ursa.Demo.csproj index 272045d..101380b 100644 --- a/demo/Ursa.Demo/Ursa.Demo.csproj +++ b/demo/Ursa.Demo/Ursa.Demo.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml b/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml index 4165994..5c663d5 100644 --- a/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml +++ b/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml @@ -3,55 +3,88 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:u="https://irihi.tech/ursa"> + + + + + + + - - + - + - - + - + - + MinWidth="48" + VerticalAlignment="Center" + Cursor="IBeam" /> - + + MinWidth="30" + VerticalAlignment="Center" + Cursor="IBeam" + IsVisible="{TemplateBinding ShowPort}" /> + diff --git a/src/Ursa/Controls/IPv4Box.cs b/src/Ursa/Controls/IPv4Box.cs index fc933f2..c0edc31 100644 --- a/src/Ursa/Controls/IPv4Box.cs +++ b/src/Ursa/Controls/IPv4Box.cs @@ -10,23 +10,30 @@ using Avalonia.Interactivity; namespace Ursa.Controls; -[TemplatePart(PART_FirstTextBox, typeof(TextBox))] -[TemplatePart(PART_SecondTextBox, typeof(TextBox))] -[TemplatePart(PART_ThirdTextBox, typeof(TextBox))] -[TemplatePart(PART_FourthTextBox, typeof(TextBox))] -[TemplatePart(PART_PortTextBox, typeof(TextBox))] +[TemplatePart(PART_FirstTextPresenter, typeof(TextPresenter))] +[TemplatePart(PART_SecondTextPresenter, typeof(TextPresenter))] +[TemplatePart(PART_ThirdTextPresenter, typeof(TextPresenter))] +[TemplatePart(PART_FourthTextPresenter, typeof(TextPresenter))] +[TemplatePart(PART_PortTextPresenter, typeof(TextPresenter))] public class IPv4Box: TemplatedControl { - public const string PART_FirstTextBox = "PART_FirstTextPresenter"; - public const string PART_SecondTextBox = "PART_SecondTextPresenter"; - public const string PART_ThirdTextBox = "PART_ThirdTextPresenter"; - public const string PART_FourthTextBox = "PART_FourthTextPresenter"; - public const string PART_PortTextBox = "PART_PortNumericInput"; - private TextBox? _firstText; - private TextBox? _secondText; - private TextBox? _thirdText; - private TextBox? _fourthText; - private TextBox? _portText; + public const string PART_FirstTextPresenter = "PART_FirstTextPresenter"; + public const string PART_SecondTextPresenter = "PART_SecondTextPresenter"; + public const string PART_ThirdTextPresenter = "PART_ThirdTextPresenter"; + public const string PART_FourthTextPresenter = "PART_FourthTextPresenter"; + public const string PART_PortTextPresenter = "PART_PortTextPresenter"; + private TextPresenter? _firstText; + private TextPresenter? _secondText; + private TextPresenter? _thirdText; + private TextPresenter? _fourthText; + private TextPresenter? _portText; + private byte _firstByte; + private byte _secondByte; + private byte _thridByte; + private byte _fourthByte; + private int _port; + private TextPresenter?[] _presenters = new TextPresenter?[5]; + private TextPresenter? _currentActivePresenter; public static readonly StyledProperty ShowPortProperty = AvaloniaProperty.Register( nameof(ShowPort)); @@ -37,10 +44,10 @@ public class IPv4Box: TemplatedControl set => SetValue(ShowPortProperty, value); } - public static readonly StyledProperty IPAddressProperty = AvaloniaProperty.Register( + public static readonly StyledProperty IPAddressProperty = AvaloniaProperty.Register( nameof(IPAddress)); - public IPAddress IPAddress + public IPAddress? IPAddress { get => GetValue(IPAddressProperty); set => SetValue(IPAddressProperty, value); @@ -54,70 +61,79 @@ public class IPv4Box: TemplatedControl get => GetValue(PortProperty); set => SetValue(PortProperty, value); } - - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - _firstText = e.NameScope.Find(PART_FirstTextBox); - _secondText = e.NameScope.Find(PART_SecondTextBox); - _thirdText = e.NameScope.Find(PART_ThirdTextBox); - _fourthText = e.NameScope.Find(PART_FourthTextBox); - _portText = e.NameScope.Find(PART_PortTextBox); - - _firstText.LostFocus += OnTextLostFocus; + ClearTextPresenterEvents(_firstText); + ClearTextPresenterEvents(_secondText); + ClearTextPresenterEvents(_thirdText); + ClearTextPresenterEvents(_fourthText); + ClearTextPresenterEvents(_portText); + _firstText = e.NameScope.Find(PART_FirstTextPresenter); + _secondText = e.NameScope.Find(PART_SecondTextPresenter); + _thirdText = e.NameScope.Find(PART_ThirdTextPresenter); + _fourthText = e.NameScope.Find(PART_FourthTextPresenter); + _portText = e.NameScope.Find(PART_PortTextPresenter); + _presenters[0] = _portText; + _presenters[1] = _firstText; + _presenters[2] = _secondText; + _presenters[3] = _thirdText; + _presenters[4] = _fourthText; + RegisterTextPresenterEvents(_firstText); + RegisterTextPresenterEvents(_secondText); + RegisterTextPresenterEvents(_thirdText); + RegisterTextPresenterEvents(_fourthText); + RegisterTextPresenterEvents(_portText); } - private void OnTextKeyDown(object sender, KeyEventArgs args) + private void ClearTextPresenterEvents(TextPresenter? presenter) { - if (args.Key == Key.Right) + if (presenter is null) return; + presenter.LostFocus -= OnTextPresenterLostFocus; + } + + private void RegisterTextPresenterEvents(TextPresenter? presenter) + { + if(presenter is null) return; + presenter.LostFocus += OnTextPresenterLostFocus; + } + + private void OnTextPresenterLostFocus(object? sender, RoutedEventArgs args) + { + if (sender is TextPresenter p) { - _secondText?.Focus(); + p.HideCaret(); } } - private void OnTextLostFocus(object? sender, RoutedEventArgs args) + protected override void OnPointerPressed(PointerPressedEventArgs e) { - if (sender?.Equals(_firstText)??false) + var source = e.Source; + Point position = e.GetPosition(_firstText); + foreach (var presenter in _presenters) { - Debug.WriteLine("FIRST"); - } - } - - protected override void OnKeyDown(KeyEventArgs e) - { - if (e.Key == Key.Left) - { - if ((_firstText?.IsFocused??false) && _firstText.SelectionStart == 0) + if (presenter?.Bounds.Contains(position) ?? false) { - _secondText?.Focus(); + presenter?.ShowCaret(); + _currentActivePresenter = presenter; + } + else + { + presenter?.HideCaret(); } } - else if (e.Key == Key.Right) - { - if ((_firstText?.IsFocused ?? false) && _firstText?.SelectionEnd == 2) - { - _firstText.Focus(); - } - } - else if (e.Key == Key.Tab) - { - - } - else if (!(e.Key is >= Key.D0 and <= Key.D9 || e.Key is >= Key.NumPad0 and <= Key.NumPad9)) - { - return; - } - base.OnKeyDown(e); + Debug.WriteLine(_currentActivePresenter?.Name); + base.OnPointerPressed(e); } - protected override void OnTextInput(TextInputEventArgs e) + protected override void OnLostFocus(RoutedEventArgs e) { - if (_firstText != null) - { - _firstText.Text = e.Text; - } - base.OnTextInput(e); + _firstText?.HideCaret(); + _secondText?.HideCaret(); + _thirdText?.HideCaret(); + _fourthText?.HideCaret(); + _portText?.HideCaret(); + _currentActivePresenter = null; } } \ No newline at end of file diff --git a/src/Ursa/Ursa.csproj b/src/Ursa/Ursa.csproj index c6df5d3..fb43200 100644 --- a/src/Ursa/Ursa.csproj +++ b/src/Ursa/Ursa.csproj @@ -12,4 +12,8 @@ + + + + From 539a6e99a60c94f5a3a137b60783fddfca59c14e Mon Sep 17 00:00:00 2001 From: rabbitism Date: Fri, 24 Feb 2023 17:31:59 +0800 Subject: [PATCH 04/15] feat: add navigation key handling. --- demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml | 4 +- src/Ursa.Themes.Semi/Controls/IPv4Box.axaml | 16 +- src/Ursa/Controls/IPv4Box.cs | 235 +++++++++++++++++--- 3 files changed, 220 insertions(+), 35 deletions(-) diff --git a/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml index 2da5968..51f44ff 100644 --- a/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml +++ b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml @@ -10,7 +10,9 @@ mc:Ignorable="d"> - + + + diff --git a/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml b/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml index 5c663d5..4f75b04 100644 --- a/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml +++ b/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml @@ -28,6 +28,8 @@ MinWidth="48" VerticalAlignment="Center" Cursor="IBeam" + SelectionBrush="Blue" + SelectionForegroundBrush="White" Text="123" TextAlignment="Center" /> + Cursor="IBeam" + SelectionBrush="Blue" + SelectionForegroundBrush="White" /> + Cursor="IBeam" + SelectionBrush="Blue" + SelectionForegroundBrush="White" /> + IsVisible="{TemplateBinding ShowPort}" + SelectionBrush="Blue" + SelectionForegroundBrush="White" /> diff --git a/src/Ursa/Controls/IPv4Box.cs b/src/Ursa/Controls/IPv4Box.cs index c0edc31..9153b50 100644 --- a/src/Ursa/Controls/IPv4Box.cs +++ b/src/Ursa/Controls/IPv4Box.cs @@ -7,6 +7,7 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.Media.TextFormatting; namespace Ursa.Controls; @@ -29,10 +30,11 @@ public class IPv4Box: TemplatedControl private TextPresenter? _portText; private byte _firstByte; private byte _secondByte; - private byte _thridByte; + private byte _thirdByte; private byte _fourthByte; private int _port; private TextPresenter?[] _presenters = new TextPresenter?[5]; + private byte[] _bytes = new byte[4]; private TextPresenter? _currentActivePresenter; public static readonly StyledProperty ShowPortProperty = AvaloniaProperty.Register( @@ -65,11 +67,6 @@ public class IPv4Box: TemplatedControl protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - ClearTextPresenterEvents(_firstText); - ClearTextPresenterEvents(_secondText); - ClearTextPresenterEvents(_thirdText); - ClearTextPresenterEvents(_fourthText); - ClearTextPresenterEvents(_portText); _firstText = e.NameScope.Find(PART_FirstTextPresenter); _secondText = e.NameScope.Find(PART_SecondTextPresenter); _thirdText = e.NameScope.Find(PART_ThirdTextPresenter); @@ -80,47 +77,153 @@ public class IPv4Box: TemplatedControl _presenters[2] = _secondText; _presenters[3] = _thirdText; _presenters[4] = _fourthText; - RegisterTextPresenterEvents(_firstText); - RegisterTextPresenterEvents(_secondText); - RegisterTextPresenterEvents(_thirdText); - RegisterTextPresenterEvents(_fourthText); - RegisterTextPresenterEvents(_portText); } - private void ClearTextPresenterEvents(TextPresenter? presenter) + protected override void OnKeyDown(KeyEventArgs e) + { + if (e.Key == Key.Tab) + { + _currentActivePresenter?.HideCaret(); + ClearSelection(_currentActivePresenter); + if (Equals(_currentActivePresenter, _fourthText)) + { + base.OnKeyDown(e); + return; + } + MoveToNextPresenter(_currentActivePresenter, true); + _currentActivePresenter?.ShowCaret(); + SetIPAddress(); + e.Handled = true; + } + else if (e.Key == Key.Back) + { + DeleteCurrentCharacter(_currentActivePresenter); + } + else if (e.Key == Key.Right ) + { + if (_currentActivePresenter != null) + { + if (_currentActivePresenter.CaretIndex >= _currentActivePresenter.Text?.Length) + { + _currentActivePresenter.HideCaret(); + MoveToNextPresenter(_currentActivePresenter, false); + _currentActivePresenter.SelectionStart = 0; + _currentActivePresenter.SelectionEnd = 0; + _currentActivePresenter?.ShowCaret(); + } + else + { + _currentActivePresenter.CaretIndex++; + } + } + } + else if (e.Key == Key.Left) + { + if (_currentActivePresenter != null) + { + if (_currentActivePresenter.CaretIndex == 0) + { + _currentActivePresenter.HideCaret(); + bool success = MoveToPreviousTextPresenter(_currentActivePresenter); + _currentActivePresenter.ShowCaret(); + if (success) + { + _currentActivePresenter.CaretIndex = _currentActivePresenter.Text?.Length ?? 0; + } + + } + else + { + _currentActivePresenter.CaretIndex--; + } + } + } + else + { + base.OnKeyDown(e); + } + + } + + private void SelectAll(TextPresenter? presenter) + { + if(presenter is null) return; + presenter.SelectionStart = 0; + presenter.SelectionEnd = presenter.Text?.Length+1??0; + } + + protected override void OnTextInput(TextInputEventArgs e) + { + if (e.Handled) return; + string? s = e.Text; + if (string.IsNullOrEmpty(s)) return; + if (_currentActivePresenter != null) + { + int index = _currentActivePresenter.CaretIndex; + string? oldText = _currentActivePresenter.Text; + if (oldText is null) + { + _currentActivePresenter.Text = s; + _currentActivePresenter.MoveCaretHorizontal(); + } + else + { + DeleteSelection(_currentActivePresenter); + ClearSelection(_currentActivePresenter); + oldText = _currentActivePresenter.Text; + + string? newText = string.IsNullOrEmpty(oldText) + ? s + : (oldText?.Substring(0, index) + s + oldText?.Substring(index)); + _currentActivePresenter.Text = newText; + _currentActivePresenter.MoveCaretHorizontal(); + } + } + } + + private void DeleteSelection(TextPresenter? presenter) { if (presenter is null) return; - presenter.LostFocus -= OnTextPresenterLostFocus; - } - - private void RegisterTextPresenterEvents(TextPresenter? presenter) - { - if(presenter is null) return; - presenter.LostFocus += OnTextPresenterLostFocus; - } - - private void OnTextPresenterLostFocus(object? sender, RoutedEventArgs args) - { - if (sender is TextPresenter p) + int selectionStart = presenter.SelectionStart; + int selectionEnd = presenter.SelectionEnd; + if (selectionStart != selectionEnd) { - p.HideCaret(); + var start = Math.Min(selectionStart, selectionEnd); + var end = Math.Max(selectionStart, selectionEnd); + var text = presenter.Text; + + string newText = text is null ? string.Empty : text.Substring(0, start) + text.Substring(end); + presenter.Text = newText; + presenter.MoveCaretToTextPosition(start); } } + private void ClearSelection(TextPresenter? presenter) + { + if(presenter is null) return; + presenter.SelectionStart = 0; + presenter.SelectionEnd = 0; + } + protected override void OnPointerPressed(PointerPressedEventArgs e) { var source = e.Source; + PointerPoint? clickInfo = e.GetCurrentPoint(this); Point position = e.GetPosition(_firstText); foreach (var presenter in _presenters) { - if (presenter?.Bounds.Contains(position) ?? false) + if (presenter?.Bounds.Contains(position)??false) { presenter?.ShowCaret(); _currentActivePresenter = presenter; + var caretPosition = position.WithX(position.X - presenter.Bounds.X); + SetIPAddress(); + presenter?.MoveCaretToPoint(caretPosition); } else { presenter?.HideCaret(); + ClearSelection(presenter); } } Debug.WriteLine(_currentActivePresenter?.Name); @@ -129,11 +232,81 @@ public class IPv4Box: TemplatedControl protected override void OnLostFocus(RoutedEventArgs e) { - _firstText?.HideCaret(); - _secondText?.HideCaret(); - _thirdText?.HideCaret(); - _fourthText?.HideCaret(); - _portText?.HideCaret(); + foreach (var pre in _presenters) + { + pre?.HideCaret(); + ClearSelection(pre); + } _currentActivePresenter = null; + SetIPAddress(); + } + + protected override void OnGotFocus(GotFocusEventArgs e) + { + _currentActivePresenter = _firstText; + _currentActivePresenter?.ShowCaret(); + base.OnGotFocus(e); + } + + private void MoveToNextPresenter(TextPresenter? presenter, bool selectAllAfterMove) + { + if (presenter is null) return; + if (Equals(presenter, _firstText)) _currentActivePresenter = _secondText; + else if (Equals(presenter, _secondText)) _currentActivePresenter = _thirdText; + else if (Equals(presenter, _thirdText)) _currentActivePresenter = _fourthText; + else if (Equals(presenter, _fourthText)) + { + if (ShowPort) + { + _currentActivePresenter = _portText; + } + } + if(selectAllAfterMove) SelectAll(_currentActivePresenter); + } + + private bool MoveToPreviousTextPresenter(TextPresenter? presenter) + { + if (presenter is null) return false; + if (Equals(presenter, _firstText)) return false; + if (Equals(presenter, _portText)) _currentActivePresenter = _fourthText; + else if (Equals(presenter, _fourthText)) _currentActivePresenter = _thirdText; + else if (Equals(presenter, _thirdText)) _currentActivePresenter = _secondText; + else if (Equals(presenter, _secondText)) _currentActivePresenter = _firstText; + return true; + } + + public void Clear() + { + foreach (var presenter in _presenters) + { + if (presenter != null) presenter.Text = null; + } + IPAddress = null; + } + + private void SetIPAddress() + { + long address = 0; + address += _firstByte; + address += _secondByte << 8; + address += _thirdByte << 16; + address += _fourthByte << 24; + IPAddress = new IPAddress(address); + } + + private void DeleteCurrentCharacter(TextPresenter? presenter) + { + if(presenter is null) return; + var oldText = presenter.Text; + if (string.IsNullOrWhiteSpace(oldText)) + { + MoveToPreviousTextPresenter(presenter); + return; + } + int index = presenter.CaretIndex; + if (index == 0) return; + string newText = oldText?.Substring(0, index - 1) + oldText?.Substring(index); + presenter.MoveCaretHorizontal(LogicalDirection.Backward); + presenter.Text = newText; } } \ No newline at end of file From d433c16314d84847f03ae024177ed027a16166ec Mon Sep 17 00:00:00 2001 From: rabbitism Date: Fri, 24 Feb 2023 18:56:54 +0800 Subject: [PATCH 05/15] feat: remove port from ip box. --- demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml | 5 +- src/Ursa.Themes.Semi/Controls/IPv4Box.axaml | 35 +++++-------- src/Ursa/Controls/IPv4Box.cs | 54 ++++++--------------- 3 files changed, 29 insertions(+), 65 deletions(-) diff --git a/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml index 51f44ff..c227749 100644 --- a/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml +++ b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml @@ -9,9 +9,8 @@ d:DesignWidth="800" mc:Ignorable="d"> - - - + + diff --git a/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml b/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml index 4f75b04..90e1687 100644 --- a/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml +++ b/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml @@ -21,11 +21,11 @@ BorderBrush="Black" BorderThickness="1" CornerRadius="3"> - + + Text="123" + TextAlignment="Center" /> + SelectionForegroundBrush="White" + TextAlignment="Center" /> - - + SelectionForegroundBrush="White" + TextAlignment="Center" /> diff --git a/src/Ursa/Controls/IPv4Box.cs b/src/Ursa/Controls/IPv4Box.cs index 9153b50..a14b3a9 100644 --- a/src/Ursa/Controls/IPv4Box.cs +++ b/src/Ursa/Controls/IPv4Box.cs @@ -15,37 +15,24 @@ namespace Ursa.Controls; [TemplatePart(PART_SecondTextPresenter, typeof(TextPresenter))] [TemplatePart(PART_ThirdTextPresenter, typeof(TextPresenter))] [TemplatePart(PART_FourthTextPresenter, typeof(TextPresenter))] -[TemplatePart(PART_PortTextPresenter, typeof(TextPresenter))] public class IPv4Box: TemplatedControl { public const string PART_FirstTextPresenter = "PART_FirstTextPresenter"; public const string PART_SecondTextPresenter = "PART_SecondTextPresenter"; public const string PART_ThirdTextPresenter = "PART_ThirdTextPresenter"; public const string PART_FourthTextPresenter = "PART_FourthTextPresenter"; - public const string PART_PortTextPresenter = "PART_PortTextPresenter"; private TextPresenter? _firstText; private TextPresenter? _secondText; private TextPresenter? _thirdText; private TextPresenter? _fourthText; - private TextPresenter? _portText; private byte _firstByte; private byte _secondByte; private byte _thirdByte; private byte _fourthByte; - private int _port; - private TextPresenter?[] _presenters = new TextPresenter?[5]; + private readonly TextPresenter?[] _presenters = new TextPresenter?[4]; private byte[] _bytes = new byte[4]; private TextPresenter? _currentActivePresenter; - public static readonly StyledProperty ShowPortProperty = AvaloniaProperty.Register( - nameof(ShowPort)); - - public bool ShowPort - { - get => GetValue(ShowPortProperty); - set => SetValue(ShowPortProperty, value); - } - public static readonly StyledProperty IPAddressProperty = AvaloniaProperty.Register( nameof(IPAddress)); @@ -54,15 +41,6 @@ public class IPv4Box: TemplatedControl get => GetValue(IPAddressProperty); set => SetValue(IPAddressProperty, value); } - - public static readonly StyledProperty PortProperty = AvaloniaProperty.Register( - nameof(Port)); - - public int Port - { - get => GetValue(PortProperty); - set => SetValue(PortProperty, value); - } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { @@ -71,12 +49,10 @@ public class IPv4Box: TemplatedControl _secondText = e.NameScope.Find(PART_SecondTextPresenter); _thirdText = e.NameScope.Find(PART_ThirdTextPresenter); _fourthText = e.NameScope.Find(PART_FourthTextPresenter); - _portText = e.NameScope.Find(PART_PortTextPresenter); - _presenters[0] = _portText; - _presenters[1] = _firstText; - _presenters[2] = _secondText; - _presenters[3] = _thirdText; - _presenters[4] = _fourthText; + _presenters[0] = _firstText; + _presenters[1] = _secondText; + _presenters[2] = _thirdText; + _presenters[3] = _fourthText; } protected override void OnKeyDown(KeyEventArgs e) @@ -238,6 +214,10 @@ public class IPv4Box: TemplatedControl ClearSelection(pre); } _currentActivePresenter = null; + _firstByte = byte.TryParse(_firstText?.Text, out byte b1) ? b1 : (byte)0; + _secondByte = byte.TryParse(_secondText?.Text, out byte b2) ? b2 : (byte)0; + _thirdByte = byte.TryParse(_thirdText?.Text, out byte b3) ? b3 : (byte)0; + _fourthByte = byte.TryParse(_fourthText?.Text, out byte b4) ? b4 : (byte)0; SetIPAddress(); } @@ -254,13 +234,6 @@ public class IPv4Box: TemplatedControl if (Equals(presenter, _firstText)) _currentActivePresenter = _secondText; else if (Equals(presenter, _secondText)) _currentActivePresenter = _thirdText; else if (Equals(presenter, _thirdText)) _currentActivePresenter = _fourthText; - else if (Equals(presenter, _fourthText)) - { - if (ShowPort) - { - _currentActivePresenter = _portText; - } - } if(selectAllAfterMove) SelectAll(_currentActivePresenter); } @@ -268,8 +241,7 @@ public class IPv4Box: TemplatedControl { if (presenter is null) return false; if (Equals(presenter, _firstText)) return false; - if (Equals(presenter, _portText)) _currentActivePresenter = _fourthText; - else if (Equals(presenter, _fourthText)) _currentActivePresenter = _thirdText; + if (Equals(presenter, _fourthText)) _currentActivePresenter = _thirdText; else if (Equals(presenter, _thirdText)) _currentActivePresenter = _secondText; else if (Equals(presenter, _secondText)) _currentActivePresenter = _firstText; return true; @@ -300,7 +272,13 @@ public class IPv4Box: TemplatedControl var oldText = presenter.Text; if (string.IsNullOrWhiteSpace(oldText)) { + presenter.HideCaret(); MoveToPreviousTextPresenter(presenter); + if (_currentActivePresenter != null) + { + _currentActivePresenter.ShowCaret(); + _currentActivePresenter.CaretIndex = _currentActivePresenter.Text?.Length ?? 0; + } return; } int index = presenter.CaretIndex; From 573e3ada21c687ee5cbb0b24a827d7f80e0fc520 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Fri, 24 Feb 2023 22:47:20 +0800 Subject: [PATCH 06/15] feat: add more input handling. --- src/Ursa/Controls/IPv4Box.cs | 99 +++++++++++++++++++++++++++++------- 1 file changed, 82 insertions(+), 17 deletions(-) diff --git a/src/Ursa/Controls/IPv4Box.cs b/src/Ursa/Controls/IPv4Box.cs index a14b3a9..0006a6a 100644 --- a/src/Ursa/Controls/IPv4Box.cs +++ b/src/Ursa/Controls/IPv4Box.cs @@ -6,6 +6,7 @@ using Avalonia.Controls.Metadata; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Media.TextFormatting; @@ -54,9 +55,23 @@ public class IPv4Box: TemplatedControl _presenters[2] = _thirdText; _presenters[3] = _fourthText; } - protected override void OnKeyDown(KeyEventArgs e) { + if (_currentActivePresenter is null) return; + var keymap = AvaloniaLocator.Current.GetRequiredService(); + bool Match(List gestures) => gestures.Any(g => g.Matches(e)); + if (e.Key == Key.Enter) + { + ParseBytes(); + SetIPAddress(); + return; + } + if (Match(keymap.SelectAll)) + { + _currentActivePresenter.SelectionStart = 0; + _currentActivePresenter.SelectionEnd = _currentActivePresenter.Text?.Length ?? 0; + return; + } if (e.Key == Key.Tab) { _currentActivePresenter?.HideCaret(); @@ -73,7 +88,7 @@ public class IPv4Box: TemplatedControl } else if (e.Key == Key.Back) { - DeleteCurrentCharacter(_currentActivePresenter); + DeleteImplementation(_currentActivePresenter); } else if (e.Key == Key.Right ) { @@ -133,6 +148,7 @@ public class IPv4Box: TemplatedControl if (e.Handled) return; string? s = e.Text; if (string.IsNullOrEmpty(s)) return; + if (!char.IsNumber(s[0])) return; if (_currentActivePresenter != null) { int index = _currentActivePresenter.CaretIndex; @@ -151,8 +167,23 @@ public class IPv4Box: TemplatedControl string? newText = string.IsNullOrEmpty(oldText) ? s : (oldText?.Substring(0, index) + s + oldText?.Substring(index)); + if (newText.Length > 3) + { + newText = newText.Substring(0, 3); + } _currentActivePresenter.Text = newText; _currentActivePresenter.MoveCaretHorizontal(); + if (_currentActivePresenter.CaretIndex == 3) + { + _currentActivePresenter.HideCaret(); + bool success = MoveToNextPresenter(_currentActivePresenter, true); + _currentActivePresenter.ShowCaret(); + if (success) + { + SelectAll(_currentActivePresenter); + _currentActivePresenter.CaretIndex = 0; + } + } } } } @@ -168,7 +199,9 @@ public class IPv4Box: TemplatedControl var end = Math.Max(selectionStart, selectionEnd); var text = presenter.Text; - string newText = text is null ? string.Empty : text.Substring(0, start) + text.Substring(end); + string newText = text is null + ? string.Empty + : text.Substring(0, start) + text.Substring(Math.Min(end, text.Length)); presenter.Text = newText; presenter.MoveCaretToTextPosition(start); } @@ -193,7 +226,6 @@ public class IPv4Box: TemplatedControl presenter?.ShowCaret(); _currentActivePresenter = presenter; var caretPosition = position.WithX(position.X - presenter.Bounds.X); - SetIPAddress(); presenter?.MoveCaretToPoint(caretPosition); } else @@ -214,11 +246,20 @@ public class IPv4Box: TemplatedControl ClearSelection(pre); } _currentActivePresenter = null; + ParseBytes(); + SetIPAddress(); + } + + private void ParseBytes() + { _firstByte = byte.TryParse(_firstText?.Text, out byte b1) ? b1 : (byte)0; _secondByte = byte.TryParse(_secondText?.Text, out byte b2) ? b2 : (byte)0; _thirdByte = byte.TryParse(_thirdText?.Text, out byte b3) ? b3 : (byte)0; _fourthByte = byte.TryParse(_fourthText?.Text, out byte b4) ? b4 : (byte)0; - SetIPAddress(); + if (_firstText != null) _firstText.Text = _firstByte.ToString(); + if (_secondText != null) _secondText.Text = _secondByte.ToString(); + if (_thirdText != null) _thirdText.Text = _thirdByte.ToString(); + if (_fourthText != null) _fourthText.Text = _fourthByte.ToString(); } protected override void OnGotFocus(GotFocusEventArgs e) @@ -228,13 +269,15 @@ public class IPv4Box: TemplatedControl base.OnGotFocus(e); } - private void MoveToNextPresenter(TextPresenter? presenter, bool selectAllAfterMove) + private bool MoveToNextPresenter(TextPresenter? presenter, bool selectAllAfterMove) { - if (presenter is null) return; + if (presenter is null) return false; + if (Equals(presenter, _fourthText)) return false; if (Equals(presenter, _firstText)) _currentActivePresenter = _secondText; else if (Equals(presenter, _secondText)) _currentActivePresenter = _thirdText; else if (Equals(presenter, _thirdText)) _currentActivePresenter = _fourthText; if(selectAllAfterMove) SelectAll(_currentActivePresenter); + return true; } private bool MoveToPreviousTextPresenter(TextPresenter? presenter) @@ -262,15 +305,30 @@ public class IPv4Box: TemplatedControl address += _firstByte; address += _secondByte << 8; address += _thirdByte << 16; - address += _fourthByte << 24; - IPAddress = new IPAddress(address); + address += (long)_fourthByte << 24; + try + { + IPAddress = new IPAddress(address); + } + catch + { + IPAddress = null; + } } - private void DeleteCurrentCharacter(TextPresenter? presenter) + private void DeleteImplementation(TextPresenter? presenter) { if(presenter is null) return; var oldText = presenter.Text; - if (string.IsNullOrWhiteSpace(oldText)) + if (presenter.SelectionStart != presenter.SelectionEnd) + { + int start = presenter.SelectionStart; + int end = presenter.SelectionEnd; + string newText = oldText?.Substring(0, start) + oldText?.Substring(end); + presenter.Text = newText; + presenter.CaretIndex = start; + } + else if (string.IsNullOrWhiteSpace(oldText)) { presenter.HideCaret(); MoveToPreviousTextPresenter(presenter); @@ -279,12 +337,19 @@ public class IPv4Box: TemplatedControl _currentActivePresenter.ShowCaret(); _currentActivePresenter.CaretIndex = _currentActivePresenter.Text?.Length ?? 0; } - return; } - int index = presenter.CaretIndex; - if (index == 0) return; - string newText = oldText?.Substring(0, index - 1) + oldText?.Substring(index); - presenter.MoveCaretHorizontal(LogicalDirection.Backward); - presenter.Text = newText; + else + { + int index = presenter.CaretIndex; + if (index == 0) return; + string newText = oldText?.Substring(0, index - 1) + oldText?.Substring(index); + presenter.MoveCaretHorizontal(LogicalDirection.Backward); + presenter.Text = newText; + } + } + + public async void Cut() + { + } } \ No newline at end of file From db4db9f1fe8e40f5c69802beb8eeacc9f7244ee1 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Fri, 24 Feb 2023 23:57:41 +0800 Subject: [PATCH 07/15] feat: add style. --- demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml | 2 +- src/Ursa.Themes.Semi/Controls/IPv4Box.axaml | 76 ++++++++----- .../Themes/Dark/IPv4Box.axaml | 12 +++ src/Ursa.Themes.Semi/Themes/Dark/_index.axaml | 1 + .../Themes/Light/IPv4Box.axaml | 12 +++ .../Themes/Light/_index.axaml | 1 + .../Themes/Shared/IPv4Box.axaml | 8 ++ .../Themes/Shared/_index.axaml | 1 + src/Ursa/Controls/IPv4Box.cs | 102 ++++++++++++++---- 9 files changed, 167 insertions(+), 48 deletions(-) create mode 100644 src/Ursa.Themes.Semi/Themes/Dark/IPv4Box.axaml create mode 100644 src/Ursa.Themes.Semi/Themes/Light/IPv4Box.axaml create mode 100644 src/Ursa.Themes.Semi/Themes/Shared/IPv4Box.axaml diff --git a/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml index c227749..b6b7d65 100644 --- a/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml +++ b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml @@ -10,7 +10,7 @@ mc:Ignorable="d"> - + diff --git a/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml b/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml index 90e1687..2f5095b 100644 --- a/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml +++ b/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml @@ -10,78 +10,96 @@ + + + + + + + + + + Background="{TemplateBinding Background}" + BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}"> + SelectionBrush="{TemplateBinding SelectionBrush}" + SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}" + TextAlignment="{TemplateBinding TextAlignment}" /> + SelectionBrush="{TemplateBinding SelectionBrush}" + SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}" + TextAlignment="{TemplateBinding TextAlignment}" /> + SelectionBrush="{TemplateBinding SelectionBrush}" + SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}" + TextAlignment="{TemplateBinding TextAlignment}" /> + SelectionBrush="{TemplateBinding SelectionBrush}" + SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}" + TextAlignment="{TemplateBinding TextAlignment}" /> - + + + diff --git a/src/Ursa.Themes.Semi/Themes/Dark/IPv4Box.axaml b/src/Ursa.Themes.Semi/Themes/Dark/IPv4Box.axaml new file mode 100644 index 0000000..b567dd0 --- /dev/null +++ b/src/Ursa.Themes.Semi/Themes/Dark/IPv4Box.axaml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Themes/Dark/_index.axaml b/src/Ursa.Themes.Semi/Themes/Dark/_index.axaml index 8b9ac73..f0db1d0 100644 --- a/src/Ursa.Themes.Semi/Themes/Dark/_index.axaml +++ b/src/Ursa.Themes.Semi/Themes/Dark/_index.axaml @@ -3,5 +3,6 @@ + diff --git a/src/Ursa.Themes.Semi/Themes/Light/IPv4Box.axaml b/src/Ursa.Themes.Semi/Themes/Light/IPv4Box.axaml new file mode 100644 index 0000000..25001cd --- /dev/null +++ b/src/Ursa.Themes.Semi/Themes/Light/IPv4Box.axaml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Themes/Light/_index.axaml b/src/Ursa.Themes.Semi/Themes/Light/_index.axaml index 8b9ac73..f0db1d0 100644 --- a/src/Ursa.Themes.Semi/Themes/Light/_index.axaml +++ b/src/Ursa.Themes.Semi/Themes/Light/_index.axaml @@ -3,5 +3,6 @@ + diff --git a/src/Ursa.Themes.Semi/Themes/Shared/IPv4Box.axaml b/src/Ursa.Themes.Semi/Themes/Shared/IPv4Box.axaml new file mode 100644 index 0000000..3f808ba --- /dev/null +++ b/src/Ursa.Themes.Semi/Themes/Shared/IPv4Box.axaml @@ -0,0 +1,8 @@ + + + 32 + 24 + 40 + 1 + 3 + diff --git a/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml b/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml index 8b9ac73..f0db1d0 100644 --- a/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml +++ b/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml @@ -3,5 +3,6 @@ + diff --git a/src/Ursa/Controls/IPv4Box.cs b/src/Ursa/Controls/IPv4Box.cs index 0006a6a..6c6c649 100644 --- a/src/Ursa/Controls/IPv4Box.cs +++ b/src/Ursa/Controls/IPv4Box.cs @@ -8,6 +8,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; +using Avalonia.Media; using Avalonia.Media.TextFormatting; namespace Ursa.Controls; @@ -43,6 +44,60 @@ public class IPv4Box: TemplatedControl set => SetValue(IPAddressProperty, value); } + public static readonly StyledProperty TextAlignmentProperty = + TextBox.TextAlignmentProperty.AddOwner(); + public TextAlignment TextAlignment + { + get => GetValue(TextAlignmentProperty); + set => SetValue(TextAlignmentProperty, value); + } + + public static readonly StyledProperty SelectionBrushProperty = + TextBox.SelectionBrushProperty.AddOwner(); + + public IBrush? SelectionBrush + { + get => GetValue(SelectionBrushProperty); + set => SetValue(SelectionBrushProperty, value); + } + + public static readonly StyledProperty SelectionForegroundBrushProperty = + TextBox.SelectionForegroundBrushProperty.AddOwner(); + + public IBrush? SelectionForegroundBrush + { + get => GetValue(SelectionForegroundBrushProperty); + set => SetValue(SelectionForegroundBrushProperty, value); + } + + public static readonly StyledProperty CaretBrushProperty = TextBox.CaretBrushProperty.AddOwner(); + + public IBrush? CaretBrush + { + get => GetValue(CaretBrushProperty); + set => SetValue(CaretBrushProperty, value); + } + + public static readonly StyledProperty ShowLeadingZeroProperty = AvaloniaProperty.Register( + nameof(ShowLeadingZero)); + + public bool ShowLeadingZero + { + get => GetValue(ShowLeadingZeroProperty); + set => SetValue(ShowLeadingZeroProperty, value); + } + + static IPv4Box() + { + ShowLeadingZeroProperty.Changed.AddClassHandler((o, e) => o.OnFormatChange(e)); + } + + private void OnFormatChange(AvaloniaPropertyChangedEventArgs arg) + { + bool showLeadingZero = arg.GetNewValue(); + ParseBytes(showLeadingZero); + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -62,7 +117,7 @@ public class IPv4Box: TemplatedControl bool Match(List gestures) => gestures.Any(g => g.Matches(e)); if (e.Key == Key.Enter) { - ParseBytes(); + ParseBytes(ShowLeadingZero); SetIPAddress(); return; } @@ -83,7 +138,6 @@ public class IPv4Box: TemplatedControl } MoveToNextPresenter(_currentActivePresenter, true); _currentActivePresenter?.ShowCaret(); - SetIPAddress(); e.Handled = true; } else if (e.Key == Key.Back) @@ -104,6 +158,7 @@ public class IPv4Box: TemplatedControl } else { + ClearSelection(_currentActivePresenter); _currentActivePresenter.CaretIndex++; } } @@ -125,6 +180,7 @@ public class IPv4Box: TemplatedControl } else { + ClearSelection(_currentActivePresenter); _currentActivePresenter.CaretIndex--; } } @@ -223,10 +279,18 @@ public class IPv4Box: TemplatedControl { if (presenter?.Bounds.Contains(position)??false) { - presenter?.ShowCaret(); - _currentActivePresenter = presenter; - var caretPosition = position.WithX(position.X - presenter.Bounds.X); - presenter?.MoveCaretToPoint(caretPosition); + if (e.ClickCount == 1) + { + presenter?.ShowCaret(); + _currentActivePresenter = presenter; + var caretPosition = position.WithX(position.X - presenter.Bounds.X); + presenter?.MoveCaretToPoint(caretPosition); + } + else if (e.ClickCount == 2) + { + SelectAll(presenter); + presenter.CaretIndex = presenter.Text?.Length??0; + } } else { @@ -246,26 +310,33 @@ public class IPv4Box: TemplatedControl ClearSelection(pre); } _currentActivePresenter = null; - ParseBytes(); + ParseBytes(ShowLeadingZero); SetIPAddress(); } - private void ParseBytes() + private void ParseBytes(bool showLeadingZero) { + string format = showLeadingZero ? "D3" : ""; _firstByte = byte.TryParse(_firstText?.Text, out byte b1) ? b1 : (byte)0; _secondByte = byte.TryParse(_secondText?.Text, out byte b2) ? b2 : (byte)0; _thirdByte = byte.TryParse(_thirdText?.Text, out byte b3) ? b3 : (byte)0; _fourthByte = byte.TryParse(_fourthText?.Text, out byte b4) ? b4 : (byte)0; - if (_firstText != null) _firstText.Text = _firstByte.ToString(); - if (_secondText != null) _secondText.Text = _secondByte.ToString(); - if (_thirdText != null) _thirdText.Text = _thirdByte.ToString(); - if (_fourthText != null) _fourthText.Text = _fourthByte.ToString(); + if (_firstText != null) _firstText.Text = _firstByte.ToString(format); + if (_secondText != null) _secondText.Text = _secondByte.ToString(format); + if (_thirdText != null) _thirdText.Text = _thirdByte.ToString(format); + if (_fourthText != null) _fourthText.Text = _fourthByte.ToString(format); } protected override void OnGotFocus(GotFocusEventArgs e) { _currentActivePresenter = _firstText; - _currentActivePresenter?.ShowCaret(); + if (_currentActivePresenter is null) + { + base.OnGotFocus(e); + return; + } + _currentActivePresenter.ShowCaret(); + _currentActivePresenter.CaretIndex = 0; base.OnGotFocus(e); } @@ -347,9 +418,4 @@ public class IPv4Box: TemplatedControl presenter.Text = newText; } } - - public async void Cut() - { - - } } \ No newline at end of file From db1c674968b56b2f0300b6cbe5cd99f0c5553f2a Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sat, 25 Feb 2023 00:16:31 +0800 Subject: [PATCH 08/15] feat: update demo --- demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml | 22 ++++++++++++++++-- demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml.cs | 15 +++++++++++-- src/Ursa/Controls/IPv4Box.cs | 27 +++++++++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml index b6b7d65..b6edf53 100644 --- a/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml +++ b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml @@ -4,14 +4,32 @@ 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:pages="clr-namespace:Ursa.Demo.Pages" xmlns:u="https://irihi.tech/ursa" d:DesignHeight="450" d:DesignWidth="800" mc:Ignorable="d"> + + + - - + + + + + + + diff --git a/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml.cs b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml.cs index 4c7d175..c865da8 100644 --- a/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml.cs +++ b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml.cs @@ -1,6 +1,9 @@ +using System; +using System.Net; using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using CommunityToolkit.Mvvm.ComponentModel; namespace Ursa.Demo.Pages; @@ -9,10 +12,18 @@ public partial class IPv4BoxDemo : UserControl public IPv4BoxDemo() { InitializeComponent(); + DataContext = new IPv4DemoViewMode(); } +} - private void InitializeComponent() +public partial class IPv4DemoViewMode: ObservableObject +{ + [ObservableProperty] + private IPAddress? _address; + + public void ChangeAddress() { - AvaloniaXamlLoader.Load(this); + long l = Random.Shared.NextInt64(0x00000000FFFFFFFF); + Address = new IPAddress(l); } } \ No newline at end of file diff --git a/src/Ursa/Controls/IPv4Box.cs b/src/Ursa/Controls/IPv4Box.cs index 6c6c649..30bcbf5 100644 --- a/src/Ursa/Controls/IPv4Box.cs +++ b/src/Ursa/Controls/IPv4Box.cs @@ -90,6 +90,7 @@ public class IPv4Box: TemplatedControl static IPv4Box() { ShowLeadingZeroProperty.Changed.AddClassHandler((o, e) => o.OnFormatChange(e)); + IPAddressProperty.Changed.AddClassHandler((o, e) => o.OnIPChanged(e)); } private void OnFormatChange(AvaloniaPropertyChangedEventArgs arg) @@ -98,6 +99,32 @@ public class IPv4Box: TemplatedControl ParseBytes(showLeadingZero); } + private void OnIPChanged(AvaloniaPropertyChangedEventArgs arg) + { + IPAddress? address = arg.GetNewValue(); + if (address is null) + { + foreach (var presenter in _presenters) + { + if(presenter!=null) presenter.Text = string.Empty; + } + ParseBytes(ShowLeadingZero); + } + else + { + var sections = address.ToString().Split('.'); + for (int i = 0; i < 4; i++) + { + var presenter = _presenters[i]; + if (presenter != null) + { + presenter.Text = sections[i]; + } + } + ParseBytes(ShowLeadingZero); + } + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); From 4e08e8ffecde0a43b0e94218425c94d63b42ae99 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sat, 25 Feb 2023 00:20:38 +0800 Subject: [PATCH 09/15] fix: fix a deletion issue. --- src/Ursa/Controls/IPv4Box.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ursa/Controls/IPv4Box.cs b/src/Ursa/Controls/IPv4Box.cs index 30bcbf5..e8e6b92 100644 --- a/src/Ursa/Controls/IPv4Box.cs +++ b/src/Ursa/Controls/IPv4Box.cs @@ -440,7 +440,7 @@ public class IPv4Box: TemplatedControl { int index = presenter.CaretIndex; if (index == 0) return; - string newText = oldText?.Substring(0, index - 1) + oldText?.Substring(index); + string newText = oldText?.Substring(0, index - 1) + oldText?.Substring(Math.Min(index, oldText.Length)); presenter.MoveCaretHorizontal(LogicalDirection.Backward); presenter.Text = newText; } From 6d63cd566ea3bb31a31d564e1007709755747960 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sat, 25 Feb 2023 00:29:54 +0800 Subject: [PATCH 10/15] feat: add dot/decimal support. --- src/Ursa/Controls/IPv4Box.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ursa/Controls/IPv4Box.cs b/src/Ursa/Controls/IPv4Box.cs index e8e6b92..34f60f2 100644 --- a/src/Ursa/Controls/IPv4Box.cs +++ b/src/Ursa/Controls/IPv4Box.cs @@ -154,7 +154,7 @@ public class IPv4Box: TemplatedControl _currentActivePresenter.SelectionEnd = _currentActivePresenter.Text?.Length ?? 0; return; } - if (e.Key == Key.Tab) + if (e.Key == Key.Tab || e.Key == Key.OemPeriod || e.Key == Key.Decimal) { _currentActivePresenter?.HideCaret(); ClearSelection(_currentActivePresenter); From 9b28cd07b80071cc66b8103f438c479cb0581a1b Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sat, 25 Feb 2023 13:22:57 +0800 Subject: [PATCH 11/15] feat: add input mode, allow null ipaddress, refactor code for better readbility. --- demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml | 10 +- .../Themes/Light/IPv4Box.axaml | 2 +- src/Ursa/Controls/IPv4Box.cs | 566 ++++++++++-------- 3 files changed, 321 insertions(+), 257 deletions(-) diff --git a/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml index b6edf53..0d674dd 100644 --- a/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml +++ b/demo/Ursa.Demo/Pages/IPv4BoxDemo.axaml @@ -17,19 +17,27 @@ Name="format" Margin="0,0,0,50" Content="Show Leading Zeroes" /> - + + + + + + diff --git a/src/Ursa.Themes.Semi/Themes/Light/IPv4Box.axaml b/src/Ursa.Themes.Semi/Themes/Light/IPv4Box.axaml index 25001cd..5c3fb6b 100644 --- a/src/Ursa.Themes.Semi/Themes/Light/IPv4Box.axaml +++ b/src/Ursa.Themes.Semi/Themes/Light/IPv4Box.axaml @@ -4,7 +4,7 @@ - + diff --git a/src/Ursa/Controls/IPv4Box.cs b/src/Ursa/Controls/IPv4Box.cs index 34f60f2..782d480 100644 --- a/src/Ursa/Controls/IPv4Box.cs +++ b/src/Ursa/Controls/IPv4Box.cs @@ -13,6 +13,13 @@ using Avalonia.Media.TextFormatting; namespace Ursa.Controls; +public enum IPv4BoxInputMode +{ + Normal, + // In fast mode, automatically move to next session after 3 digits input. + Fast, +} + [TemplatePart(PART_FirstTextPresenter, typeof(TextPresenter))] [TemplatePart(PART_SecondTextPresenter, typeof(TextPresenter))] [TemplatePart(PART_ThirdTextPresenter, typeof(TextPresenter))] @@ -27,17 +34,15 @@ public class IPv4Box: TemplatedControl private TextPresenter? _secondText; private TextPresenter? _thirdText; private TextPresenter? _fourthText; - private byte _firstByte; - private byte _secondByte; - private byte _thirdByte; - private byte _fourthByte; + private byte? _firstByte; + private byte? _secondByte; + private byte? _thirdByte; + private byte? _fourthByte; private readonly TextPresenter?[] _presenters = new TextPresenter?[4]; - private byte[] _bytes = new byte[4]; private TextPresenter? _currentActivePresenter; public static readonly StyledProperty IPAddressProperty = AvaloniaProperty.Register( nameof(IPAddress)); - public IPAddress? IPAddress { get => GetValue(IPAddressProperty); @@ -54,7 +59,6 @@ public class IPv4Box: TemplatedControl public static readonly StyledProperty SelectionBrushProperty = TextBox.SelectionBrushProperty.AddOwner(); - public IBrush? SelectionBrush { get => GetValue(SelectionBrushProperty); @@ -63,7 +67,6 @@ public class IPv4Box: TemplatedControl public static readonly StyledProperty SelectionForegroundBrushProperty = TextBox.SelectionForegroundBrushProperty.AddOwner(); - public IBrush? SelectionForegroundBrush { get => GetValue(SelectionForegroundBrushProperty); @@ -71,7 +74,6 @@ public class IPv4Box: TemplatedControl } public static readonly StyledProperty CaretBrushProperty = TextBox.CaretBrushProperty.AddOwner(); - public IBrush? CaretBrush { get => GetValue(CaretBrushProperty); @@ -80,18 +82,241 @@ public class IPv4Box: TemplatedControl public static readonly StyledProperty ShowLeadingZeroProperty = AvaloniaProperty.Register( nameof(ShowLeadingZero)); - public bool ShowLeadingZero { get => GetValue(ShowLeadingZeroProperty); set => SetValue(ShowLeadingZeroProperty, value); } + public static readonly StyledProperty InputModeProperty = AvaloniaProperty.Register( + nameof(InputMode)); + public IPv4BoxInputMode InputMode + { + get => GetValue(InputModeProperty); + set => SetValue(InputModeProperty, value); + } + static IPv4Box() { ShowLeadingZeroProperty.Changed.AddClassHandler((o, e) => o.OnFormatChange(e)); IPAddressProperty.Changed.AddClassHandler((o, e) => o.OnIPChanged(e)); } + + #region Overrides + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + _firstText = e.NameScope.Find(PART_FirstTextPresenter); + _secondText = e.NameScope.Find(PART_SecondTextPresenter); + _thirdText = e.NameScope.Find(PART_ThirdTextPresenter); + _fourthText = e.NameScope.Find(PART_FourthTextPresenter); + _presenters[0] = _firstText; + _presenters[1] = _secondText; + _presenters[2] = _thirdText; + _presenters[3] = _fourthText; + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if (_currentActivePresenter is null) return; + var keymap = AvaloniaLocator.Current.GetRequiredService(); + bool Match(List gestures) => gestures.Any(g => g.Matches(e)); + if (e.Key == Key.Enter) + { + ParseBytes(ShowLeadingZero); + SetIPAddress(); + return; + } + if (Match(keymap.SelectAll)) + { + _currentActivePresenter.SelectionStart = 0; + _currentActivePresenter.SelectionEnd = _currentActivePresenter.Text?.Length ?? 0; + return; + } + if (e.Key == Key.Tab) + { + _currentActivePresenter?.HideCaret(); + _currentActivePresenter.ClearSelection(); + if (Equals(_currentActivePresenter, _fourthText)) + { + base.OnKeyDown(e); + return; + } + MoveToNextPresenter(_currentActivePresenter, true); + _currentActivePresenter?.ShowCaret(); + e.Handled = true; + } + else if (e.Key == Key.OemPeriod || e.Key == Key.Decimal) + { + if (string.IsNullOrEmpty(_currentActivePresenter.Text)) + { + base.OnKeyDown(e); + return; + } + _currentActivePresenter?.HideCaret(); + _currentActivePresenter.ClearSelection(); + if (Equals(_currentActivePresenter, _fourthText)) + { + base.OnKeyDown(e); + return; + } + MoveToNextPresenter(_currentActivePresenter, false); + _currentActivePresenter?.ShowCaret(); + _currentActivePresenter.MoveCaretToStart(); + e.Handled = true; + } + else if (e.Key == Key.Back) + { + DeleteImplementation(_currentActivePresenter); + } + else if (e.Key == Key.Right ) + { + if (_currentActivePresenter != null) + { + if (_currentActivePresenter.CaretIndex >= _currentActivePresenter.Text?.Length) + { + _currentActivePresenter.HideCaret(); + MoveToNextPresenter(_currentActivePresenter, false); + _currentActivePresenter.SelectionStart = 0; + _currentActivePresenter.SelectionEnd = 0; + _currentActivePresenter?.ShowCaret(); + } + else + { + _currentActivePresenter.ClearSelection(); + _currentActivePresenter.CaretIndex++; + } + } + } + else if (e.Key == Key.Left) + { + if (_currentActivePresenter != null) + { + if (_currentActivePresenter.CaretIndex == 0) + { + _currentActivePresenter.HideCaret(); + bool success = MoveToPreviousTextPresenter(_currentActivePresenter); + _currentActivePresenter.ShowCaret(); + if (success) + { + _currentActivePresenter.MoveCaretToEnd(); + } + } + else + { + _currentActivePresenter.ClearSelection(); + _currentActivePresenter.CaretIndex--; + } + } + } + else + { + base.OnKeyDown(e); + } + } + + protected override void OnTextInput(TextInputEventArgs e) + { + if (e.Handled) return; + string? s = e.Text; + if (string.IsNullOrEmpty(s)) return; + if (!char.IsNumber(s[0])) return; + if (_currentActivePresenter != null) + { + int index = _currentActivePresenter.CaretIndex; + string? oldText = _currentActivePresenter.Text; + if (oldText is null) + { + _currentActivePresenter.Text = s; + _currentActivePresenter.MoveCaretHorizontal(); + } + else + { + _currentActivePresenter.DeleteSelection(); + _currentActivePresenter.ClearSelection(); + oldText = _currentActivePresenter.Text; + + string? newText = string.IsNullOrEmpty(oldText) + ? s + : oldText?.Substring(0, index) + s + oldText?.Substring(Math.Min(index, oldText.Length)); + if (newText.Length > 3) + { + newText = newText.Substring(0, 3); + } + _currentActivePresenter.Text = newText; + _currentActivePresenter.MoveCaretHorizontal(); + if (_currentActivePresenter.CaretIndex == 3 && InputMode == IPv4BoxInputMode.Fast) + { + _currentActivePresenter.HideCaret(); + bool success = MoveToNextPresenter(_currentActivePresenter, true); + _currentActivePresenter.ShowCaret(); + if (success) + { + _currentActivePresenter.SelectAll(); + _currentActivePresenter.MoveCaretToStart(); + } + } + } + } + } + + protected override void OnPointerPressed(PointerPressedEventArgs e) + { + var source = e.Source; + PointerPoint? clickInfo = e.GetCurrentPoint(this); + Point position = e.GetPosition(_firstText); + foreach (var presenter in _presenters) + { + if (presenter?.Bounds.Contains(position)??false) + { + if (e.ClickCount == 1) + { + presenter?.ShowCaret(); + _currentActivePresenter = presenter; + var caretPosition = position.WithX(position.X - presenter?.Bounds.X ?? 0); + presenter?.MoveCaretToPoint(caretPosition); + } + else if (e.ClickCount == 2) + { + presenter.SelectAll(); + presenter.MoveCaretToEnd(); + } + } + else + { + presenter?.HideCaret(); + presenter.ClearSelection(); + } + } + Debug.WriteLine(_currentActivePresenter?.Name); + base.OnPointerPressed(e); + } + + protected override void OnLostFocus(RoutedEventArgs e) + { + foreach (var pre in _presenters) + { + pre?.HideCaret(); + pre.ClearSelection(); + } + _currentActivePresenter = null; + ParseBytes(ShowLeadingZero); + SetIPAddress(); + } + + protected override void OnGotFocus(GotFocusEventArgs e) + { + _currentActivePresenter = _firstText; + if (_currentActivePresenter is null) + { + base.OnGotFocus(e); + return; + } + _currentActivePresenter.ShowCaret(); + _currentActivePresenter.MoveCaretToStart(); + base.OnGotFocus(e); + } + #endregion private void OnFormatChange(AvaloniaPropertyChangedEventArgs arg) { @@ -125,247 +350,28 @@ public class IPv4Box: TemplatedControl } } - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - base.OnApplyTemplate(e); - _firstText = e.NameScope.Find(PART_FirstTextPresenter); - _secondText = e.NameScope.Find(PART_SecondTextPresenter); - _thirdText = e.NameScope.Find(PART_ThirdTextPresenter); - _fourthText = e.NameScope.Find(PART_FourthTextPresenter); - _presenters[0] = _firstText; - _presenters[1] = _secondText; - _presenters[2] = _thirdText; - _presenters[3] = _fourthText; - } - protected override void OnKeyDown(KeyEventArgs e) - { - if (_currentActivePresenter is null) return; - var keymap = AvaloniaLocator.Current.GetRequiredService(); - bool Match(List gestures) => gestures.Any(g => g.Matches(e)); - if (e.Key == Key.Enter) - { - ParseBytes(ShowLeadingZero); - SetIPAddress(); - return; - } - if (Match(keymap.SelectAll)) - { - _currentActivePresenter.SelectionStart = 0; - _currentActivePresenter.SelectionEnd = _currentActivePresenter.Text?.Length ?? 0; - return; - } - if (e.Key == Key.Tab || e.Key == Key.OemPeriod || e.Key == Key.Decimal) - { - _currentActivePresenter?.HideCaret(); - ClearSelection(_currentActivePresenter); - if (Equals(_currentActivePresenter, _fourthText)) - { - base.OnKeyDown(e); - return; - } - MoveToNextPresenter(_currentActivePresenter, true); - _currentActivePresenter?.ShowCaret(); - e.Handled = true; - } - else if (e.Key == Key.Back) - { - DeleteImplementation(_currentActivePresenter); - } - else if (e.Key == Key.Right ) - { - if (_currentActivePresenter != null) - { - if (_currentActivePresenter.CaretIndex >= _currentActivePresenter.Text?.Length) - { - _currentActivePresenter.HideCaret(); - MoveToNextPresenter(_currentActivePresenter, false); - _currentActivePresenter.SelectionStart = 0; - _currentActivePresenter.SelectionEnd = 0; - _currentActivePresenter?.ShowCaret(); - } - else - { - ClearSelection(_currentActivePresenter); - _currentActivePresenter.CaretIndex++; - } - } - } - else if (e.Key == Key.Left) - { - if (_currentActivePresenter != null) - { - if (_currentActivePresenter.CaretIndex == 0) - { - _currentActivePresenter.HideCaret(); - bool success = MoveToPreviousTextPresenter(_currentActivePresenter); - _currentActivePresenter.ShowCaret(); - if (success) - { - _currentActivePresenter.CaretIndex = _currentActivePresenter.Text?.Length ?? 0; - } - - } - else - { - ClearSelection(_currentActivePresenter); - _currentActivePresenter.CaretIndex--; - } - } - } - else - { - base.OnKeyDown(e); - } - - } - - private void SelectAll(TextPresenter? presenter) - { - if(presenter is null) return; - presenter.SelectionStart = 0; - presenter.SelectionEnd = presenter.Text?.Length+1??0; - } - - protected override void OnTextInput(TextInputEventArgs e) - { - if (e.Handled) return; - string? s = e.Text; - if (string.IsNullOrEmpty(s)) return; - if (!char.IsNumber(s[0])) return; - if (_currentActivePresenter != null) - { - int index = _currentActivePresenter.CaretIndex; - string? oldText = _currentActivePresenter.Text; - if (oldText is null) - { - _currentActivePresenter.Text = s; - _currentActivePresenter.MoveCaretHorizontal(); - } - else - { - DeleteSelection(_currentActivePresenter); - ClearSelection(_currentActivePresenter); - oldText = _currentActivePresenter.Text; - - string? newText = string.IsNullOrEmpty(oldText) - ? s - : (oldText?.Substring(0, index) + s + oldText?.Substring(index)); - if (newText.Length > 3) - { - newText = newText.Substring(0, 3); - } - _currentActivePresenter.Text = newText; - _currentActivePresenter.MoveCaretHorizontal(); - if (_currentActivePresenter.CaretIndex == 3) - { - _currentActivePresenter.HideCaret(); - bool success = MoveToNextPresenter(_currentActivePresenter, true); - _currentActivePresenter.ShowCaret(); - if (success) - { - SelectAll(_currentActivePresenter); - _currentActivePresenter.CaretIndex = 0; - } - } - } - } - } - - private void DeleteSelection(TextPresenter? presenter) - { - if (presenter is null) return; - int selectionStart = presenter.SelectionStart; - int selectionEnd = presenter.SelectionEnd; - if (selectionStart != selectionEnd) - { - var start = Math.Min(selectionStart, selectionEnd); - var end = Math.Max(selectionStart, selectionEnd); - var text = presenter.Text; - - string newText = text is null - ? string.Empty - : text.Substring(0, start) + text.Substring(Math.Min(end, text.Length)); - presenter.Text = newText; - presenter.MoveCaretToTextPosition(start); - } - } - - private void ClearSelection(TextPresenter? presenter) - { - if(presenter is null) return; - presenter.SelectionStart = 0; - presenter.SelectionEnd = 0; - } - - protected override void OnPointerPressed(PointerPressedEventArgs e) - { - var source = e.Source; - PointerPoint? clickInfo = e.GetCurrentPoint(this); - Point position = e.GetPosition(_firstText); - foreach (var presenter in _presenters) - { - if (presenter?.Bounds.Contains(position)??false) - { - if (e.ClickCount == 1) - { - presenter?.ShowCaret(); - _currentActivePresenter = presenter; - var caretPosition = position.WithX(position.X - presenter.Bounds.X); - presenter?.MoveCaretToPoint(caretPosition); - } - else if (e.ClickCount == 2) - { - SelectAll(presenter); - presenter.CaretIndex = presenter.Text?.Length??0; - } - } - else - { - presenter?.HideCaret(); - ClearSelection(presenter); - } - } - Debug.WriteLine(_currentActivePresenter?.Name); - base.OnPointerPressed(e); - } - - protected override void OnLostFocus(RoutedEventArgs e) - { - foreach (var pre in _presenters) - { - pre?.HideCaret(); - ClearSelection(pre); - } - _currentActivePresenter = null; - ParseBytes(ShowLeadingZero); - SetIPAddress(); - } - private void ParseBytes(bool showLeadingZero) { string format = showLeadingZero ? "D3" : ""; + if (string.IsNullOrEmpty(_firstText?.Text) && string.IsNullOrEmpty(_secondText?.Text) && string.IsNullOrEmpty(_thirdText?.Text) && string.IsNullOrEmpty(_fourthText?.Text)) + { + _firstByte = null; + _secondByte = null; + _thirdByte = null; + _fourthByte = null; + return; + } _firstByte = byte.TryParse(_firstText?.Text, out byte b1) ? b1 : (byte)0; _secondByte = byte.TryParse(_secondText?.Text, out byte b2) ? b2 : (byte)0; _thirdByte = byte.TryParse(_thirdText?.Text, out byte b3) ? b3 : (byte)0; _fourthByte = byte.TryParse(_fourthText?.Text, out byte b4) ? b4 : (byte)0; - if (_firstText != null) _firstText.Text = _firstByte.ToString(format); - if (_secondText != null) _secondText.Text = _secondByte.ToString(format); - if (_thirdText != null) _thirdText.Text = _thirdByte.ToString(format); - if (_fourthText != null) _fourthText.Text = _fourthByte.ToString(format); + if (_firstText != null) _firstText.Text = _firstByte?.ToString(format); + if (_secondText != null) _secondText.Text = _secondByte?.ToString(format); + if (_thirdText != null) _thirdText.Text = _thirdByte?.ToString(format); + if (_fourthText != null) _fourthText.Text = _fourthByte?.ToString(format); } - protected override void OnGotFocus(GotFocusEventArgs e) - { - _currentActivePresenter = _firstText; - if (_currentActivePresenter is null) - { - base.OnGotFocus(e); - return; - } - _currentActivePresenter.ShowCaret(); - _currentActivePresenter.CaretIndex = 0; - base.OnGotFocus(e); - } + private bool MoveToNextPresenter(TextPresenter? presenter, bool selectAllAfterMove) { @@ -374,7 +380,7 @@ public class IPv4Box: TemplatedControl if (Equals(presenter, _firstText)) _currentActivePresenter = _secondText; else if (Equals(presenter, _secondText)) _currentActivePresenter = _thirdText; else if (Equals(presenter, _thirdText)) _currentActivePresenter = _fourthText; - if(selectAllAfterMove) SelectAll(_currentActivePresenter); + if(selectAllAfterMove) _currentActivePresenter.SelectAll(); return true; } @@ -399,11 +405,16 @@ public class IPv4Box: TemplatedControl private void SetIPAddress() { + if (_firstByte is null && _secondByte is null && _thirdByte is null && _fourthByte is null) + { + IPAddress = null; + return; + } long address = 0; - address += _firstByte; - address += _secondByte << 8; - address += _thirdByte << 16; - address += (long)_fourthByte << 24; + address += _firstByte??0; + address += (_secondByte??0) << 8; + address += (_thirdByte??0) << 16; + address += ((long?)_fourthByte ?? 0) << 24; try { IPAddress = new IPAddress(address); @@ -420,29 +431,74 @@ public class IPv4Box: TemplatedControl var oldText = presenter.Text; if (presenter.SelectionStart != presenter.SelectionEnd) { - int start = presenter.SelectionStart; - int end = presenter.SelectionEnd; - string newText = oldText?.Substring(0, start) + oldText?.Substring(end); - presenter.Text = newText; - presenter.CaretIndex = start; + presenter.DeleteSelection(); + presenter.ClearSelection(); } - else if (string.IsNullOrWhiteSpace(oldText)) + else if (string.IsNullOrWhiteSpace(oldText) || presenter.CaretIndex == 0) { presenter.HideCaret(); MoveToPreviousTextPresenter(presenter); if (_currentActivePresenter != null) { _currentActivePresenter.ShowCaret(); - _currentActivePresenter.CaretIndex = _currentActivePresenter.Text?.Length ?? 0; + _currentActivePresenter.MoveCaretToEnd(); } } else { int index = presenter.CaretIndex; - if (index == 0) return; string newText = oldText?.Substring(0, index - 1) + oldText?.Substring(Math.Min(index, oldText.Length)); presenter.MoveCaretHorizontal(LogicalDirection.Backward); presenter.Text = newText; } } +} + +public static class TextPresenterHelper +{ + public static void MoveCaretToStart(this TextPresenter? presenter) + { + if (presenter is null) return; + presenter.MoveCaretToTextPosition(0); + } + + public static void MoveCaretToEnd(this TextPresenter? presenter) + { + if(presenter is null) return; + presenter.MoveCaretToTextPosition(presenter.Text?.Length ?? 0); + } + + public static void ClearSelection(this TextPresenter? presenter) + { + if (presenter is null) return; + presenter.SelectionStart = 0; + presenter.SelectionEnd = 0; + } + + public static void SelectAll(this TextPresenter? presenter) + { + if(presenter is null) return; + if(presenter.Text is null) return; + presenter.SelectionStart = 0; + presenter.SelectionEnd = presenter.Text.Length; + } + + public static void DeleteSelection(this TextPresenter? presenter) + { + if (presenter is null) return; + int selectionStart = presenter.SelectionStart; + int selectionEnd = presenter.SelectionEnd; + if (selectionStart != selectionEnd) + { + var start = Math.Min(selectionStart, selectionEnd); + var end = Math.Max(selectionStart, selectionEnd); + var text = presenter.Text; + + string newText = text is null + ? string.Empty + : text.Substring(0, start) + text.Substring(Math.Min(end, text.Length)); + presenter.Text = newText; + presenter.MoveCaretToTextPosition(start); + } + } } \ No newline at end of file From f7763d6d30cb8812efcd1773fd02d9ac89539a7f Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sat, 25 Feb 2023 13:28:34 +0800 Subject: [PATCH 12/15] misc: remove warning. --- src/Ursa/Controls/IPv4Box.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Ursa/Controls/IPv4Box.cs b/src/Ursa/Controls/IPv4Box.cs index 782d480..04b7d19 100644 --- a/src/Ursa/Controls/IPv4Box.cs +++ b/src/Ursa/Controls/IPv4Box.cs @@ -10,6 +10,7 @@ using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Media.TextFormatting; +// ReSharper disable InconsistentNaming namespace Ursa.Controls; @@ -40,7 +41,7 @@ public class IPv4Box: TemplatedControl private byte? _fourthByte; private readonly TextPresenter?[] _presenters = new TextPresenter?[4]; private TextPresenter? _currentActivePresenter; - + public static readonly StyledProperty IPAddressProperty = AvaloniaProperty.Register( nameof(IPAddress)); public IPAddress? IPAddress @@ -220,7 +221,7 @@ public class IPv4Box: TemplatedControl if (e.Handled) return; string? s = e.Text; if (string.IsNullOrEmpty(s)) return; - if (!char.IsNumber(s[0])) return; + if (!char.IsNumber(s![0])) return; if (_currentActivePresenter != null) { int index = _currentActivePresenter.CaretIndex; @@ -236,7 +237,7 @@ public class IPv4Box: TemplatedControl _currentActivePresenter.ClearSelection(); oldText = _currentActivePresenter.Text; - string? newText = string.IsNullOrEmpty(oldText) + string newText = string.IsNullOrEmpty(oldText) ? s : oldText?.Substring(0, index) + s + oldText?.Substring(Math.Min(index, oldText.Length)); if (newText.Length > 3) @@ -262,8 +263,6 @@ public class IPv4Box: TemplatedControl protected override void OnPointerPressed(PointerPressedEventArgs e) { - var source = e.Source; - PointerPoint? clickInfo = e.GetCurrentPoint(this); Point position = e.GetPosition(_firstText); foreach (var presenter in _presenters) { @@ -271,10 +270,10 @@ public class IPv4Box: TemplatedControl { if (e.ClickCount == 1) { - presenter?.ShowCaret(); + presenter.ShowCaret(); _currentActivePresenter = presenter; - var caretPosition = position.WithX(position.X - presenter?.Bounds.X ?? 0); - presenter?.MoveCaretToPoint(caretPosition); + var caretPosition = position.WithX(position.X - presenter.Bounds.X); + presenter.MoveCaretToPoint(caretPosition); } else if (e.ClickCount == 2) { @@ -402,7 +401,7 @@ public class IPv4Box: TemplatedControl } IPAddress = null; } - + private void SetIPAddress() { if (_firstByte is null && _secondByte is null && _thirdByte is null && _fourthByte is null) From f2e3e03ee71c5f7dff98143b890d95cd71a1b3bb Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sat, 25 Feb 2023 13:42:26 +0800 Subject: [PATCH 13/15] feat: Update left right navigation. --- src/Ursa/Controls/IPv4Box.cs | 100 +++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 34 deletions(-) diff --git a/src/Ursa/Controls/IPv4Box.cs b/src/Ursa/Controls/IPv4Box.cs index 04b7d19..536f5b2 100644 --- a/src/Ursa/Controls/IPv4Box.cs +++ b/src/Ursa/Controls/IPv4Box.cs @@ -172,43 +172,11 @@ public class IPv4Box: TemplatedControl } else if (e.Key == Key.Right ) { - if (_currentActivePresenter != null) - { - if (_currentActivePresenter.CaretIndex >= _currentActivePresenter.Text?.Length) - { - _currentActivePresenter.HideCaret(); - MoveToNextPresenter(_currentActivePresenter, false); - _currentActivePresenter.SelectionStart = 0; - _currentActivePresenter.SelectionEnd = 0; - _currentActivePresenter?.ShowCaret(); - } - else - { - _currentActivePresenter.ClearSelection(); - _currentActivePresenter.CaretIndex++; - } - } + OnPressRightKey(); } else if (e.Key == Key.Left) { - if (_currentActivePresenter != null) - { - if (_currentActivePresenter.CaretIndex == 0) - { - _currentActivePresenter.HideCaret(); - bool success = MoveToPreviousTextPresenter(_currentActivePresenter); - _currentActivePresenter.ShowCaret(); - if (success) - { - _currentActivePresenter.MoveCaretToEnd(); - } - } - else - { - _currentActivePresenter.ClearSelection(); - _currentActivePresenter.CaretIndex--; - } - } + OnPressLeftKey(); } else { @@ -376,6 +344,7 @@ public class IPv4Box: TemplatedControl { if (presenter is null) return false; if (Equals(presenter, _fourthText)) return false; + presenter.ClearSelection(); if (Equals(presenter, _firstText)) _currentActivePresenter = _secondText; else if (Equals(presenter, _secondText)) _currentActivePresenter = _thirdText; else if (Equals(presenter, _thirdText)) _currentActivePresenter = _fourthText; @@ -387,6 +356,7 @@ public class IPv4Box: TemplatedControl { if (presenter is null) return false; if (Equals(presenter, _firstText)) return false; + presenter.ClearSelection(); if (Equals(presenter, _fourthText)) _currentActivePresenter = _thirdText; else if (Equals(presenter, _thirdText)) _currentActivePresenter = _secondText; else if (Equals(presenter, _secondText)) _currentActivePresenter = _firstText; @@ -451,6 +421,62 @@ public class IPv4Box: TemplatedControl presenter.Text = newText; } } + + private void OnPressRightKey() + { + if (_currentActivePresenter is null) return; + if (_currentActivePresenter.IsTextSelected()) + { + int end = _currentActivePresenter.SelectionEnd; + _currentActivePresenter.ClearSelection(); + _currentActivePresenter.MoveCaretToTextPosition(end); + return; + } + if (_currentActivePresenter.CaretIndex >= _currentActivePresenter.Text?.Length) + { + _currentActivePresenter.HideCaret(); + bool success = MoveToNextPresenter(_currentActivePresenter, false); + _currentActivePresenter.ClearSelection(); + _currentActivePresenter.ShowCaret(); + if (success) + { + _currentActivePresenter.MoveCaretToStart(); + } + } + else + { + _currentActivePresenter.ClearSelection(); + _currentActivePresenter.CaretIndex++; + } + } + + private void OnPressLeftKey() + { + if (_currentActivePresenter is null) return; + if (_currentActivePresenter.IsTextSelected()) + { + int start = _currentActivePresenter.SelectionStart; + _currentActivePresenter.ClearSelection(); + _currentActivePresenter.MoveCaretToTextPosition(start); + return; + } + if (_currentActivePresenter.CaretIndex == 0) + { + _currentActivePresenter.HideCaret(); + bool success = MoveToPreviousTextPresenter(_currentActivePresenter); + _currentActivePresenter.ClearSelection(); + _currentActivePresenter.ShowCaret(); + if (success) + { + _currentActivePresenter.MoveCaretToEnd(); + } + } + else + { + _currentActivePresenter.ClearSelection(); + _currentActivePresenter.CaretIndex--; + } + } } public static class TextPresenterHelper @@ -500,4 +526,10 @@ public static class TextPresenterHelper presenter.MoveCaretToTextPosition(start); } } + + public static bool IsTextSelected(this TextPresenter? presenter) + { + if (presenter is null) return false; + return presenter.SelectionStart != presenter.SelectionEnd; + } } \ No newline at end of file From 34da3489b5365921fc00296934cc3ff496546e50 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sat, 25 Feb 2023 13:59:56 +0800 Subject: [PATCH 14/15] feat: add copy paste hotkey support. --- src/Ursa/Controls/IPv4Box.cs | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/Ursa/Controls/IPv4Box.cs b/src/Ursa/Controls/IPv4Box.cs index 536f5b2..a93e044 100644 --- a/src/Ursa/Controls/IPv4Box.cs +++ b/src/Ursa/Controls/IPv4Box.cs @@ -125,7 +125,8 @@ public class IPv4Box: TemplatedControl if (e.Key == Key.Enter) { ParseBytes(ShowLeadingZero); - SetIPAddress(); + SetIPAddressInternal(); + base.OnKeyDown(e); return; } if (Match(keymap.SelectAll)) @@ -134,6 +135,14 @@ public class IPv4Box: TemplatedControl _currentActivePresenter.SelectionEnd = _currentActivePresenter.Text?.Length ?? 0; return; } + else if (Match(keymap.Copy)) + { + OnCopy(); + } + else if (Match(keymap.Paste)) + { + OnPaste(); + } if (e.Key == Key.Tab) { _currentActivePresenter?.HideCaret(); @@ -268,7 +277,7 @@ public class IPv4Box: TemplatedControl } _currentActivePresenter = null; ParseBytes(ShowLeadingZero); - SetIPAddress(); + SetIPAddressInternal(); } protected override void OnGotFocus(GotFocusEventArgs e) @@ -372,7 +381,7 @@ public class IPv4Box: TemplatedControl IPAddress = null; } - private void SetIPAddress() + private void SetIPAddressInternal() { if (_firstByte is null && _secondByte is null && _thirdByte is null && _fourthByte is null) { @@ -477,6 +486,24 @@ public class IPv4Box: TemplatedControl _currentActivePresenter.CaretIndex--; } } + + public async void OnCopy() + { + string s = string.Join(".", _firstText?.Text, _secondText?.Text, _thirdText?.Text, _fourthText?.Text); + IClipboard? clipboard = AvaloniaLocator.Current.GetService(); + clipboard?.SetTextAsync(s); + } + + public async void OnPaste() + { + IClipboard? clipboard = AvaloniaLocator.Current.GetService(); + if (clipboard is null) return; + string? s = await clipboard.GetTextAsync(); + if (IPAddress.TryParse(s, out var address)) + { + IPAddress = address; + } + } } public static class TextPresenterHelper From a3243991348c802b78b2e6899c8de1ecbd8e355b Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sat, 25 Feb 2023 14:25:11 +0800 Subject: [PATCH 15/15] feat: add menu flyout. --- src/Ursa.Themes.Semi/Controls/IPv4Box.axaml | 22 ++++++++++++++++ src/Ursa/Controls/IPv4Box.cs | 28 +++++++++++++++++---- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml b/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml index 2f5095b..4a55d21 100644 --- a/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml +++ b/src/Ursa.Themes.Semi/Controls/IPv4Box.axaml @@ -8,6 +8,27 @@ + + + + + + @@ -20,6 +41,7 @@ + (); clipboard?.SetTextAsync(s); } + + public static KeyGesture? CopyKeyGesture { get; } = AvaloniaLocator.Current.GetService()?.Copy.FirstOrDefault(); + public static KeyGesture? PasteKeyGesture { get; } = AvaloniaLocator.Current.GetService()?.Paste.FirstOrDefault(); + public static KeyGesture? CutKeyGesture { get; } = AvaloniaLocator.Current.GetService()?.Cut.FirstOrDefault(); - public async void OnPaste() + public async void Paste() { IClipboard? clipboard = AvaloniaLocator.Current.GetService(); if (clipboard is null) return; - string? s = await clipboard.GetTextAsync(); + string s = await clipboard.GetTextAsync(); if (IPAddress.TryParse(s, out var address)) { IPAddress = address; } } + + public async void Cut() + { + IClipboard? clipboard = AvaloniaLocator.Current.GetService(); + if(clipboard is null) return; + string s = string.Join(".", _firstText?.Text, _secondText?.Text, _thirdText?.Text, _fourthText?.Text); + await clipboard.SetTextAsync(s); + Clear(); + } } public static class TextPresenterHelper