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