Browse Source
* add text selection handles * only show selectors on pen and touch input * show mobile themed context menu on mobile * cleanup and track textbox layout changes with EffectiveViewportChanged * make SelectionHandleType as clr property * fix custom flyouts not showing with selection handles * simplify text context menu for mobile. * cleanup * address review, simplify styles, add docs * remove unused resource * remove unused event handler, reformat selection canvas class * prevent thumb positions from swapping * adjust caret position when touch pointer moves * cleanup, disable scrolling in textbox when intent is selectionpull/13445/head
committed by
GitHub
16 changed files with 908 additions and 68 deletions
@ -0,0 +1,23 @@ |
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Represents which part of the selection the TextSelectionHandle controls.
|
|||
/// </summary>
|
|||
public enum SelectionHandleType |
|||
{ |
|||
/// <summary>
|
|||
/// The Handle controls the caret position.
|
|||
/// </summary>
|
|||
Caret, |
|||
|
|||
/// <summary>
|
|||
/// The Handle controls the start of the text selection.
|
|||
/// </summary>
|
|||
Start, |
|||
|
|||
/// <summary>
|
|||
/// The Handle controls the end of the text selection.
|
|||
/// </summary>
|
|||
End |
|||
} |
|||
} |
|||
@ -0,0 +1,387 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using Avalonia.Controls.Presenters; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Layout; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
internal class TextSelectionHandleCanvas : Canvas |
|||
{ |
|||
private const int ContextMenuPadding = 16; |
|||
|
|||
private readonly TextSelectionHandle _caretHandle; |
|||
private readonly TextSelectionHandle _startHandle; |
|||
private readonly TextSelectionHandle _endHandle; |
|||
private TextPresenter? _presenter; |
|||
private TextBox? _textBox; |
|||
private bool _showHandle; |
|||
|
|||
internal bool ShowHandles |
|||
{ |
|||
get => _showHandle; |
|||
set |
|||
{ |
|||
_showHandle = value; |
|||
|
|||
if (!value) |
|||
{ |
|||
_startHandle.IsVisible = false; |
|||
_endHandle.IsVisible = false; |
|||
_caretHandle.IsVisible = false; |
|||
} |
|||
|
|||
IsVisible = value; |
|||
} |
|||
} |
|||
|
|||
public TextSelectionHandleCanvas() |
|||
{ |
|||
_caretHandle = new TextSelectionHandle() { SelectionHandleType = SelectionHandleType.Caret }; |
|||
_startHandle = new TextSelectionHandle(); |
|||
_endHandle = new TextSelectionHandle(); |
|||
|
|||
Children.Add(_caretHandle); |
|||
Children.Add(_startHandle); |
|||
Children.Add(_endHandle); |
|||
|
|||
_caretHandle.DragStarted += Handle_DragStarted; |
|||
_caretHandle.DragDelta += CaretHandle_DragDelta; |
|||
_caretHandle.DragCompleted += Handle_DragCompleted; |
|||
_startHandle.DragDelta += StartHandle_DragDelta; |
|||
_startHandle.DragCompleted += Handle_DragCompleted; |
|||
_startHandle.DragStarted += Handle_DragStarted; |
|||
_endHandle.DragDelta += EndHandle_DragDelta; |
|||
_endHandle.DragCompleted += Handle_DragCompleted; |
|||
_endHandle.DragStarted += Handle_DragStarted; |
|||
|
|||
_caretHandle.Classes.Add("caret"); |
|||
_startHandle.Classes.Add("start"); |
|||
_endHandle.Classes.Add("end"); |
|||
|
|||
_startHandle.SetTopLeft(default); |
|||
_caretHandle.SetTopLeft(default); |
|||
_endHandle.SetTopLeft(default); |
|||
|
|||
IsVisible = ShowHandles; |
|||
|
|||
ClipToBounds = false; |
|||
} |
|||
|
|||
private void Handle_DragStarted(object? sender, VectorEventArgs e) |
|||
{ |
|||
if (_textBox?.ContextFlyout is { } flyout) |
|||
{ |
|||
flyout.Hide(); |
|||
} |
|||
} |
|||
|
|||
private void EndHandle_DragDelta(object? sender, VectorEventArgs e) |
|||
{ |
|||
if (sender is TextSelectionHandle handle) |
|||
DragSelectionHandle(handle); |
|||
} |
|||
|
|||
private void StartHandle_DragDelta(object? sender, VectorEventArgs e) |
|||
{ |
|||
if (sender is TextSelectionHandle handle) |
|||
DragSelectionHandle(handle); |
|||
} |
|||
|
|||
private void CaretHandle_DragDelta(object? sender, VectorEventArgs e) |
|||
{ |
|||
if (_presenter != null && _textBox != null) |
|||
{ |
|||
var point = ToPresenter(_caretHandle.IndicatorPosition); |
|||
_presenter.MoveCaretToPoint(point); |
|||
_textBox.SelectionStart = _textBox.SelectionEnd = _presenter.CaretIndex; |
|||
var points = _presenter.GetCaretPoints(); |
|||
|
|||
_caretHandle?.SetTopLeft(ToLayer(points.Item2)); |
|||
} |
|||
} |
|||
|
|||
private void Handle_DragCompleted(object? sender, VectorEventArgs e) |
|||
{ |
|||
MoveHandlesToSelection(); |
|||
|
|||
ShowContextMenu(); |
|||
} |
|||
|
|||
private void EnsureVisible() |
|||
{ |
|||
if (_textBox is { } t && t.VisualRoot is Visual r) |
|||
{ |
|||
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; |
|||
} |
|||
} |
|||
|
|||
private void DragSelectionHandle(TextSelectionHandle handle) |
|||
{ |
|||
if (_presenter != null && _textBox != null) |
|||
{ |
|||
if (_textBox.ContextFlyout is { } flyout) |
|||
{ |
|||
flyout.Hide(); |
|||
} |
|||
|
|||
var point = ToPresenter(handle.IndicatorPosition); |
|||
point = point.WithY(point.Y - _presenter.FontSize / 2); |
|||
var hit = _presenter.TextLayout.HitTestPoint(point); |
|||
var position = hit.CharacterHit.FirstCharacterIndex + hit.CharacterHit.TrailingLength; |
|||
|
|||
var otherHandle = handle == _startHandle ? _endHandle : _startHandle; |
|||
|
|||
if (handle.SelectionHandleType == SelectionHandleType.Start) |
|||
{ |
|||
if (position >= _textBox.SelectionEnd) |
|||
position = _textBox.SelectionEnd - 1; |
|||
_textBox.SelectionStart = position; |
|||
} |
|||
else |
|||
{ |
|||
if (position <= _textBox.SelectionStart) |
|||
position = _textBox.SelectionStart + 1; |
|||
_textBox.SelectionEnd = position; |
|||
} |
|||
|
|||
var selectionStart = _textBox.SelectionStart; |
|||
var selectionEnd = _textBox.SelectionEnd; |
|||
var start = Math.Min(selectionStart, selectionEnd); |
|||
var length = Math.Max(selectionStart, selectionEnd) - start; |
|||
|
|||
var rects = _presenter.TextLayout.HitTestTextRange(start, length); |
|||
|
|||
if (rects.Any()) |
|||
{ |
|||
var first = rects.First(); |
|||
var last = rects.Last(); |
|||
|
|||
if (handle.SelectionHandleType == SelectionHandleType.Start) |
|||
handle?.SetTopLeft(ToLayer(first.BottomLeft)); |
|||
else |
|||
handle?.SetTopLeft(ToLayer(last.BottomRight)); |
|||
|
|||
if (otherHandle.SelectionHandleType == SelectionHandleType.Start) |
|||
otherHandle?.SetTopLeft(ToLayer(first.BottomLeft)); |
|||
else |
|||
otherHandle?.SetTopLeft(ToLayer(last.BottomRight)); |
|||
} |
|||
|
|||
_presenter?.MoveCaretToTextPosition(position); |
|||
} |
|||
|
|||
EnsureVisible(); |
|||
} |
|||
|
|||
private Point ToLayer(Point point) |
|||
{ |
|||
return (_presenter?.VisualRoot is Visual v) ? _presenter?.TranslatePoint(point, v) ?? point : point; |
|||
} |
|||
|
|||
private Point ToPresenter(Point point) |
|||
{ |
|||
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) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var hasSelection = _textBox.SelectionStart != _textBox.SelectionEnd; |
|||
|
|||
var points = _presenter.GetCaretPoints(); |
|||
|
|||
_caretHandle.SetTopLeft(ToLayer(points.Item2)); |
|||
|
|||
if (hasSelection) |
|||
{ |
|||
var selectionStart = _textBox.SelectionStart; |
|||
var selectionEnd = _textBox.SelectionEnd; |
|||
var start = Math.Min(selectionStart, selectionEnd); |
|||
var length = Math.Max(selectionStart, selectionEnd) - start; |
|||
|
|||
var rects = _presenter.TextLayout.HitTestTextRange(start, length); |
|||
|
|||
if (rects.Any()) |
|||
{ |
|||
var first = rects.First(); |
|||
var last = rects.Last(); |
|||
|
|||
if (!_startHandle.IsDragging) |
|||
{ |
|||
_startHandle.SetTopLeft(ToLayer(first.BottomLeft)); |
|||
_startHandle.SelectionHandleType = selectionStart < selectionEnd ? |
|||
SelectionHandleType.Start : |
|||
SelectionHandleType.End; |
|||
} |
|||
|
|||
if (!_endHandle.IsDragging) |
|||
{ |
|||
_endHandle.SetTopLeft(ToLayer(last.BottomRight)); |
|||
_endHandle.SelectionHandleType = selectionStart > selectionEnd ? |
|||
SelectionHandleType.Start : |
|||
SelectionHandleType.End; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
internal void SetPresenter(TextPresenter? textPresenter) |
|||
{ |
|||
_presenter = textPresenter; |
|||
if (_presenter != null) |
|||
{ |
|||
_textBox = _presenter.FindAncestorOfType<TextBox>(); |
|||
|
|||
if (_textBox != null) |
|||
{ |
|||
_textBox.AddHandler(TextBox.TextChangingEvent, TextChanged, handledEventsToo: true); |
|||
_textBox.AddHandler(KeyDownEvent, TextBoxKeyDown, handledEventsToo: true); |
|||
_textBox.AddHandler(LostFocusEvent, TextBoxLostFocus, handledEventsToo: true); |
|||
_textBox.AddHandler(PointerReleasedEvent, TextBoxPointerReleased, handledEventsToo: true); |
|||
_textBox.AddHandler(Gestures.HoldingEvent, TextBoxHolding, handledEventsToo: true); |
|||
|
|||
_textBox.PropertyChanged += TextBoxPropertyChanged; |
|||
_textBox.EffectiveViewportChanged += TextBoxEffectiveViewportChanged; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (_textBox != null) |
|||
{ |
|||
_textBox.RemoveHandler(TextBox.TextChangingEvent, TextChanged); |
|||
_textBox.RemoveHandler(KeyDownEvent, TextBoxKeyDown); |
|||
_textBox.RemoveHandler(PointerReleasedEvent, TextBoxPointerReleased); |
|||
_textBox.RemoveHandler(LostFocusEvent, TextBoxLostFocus); |
|||
_textBox.RemoveHandler(Gestures.HoldingEvent, TextBoxHolding); |
|||
|
|||
_textBox.PropertyChanged -= TextBoxPropertyChanged; |
|||
_textBox.EffectiveViewportChanged -= TextBoxEffectiveViewportChanged; |
|||
} |
|||
|
|||
_textBox = null; |
|||
} |
|||
} |
|||
|
|||
private void TextBoxEffectiveViewportChanged(object? sender, EffectiveViewportChangedEventArgs e) |
|||
{ |
|||
if (ShowHandles) |
|||
{ |
|||
MoveHandlesToSelection(); |
|||
EnsureVisible(); |
|||
} |
|||
} |
|||
|
|||
private void TextBoxHolding(object? sender, HoldingRoutedEventArgs e) |
|||
{ |
|||
if (ShowContextMenu()) |
|||
e.Handled = true; |
|||
} |
|||
|
|||
internal bool ShowContextMenu() |
|||
{ |
|||
if (_textBox != null) |
|||
{ |
|||
if (_textBox.ContextFlyout is PopupFlyoutBase flyout) |
|||
{ |
|||
var verticalOffset = (double.IsNaN(_textBox.LineHeight) ? _textBox.FontSize : _textBox.LineHeight) + |
|||
ContextMenuPadding; |
|||
|
|||
TextSelectionHandle? handle = null; |
|||
|
|||
if (_textBox.SelectionStart != _textBox.SelectionEnd) |
|||
{ |
|||
if (_startHandle.IsEffectivelyVisible) |
|||
handle = _startHandle; |
|||
else if (_endHandle.IsEffectivelyVisible) |
|||
handle = _endHandle; |
|||
} |
|||
else |
|||
{ |
|||
if (_caretHandle.IsEffectivelyVisible) |
|||
{ |
|||
handle = _caretHandle; |
|||
} |
|||
} |
|||
|
|||
if (handle != null) |
|||
{ |
|||
var topLeft = ToTextBox(handle.GetTopLeft()); |
|||
flyout.VerticalOffset = topLeft.Y - verticalOffset; |
|||
flyout.HorizontalOffset = topLeft.X; |
|||
flyout.Placement = PlacementMode.TopEdgeAlignedLeft; |
|||
_textBox.RaiseEvent(new ContextRequestedEventArgs()); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
_textBox.RaiseEvent(new ContextRequestedEventArgs()); |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
private void TextBoxPointerReleased(object? sender, PointerReleasedEventArgs e) |
|||
{ |
|||
if (e.Pointer.Type != PointerType.Mouse) |
|||
{ |
|||
ShowHandles = true; |
|||
|
|||
MoveHandlesToSelection(); |
|||
EnsureVisible(); |
|||
} |
|||
} |
|||
|
|||
private void TextBoxPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (ShowHandles && (e.Property == TextBox.SelectionStartProperty || |
|||
e.Property == TextBox.SelectionEndProperty)) |
|||
{ |
|||
MoveHandlesToSelection(); |
|||
EnsureVisible(); |
|||
} |
|||
} |
|||
|
|||
private void TextBoxLostFocus(object? sender, RoutedEventArgs e) |
|||
{ |
|||
ShowHandles = false; |
|||
} |
|||
|
|||
private void TextBoxKeyDown(object? sender, KeyEventArgs e) |
|||
{ |
|||
ShowHandles = false; |
|||
} |
|||
|
|||
private void TextChanged(object? sender, TextChangingEventArgs e) |
|||
{ |
|||
ShowHandles = false; |
|||
if (_textBox?.ContextFlyout is { } flyout && flyout.IsOpen) |
|||
flyout.Hide(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,184 @@ |
|||
using System; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// A controls that enables easy control over text selection using touch based input
|
|||
/// </summary>
|
|||
public class TextSelectionHandle : Thumb |
|||
{ |
|||
internal SelectionHandleType SelectionHandleType { get; set; } |
|||
|
|||
private Point _startPosition; |
|||
|
|||
internal Point IndicatorPosition => IsDragging ? _startPosition.WithX(_startPosition.X) + _delta : Bounds.Position.WithX(Bounds.Position.X).WithY(Bounds.Y); |
|||
|
|||
internal bool IsDragging { get; private set; } |
|||
|
|||
private Vector _delta; |
|||
private Point? _lastPoint; |
|||
private TranslateTransform? _transform; |
|||
|
|||
static TextSelectionHandle() |
|||
{ |
|||
DragStartedEvent.AddClassHandler<TextSelectionHandle>((x, e) => x.OnDragStarted(e), RoutingStrategies.Bubble); |
|||
DragDeltaEvent.AddClassHandler<TextSelectionHandle>((x, e) => x.OnDragDelta(e), RoutingStrategies.Bubble); |
|||
DragCompletedEvent.AddClassHandler<TextSelectionHandle>((x, e) => x.OnDragCompleted(e), RoutingStrategies.Bubble); |
|||
} |
|||
|
|||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnAttachedToVisualTree(e); |
|||
|
|||
if (_transform == null) |
|||
{ |
|||
_transform = new TranslateTransform(); |
|||
} |
|||
|
|||
RenderTransform = _transform; |
|||
} |
|||
|
|||
protected override void OnLoaded(RoutedEventArgs args) |
|||
{ |
|||
base.OnLoaded(args); |
|||
|
|||
InvalidateMeasure(); |
|||
} |
|||
protected override void OnDragStarted(VectorEventArgs e) |
|||
{ |
|||
base.OnDragStarted(e); |
|||
|
|||
_startPosition = Bounds.Position; |
|||
_delta = default; |
|||
IsDragging = true; |
|||
} |
|||
|
|||
protected override void OnDragDelta(VectorEventArgs e) |
|||
{ |
|||
base.OnDragDelta(e); |
|||
|
|||
_delta = e.Vector; |
|||
UpdateTextSelectionHandlePosition(); |
|||
} |
|||
|
|||
protected override void OnDragCompleted(VectorEventArgs e) |
|||
{ |
|||
base.OnDragCompleted(e); |
|||
IsDragging = false; |
|||
|
|||
_startPosition = default; |
|||
} |
|||
|
|||
private void UpdateTextSelectionHandlePosition() |
|||
{ |
|||
SetTopLeft(IndicatorPosition); |
|||
} |
|||
|
|||
protected override void ArrangeCore(Rect finalRect) |
|||
{ |
|||
base.ArrangeCore(finalRect); |
|||
|
|||
if (_transform != null) |
|||
{ |
|||
if (SelectionHandleType == SelectionHandleType.Caret) |
|||
{ |
|||
HasMirrorTransform = true; |
|||
_transform.X = Bounds.Width / 2 * -1; |
|||
} |
|||
else if (SelectionHandleType == SelectionHandleType.Start) |
|||
{ |
|||
HasMirrorTransform = true; |
|||
_transform.X = Bounds.Width * -1; |
|||
} |
|||
else |
|||
{ |
|||
HasMirrorTransform = false; |
|||
_transform.X = 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
internal void SetTopLeft(Point point) |
|||
{ |
|||
Canvas.SetTop(this, point.Y); |
|||
Canvas.SetLeft(this, point.X); |
|||
} |
|||
|
|||
internal Point GetTopLeft() |
|||
{ |
|||
return new Point(Canvas.GetLeft(this), Canvas.GetTop(this)); |
|||
} |
|||
|
|||
protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e) |
|||
{ |
|||
if (_lastPoint.HasValue) |
|||
{ |
|||
var ev = new VectorEventArgs |
|||
{ |
|||
RoutedEvent = DragCompletedEvent, |
|||
Vector = _lastPoint.Value, |
|||
}; |
|||
|
|||
_lastPoint = null; |
|||
|
|||
RaiseEvent(ev); |
|||
} |
|||
|
|||
PseudoClasses.Remove(":pressed"); |
|||
|
|||
base.OnPointerCaptureLost(e); |
|||
} |
|||
|
|||
protected override void OnPointerMoved(PointerEventArgs e) |
|||
{ |
|||
if (_lastPoint.HasValue) |
|||
{ |
|||
var ev = new VectorEventArgs |
|||
{ |
|||
RoutedEvent = DragDeltaEvent, |
|||
Vector = e.GetPosition(VisualRoot as Visual) - _lastPoint.Value, |
|||
}; |
|||
|
|||
RaiseEvent(ev); |
|||
} |
|||
} |
|||
|
|||
protected override void OnPointerPressed(PointerPressedEventArgs e) |
|||
{ |
|||
e.Handled = true; |
|||
_lastPoint = e.GetPosition(VisualRoot as Visual); |
|||
|
|||
var ev = new VectorEventArgs |
|||
{ |
|||
RoutedEvent = DragStartedEvent, |
|||
Vector = (Vector)_lastPoint, |
|||
}; |
|||
|
|||
PseudoClasses.Add(":pressed"); |
|||
|
|||
RaiseEvent(ev); |
|||
} |
|||
|
|||
protected override void OnPointerReleased(PointerReleasedEventArgs e) |
|||
{ |
|||
if (_lastPoint.HasValue) |
|||
{ |
|||
e.Handled = true; |
|||
_lastPoint = null; |
|||
|
|||
var ev = new VectorEventArgs |
|||
{ |
|||
RoutedEvent = DragCompletedEvent, |
|||
Vector = (Vector)e.GetPosition(VisualRoot as Visual), |
|||
}; |
|||
|
|||
RaiseEvent(ev); |
|||
} |
|||
|
|||
PseudoClasses.Remove(":pressed"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
using System.Linq; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
public class TextSelectorLayer : Canvas |
|||
{ |
|||
protected override bool BypassFlowDirectionPolicies => true; |
|||
public Size AvailableSize { get; private set; } |
|||
public static TextSelectorLayer? GetTextSelectorLayer(Visual visual) |
|||
{ |
|||
foreach (var v in visual.GetVisualAncestors()) |
|||
if (v is VisualLayerManager vlm) |
|||
if (vlm.TextSelectorLayer != null) |
|||
return vlm.TextSelectorLayer; |
|||
if (visual is TopLevel tl) |
|||
{ |
|||
var layers = tl.GetVisualDescendants().OfType<VisualLayerManager>().FirstOrDefault(); |
|||
return layers?.TextSelectorLayer; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
protected override Size MeasureOverride(Size availableSize) |
|||
{ |
|||
foreach (Control child in Children) |
|||
child.Measure(availableSize); |
|||
return availableSize; |
|||
} |
|||
|
|||
protected override Size ArrangeOverride(Size finalSize) |
|||
{ |
|||
AvailableSize = finalSize; |
|||
return base.ArrangeOverride(finalSize); |
|||
} |
|||
|
|||
public void Add(Control control) |
|||
{ |
|||
Children.Add(control); |
|||
} |
|||
|
|||
public void Remove(Control control) |
|||
{ |
|||
Children.Remove(control); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
<ResourceDictionary xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<x:Double x:Key="TextSelectHandleSize">32</x:Double> |
|||
<GeometryGroup x:Key="TextSelectionHandlePath" FillRule="NonZero"> |
|||
<RectangleGeometry Rect="0,0,16,16"/> |
|||
<EllipseGeometry Center="16,16" |
|||
RadiusX="16" |
|||
RadiusY="16"/> |
|||
</GeometryGroup> |
|||
<GeometryGroup x:Key="TextCaretHandlePath" FillRule="NonZero"> |
|||
<RectangleGeometry Rect="0,0,16,16"> |
|||
<RectangleGeometry.Transform> |
|||
<TransformGroup > |
|||
<RotateTransform CenterX="16" |
|||
Angle="-45"/> |
|||
</TransformGroup> |
|||
</RectangleGeometry.Transform> |
|||
</RectangleGeometry> |
|||
<EllipseGeometry Center="16,22.7" |
|||
RadiusX="16" |
|||
RadiusY="16"/> |
|||
</GeometryGroup> |
|||
<ControlTheme x:Key="{x:Type TextSelectionHandle}" |
|||
TargetType="TextSelectionHandle"> |
|||
<Setter Property="Background" |
|||
Value="{DynamicResource TextControlSelectionHighlightColor}" /> |
|||
<Setter Property="HorizontalAlignment" |
|||
Value="Center" /> |
|||
<Setter Property="Template"> |
|||
<Setter.Value> |
|||
<ControlTemplate> |
|||
<Grid> |
|||
<PathIcon |
|||
Cursor="Arrow" |
|||
Name="PART_HandlePathIcon" |
|||
HorizontalAlignment="Stretch" |
|||
Height="{DynamicResource TextSelectHandleSize}" |
|||
Foreground="{TemplateBinding Background}" |
|||
Data="{DynamicResource TextSelectionHandlePath}" |
|||
/> |
|||
</Grid> |
|||
</ControlTemplate> |
|||
</Setter.Value> |
|||
</Setter> |
|||
<Style Selector="^.caret /template/ PathIcon#PART_HandlePathIcon"> |
|||
<Setter Property="Data" Value="{DynamicResource TextCaretHandlePath}" /> |
|||
</Style> |
|||
</ControlTheme> |
|||
</ResourceDictionary> |
|||
@ -0,0 +1,52 @@ |
|||
<ResourceDictionary xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<x:Double x:Key="TextSelectHandleSize">32</x:Double> |
|||
<ControlTheme x:Key="{x:Type TextSelectionHandle}" |
|||
TargetType="TextSelectionHandle"> |
|||
<Setter Property="Background" |
|||
Value="{DynamicResource HighlightBrush}" /> |
|||
<Setter Property="HorizontalAlignment" |
|||
Value="Center" /> |
|||
<Setter Property="Template"> |
|||
<Setter.Value> |
|||
<ControlTemplate> |
|||
<Grid> |
|||
<PathIcon |
|||
Cursor="Arrow" |
|||
Name="PART_HandlePathIcon" |
|||
HorizontalAlignment="Stretch" |
|||
Height="{DynamicResource TextSelectHandleSize}" |
|||
Foreground="{TemplateBinding Background}" |
|||
> |
|||
<PathIcon.Data> |
|||
<GeometryGroup FillRule="NonZero"> |
|||
<RectangleGeometry Rect="0,0,16,16"/> |
|||
<EllipseGeometry Center="16,16" |
|||
RadiusX="16" |
|||
RadiusY="16"/> |
|||
</GeometryGroup> |
|||
</PathIcon.Data> |
|||
</PathIcon> |
|||
</Grid> |
|||
</ControlTemplate> |
|||
</Setter.Value> |
|||
</Setter> |
|||
<Style Selector="^.caret /template/ PathIcon#PART_HandlePathIcon"> |
|||
<Setter Property="Data"> |
|||
<GeometryGroup FillRule="NonZero"> |
|||
<RectangleGeometry Rect="0,0,16,16"> |
|||
<RectangleGeometry.Transform> |
|||
<TransformGroup > |
|||
<RotateTransform CenterX="16" |
|||
Angle="-45"/> |
|||
</TransformGroup> |
|||
</RectangleGeometry.Transform> |
|||
</RectangleGeometry> |
|||
<EllipseGeometry Center="16,22.7" |
|||
RadiusX="16" |
|||
RadiusY="16"/> |
|||
</GeometryGroup> |
|||
</Setter> |
|||
</Style> |
|||
</ControlTheme> |
|||
</ResourceDictionary> |
|||
Loading…
Reference in new issue