diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index 4e742b3b7b..2506fd0624 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/src/Avalonia.Controls/TextBox.cs
@@ -369,6 +369,14 @@ namespace Avalonia.Controls
get { return _newLine; }
set { SetAndRaise(NewLineProperty, ref _newLine, value); }
}
+
+ ///
+ /// Clears the current selection, maintaining the
+ ///
+ public void ClearSelection()
+ {
+ SelectionStart = SelectionEnd = CaretIndex;
+ }
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
@@ -413,8 +421,7 @@ namespace Avalonia.Controls
if (ContextMenu == null || !ContextMenu.IsOpen)
{
- SelectionStart = 0;
- SelectionEnd = 0;
+ ClearSelection();
RevealPassword = false;
}
@@ -444,7 +451,7 @@ namespace Avalonia.Controls
text = Text ?? string.Empty;
SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
CaretIndex += input.Length;
- SelectionStart = SelectionEnd = CaretIndex;
+ ClearSelection();
_undoRedoHelper.DiscardRedo();
}
}
@@ -662,7 +669,7 @@ namespace Avalonia.Controls
SetTextInternal(text.Substring(0, caretIndex - removedCharacters) +
text.Substring(caretIndex));
CaretIndex -= removedCharacters;
- SelectionStart = SelectionEnd = CaretIndex;
+ ClearSelection();
}
_undoRedoHelper.Snapshot();
@@ -735,7 +742,7 @@ namespace Avalonia.Controls
}
else if (movement)
{
- SelectionStart = SelectionEnd = CaretIndex;
+ ClearSelection();
}
if (handled || movement)
@@ -1042,7 +1049,8 @@ namespace Avalonia.Controls
var end = Math.Max(selectionStart, selectionEnd);
var text = Text;
SetTextInternal(text.Substring(0, start) + text.Substring(end));
- SelectionStart = SelectionEnd = CaretIndex = start;
+ CaretIndex = start;
+ ClearSelection();
return true;
}
else
@@ -1131,7 +1139,8 @@ namespace Avalonia.Controls
set
{
Text = value.Text;
- SelectionStart = SelectionEnd = CaretIndex = value.CaretPosition;
+ CaretIndex = value.CaretPosition;
+ ClearSelection();
}
}
}
diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
index 438cbc8b27..3128753781 100644
--- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
+++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
@@ -141,7 +141,7 @@ namespace Avalonia.Controls.Utils
var radiusY = keypoints.RightTop.Y - boundRect.TopRight.Y;
if (radiusX != 0 || radiusY != 0)
{
- context.ArcTo(keypoints.RightTop, new Size(radiusY, radiusY), 0, false, SweepDirection.Clockwise);
+ context.ArcTo(keypoints.RightTop, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
}
// Right
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
index acc3ef16c2..1c49b24f52 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
@@ -18,12 +18,13 @@ namespace Avalonia.Diagnostics.ViewModels
private int _selectedTab;
private string _focusedControl;
private string _pointerOverElement;
+ private bool _shouldVisualizeMarginPadding = true;
public MainViewModel(IControl root)
{
_root = root;
- _logicalTree = new TreePageViewModel(LogicalTreeNode.Create(root));
- _visualTree = new TreePageViewModel(VisualTreeNode.Create(root));
+ _logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root));
+ _visualTree = new TreePageViewModel(this, VisualTreeNode.Create(root));
_events = new EventsPageViewModel(root);
UpdateFocusedControl();
@@ -34,6 +35,17 @@ namespace Avalonia.Diagnostics.ViewModels
Console = new ConsoleViewModel(UpdateConsoleContext);
}
+ public bool ShouldVisualizeMarginPadding
+ {
+ get => _shouldVisualizeMarginPadding;
+ set => RaiseAndSetIfChanged(ref _shouldVisualizeMarginPadding, value);
+ }
+
+ public void ToggleVisualizeMarginPadding()
+ {
+ ShouldVisualizeMarginPadding = !ShouldVisualizeMarginPadding;
+ }
+
public ConsoleViewModel Console { get; }
public ViewModelBase Content
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
index ec48cff399..748f67523b 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
@@ -10,8 +10,9 @@ namespace Avalonia.Diagnostics.ViewModels
private ControlDetailsViewModel _details;
private string _propertyFilter;
- public TreePageViewModel(TreeNode[] nodes)
+ public TreePageViewModel(MainViewModel mainView, TreeNode[] nodes)
{
+ MainView = mainView;
Nodes = nodes;
Selection = new SelectionModel
{
@@ -23,7 +24,9 @@ namespace Avalonia.Diagnostics.ViewModels
{
SelectedNode = (TreeNode)Selection.SelectedItem;
};
- }
+ }
+
+ public MainViewModel MainView { get; }
public TreeNode[] Nodes { get; protected set; }
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
index 663722acba..0165398718 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
@@ -16,6 +16,15 @@
+
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs
index 633d18ddd8..1b61986ce6 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs
@@ -1,7 +1,7 @@
+using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Primitives;
-using Avalonia.Controls.Shapes;
using Avalonia.Diagnostics.ViewModels;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
@@ -11,45 +11,78 @@ namespace Avalonia.Diagnostics.Views
{
internal class TreePageView : UserControl
{
- private Control _adorner;
+ private readonly Panel _adorner;
+ private AdornerLayer _currentLayer;
private TreeView _tree;
public TreePageView()
{
- this.InitializeComponent();
+ InitializeComponent();
_tree.ItemContainerGenerator.Index.Materialized += TreeViewItemMaterialized;
+
+ _adorner = new Panel
+ {
+ ClipToBounds = false,
+ Children =
+ {
+ //Padding frame
+ new Border { BorderBrush = new SolidColorBrush(Colors.Green, 0.5) },
+ //Content frame
+ new Border { Background = new SolidColorBrush(Color.FromRgb(160, 197, 232), 0.5) },
+ //Margin frame
+ new Border { BorderBrush = new SolidColorBrush(Colors.Yellow, 0.5) }
+ },
+ };
}
protected void AddAdorner(object sender, PointerEventArgs e)
{
var node = (TreeNode)((Control)sender).DataContext;
- var layer = AdornerLayer.GetAdornerLayer(node.Visual);
+ var visual = (Visual)node.Visual;
+
+ _currentLayer = AdornerLayer.GetAdornerLayer(visual);
- if (layer != null)
+ if (_currentLayer == null ||
+ _currentLayer.Children.Contains(_adorner))
{
- if (_adorner != null)
- {
- ((Panel)_adorner.Parent).Children.Remove(_adorner);
- _adorner = null;
- }
+ return;
+ }
- _adorner = new Rectangle
- {
- Fill = new SolidColorBrush(0x80a0c5e8),
- [AdornerLayer.AdornedElementProperty] = node.Visual,
- };
+ _currentLayer.Children.Add(_adorner);
+ AdornerLayer.SetAdornedElement(_adorner, visual);
+
+ var vm = (TreePageViewModel) DataContext;
- layer.Children.Add(_adorner);
+ if (vm.MainView.ShouldVisualizeMarginPadding)
+ {
+ var paddingBorder = (Border)_adorner.Children[0];
+ paddingBorder.BorderThickness = visual.GetValue(PaddingProperty);
+
+ var contentBorder = (Border)_adorner.Children[1];
+ contentBorder.Margin = visual.GetValue(PaddingProperty);
+
+ var marginBorder = (Border)_adorner.Children[2];
+ marginBorder.BorderThickness = visual.GetValue(MarginProperty);
+ marginBorder.Margin = InvertThickness(visual.GetValue(MarginProperty));
}
}
+ private static Thickness InvertThickness(Thickness input)
+ {
+ return new Thickness(-input.Left, -input.Top, -input.Right, -input.Bottom);
+ }
+
protected void RemoveAdorner(object sender, PointerEventArgs e)
{
- if (_adorner != null)
+ foreach (var border in _adorner.Children.OfType())
{
- ((Panel)_adorner.Parent).Children.Remove(_adorner);
- _adorner = null;
+ border.Margin = default;
+ border.Padding = default;
+ border.BorderThickness = default;
}
+
+ _currentLayer?.Children.Remove(_adorner);
+ _currentLayer = null;
}
private void InitializeComponent()
diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs
index cfd47d48de..804cf7f8ac 100644
--- a/src/Avalonia.Native/AvaloniaNativePlatform.cs
+++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs
@@ -110,11 +110,20 @@ namespace Avalonia.Native
.Bind().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
.Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta))
.Bind().ToConstant(new MacOSMountedVolumeInfoProvider())
- .Bind().ToConstant(new AvaloniaNativeDragSource(_factory))
- ;
+ .Bind().ToConstant(new AvaloniaNativeDragSource(_factory));
+
if (_options.UseGpu)
- AvaloniaLocator.CurrentMutable.Bind()
- .ToConstant(_glFeature = new GlPlatformFeature(_factory.ObtainGlDisplay()));
+ {
+ try
+ {
+ AvaloniaLocator.CurrentMutable.Bind()
+ .ToConstant(_glFeature = new GlPlatformFeature(_factory.ObtainGlDisplay()));
+ }
+ catch (Exception)
+ {
+ // ignored
+ }
+ }
}
public IWindowImpl CreateWindow()
diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs
index 4b13666edd..08c5d51ea0 100644
--- a/src/Avalonia.Native/WindowImplBase.cs
+++ b/src/Avalonia.Native/WindowImplBase.cs
@@ -351,12 +351,12 @@ namespace Avalonia.Native
public Point PointToClient(PixelPoint point)
{
- return _native.PointToClient(point.ToAvnPoint()).ToAvaloniaPoint();
+ return _native?.PointToClient(point.ToAvnPoint()).ToAvaloniaPoint() ?? default;
}
public PixelPoint PointToScreen(Point point)
{
- return _native.PointToScreen(point.ToAvnPoint()).ToAvaloniaPixelPoint();
+ return _native?.PointToScreen(point.ToAvnPoint()).ToAvaloniaPixelPoint() ?? default;
}
public void Hide()
diff --git a/src/Avalonia.Visuals/Media/GlyphRun.cs b/src/Avalonia.Visuals/Media/GlyphRun.cs
index a32a3e1b6c..da3a1f721c 100644
--- a/src/Avalonia.Visuals/Media/GlyphRun.cs
+++ b/src/Avalonia.Visuals/Media/GlyphRun.cs
@@ -555,7 +555,7 @@ namespace Avalonia.Media
}
}
- return new Rect(0, 0, width, height);
+ return new Rect(0, GlyphTypeface.Ascent * Scale, width, height);
}
private void Set(ref T field, T value)
@@ -595,8 +595,6 @@ namespace Avalonia.Media
_glyphRunImpl = platformRenderInterface.CreateGlyphRun(this, out var width);
var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale;
-
- _bounds = new Rect(0, 0, width, height);
}
void IDisposable.Dispose()
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
index 8b44e32c48..08d9107bb1 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
@@ -181,6 +181,17 @@ namespace Avalonia.Media.TextFormatting
return nextCharacterHit;
}
+ if (characterHit.FirstCharacterIndex + characterHit.TrailingLength <= TextRange.Start + TextRange.Length)
+ {
+ return characterHit; // Can't move, we're after the last character
+ }
+
+ var runIndex = GetRunIndexAtCodepointIndex(TextRange.End);
+
+ var textRun = _textRuns[runIndex];
+
+ characterHit = textRun.GlyphRun.GetNextCaretCharacterHit(characterHit);
+
return characterHit; // Can't move, we're after the last character
}
@@ -192,6 +203,11 @@ namespace Avalonia.Media.TextFormatting
return previousCharacterHit;
}
+ if (characterHit.FirstCharacterIndex < TextRange.Start)
+ {
+ characterHit = new CharacterHit(TextRange.Start);
+ }
+
return characterHit; // Can't move, we're before the first character
}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs
index eaf4effdbe..bdf05c4f86 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs
@@ -25,7 +25,7 @@ namespace Avalonia.Rendering.SceneGraph
GlyphRun glyphRun,
Point baselineOrigin,
IDictionary childScenes = null)
- : base(glyphRun.Bounds, transform)
+ : base(glyphRun.Bounds.Translate(baselineOrigin), transform)
{
Transform = transform;
Foreground = foreground?.ToImmutable();
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
index c4d67deb4c..349143253e 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
@@ -24,7 +24,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
AvaloniaXamlIlDataContextTypeMetadataNode inferredDataContextTypeNode = null;
AvaloniaXamlIlDataContextTypeMetadataNode directiveDataContextTypeNode = null;
- bool isDataTemplate = on.Type.GetClrType().Equals(context.GetAvaloniaTypes().DataTemplate);
for (int i = 0; i < on.Children.Count; ++i)
{
@@ -57,7 +56,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
inferredDataContextTypeNode = ParseDataContext(context, on, obj);
}
- else if(isDataTemplate
+ else if(context.GetAvaloniaTypes().DataTemplate.IsAssignableFrom(on.Type.GetClrType())
&& pa.Property.Name == "DataType"
&& pa.Values[0] is XamlTypeExtensionNode dataTypeNode)
{
@@ -70,7 +69,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
// do more specialized inference
if (directiveDataContextTypeNode is null)
{
- if (isDataTemplate && inferredDataContextTypeNode is null)
+ if (context.GetAvaloniaTypes().IDataTemplate.IsAssignableFrom(on.Type.GetClrType())
+ && inferredDataContextTypeNode is null)
{
// Infer data type from collection binding on a control that displays items.
var parentObject = context.ParentNodes().OfType().FirstOrDefault();
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
index f4ca76c21c..3dec96dc43 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
@@ -41,6 +41,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType ResolveByNameExtension { get; }
public IXamlType DataTemplate { get; }
+ public IXamlType IDataTemplate { get; }
public IXamlType IItemsPresenterHost { get; }
public IXamlType ItemsRepeater { get; }
public IXamlType ReflectionBindingExtension { get; }
@@ -98,6 +99,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
CompiledBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension");
ResolveByNameExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ResolveByNameExtension");
DataTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.DataTemplate");
+ IDataTemplate = cfg.TypeSystem.GetType("Avalonia.Controls.Templates.IDataTemplate");
IItemsPresenterHost = cfg.TypeSystem.GetType("Avalonia.Controls.Presenters.IItemsPresenterHost");
ItemsRepeater = cfg.TypeSystem.GetType("Avalonia.Controls.ItemsRepeater");
ReflectionBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension");
diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
index f41938a9bb..fe25fa7346 100644
--- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
@@ -562,6 +562,41 @@ namespace Avalonia.Controls.UnitTests
}
}
+ [Fact]
+ public void TextBox_CaretIndex_Persists_When_Focus_Lost()
+ {
+ using (UnitTestApplication.Start(FocusServices))
+ {
+ var target1 = new TextBox
+ {
+ Template = CreateTemplate(),
+ Text = "1234"
+ };
+ var target2 = new TextBox
+ {
+ Template = CreateTemplate(),
+ Text = "5678"
+ };
+ var sp = new StackPanel();
+ sp.Children.Add(target1);
+ sp.Children.Add(target2);
+
+ target1.ApplyTemplate();
+ target2.ApplyTemplate();
+
+ var root = new TestRoot { Child = sp };
+
+ target2.Focus();
+ target2.CaretIndex = 2;
+ Assert.False(target1.IsFocused);
+ Assert.True(target2.IsFocused);
+
+ target1.Focus();
+
+ Assert.Equal(2, target2.CaretIndex);
+ }
+ }
+
[Fact]
public void TextBox_Reveal_Password_Reset_When_Lost_Focus()
{
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
index 3655d78c9d..7abfe29f11 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Linq;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
@@ -10,6 +9,65 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
public class TextLineTests
{
+ private static readonly string s_multiLineText = "012345678\r\r0123456789";
+
+ [Fact]
+ public void Should_Get_First_CharacterHit()
+ {
+ using (Start())
+ {
+ var defaultProperties = new GenericTextRunProperties(Typeface.Default);
+
+ var textSource = new SingleBufferTextSource(s_multiLineText, defaultProperties);
+
+ var formatter = new TextFormatterImpl();
+
+ var currentIndex = 0;
+
+ while (currentIndex < s_multiLineText.Length)
+ {
+ var textLine =
+ formatter.FormatLine(textSource, currentIndex, double.PositiveInfinity,
+ new GenericTextParagraphProperties(defaultProperties));
+
+ var firstCharacterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(int.MinValue));
+
+ Assert.Equal(textLine.TextRange.Start, firstCharacterHit.FirstCharacterIndex);
+
+ currentIndex += textLine.TextRange.Length;
+ }
+ }
+ }
+
+ [Fact]
+ public void Should_Get_Last_CharacterHit()
+ {
+ using (Start())
+ {
+ var defaultProperties = new GenericTextRunProperties(Typeface.Default);
+
+ var textSource = new SingleBufferTextSource(s_multiLineText, defaultProperties);
+
+ var formatter = new TextFormatterImpl();
+
+ var currentIndex = 0;
+
+ while (currentIndex < s_multiLineText.Length)
+ {
+ var textLine =
+ formatter.FormatLine(textSource, currentIndex, double.PositiveInfinity,
+ new GenericTextParagraphProperties(defaultProperties));
+
+ var lastCharacterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(int.MaxValue));
+
+ Assert.Equal(textLine.TextRange.Start + textLine.TextRange.Length,
+ lastCharacterHit.FirstCharacterIndex + lastCharacterHit.TrailingLength);
+
+ currentIndex += textLine.TextRange.Length;
+ }
+ }
+ }
+
[InlineData("𐐷𐐷𐐷𐐷𐐷")]
[InlineData("𐐷1234")]
[Theory]