diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
index 7f2bb128da..f133fa34f6 100644
--- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
@@ -298,14 +298,15 @@ HRESULT WindowBaseImpl::Resize(double x, double y, AvnPlatformResizeReason reaso
}
@try {
- lastSize = NSSize {x, y};
-
- if (!_shown) {
- BaseEvents->Resized(AvnSize{x, y}, reason);
- }
- else if(Window != nullptr) {
- [Window setContentSize:lastSize];
- [Window invalidateShadow];
+ if(x != lastSize.width || y != lastSize.height) {
+ lastSize = NSSize{x, y};
+
+ if (!_shown) {
+ BaseEvents->Resized(AvnSize{x, y}, reason);
+ } else if (Window != nullptr) {
+ [Window setContentSize:lastSize];
+ [Window invalidateShadow];
+ }
}
}
@finally {
diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
index ec88852feb..e52430f50b 100644
--- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
+++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
@@ -9,42 +9,37 @@
1.0
apk
true
+ android-arm64;android-x64
-
-
-
Resources\drawable\Icon.png
-
- False
- False
+
True
+
+
+
True
no-write-symbols,nodebug
Hybrid
True
-
- False
- False
-
-
-
- True
+
+ True
+ True
-
-
+
+
-
\ No newline at end of file
+
diff --git a/samples/ControlCatalog.Android/Properties/AndroidManifest.xml b/samples/ControlCatalog.Android/Properties/AndroidManifest.xml
index aa570ec504..6f551d2b01 100644
--- a/samples/ControlCatalog.Android/Properties/AndroidManifest.xml
+++ b/samples/ControlCatalog.Android/Properties/AndroidManifest.xml
@@ -1,4 +1,5 @@
-
+
+
diff --git a/samples/ControlCatalog.Android/environment.device.txt b/samples/ControlCatalog.Android/environment.device.txt
new file mode 100644
index 0000000000..107d68ca1b
--- /dev/null
+++ b/samples/ControlCatalog.Android/environment.device.txt
@@ -0,0 +1 @@
+DOTNET_DiagnosticPorts=127.0.0.1:9000,suspend
diff --git a/samples/ControlCatalog.Android/environment.emulator.txt b/samples/ControlCatalog.Android/environment.emulator.txt
new file mode 100644
index 0000000000..299a0ec30b
--- /dev/null
+++ b/samples/ControlCatalog.Android/environment.emulator.txt
@@ -0,0 +1 @@
+DOTNET_DiagnosticPorts=10.0.2.2:9001,suspend
diff --git a/samples/RenderDemo/Pages/GlyphRunPage.xaml b/samples/RenderDemo/Pages/GlyphRunPage.xaml
index c2914e8847..7db58e5286 100644
--- a/samples/RenderDemo/Pages/GlyphRunPage.xaml
+++ b/samples/RenderDemo/Pages/GlyphRunPage.xaml
@@ -2,13 +2,13 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:local="clr-namespace:RenderDemo.Pages"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="RenderDemo.Pages.GlyphRunPage">
-
-
-
-
+
+
+
diff --git a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
index 7f85606957..674ed8e61f 100644
--- a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
+++ b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
@@ -9,14 +9,6 @@ namespace RenderDemo.Pages
{
public class GlyphRunPage : UserControl
{
- private Image _imageControl;
- private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
- private readonly Random _rand = new Random();
- private ushort[] _glyphIndices = new ushort[1];
- private char[] _characters = new char[1];
- private float _fontSize = 20;
- private int _direction = 10;
-
public GlyphRunPage()
{
this.InitializeComponent();
@@ -25,19 +17,43 @@ namespace RenderDemo.Pages
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
+ }
+ }
+
+ public class GlyphRunControl : Control
+ {
+ private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
+ private readonly Random _rand = new Random();
+ private ushort[] _glyphIndices = new ushort[1];
+ private char[] _characters = new char[1];
+ private float _fontSize = 20;
+ private int _direction = 10;
- _imageControl = this.FindControl("imageControl");
- _imageControl.Source = new DrawingImage();
+ private DispatcherTimer _timer;
- DispatcherTimer.Run(() =>
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ _timer = new DispatcherTimer
+ {
+ Interval = TimeSpan.FromSeconds(1)
+ };
+
+ _timer.Tick += (s,e) =>
{
- UpdateGlyphRun();
+ InvalidateVisual();
+ };
+
+ _timer.Start();
+ }
+
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ _timer.Stop();
- return true;
- }, TimeSpan.FromSeconds(1));
+ _timer = null;
}
- private void UpdateGlyphRun()
+ public override void Render(DrawingContext context)
{
var c = (char)_rand.Next(65, 90);
@@ -57,27 +73,70 @@ namespace RenderDemo.Pages
_characters[0] = c;
- var scale = (double)_fontSize / _glyphTypeface.DesignEmHeight;
+ var glyphRun = new GlyphRun(_glyphTypeface, _fontSize, _characters, _glyphIndices);
- var drawingGroup = new DrawingGroup();
+ context.DrawGlyphRun(Brushes.Black, glyphRun);
+ }
+ }
- var glyphRunDrawing = new GlyphRunDrawing
+ public class GlyphRunGeometryControl : Control
+ {
+ private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
+ private readonly Random _rand = new Random();
+ private ushort[] _glyphIndices = new ushort[1];
+ private char[] _characters = new char[1];
+ private float _fontSize = 20;
+ private int _direction = 10;
+
+ private DispatcherTimer _timer;
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ _timer = new DispatcherTimer
{
- Foreground = Brushes.Black,
- GlyphRun = new GlyphRun(_glyphTypeface, _fontSize, _characters, _glyphIndices)
+ Interval = TimeSpan.FromSeconds(1)
};
- drawingGroup.Children.Add(glyphRunDrawing);
-
- var geometryDrawing = new GeometryDrawing
+ _timer.Tick += (s, e) =>
{
- Pen = new Pen(Brushes.Black),
- Geometry = new RectangleGeometry { Rect = new Rect(glyphRunDrawing.GlyphRun.Size) }
+ InvalidateVisual();
};
- drawingGroup.Children.Add(geometryDrawing);
+ _timer.Start();
+ }
+
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ _timer.Stop();
+
+ _timer = null;
+ }
+
+ public override void Render(DrawingContext context)
+ {
+ var c = (char)_rand.Next(65, 90);
+
+ if (_fontSize + _direction > 200)
+ {
+ _direction = -10;
+ }
+
+ if (_fontSize + _direction < 20)
+ {
+ _direction = 10;
+ }
+
+ _fontSize += _direction;
+
+ _glyphIndices[0] = _glyphTypeface.GetGlyph(c);
+
+ _characters[0] = c;
+
+ var glyphRun = new GlyphRun(_glyphTypeface, _fontSize, _characters, _glyphIndices);
+
+ var geometry = glyphRun.BuildGeometry();
- (_imageControl.Source as DrawingImage).Drawing = drawingGroup;
+ context.DrawGeometry(Brushes.Green, null, geometry);
}
}
}
diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs
index fc988a8d6c..446f135c83 100644
--- a/src/Avalonia.Base/Layout/LayoutManager.cs
+++ b/src/Avalonia.Base/Layout/LayoutManager.cs
@@ -28,7 +28,7 @@ namespace Avalonia.Layout
public LayoutManager(ILayoutRoot owner)
{
_owner = owner ?? throw new ArgumentNullException(nameof(owner));
- _executeLayoutPass = ExecuteLayoutPass;
+ _executeLayoutPass = ExecuteQueuedLayoutPass;
}
public virtual event EventHandler? LayoutUpdated;
@@ -94,6 +94,16 @@ namespace Avalonia.Layout
QueueLayoutPass();
}
+ private void ExecuteQueuedLayoutPass()
+ {
+ if (!_queued)
+ {
+ return;
+ }
+
+ ExecuteLayoutPass();
+ }
+
///
public virtual void ExecuteLayoutPass()
{
@@ -319,8 +329,8 @@ namespace Avalonia.Layout
{
if (!_queued && !_running)
{
- Dispatcher.UIThread.Post(_executeLayoutPass, DispatcherPriority.Layout);
_queued = true;
+ Dispatcher.UIThread.Post(_executeLayoutPass, DispatcherPriority.Layout);
}
}
diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs
index 22be8d8865..25c35a28e5 100644
--- a/src/Avalonia.Base/Media/GlyphRun.cs
+++ b/src/Avalonia.Base/Media/GlyphRun.cs
@@ -194,6 +194,25 @@ namespace Avalonia.Media
}
}
+ ///
+ /// Obtains geometry for the glyph run.
+ ///
+ /// The geometry returned contains the combined geometry of all glyphs in the glyph run.
+ public Geometry BuildGeometry()
+ {
+ var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService();
+
+ var geometryImpl = platformRenderInterface.BuildGlyphRunGeometry(this, out var scale);
+
+ var geometry = new PlatformGeometry(geometryImpl);
+
+ var transform = new MatrixTransform(Matrix.CreateTranslation(geometry.Bounds.Left, -geometry.Bounds.Top) * scale);
+
+ geometry.Transform = transform;
+
+ return geometry;
+ }
+
///
/// Retrieves the offset from the leading edge of the
/// to the leading or trailing edge of a caret stop containing the specified character hit.
diff --git a/src/Avalonia.Base/Media/PlatformGeometry.cs b/src/Avalonia.Base/Media/PlatformGeometry.cs
new file mode 100644
index 0000000000..f25a14540f
--- /dev/null
+++ b/src/Avalonia.Base/Media/PlatformGeometry.cs
@@ -0,0 +1,24 @@
+using Avalonia.Platform;
+
+namespace Avalonia.Media
+{
+ internal class PlatformGeometry : Geometry
+ {
+ private readonly IGeometryImpl _geometryImpl;
+
+ public PlatformGeometry(IGeometryImpl geometryImpl)
+ {
+ _geometryImpl = geometryImpl;
+ }
+
+ public override Geometry Clone()
+ {
+ return new PlatformGeometry(_geometryImpl);
+ }
+
+ protected override IGeometryImpl? CreateDefiningGeometry()
+ {
+ return _geometryImpl;
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
index 0eeefddf0b..bfa9e70fce 100644
--- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
+++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
@@ -58,6 +58,14 @@ namespace Avalonia.Platform
/// A combined geometry.
IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2);
+ ///
+ /// Created a geometry implementation for the glyph run.
+ ///
+ /// The glyph run to build a geometry from.
+ /// The scaling of the produces geometry.
+ /// The geometry returned contains the combined geometry of all glyphs in the glyph run.
+ IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale);
+
///
/// Creates a renderer.
///
diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/GridSplitter.cs
index 216e43e1f0..784d33ed58 100644
--- a/src/Avalonia.Controls/GridSplitter.cs
+++ b/src/Avalonia.Controls/GridSplitter.cs
@@ -221,7 +221,8 @@ namespace Avalonia.Controls
ShowsPreview = showsPreview,
ResizeDirection = resizeDirection,
SplitterLength = Math.Min(Bounds.Width, Bounds.Height),
- ResizeBehavior = GetEffectiveResizeBehavior(resizeDirection)
+ ResizeBehavior = GetEffectiveResizeBehavior(resizeDirection),
+ Scaling = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1,
};
// Store the rows and columns to resize on drag events.
@@ -630,13 +631,17 @@ namespace Avalonia.Controls
{
double actualLength1 = GetActualLength(definition1);
double actualLength2 = GetActualLength(definition2);
+ double pixelLength = 1 / _resizeData.Scaling;
+ double epsilon = pixelLength + LayoutHelper.LayoutEpsilon;
// When splitting, Check to see if the total pixels spanned by the definitions
- // is the same as before starting resize. If not cancel the drag.
+ // is the same as before starting resize. If not cancel the drag. We need to account for
+ // layout rounding here, so ignore differences of less than a device pixel to avoid problems
+ // that WPF has, such as https://stackoverflow.com/questions/28464843.
if (_resizeData.SplitBehavior == SplitBehavior.Split &&
!MathUtilities.AreClose(
actualLength1 + actualLength2,
- _resizeData.OriginalDefinition1ActualLength + _resizeData.OriginalDefinition2ActualLength, LayoutHelper.LayoutEpsilon))
+ _resizeData.OriginalDefinition1ActualLength + _resizeData.OriginalDefinition2ActualLength, epsilon))
{
CancelResize();
@@ -798,6 +803,9 @@ namespace Avalonia.Controls
// The minimum of Width/Height of Splitter. Used to ensure splitter
// isn't hidden by resizing a row/column smaller than the splitter.
public double SplitterLength;
+
+ // The current layout scaling factor.
+ public double Scaling;
}
}
diff --git a/src/Avalonia.Controls/Templates/DataTemplates.cs b/src/Avalonia.Controls/Templates/DataTemplates.cs
index f203539536..d4eeda7908 100644
--- a/src/Avalonia.Controls/Templates/DataTemplates.cs
+++ b/src/Avalonia.Controls/Templates/DataTemplates.cs
@@ -1,3 +1,4 @@
+using System;
using Avalonia.Collections;
namespace Avalonia.Controls.Templates
@@ -13,6 +14,22 @@ namespace Avalonia.Controls.Templates
public DataTemplates()
{
ResetBehavior = ResetBehavior.Remove;
+
+ Validate += ValidateDataTemplate;
+ }
+
+ private static void ValidateDataTemplate(IDataTemplate template)
+ {
+ var valid = template switch
+ {
+ ITypedDataTemplate typed => typed.DataType is not null,
+ _ => true
+ };
+
+ if (!valid)
+ {
+ throw new InvalidOperationException("DataTemplate inside of DataTemplates must have a DataType set. Set DataType property or use ItemTemplate with single template instead.");
+ }
}
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Templates/ITypedDataTemplate.cs b/src/Avalonia.Controls/Templates/ITypedDataTemplate.cs
new file mode 100644
index 0000000000..239dbd79f4
--- /dev/null
+++ b/src/Avalonia.Controls/Templates/ITypedDataTemplate.cs
@@ -0,0 +1,10 @@
+using System;
+using Avalonia.Metadata;
+
+namespace Avalonia.Controls.Templates;
+
+public interface ITypedDataTemplate : IDataTemplate
+{
+ [DataType]
+ Type? DataType { get; }
+}
diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
index addc248d58..6471b87bfd 100644
--- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
+++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
@@ -114,6 +114,13 @@ namespace Avalonia.Headless
return new HeadlessGlyphRunStub();
}
+ public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
+ {
+ scale = Matrix.Identity;
+
+ return new HeadlessGeometryStub(new Rect(glyphRun.Size));
+ }
+
class HeadlessGeometryStub : IGeometryImpl
{
public HeadlessGeometryStub(Rect bounds)
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
index 1ca7be67a7..04a61e5f10 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
@@ -35,7 +35,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Transformers.Insert(2, _designTransformer = new AvaloniaXamlIlDesignPropertiesTransformer());
Transformers.Insert(3, _bindingTransformer = new AvaloniaBindingExtensionTransformer());
-
// Targeted
InsertBefore(
new AvaloniaXamlIlResolveClassesPropertiesTransformer(),
@@ -57,6 +56,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer()
);
+ InsertAfter(
+ new XDataTypeTransformer());
+
// After everything else
InsertBefore(
new AddNameScopeRegistration(),
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XDataTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XDataTypeTransformer.cs
new file mode 100644
index 0000000000..845dc5f831
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XDataTypeTransformer.cs
@@ -0,0 +1,73 @@
+using System.Collections.Generic;
+using System.Linq;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ internal class XDataTypeTransformer : IXamlAstTransformer
+ {
+ private const string DataTypePropertyName = "DataType";
+
+ ///
+ /// Converts x:DataType directives to regular DataType assignments if property with Avalonia.Metadata.DataTypeAttribute exists.
+ ///
+ ///
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (node is XamlAstObjectNode on)
+ {
+ for (var c = 0; c < on.Children.Count; c++)
+ {
+ var ch = on.Children[c];
+ if (ch is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: DataTypePropertyName } d)
+ {
+ if (on.Children.OfType()
+ .Any(p => ((XamlAstNamePropertyReference)p.Property)?.Name == DataTypePropertyName))
+ {
+ // Break iteration if any DataType property was already set by user code.
+ break;
+ }
+
+ var templateDataTypeAttribute = context.GetAvaloniaTypes().DataTypeAttribute;
+
+ var clrType = (on.Type as XamlAstClrTypeReference)?.Type;
+ if (clrType is null)
+ {
+ break;
+ }
+
+ // Technically it's possible to map "x:DataType" to a property with [DataType] attribute regardless of its name,
+ // but we go explicitly strict here and check the name as well.
+ var (declaringType, dataTypeProperty) = GetAllProperties(clrType)
+ .FirstOrDefault(t => t.property.Name == DataTypePropertyName && t.property.CustomAttributes
+ .Any(a => a.Type == templateDataTypeAttribute));
+
+ if (dataTypeProperty is not null)
+ {
+ on.Children[c] = new XamlAstXamlPropertyValueNode(d,
+ new XamlAstNamePropertyReference(d,
+ new XamlAstClrTypeReference(ch, declaringType, false), dataTypeProperty.Name,
+ on.Type),
+ d.Values);
+ }
+ }
+ }
+ }
+
+ return node;
+ }
+
+ private static IEnumerable<(IXamlType declaringType, IXamlProperty property)> GetAllProperties(IXamlType t)
+ {
+ foreach (var p in t.Properties)
+ yield return (t, p);
+ if(t.BaseType!=null)
+ foreach (var tuple in GetAllProperties(t.BaseType))
+ yield return tuple;
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
index d2b24979cc..4da6b1b791 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
@@ -5,7 +5,7 @@ using Avalonia.Metadata;
namespace Avalonia.Markup.Xaml.Templates
{
- public class DataTemplate : IRecyclingDataTemplate
+ public class DataTemplate : IRecyclingDataTemplate, ITypedDataTemplate
{
[DataType]
public Type DataType { get; set; }
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
index 10061c3d48..04e8b0a9c0 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
@@ -9,7 +9,7 @@ using Avalonia.Metadata;
namespace Avalonia.Markup.Xaml.Templates
{
- public class TreeDataTemplate : ITreeDataTemplate
+ public class TreeDataTemplate : ITreeDataTemplate, ITypedDataTemplate
{
[DataType]
public Type DataType { get; set; }
diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
index d3c3585cd0..727677c82e 100644
--- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
+++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
@@ -62,6 +62,41 @@ namespace Avalonia.Skia
return new CombinedGeometryImpl(combineMode, g1, g2);
}
+ public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
+ {
+ if (glyphRun.GlyphTypeface.PlatformImpl is not GlyphTypefaceImpl glyphTypeface)
+ {
+ throw new InvalidOperationException("PlatformImpl can't be null.");
+ }
+
+ var fontRenderingEmSize = (float)glyphRun.FontRenderingEmSize;
+ var skFont = new SKFont(glyphTypeface.Typeface, fontRenderingEmSize)
+ {
+ Size = fontRenderingEmSize,
+ Edging = SKFontEdging.Antialias,
+ Hinting = SKFontHinting.None,
+ LinearMetrics = true
+ };
+
+ SKPath path = new SKPath();
+ var matrix = SKMatrix.Identity;
+
+ var currentX = 0f;
+
+ foreach (var glyph in glyphRun.GlyphIndices)
+ {
+ var p = skFont.GetGlyphPath(glyph);
+
+ path.AddPath(p, currentX, 0);
+
+ currentX += p.Bounds.Right;
+ }
+
+ scale = Matrix.CreateScale(matrix.ScaleX, matrix.ScaleY);
+
+ return new StreamGeometryImpl(path);
+ }
+
///
public IBitmapImpl LoadBitmap(string fileName)
{
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index d9e992bb80..04025f92e4 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
@@ -159,6 +159,34 @@ namespace Avalonia.Direct2D1
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) => new GeometryGroupImpl(fillRule, children);
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => new CombinedGeometryImpl(combineMode, g1, g2);
+ public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
+ {
+ if (glyphRun.GlyphTypeface.PlatformImpl is not GlyphTypefaceImpl glyphTypeface)
+ {
+ throw new InvalidOperationException("PlatformImpl can't be null.");
+ }
+
+ var pathGeometry = new SharpDX.Direct2D1.PathGeometry(Direct2D1Factory);
+
+ using (var sink = pathGeometry.Open())
+ {
+ var glyphs = new short[glyphRun.GlyphIndices.Count];
+
+ for (int i = 0; i < glyphRun.GlyphIndices.Count; i++)
+ {
+ glyphs[i] = (short)glyphRun.GlyphIndices[i];
+ }
+
+ glyphTypeface.FontFace.GetGlyphRunOutline((float)glyphRun.FontRenderingEmSize, glyphs, null, null, false, !glyphRun.IsLeftToRight, sink);
+
+ sink.Close();
+ }
+
+ scale = Matrix.Identity;
+
+ return new StreamGeometryImpl(pathGeometry);
+ }
+
///
public IBitmapImpl LoadBitmap(string fileName)
{
diff --git a/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs b/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs
index f0e8e1cd11..37e07c244e 100644
--- a/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs
@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Layout;
+using Avalonia.Threading;
using Xunit;
namespace Avalonia.Base.UnitTests.Layout
@@ -421,5 +422,22 @@ namespace Avalonia.Base.UnitTests.Layout
Assert.Equal(new Size(200, 200), control.Bounds.Size);
Assert.Equal(new Size(200, 200), control.DesiredSize);
}
+
+ [Fact]
+ public void LayoutManager_Execute_Layout_Pass_Should_Clear_Queued_LayoutPasses()
+ {
+ var control = new LayoutTestControl();
+ var root = new LayoutTestRoot { Child = control };
+
+ int layoutCount = 0;
+ root.LayoutUpdated += (_, _) => layoutCount++;
+
+ root.LayoutManager.InvalidateArrange(control);
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
+
+ Assert.Equal(1, layoutCount);
+ }
}
}
diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
index add8f7fd73..183177495a 100644
--- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
+++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
@@ -121,6 +121,11 @@ namespace Avalonia.Base.UnitTests.VisualTree
throw new NotImplementedException();
}
+ public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
+ {
+ throw new NotImplementedException();
+ }
+
class MockStreamGeometry : IStreamGeometryImpl
{
private MockStreamGeometryContext _impl = new MockStreamGeometryContext();
diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
index 5cbb3b2c49..51e75b6611 100644
--- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
+++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
@@ -117,6 +117,11 @@ namespace Avalonia.Benchmarks
return new NullGlyphRun();
}
+ public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
+ {
+ throw new NotImplementedException();
+ }
+
public bool SupportsIndividualRoundRects => true;
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
index 7e721fd7b2..f3f2d2f1e4 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
@@ -17,6 +17,7 @@ using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.UnitTests;
+using JetBrains.Annotations;
using XamlX;
using Xunit;
@@ -413,11 +414,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:TestDataContext'>
-
+
-
+
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
@@ -1527,7 +1528,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
[TemplateContent]
public object Content { get; set; }
- public bool Match(object data) => FancyDataType.IsInstanceOfType(data);
+ public bool Match(object data) => FancyDataType?.IsInstanceOfType(data) ?? true;
public IControl Build(object data) => TemplateContent.Load(Content)?.Control;
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs
index 8188b212e1..affa292a7d 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs
@@ -74,18 +74,18 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
-
+
-
+
-
+
-
+
";
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs
index 53881467e7..e005964ad0 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs
@@ -1,5 +1,11 @@
+using System;
+using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Templates;
+using Avalonia.Markup.Xaml.Templates;
+using Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;
+using Avalonia.Metadata;
using Avalonia.UnitTests;
using Xunit;
@@ -89,6 +95,93 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
}
}
+ [Fact]
+ public void XDataType_Should_Be_Assigned_To_Clr_Property()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+";
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var target = window.FindControl("target");
+ var template = (DataTemplate)window.DataTemplates.First();
+
+ window.ApplyTemplate();
+ target.ApplyTemplate();
+ ((ContentPresenter)target.Presenter).UpdateChild();
+
+ Assert.Equal(typeof(string), template.DataType);
+ Assert.IsType