Browse Source

wip touch improvement textbox

text_selector_zindex
Emmanuel Hansen 3 weeks ago
parent
commit
d0bdab0fa4
  1. 2
      samples/ControlCatalog/MainView.xaml
  2. 95
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  3. 11
      src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
  4. 17
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  5. 348
      src/Avalonia.Controls/Primitives/TextSelectionCanvas.cs
  6. 16
      src/Avalonia.Controls/Primitives/TextSelectionHandle.cs
  7. 399
      src/Avalonia.Controls/TextBox.cs
  8. 3
      src/Avalonia.Controls/TextBoxTextInputMethodClient.cs

2
samples/ControlCatalog/MainView.xaml

@ -173,7 +173,7 @@
<TabItem Header="TabStrip">
<pages:TabStripPage />
</TabItem>
<TabItem Header="TextBox">
<TabItem Header="TextBox" IsSelected="True">
<pages:TextBoxPage />
</TabItem>
<TabItem Header="TextBlock">

95
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -8,97 +8,10 @@
<WrapPanel Margin="-8,0"
HorizontalAlignment="Center">
<StackPanel Orientation="Vertical" Spacing="8" Margin="8">
<TextBox Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit." Width="200"
FontFamily="Comic Sans MS"
Foreground="Blue">
<TextBox.ContextFlyout>
<Flyout>
<TextBlock>Custom context flyout</TextBlock>
</Flyout>
</TextBox.ContextFlyout>
</TextBox>
<TextBox Width="200" PlaceholderText="ReadOnly" IsReadOnly="True" Text="This is read only"/>
<TextBox Width="200" PlaceholderText="Numeric with placeholder" TextInputOptions.ContentType="Number" />
<TextBox Width="200"
PlaceholderText="Floating Placeholder"
UseFloatingPlaceholder="True"
Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<TextBox Width="200"
PlaceholderText="Custom Placeholder Color"
PlaceholderForeground="Red"/>
<TextBox Width="200"
PlaceholderText="Floating Placeholder Color"
UseFloatingPlaceholder="True"
PlaceholderForeground="Purple"/>
<MaskedTextBox Width="200" ResetOnSpace="False" Mask="(LLL) 999-0000"/>
<TextBox Width="200" Text="Validation Error">
<DataValidationErrors.Error>
<sys:Exception />
</DataValidationErrors.Error>
</TextBox>
<TextBox Width="200"
PlaceholderText="Password Box"
Classes="revealPasswordButton"
TextInputOptions.ContentType="Password"
UseFloatingPlaceholder="True"
PasswordChar="*"
Text="Password" />
<TextBox Width="200" PlaceholderText="Suggestions are hidden" TextInputOptions.ShowSuggestions="False" />
<TextBox Width="200" Text="Left aligned text" TextAlignment="Left" AcceptsTab="True" />
<TextBox Width="200" Text="Center aligned text" TextAlignment="Center" />
<TextBox Width="200" Text="Right aligned text" TextAlignment="Right" />
<TextBox Width="200" Text="Custom selection brush"
SelectionStart="5" SelectionEnd="22"
SelectionBrush="Green" SelectionForegroundBrush="Yellow" ClearSelectionOnLostFocus="False"/>
<TextBox Width="200" Text="Custom caret brush" CaretBrush="DarkOrange"/>
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="8" Margin="8">
<TextBox AcceptsReturn="True" TextWrapping="Wrap" Width="200" Height="125"
Text="Multiline TextBox with TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
<TextBox AcceptsReturn="True" Width="200" Height="125"
Text="Multiline TextBox with no TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
<TextBox Classes="clearButton" Text="Clear Content" Width="200" FontWeight="Normal" FontStyle="Normal" PlaceholderText="Placeholder" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
<TextBox Text="IME small font" Width="200"
FontFamily="Comic Sans MS"
FontSize="10"
Foreground="Red"/>
<TextBox Text="IME large font" Width="200"
FontFamily="Comic Sans MS"
FontSize="22"
Foreground="Red"/>
<TextBox Text="IME disabled" Width="200"
FontFamily="Comic Sans MS"
InputMethod.IsInputMethodEnabled="False"
Foreground="Red"/>
<TextBox AcceptsReturn="True"
TextWrapping="Wrap"
Width="200"
Height="125"
LineHeight="32"
Text="Multiline TextBox with TextWrapping and increased LineHeight.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="8" Margin="8">
<Label Classes="h2" Target="{Binding #firstResMFont}">res_m fonts</Label>
<TextBox Width="200" x:Name="firstResMFont" Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="resm:ControlCatalog.Assets.Fonts?assembly=ControlCatalog#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="resm:ControlCatalog.Assets.Fonts?assembly=ControlCatalog#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="resm:ControlCatalog.Assets.Fonts.SourceSansPro-Italic.ttf?assembly=ControlCatalog#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="resm:ControlCatalog.Assets.Fonts.SourceSansPro-*.ttf?assembly=ControlCatalog#Source Sans Pro"/>
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="8" Margin="8">
<Label Classes="h2" Target="{Binding #firstResFont}">_res fonts</Label>
<TextBox Width="200" x:Name="firstResFont" Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-Italic.ttf#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-*.ttf#Source Sans Pro"/>
<TextBox AcceptsReturn="True" TextWrapping="Wrap" Width="500" Height="300"
Text="Multiline TextBox with TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est. Multiline TextBox with TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est. Multiline TextBox with TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est. Multiline TextBox with TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
</StackPanel>
</WrapPanel>
<TextBox AcceptsReturn="True" TextWrapping="Wrap" Height="200" MaxWidth="400"
FontFamily="avares://ControlCatalog/Assets/Fonts#WenQuanYi Micro Hei"
Text="计算机科学(是系统性研究信息与计算的理论基础以及它们在计算机系统中如何实现与应用的实用技术的学科。它通常被形容为对那些创造、描述以及转换信息的算法处理的系统研究。计算机科学包含很多分支领域;有些强调特定结果的计算,比如计算机图形学;而有些是探討计算问题的性质,比如计算复杂性理论;还有一些领域專注于怎样实现计算,比如程式語言理論是研究描述计算的方法,而程式设计是应用特定的程式語言解决特定的计算问题,人机交互则是專注于怎样使计算机和计算变得有用、好用,以及随时随地为人所用。&#xD;&#xD;有时公众会误以为计算机科学就是解决计算机问题的事业(比如信息技术),或者只是与使用计算机的经验有关,如玩游戏、上网或者文字处理。其实计算机科学所关注的,不仅仅是去理解实现类似游戏、浏览器这些软件的程序的性质,更要通过现有的知识创造新的程序或者改进已有的程序。" />
</StackPanel>
</StackPanel>
</UserControl>

