diff --git a/src/Avalonia.Controls/Primitives/TextSelectionCanvas.cs b/src/Avalonia.Controls/Primitives/TextSelectionCanvas.cs index 9fc392f98d..5fc73a74f4 100644 --- a/src/Avalonia.Controls/Primitives/TextSelectionCanvas.cs +++ b/src/Avalonia.Controls/Primitives/TextSelectionCanvas.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; @@ -123,7 +124,7 @@ namespace Avalonia.Controls.Primitives private void CloseFlyout() { - _presenter?.RaiseEvent(new Interactivity.RoutedEventArgs(InputElement.ContextCanceledEvent)); + _textBox?.RaiseEvent(new Interactivity.RoutedEventArgs(InputElement.ContextCanceledEvent)); } private void SelectionHandle_DragDelta(object? sender, VectorEventArgs e) @@ -164,6 +165,7 @@ namespace Avalonia.Controls.Primitives s_isInTouchMode = s_isInTouchMode || forceTouchMode; if (s_isInTouchMode) { + ShowHandles = true; MoveHandlesToSelection(); CheckStateAndShowFlyout(); @@ -259,7 +261,7 @@ namespace Avalonia.Controls.Primitives private void DragSelectionHandle(TextSelectionHandle handle) { - if (_presenter != null && _textBox != null) + if (_presenter is { } presenter && _textBox is { } textbox) { CloseFlyout(); @@ -274,13 +276,13 @@ namespace Avalonia.Controls.Primitives { if (handle.SelectionHandleType == SelectionHandleType.Start) { - position = position >= _textBox.SelectionEnd ? _textBox.SelectionEnd - 1 : position; - _textBox.SetCurrentValue(TextBox.SelectionStartProperty, position); + position = position >= textbox.SelectionEnd ? textbox.SelectionEnd - 1 : position; + textbox.SetCurrentValue(TextBox.SelectionStartProperty, position); } else { - position = position <= _textBox.SelectionStart ? _textBox.SelectionStart + 1 : position; - _textBox.SetCurrentValue(TextBox.SelectionEndProperty, position); + position = position <= textbox.SelectionStart ? textbox.SelectionStart + 1 : position; + textbox.SetCurrentValue(TextBox.SelectionEndProperty, position); } } else @@ -295,12 +297,12 @@ namespace Avalonia.Controls.Primitives if (position == otherPosition) { - position = hasPositionSwapped ? Math.Min(position + 1, (_textBox.Text?.Length - 1) ?? 0) + position = hasPositionSwapped ? Math.Min(position + 1, (textbox.Text?.Length - 1) ?? 0) : Math.Max(position - 1, 0); } - _textBox.SetCurrentValue(TextBox.SelectionStartProperty, hasPositionSwapped ? otherPosition : position); - _textBox.SetCurrentValue(TextBox.SelectionEndProperty, hasPositionSwapped ? position : otherPosition); + textbox.SetCurrentValue(TextBox.SelectionStartProperty, hasPositionSwapped ? otherPosition : position); + textbox.SetCurrentValue(TextBox.SelectionEndProperty, hasPositionSwapped ? position : otherPosition); otherHandle.SelectionHandleType = hasPositionSwapped ? SelectionHandleType.Start : SelectionHandleType.End; } @@ -312,19 +314,19 @@ namespace Avalonia.Controls.Primitives if (position == otherPosition) { - position = !hasPositionSwapped ? Math.Min(position + 1, (_textBox.Text?.Length - 1) ?? 0) + position = !hasPositionSwapped ? Math.Min(position + 1, (textbox.Text?.Length - 1) ?? 0) : Math.Max(position - 1, 0); } - _textBox.SetCurrentValue(TextBox.SelectionStartProperty, hasPositionSwapped ? position : otherPosition); - _textBox.SetCurrentValue(TextBox.SelectionEndProperty, hasPositionSwapped ? otherPosition : position); + textbox.SetCurrentValue(TextBox.SelectionStartProperty, hasPositionSwapped ? position : otherPosition); + textbox.SetCurrentValue(TextBox.SelectionEndProperty, hasPositionSwapped ? otherPosition : position); otherHandle.SelectionHandleType = hasPositionSwapped ? SelectionHandleType.End : SelectionHandleType.Start; } } - _presenter.MoveCaretToTextPosition(position); - var caretBound = _presenter.GetCursorRectangle(); + presenter.MoveCaretToTextPosition(position); + var caretBound = presenter.GetCursorRectangle(); handle.SetTopLeft(ToLayer(handle.SelectionHandleType == SelectionHandleType.Start ? caretBound.BottomLeft : caretBound.BottomLeft)); MoveHandlesToSelection(); @@ -334,7 +336,7 @@ namespace Avalonia.Controls.Primitives { var indicatorPosition = GetSearchPoint(handle); var point = ToPresenter(indicatorPosition); - var hit = _presenter.TextLayout.HitTestPoint(point); + var hit = presenter.TextLayout.HitTestPoint(point); var position = hit.CharacterHit.FirstCharacterIndex + hit.CharacterHit.TrailingLength; return position; } @@ -347,7 +349,12 @@ namespace Avalonia.Controls.Primitives private Point ToPresenter(Point point) { - return (_presenter is { } p) ? (p.VisualRoot as Visual)?.TranslatePoint(point, p) ?? point : point; + return (_presenter is { } presenter) ? (presenter.VisualRoot as Visual)?.TranslatePoint(point, presenter) ?? point : point; + } + + private Point ToTextBox(Point point) + { + return (_textBox is { } textbox) ? (textbox.VisualRoot as Visual)?.TranslatePoint(point, textbox) ?? point : point; } public void MoveHandlesToSelection() @@ -361,8 +368,6 @@ namespace Avalonia.Controls.Primitives var selectionEnd = _presenter.SelectionEnd; var hasSelection = selectionStart != selectionEnd; - ShowHandles = true; - if (!_caretHandle.IsDragging) { var points = _presenter.GetCaretPoints(); @@ -517,37 +522,49 @@ namespace Avalonia.Controls.Primitives var verticalOffset = (double.IsNaN(_textBox.LineHeight) ? _textBox.FontSize : _textBox.LineHeight) + ContextMenuPadding; - TextSelectionHandle? handle = null; + Point? topleft = default; if (_textBox.SelectionStart != _textBox.SelectionEnd) { - if (_handle1.IsEffectivelyVisible) - handle = _handle1; - else if (_handle2.IsEffectivelyVisible) - handle = _handle2; + var p1 = _handle1.IndicatorPosition; + var p2 = _handle2.IndicatorPosition; + + topleft = new Point((p1.X + p2.X) / 2, Math.Min(p1.Y, p2.Y)); } else { if (_caretHandle.IsEffectivelyVisible) { - handle = _caretHandle; + topleft = _caretHandle.IndicatorPosition; } } - if (handle != null) + if (topleft != null) { - var oldVerticalOffset = flyout.VerticalOffset; - var oldHorizontalOffset = flyout.HorizontalOffset; var oldPlacement = flyout.Placement; - var topLeft = ToPresenter(handle.GetTopLeft()); - flyout.VerticalOffset = topLeft.Y - verticalOffset; - flyout.HorizontalOffset = topLeft.X; - flyout.Placement = PlacementMode.TopEdgeAlignedLeft; + var oldCallback = flyout.CustomPopupPlacementCallback; + var oldShowMode = flyout.ShowMode; + var point = ToTextBox(topleft.Value); + point = point.WithY(Math.Max(0, point.Y)); + + + flyout.CustomPopupPlacementCallback = (x) => Place(x); + flyout.Placement = PlacementMode.Custom; + flyout.ShowMode = FlyoutShowMode.Transient; + _textBox.RaiseEvent(new ContextRequestedEventArgs()); - flyout.VerticalOffset = oldVerticalOffset; - flyout.HorizontalOffset = oldHorizontalOffset; + flyout.Placement = oldPlacement; + flyout.CustomPopupPlacementCallback = oldCallback; + flyout.ShowMode = oldShowMode; + void Place(CustomPopupPlacement parameters) + { + parameters.Anchor = PopupAnchor.TopLeft; + var offset = parameters.Offset; + parameters.Offset = offset.WithX(point.X) + .WithY(point.Y - verticalOffset); + } return true; } } @@ -558,17 +575,15 @@ namespace Avalonia.Controls.Primitives private void TextBox_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) { - if (s_isInTouchMode && (e.Property == TextPresenter.SelectionStartProperty || - e.Property == TextPresenter.SelectionEndProperty)) + if (e.Property == TextPresenter.TextProperty) { - MoveHandlesToSelection(); - EnsureVisible(); + ShowHandles = false; + CloseFlyout(); } - else if (e.Property == TextPresenter.TextProperty) + else if(e.Property == TextBox.SelectionStartProperty || e.Property == TextBox.SelectionEndProperty + || e.Property == TextBox.CaretIndexProperty) { - ShowHandles = false; - if (_presenter?.ContextFlyout is { } flyout && flyout.IsOpen) - flyout.Hide(); + MoveHandlesToSelection(); } } diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 135bc4dbc0..898c3037d5 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1731,6 +1731,17 @@ namespace Avalonia.Controls } } + protected override void OnTapped(TappedEventArgs e) + { + base.OnTapped(e); + + if(e.Pointer.Type != PointerType.Mouse) + { + _presenter?.EnsureTextSelectionLayer(); + _presenter?.TextSelectionHandleCanvas?.Show(); + } + } + protected override void OnPointerPressed(PointerPressedEventArgs e) { if (_presenter == null) @@ -1816,6 +1827,8 @@ namespace Avalonia.Controls var selectionEnd = SelectionEnd; SelectWord(text, caretIndex, caretIndex, caretIndex); + _presenter?.EnsureTextSelectionLayer(); + _presenter?.TextSelectionHandleCanvas?.Show(); } } } @@ -2043,7 +2056,6 @@ namespace Avalonia.Controls SetCurrentValue(SelectionStartProperty, caretIndex); } } - _isInTouchMode = false; _isInTouchSelectionMode = false; _isInTouchCaretMode = false; _hasTouchSelection = false;