11
src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs

@ -133,8 +133,6 @@ namespace Avalonia.Input.GestureRecognizers
_trackedRootPoint = new Point(
_trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? ScrollStartDistance : -ScrollStartDistance),
_trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? ScrollStartDistance : -ScrollStartDistance));
Capture(e.Pointer);
}
}
@ -145,9 +143,14 @@ namespace Avalonia.Input.GestureRecognizers
_velocityTracker?.AddPosition(TimeSpan.FromMilliseconds(e.Timestamp), _pointerPressedPoint - rootPoint);
_lastMoveTimestamp = e.Timestamp;
Target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector));
var scrollEventArgs = new ScrollGestureEventArgs(_gestureId, vector);
Target!.RaiseEvent(scrollEventArgs);
_trackedRootPoint = rootPoint;
e.Handled = true;
e.Handled = scrollEventArgs.Handled;
if(e.Handled)
{
Capture(e.Pointer);
}
}
}
}

17
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -1,14 +1,14 @@
using System;
using System.Collections.Generic;
using System.Data;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Media.TextFormatting;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.VisualTree;
@ -334,6 +334,7 @@ namespace Avalonia.Controls.Presenters
protected override bool BypassFlowDirectionPolicies => true;
internal TextSelectionHandleCanvas? TextSelectionHandleCanvas { get; set; }
internal TextBoxTextInputMethodClient? CurrentImClient { get; set; }
/// <summary>
/// Creates the <see cref="TextLayout"/> used to render the text.
@ -1026,15 +1027,7 @@ namespace Avalonia.Controls.Presenters
OnPreeditChanged(PreeditText, PreeditTextCursorPosition);
}
if(change.Property == TextProperty)
{
if (!string.IsNullOrEmpty(PreeditText))
{
SetCurrentValue(PreeditTextProperty, null);
}
}
if(change.Property == CaretIndexProperty)
if(change.Property == TextProperty || change.Property == CaretIndexProperty)
{
if (!string.IsNullOrEmpty(PreeditText))
{
@ -1067,7 +1060,7 @@ namespace Avalonia.Controls.Presenters
case nameof(SelectionStart):
case nameof(SelectionEnd):
case nameof(SelectionForegroundBrush):
case nameof(ShowSelectionHighlightProperty):
case nameof(ShowSelectionHighlight):
case nameof(PasswordChar):
case nameof(RevealPassword):

348
src/Avalonia.Controls/Primitives/TextSelectionCanvas.cs

@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
@ -11,6 +12,7 @@ namespace Avalonia.Controls.Primitives
internal class TextSelectionHandleCanvas : Canvas
{
private const int ContextMenuPadding = 16;
private static bool s_isInTouchMode;
private readonly TextSelectionHandle _caretHandle;
private readonly TextSelectionHandle _startHandle;
@ -18,7 +20,8 @@ namespace Avalonia.Controls.Primitives
private TextPresenter? _presenter;
private TextBox? _textBox;
private bool _showHandle;
private bool _canShowContextMenu = true;
private IDisposable? _showDisposable;
private PresenterVisualListener _layoutListener;
internal bool ShowHandles
{
@ -66,30 +69,46 @@ namespace Avalonia.Controls.Primitives
_caretHandle.SetTopLeft(default);
_endHandle.SetTopLeft(default);
_startHandle.PointerReleased += Handle_PointerReleased;
_caretHandle.PointerReleased += Handle_PointerReleased;
_endHandle.PointerReleased += Handle_PointerReleased;
_startHandle.Holding += Caret_Holding;
_caretHandle.Holding += Caret_Holding;
_endHandle.Holding += Caret_Holding;
_startHandle.ContextCanceled += Caret_ContextCanceled;
_caretHandle.ContextCanceled += Caret_ContextCanceled;
_endHandle.ContextCanceled += Caret_ContextCanceled;
_startHandle.ContextRequested += Caret_ContextRequested;
_caretHandle.ContextRequested += Caret_ContextRequested;
_endHandle.ContextRequested += Caret_ContextRequested;
IsVisible = ShowHandles;
ClipToBounds = false;
_layoutListener = new PresenterVisualListener();
_layoutListener.Invalidated += LayoutListener_Invalidated;
}
private void Handle_PointerReleased(object? sender, PointerReleasedEventArgs e)
private void LayoutListener_Invalidated(object? sender, EventArgs e)
{
ShowContextMenu();
if (ShowHandles)
MoveHandlesToSelection();
}
private void Caret_ContextCanceled(object? sender, RoutedEventArgs e)
{
CloseFlyout();
}
private void Caret_ContextRequested(object? sender, ContextRequestedEventArgs e)
{
ShowFlyout(e);
e.Handled = true;
}
private void Handle_DragStarted(object? sender, VectorEventArgs e)
{
if (_textBox?.ContextFlyout is { } flyout)
{
flyout.Hide();
}
CloseFlyout();
}
private void CloseFlyout()
{
_presenter?.RaiseEvent(new Interactivity.RoutedEventArgs(InputElement.ContextCanceledEvent));
}
private void EndHandle_DragDelta(object? sender, VectorEventArgs e)
@ -106,41 +125,99 @@ namespace Avalonia.Controls.Primitives
private void CaretHandle_DragDelta(object? sender, VectorEventArgs e)
{
_canShowContextMenu = false;
if (_presenter != null && _textBox != null)
{
var point = ToPresenter(_caretHandle.IndicatorPosition);
using var _ = BeginChange();
_presenter.MoveCaretToPoint(point);
_textBox.SelectionStart = _textBox.SelectionEnd = _presenter.CaretIndex;
var points = _presenter.GetCaretPoints();
var caretIndex = _presenter.CaretIndex;
_textBox.SetCurrentValue(TextBox.CaretIndexProperty, caretIndex);
_textBox.SetCurrentValue(TextBox.SelectionStartProperty, caretIndex);
_textBox.SetCurrentValue(TextBox.SelectionEndProperty, caretIndex);
ClampHandle(_caretHandle);
}
}
_caretHandle?.SetTopLeft(ToLayer(points.Item2));
public void Hide()
{
ShowHandles = false;
}
internal void ShowOnFocused()
{
if (s_isInTouchMode)
{
MoveHandlesToSelection();
}
}
private IDisposable? BeginChange()
{
return _presenter?.CurrentImClient?.BeginChange();
}
private void ClampHandle(TextSelectionHandle handle)
{
var bounds = _presenter?.GetTransformedBounds();
if (bounds.HasValue)
{
var point = _caretHandle.IndicatorPosition;
var rect = bounds.Value.Clip;
if (point.X < rect.X)
point = point.WithX(rect.X);
if (point.X > rect.Right)
point = point.WithX(rect.Right);
if (point.Y < rect.Y)
point = point.WithY(rect.Y);
if (point.Y > rect.Bottom)
point = point.WithY(rect.Bottom);
handle?.SetTopLeft(point);
}
}
private void Handle_DragCompleted(object? sender, VectorEventArgs e)
{
MoveHandlesToSelection();
ShowContextMenu();
}
private void EnsureVisible()
{
if (_textBox is { } t && t.VisualRoot is Visual r)
_showDisposable?.Dispose();
_showDisposable = null;
if (_presenter is { } presenter && presenter.VisualRoot is InputElement root)
{
var bounds = t.Bounds;
var topLeft = t.TranslatePoint(default, r) ?? default;
bounds = bounds.WithX(topLeft.X).WithY(topLeft.Y);
var hasSelection = _textBox.SelectionStart != _textBox.SelectionEnd;
_startHandle.IsVisible = bounds.Contains(new Point(GetLeft(_startHandle), GetTop(_startHandle))) &&
ShowHandles && hasSelection;
_endHandle.IsVisible = bounds.Contains(new Point(GetLeft(_endHandle), GetTop(_endHandle))) &&
ShowHandles && hasSelection;
_caretHandle.IsVisible = bounds.Contains(new Point(GetLeft(_caretHandle), GetTop(_caretHandle))) &&
ShowHandles && !hasSelection;
var bounds = presenter.GetTransformedBounds();
if (bounds == null)
return;
var hasSelection = _presenter.SelectionStart != _presenter.SelectionEnd;
_startHandle.IsVisible = ShowHandles && hasSelection &&
!IsOccluded(new Point(GetLeft(_startHandle), GetTop(_startHandle)));
_endHandle.IsVisible = ShowHandles && hasSelection &&
!IsOccluded(new Point(GetLeft(_endHandle), GetTop(_endHandle)));
_caretHandle.IsVisible = ShowHandles && !hasSelection &&
!IsOccluded(new Point(GetLeft(_caretHandle), GetTop(_caretHandle)));
bool IsOccluded(Point point)
{
return !bounds.Value.Clip.Contains(point);
}
if (ShowHandles && !hasSelection)
{
_showDisposable = DispatcherTimer.RunOnce(() =>
{
ShowHandles = false;
_showDisposable?.Dispose();
}, TimeSpan.FromSeconds(5), DispatcherPriority.Background);
}
}
}
@ -148,10 +225,7 @@ namespace Avalonia.Controls.Primitives
{
if (_presenter != null && _textBox != null)
{
if (_textBox.ContextFlyout is { } flyout)
{
flyout.Hide();
}
CloseFlyout();
var point = ToPresenter(handle.IndicatorPosition);
point = point.WithY(point.Y - _presenter.FontSize / 2);
@ -159,18 +233,19 @@ namespace Avalonia.Controls.Primitives
var position = hit.CharacterHit.FirstCharacterIndex + hit.CharacterHit.TrailingLength;
var otherHandle = handle == _startHandle ? _endHandle : _startHandle;
using var _ = BeginChange();
if (handle.SelectionHandleType == SelectionHandleType.Start)
{
if (position >= _textBox.SelectionEnd)
position = _textBox.SelectionEnd - 1;
_textBox.SelectionStart = position;
_textBox.SetCurrentValue(TextBox.SelectionStartProperty, position);
}
else
{
if (position <= _textBox.SelectionStart)
position = _textBox.SelectionStart + 1;
_textBox.SelectionEnd = position;
_textBox.SetCurrentValue(TextBox.SelectionEndProperty, position);
}
var selectionStart = _textBox.SelectionStart;
@ -182,7 +257,7 @@ namespace Avalonia.Controls.Primitives
if (rects.Count > 0)
{
var first = rects[0];
var last = rects[rects.Count -1];
var last = rects[rects.Count - 1];
if (handle.SelectionHandleType == SelectionHandleType.Start)
handle?.SetTopLeft(ToLayer(first.BottomLeft));
@ -196,9 +271,9 @@ namespace Avalonia.Controls.Primitives
}
_presenter?.MoveCaretToTextPosition(position);
}
EnsureVisible();
EnsureVisible();
}
}
private Point ToLayer(Point point)
@ -211,24 +286,19 @@ namespace Avalonia.Controls.Primitives
return (_presenter is { } p) ? (p.VisualRoot as Visual)?.TranslatePoint(point, p) ?? point : point;
}
private Point ToTextBox(Point point)
{
return (_textBox is { } p) ? (p.VisualRoot as Visual)?.TranslatePoint(point, p) ?? point : point;
}
public void MoveHandlesToSelection()
{
if (_presenter == null
|| _textBox == null
|| _startHandle.IsDragging
|| _endHandle.IsDragging
|| _textBox.ContextFlyout?.IsOpen == true
|| _textBox.ContextMenu?.IsOpen == true)
|| _caretHandle.IsDragging
|| _endHandle.IsDragging)
{
return;
}
var hasSelection = _textBox.SelectionStart != _textBox.SelectionEnd;
var selectionStart = _presenter.SelectionStart;
var selectionEnd = _presenter.SelectionEnd;
var hasSelection = selectionStart != selectionEnd;
var points = _presenter.GetCaretPoints();
@ -236,8 +306,6 @@ namespace Avalonia.Controls.Primitives
if (hasSelection)
{
var selectionStart = _textBox.SelectionStart;
var selectionEnd = _textBox.SelectionEnd;
var start = Math.Min(selectionStart, selectionEnd);
var length = Math.Max(selectionStart, selectionEnd) - start;
@ -265,6 +333,10 @@ namespace Avalonia.Controls.Primitives
}
}
}
ShowHandles = true;
EnsureVisible();
}
internal void SetPresenter(TextPresenter? textPresenter)
@ -272,58 +344,77 @@ namespace Avalonia.Controls.Primitives
if (_presenter == textPresenter)
return;
if (_textBox != null)
if (_presenter != null)
{
_textBox.RemoveHandler(TextBox.TextChangingEvent, TextChanged);
_textBox.RemoveHandler(KeyDownEvent, TextBoxKeyDown);
_textBox.PropertyChanged -= TextBoxPropertyChanged;
_textBox.EffectiveViewportChanged -= TextBoxEffectiveViewportChanged;
_textBox.SizeChanged -= TextBox_SizeChanged;
_layoutListener.Detach();
_presenter.RemoveHandler(KeyDownEvent, PresenterKeyDown);
_presenter.RemoveHandler(TappedEvent, PresenterTapped);
_presenter.RemoveHandler(PointerPressedEvent, PresenterPressed);
_presenter.RemoveHandler(GotFocusEvent, PresenterFocused);
if (_textBox != null)
{
_textBox.PropertyChanged -= TextBox_PropertyChanged;
}
_textBox = null;
_presenter = null;
}
_presenter = textPresenter;
if (_presenter != null)
{
_layoutListener.Attach(_presenter);
_presenter.AddHandler(KeyDownEvent, PresenterKeyDown, handledEventsToo: true);
_presenter.AddHandler(TappedEvent, PresenterTapped);
_presenter.AddHandler(PointerPressedEvent, PresenterPressed);
_presenter.AddHandler(GotFocusEvent, PresenterFocused, handledEventsToo: true);
_textBox = _presenter.FindAncestorOfType<TextBox>();
if (_textBox != null)
{
_textBox.AddHandler(TextBox.TextChangingEvent, TextChanged, handledEventsToo: true);
_textBox.AddHandler(KeyDownEvent, TextBoxKeyDown, handledEventsToo: true);
_textBox.PropertyChanged += TextBoxPropertyChanged;
_textBox.EffectiveViewportChanged += TextBoxEffectiveViewportChanged;
_textBox.SizeChanged += TextBox_SizeChanged;
_textBox.PropertyChanged += TextBox_PropertyChanged;
}
}
}
private void TextBox_SizeChanged(object? sender, SizeChangedEventArgs e)
private void PresenterPressed(object? sender, PointerPressedEventArgs e)
{
InvalidateMeasure();
s_isInTouchMode = e.Pointer.Type != PointerType.Mouse;
}
private void TextBoxEffectiveViewportChanged(object? sender, EffectiveViewportChangedEventArgs e)
private void PresenterFocused(object? sender, GotFocusEventArgs e)
{
if (ShowHandles)
if (_presenter != null && _presenter.SelectionStart != _presenter.SelectionEnd)
{
MoveHandlesToSelection();
ShowHandles = true;
EnsureVisible();
}
}
private void Caret_Holding(object? sender, HoldingRoutedEventArgs e)
private void PresenterTapped(object? sender, TappedEventArgs e)
{
s_isInTouchMode = e.Pointer.Type != PointerType.Mouse;
if (s_isInTouchMode)
MoveHandlesToSelection();
else
{
ShowHandles = false;
_showDisposable?.Dispose();
_showDisposable = null;
}
}
private void Presenter_SizeChanged(object? sender, SizeChangedEventArgs e)
{
if (ShowContextMenu())
e.Handled = true;
InvalidateMeasure();
}
internal bool ShowContextMenu()
internal bool ShowFlyout(ContextRequestedEventArgs e)
{
if (_textBox != null && _canShowContextMenu)
if (_textBox != null)
{
if (_textBox.ContextFlyout is PopupFlyoutBase flyout)
{
@ -349,7 +440,7 @@ namespace Avalonia.Controls.Primitives
if (handle != null)
{
var topLeft = ToTextBox(handle.GetTopLeft());
var topLeft = ToPresenter(handle.GetTopLeft());
flyout.VerticalOffset = topLeft.Y - verticalOffset;
flyout.HorizontalOffset = topLeft.X;
flyout.Placement = PlacementMode.TopEdgeAlignedLeft;
@ -360,12 +451,10 @@ namespace Avalonia.Controls.Primitives
}
else
{
_textBox.RaiseEvent(new ContextRequestedEventArgs());
_textBox.RaiseEvent(new ContextRequestedEventArgs(e));
}
}
_canShowContextMenu = true;
return false;
}
@ -377,26 +466,105 @@ namespace Avalonia.Controls.Primitives
EnsureVisible();
}
private void TextBoxPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
private void TextBox_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (ShowHandles && (e.Property == TextBox.SelectionStartProperty ||
e.Property == TextBox.SelectionEndProperty))
if (s_isInTouchMode && (e.Property == TextPresenter.SelectionStartProperty ||
e.Property == TextPresenter.SelectionEndProperty))
{
MoveHandlesToSelection();
EnsureVisible();
}
else if (e.Property == TextPresenter.TextProperty)
{
ShowHandles = false;
if (_presenter?.ContextFlyout is { } flyout && flyout.IsOpen)
flyout.Hide();
}
}
private void TextBoxKeyDown(object? sender, KeyEventArgs e)
private void PresenterKeyDown(object? sender, KeyEventArgs e)
{
ShowHandles = false;
s_isInTouchMode = false;
}
private void TextChanged(object? sender, TextChangingEventArgs e)
private class PresenterVisualListener
{
ShowHandles = false;
if (_textBox?.ContextFlyout is { } flyout && flyout.IsOpen)
flyout.Hide();
private List<Visual> _attachedVisuals = new List<Visual>();
private TextPresenter? _presenter;
private object _lock = new object();
public event EventHandler? Invalidated;
public void Attach(TextPresenter presenter)
{
lock (_lock)
{
if (_presenter != null)
throw new InvalidOperationException("Listener is already attached to a TextPresenter");
_presenter = presenter;
presenter.SizeChanged += Presenter_SizeChanged;
presenter.EffectiveViewportChanged += Visual_EffectiveViewportChanged;
void AttachViewportHandler(Visual visual)
{
if (visual is Layoutable layoutable)
{
layoutable.EffectiveViewportChanged += Visual_EffectiveViewportChanged;
}
_attachedVisuals.Add(visual);
}
var visualParent = presenter.VisualParent;
while (visualParent != null)
{
AttachViewportHandler(visualParent);
visualParent = visualParent.VisualParent;
}
}
}
private void Visual_EffectiveViewportChanged(object? sender, Layout.EffectiveViewportChangedEventArgs e)
{
OnInvalidated();
}
private void Presenter_SizeChanged(object? sender, SizeChangedEventArgs e)
{
OnInvalidated();
}
public void Detach()
{
lock (_lock)
{
if (_presenter is { } presenter)
{
presenter.SizeChanged -= Presenter_SizeChanged;
presenter.EffectiveViewportChanged -= Visual_EffectiveViewportChanged;
}
foreach (var visual in _attachedVisuals)
{
if (visual is Layoutable layoutable)
{
layoutable.EffectiveViewportChanged -= Visual_EffectiveViewportChanged;
}
}
_presenter = null;
_attachedVisuals.Clear();
}
}
private void OnInvalidated()
{
Invalidated?.Invoke(this, EventArgs.Empty);
}
}
}
}

16
src/Avalonia.Controls/Primitives/TextSelectionHandle.cs

@ -10,6 +10,8 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public class TextSelectionHandle : Thumb
{
private const int DragDetectionRadius = 2;
internal SelectionHandleType SelectionHandleType { get; set; }
private Point _startPosition;
@ -59,9 +61,13 @@ namespace Avalonia.Controls.Primitives
protected override void OnDragDelta(VectorEventArgs e)
{
base.OnDragDelta(e);
var newDelta = e.Vector;
_delta = e.Vector;
UpdateTextSelectionHandlePosition();
if (Math.Abs((newDelta - _delta).Length) > DragDetectionRadius)
{
_delta = e.Vector;
UpdateTextSelectionHandlePosition();
}
}
protected override void OnDragCompleted(VectorEventArgs e)
@ -134,6 +140,9 @@ namespace Avalonia.Controls.Primitives
protected override void OnPointerMoved(PointerEventArgs e)
{
if (e.Pointer.Captured != this)
return;
VectorEventArgs ev;
if (!_lastPoint.HasValue)
@ -163,8 +172,8 @@ namespace Avalonia.Controls.Primitives
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
e.Handled = true;
PseudoClasses.Add(":pressed");
e.Pointer.Capture(this);
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
@ -184,6 +193,7 @@ namespace Avalonia.Controls.Primitives
}
PseudoClasses.Remove(":pressed");
e.Pointer.Capture(null);
}
}
}

399
src/Avalonia.Controls/TextBox.cs

@ -1,24 +1,26 @@
using Avalonia.Input.Platform;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Linq;
using Avalonia.Reactive;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.Utilities;
using Avalonia.Controls.Metadata;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Automation.Peers;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Reactive;
using Avalonia.Threading;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
@ -30,6 +32,11 @@ namespace Avalonia.Controls
[PseudoClasses(":empty")]
public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost
{
/// <summary>
/// The radius for touch input. Used to determine if selection should change from moving a touch pointer.
/// </summary>
private readonly static int s_touchRadius = (int)((AvaloniaLocator.Current?.GetService<IPlatformSettings>()?.GetTapSize(PointerType.Touch).Height ?? 10) / 2) + 5;
/// <summary>
/// Gets a platform-specific <see cref="KeyGesture"/> for the Cut action
/// </summary>
@ -368,9 +375,13 @@ namespace Avalonia.Controls
private int _wordSelectionStart = -1;
private int _selectedTextChangesMadeSinceLastUndoSnapshot;
private bool _hasDoneSnapshotOnce;
private static bool _isHolding;
private int _currentClickCount;
private bool _isDoubleTapped;
private bool _isInTouchMode;
private Point _lastPoint;
private bool _isInTouchSelectionMode;
private bool _isInTouchCaretMode;
private bool _hasTouchSelection;
private const int _maxCharsBeforeUndoSnapshot = 7;
static TextBox()
@ -467,7 +478,7 @@ namespace Avalonia.Controls
SetCurrentValue(SelectionStartProperty, newValue);
SetCurrentValue(SelectionEndProperty, newValue);
_presenter?.SetCurrentValue(TextPresenter.CaretIndexProperty, newValue);
_presenter?.SetCurrentValue(TextPresenter.CaretIndexProperty, newValue);
}
/// <summary>
@ -953,18 +964,8 @@ namespace Avalonia.Controls
{
_presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
if (_scrollViewer != null)
{
_scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged;
}
_scrollViewer = e.NameScope.Find<ScrollViewer>("PART_ScrollViewer");
if (_scrollViewer != null)
{
_scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;
}
_imClient.SetPresenter(_presenter, this);
if (IsFocused)
@ -973,11 +974,6 @@ namespace Avalonia.Controls
}
}
private void ScrollViewer_ScrollChanged(object? sender, ScrollChangedEventArgs e)
{
_presenter?.TextSelectionHandleCanvas?.MoveHandlesToSelection();
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
@ -1092,7 +1088,7 @@ namespace Avalonia.Controls
{
base.OnGotFocus(e);
if(_presenter != null)
if (_presenter != null)
{
_presenter.ShowSelectionHighlight = true;
}
@ -1112,6 +1108,9 @@ namespace Avalonia.Controls
_imClient.SetPresenter(_presenter, this);
_presenter?.ShowCaret();
if (SelectionStart != SelectionEnd)
_presenter?.TextSelectionHandleCanvas?.ShowOnFocused();
}
protected override void OnLostFocus(RoutedEventArgs e)
@ -1670,6 +1669,60 @@ namespace Avalonia.Controls
}
}
protected override void OnHolding(HoldingRoutedEventArgs e)
{
base.OnHolding(e);
if (_presenter == null || e.HoldingState != HoldingState.Started)
{
_isInTouchSelectionMode = e.HoldingState == HoldingState.Canceled;
_hasTouchSelection = false;
return;
}
var text = Text;
using var _ = _imClient.BeginChange();
if (text != null)
{
var clickInfo = e.PointerEventArgs.GetCurrentPoint(this);
_presenter.MoveCaretToPoint(clickInfo.Position);
var caretIndex = _presenter.CaretIndex;
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
var isInSelection = selectionStart != selectionEnd &&
caretIndex >= selectionStart && caretIndex <= selectionEnd;
if (isInSelection)
{
_presenter.RaiseEvent(new ContextRequestedEventArgs(e.PointerEventArgs));
}
else
{
// We select the current held word, or the whole hidden content
if (IsPasswordBox && !RevealPassword)
{
_wordSelectionStart = -1;
SelectAll();
}
else
{
selectionStart = selectionEnd = caretIndex;
SelectWord(text, caretIndex, selectionStart, selectionEnd);
_presenter?.TextSelectionHandleCanvas?.ShowOnFocused();
}
}
_hasTouchSelection = true;
e.Handled = true;
}
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
if (_presenter == null)
@ -1682,112 +1735,184 @@ namespace Avalonia.Controls
using var _ = _imClient.BeginChange();
if (text != null && (e.Pointer.Type == PointerType.Mouse || e.ClickCount >= 2) && clickInfo.Properties.IsLeftButtonPressed &&
!(clickInfo.Pointer?.Captured is Border))
_isInTouchMode = false;
_isInTouchSelectionMode = false;
_isDoubleTapped = e.ClickCount == 2;
if (text != null && clickInfo.Pointer?.Captured is not Border)
{
_currentClickCount = e.ClickCount;
var point = e.GetPosition(_presenter);
if (e.Pointer.Type == PointerType.Mouse && clickInfo.Properties.IsLeftButtonPressed)
{
_currentClickCount = e.ClickCount;
var point = e.GetPosition(_presenter);
_presenter.MoveCaretToPoint(point);
_presenter.MoveCaretToPoint(point);
var caretIndex = _presenter.CaretIndex;
var clickToSelect = e.KeyModifiers.HasFlag(KeyModifiers.Shift);
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
var caretIndex = _presenter.CaretIndex;
var clickToSelect = e.KeyModifiers.HasFlag(KeyModifiers.Shift);
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
switch (e.ClickCount)
{
case 1:
if (clickToSelect)
{
if (_wordSelectionStart >= 0)
switch (e.ClickCount)
{
case 1:
if (clickToSelect)
{
UpdateWordSelectionRange(caretIndex, ref selectionStart, ref selectionEnd);
if (_wordSelectionStart >= 0)
{
UpdateWordSelectionRange(caretIndex, ref selectionStart, ref selectionEnd);
SetCurrentValue(SelectionStartProperty, selectionStart);
SetCurrentValue(SelectionEndProperty, selectionEnd);
SetCurrentValue(SelectionStartProperty, selectionStart);
SetCurrentValue(SelectionEndProperty, selectionEnd);
}
else
{
SetCurrentValue(SelectionEndProperty, caretIndex);
}
}
else
{
SetCurrentValue(SelectionStartProperty, caretIndex);
SetCurrentValue(SelectionEndProperty, caretIndex);
_wordSelectionStart = -1;
}
}
else
{
SetCurrentValue(SelectionStartProperty, caretIndex);
SetCurrentValue(SelectionEndProperty, caretIndex);
_wordSelectionStart = -1;
}
break;
case 2:
if (IsPasswordBox && !RevealPassword)
{
// double-clicking in a cloaked single-line password box selects all text
// see https://github.com/AvaloniaUI/Avalonia/issues/14956
goto case 3;
}
break;
case 2:
SelectWord(text, caretIndex, selectionStart, selectionEnd);
if (!StringUtils.IsStartOfWord(text, caretIndex))
{
selectionStart = StringUtils.PreviousWord(text, caretIndex);
}
break;
case 3:
_wordSelectionStart = -1;
if (!StringUtils.IsEndOfWord(text, caretIndex))
{
selectionEnd = StringUtils.NextWord(text, caretIndex);
}
SelectAll();
break;
}
}
else if (e.Pointer.Type != PointerType.Mouse)
{
_isInTouchMode = true;
_lastPoint = e.GetCurrentPoint(_presenter).Position;
if (selectionStart != selectionEnd)
if (_isDoubleTapped)
{
var oldCaret = _presenter.CaretIndex;
_presenter.MoveCaretToPoint(_lastPoint);
var caretIndex = _presenter.CaretIndex;
if(Math.Abs(oldCaret - caretIndex) > 3)
{
_wordSelectionStart = selectionStart;
return;
}
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
SetCurrentValue(SelectionStartProperty, selectionStart);
SetCurrentValue(SelectionEndProperty, selectionEnd);
break;
case 3:
_wordSelectionStart = -1;
SelectAll();
break;
SelectWord(text, caretIndex, selectionStart, selectionEnd);
}
}
}
_isDoubleTapped = e.ClickCount == 2;
e.Pointer.Capture(_presenter);
e.Handled = true;
}
private void SelectWord(string text, int caretIndex, int selectionStart, int selectionEnd)
{
if (IsPasswordBox && !RevealPassword)
{
// double-clicking in a cloaked single-line password box selects all text
// see https://github.com/AvaloniaUI/Avalonia/issues/14956
_wordSelectionStart = -1;
SelectAll();
}
if (!StringUtils.IsStartOfWord(text, caretIndex))
{
selectionStart = StringUtils.PreviousWord(text, caretIndex);
}
if (!StringUtils.IsEndOfWord(text, caretIndex))
{
selectionEnd = StringUtils.NextWord(text, caretIndex);
}
if (selectionStart != selectionEnd)
{
_wordSelectionStart = selectionStart;
}
SetCurrentValue(SelectionStartProperty, selectionStart);
SetCurrentValue(SelectionEndProperty, selectionEnd);
}
protected override void OnPointerMoved(PointerEventArgs e)
{
if (_presenter == null || _isHolding)
if (_presenter == null)
{
return;
}
using var _ = _imClient.BeginChange();
var point = e.GetPosition(_presenter);
// selection should not change during pointer move if the user right clicks
if (e.Pointer.Captured == _presenter && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
if (e.Pointer.Type == PointerType.Mouse)
{
var point = e.GetPosition(_presenter);
// selection should not change during pointer move if the user right clicks
if (e.Pointer.Captured == _presenter && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
point = new Point(
MathUtilities.Clamp(point.X, 0, Math.Max(_presenter.Bounds.Width - 1, 0)),
MathUtilities.Clamp(point.Y, 0, Math.Max(_presenter.Bounds.Height - 1, 0)));
point = new Point(
MathUtilities.Clamp(point.X, 0, Math.Max(_presenter.Bounds.Width - 1, 0)),
MathUtilities.Clamp(point.Y, 0, Math.Max(_presenter.Bounds.Height - 1, 0)));
var previousIndex = _presenter.CaretIndex;
var previousIndex = _presenter.CaretIndex;
_presenter.MoveCaretToPoint(point);
_presenter.MoveCaretToPoint(point);
var caretIndex = _presenter.CaretIndex;
var caretIndex = _presenter.CaretIndex;
if (Math.Abs(caretIndex - previousIndex) == 1)
e.PreventGestureRecognition();
if (e.Pointer.Type == PointerType.Mouse || _isDoubleTapped)
{
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
if (Math.Abs(caretIndex - previousIndex) == 1)
e.PreventGestureRecognition();
if (_wordSelectionStart >= 0)
{
UpdateWordSelectionRange(caretIndex, ref selectionStart, ref selectionEnd);
if (e.Pointer.Type == PointerType.Mouse || _isDoubleTapped)
SetCurrentValue(SelectionStartProperty, selectionStart);
SetCurrentValue(SelectionEndProperty, selectionEnd);
}
else
{
SetCurrentValue(SelectionEndProperty, caretIndex);
}
}
else
{
SetCurrentValue(SelectionStartProperty, caretIndex);
SetCurrentValue(SelectionEndProperty, caretIndex);
}
}
}
else if (_isInTouchMode)
{
if (_isInTouchSelectionMode)
{
point = new Point(
MathUtilities.Clamp(point.X, 0, Math.Max(_presenter.Bounds.Width - 1, 0)),
MathUtilities.Clamp(point.Y, 0, Math.Max(_presenter.Bounds.Height - 1, 0)));
var previousIndex = _presenter.CaretIndex;
_presenter.MoveCaretToPoint(point);
var caretIndex = _presenter.CaretIndex;
if (Math.Abs(caretIndex - previousIndex) == 1)
e.PreventGestureRecognition();
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
@ -1805,8 +1930,28 @@ namespace Avalonia.Controls
}
else
{
SetCurrentValue(SelectionStartProperty, caretIndex);
SetCurrentValue(SelectionEndProperty, caretIndex);
if (!_isInTouchCaretMode)
{
var touchRect = new Rect(_lastPoint.X, _lastPoint.Y, 0, 0).Inflate(s_touchRadius);
var isInRect = touchRect.X < point.X &&
touchRect.Y < point.Y &&
touchRect.Right > point.X &&
touchRect.Bottom > point.Y;
if (!isInRect)
{
_isInTouchCaretMode = true;
}
}
if (_isInTouchCaretMode)
{
e.PreventGestureRecognition();
_presenter.MoveCaretToPoint(point);
var caretIndex = _presenter.CaretIndex;
SetCurrentValue(SelectionStartProperty, caretIndex);
SetCurrentValue(SelectionEndProperty, caretIndex);
}
}
}
}
@ -1851,52 +1996,22 @@ namespace Avalonia.Controls
using var _ = _imClient.BeginChange();
if (e.Pointer.Type != PointerType.Mouse && !_isDoubleTapped)
if (e.Pointer.Type != PointerType.Mouse && !_isInTouchSelectionMode)
{
var text = Text;
var clickInfo = e.GetCurrentPoint(this);
if (text != null && !(clickInfo.Pointer?.Captured is Border))
if (!_isDoubleTapped && !_hasTouchSelection)
{
var point = e.GetPosition(_presenter);
_presenter.MoveCaretToPoint(point);
var caretIndex = _presenter.CaretIndex;
var clickToSelect = e.KeyModifiers.HasFlag(KeyModifiers.Shift);
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
if (clickToSelect)
{
if (_wordSelectionStart >= 0)
{
UpdateWordSelectionRange(caretIndex, ref selectionStart, ref selectionEnd);
SetCurrentValue(SelectionStartProperty, selectionStart);
SetCurrentValue(SelectionEndProperty, selectionEnd);
}
else
{
SetCurrentValue(SelectionEndProperty, caretIndex);
}
}
else
{
SetCurrentValue(SelectionStartProperty, caretIndex);
SetCurrentValue(SelectionEndProperty, caretIndex);
_wordSelectionStart = -1;
}
_presenter.TextSelectionHandleCanvas?.MoveHandlesToSelection();
SetCurrentValue(CaretIndexProperty, caretIndex);
SetCurrentValue(SelectionEndProperty, caretIndex);
SetCurrentValue(SelectionStartProperty, caretIndex);
}
}
// Don't update selection if the pointer was held
if (_isHolding)
{
_isHolding = false;
}
else if (e.InitialPressMouseButton == MouseButton.Right)
if (e.InitialPressMouseButton == MouseButton.Right)
{
var point = e.GetPosition(_presenter);
@ -1917,26 +2032,10 @@ namespace Avalonia.Controls
SetCurrentValue(SelectionStartProperty, caretIndex);
}
}
else if (e.Pointer.Type == PointerType.Touch)
{
if (_currentClickCount == 1)
{
var point = e.GetPosition(_presenter);
_presenter.MoveCaretToPoint(point);
var caretIndex = _presenter.CaretIndex;
SetCurrentValue(SelectionStartProperty, caretIndex);
SetCurrentValue(SelectionEndProperty, caretIndex);
}
_presenter.TextSelectionHandleCanvas?.Show();
if (SelectionStart != SelectionEnd)
{
_presenter.TextSelectionHandleCanvas?.ShowContextMenu();
}
}
_isInTouchMode = false;
_isInTouchSelectionMode = false;
_isInTouchCaretMode = false;
_hasTouchSelection = false;
e.Pointer.Capture(null);
}

3
src/Avalonia.Controls/TextBoxTextInputMethodClient.cs

@ -133,6 +133,7 @@ namespace Avalonia.Controls
if (oldPresenter != null)
{
oldPresenter.CurrentImClient = null;
oldPresenter.ClearValue(TextPresenter.PreeditTextProperty);
oldPresenter.CaretBoundsChanged -= (s, e) => RaiseCursorRectangleChanged();
@ -142,6 +143,8 @@ namespace Avalonia.Controls
if (_presenter != null)
{
_presenter.CurrentImClient = this;
_presenter.CaretBoundsChanged += (s, e) => RaiseCursorRectangleChanged();
}

Loading…
Cancel
Save