Browse Source

Merge remote-tracking branch 'upstream/master'

pull/8368/head
Benedikt Stebner 4 years ago
parent
commit
a76f1473c0
  1. 17
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  2. 27
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  3. 3
      samples/ControlCatalog.Android/Properties/AndroidManifest.xml
  4. 1
      samples/ControlCatalog.Android/environment.device.txt
  5. 1
      samples/ControlCatalog.Android/environment.emulator.txt
  6. 12
      samples/RenderDemo/Pages/GlyphRunPage.xaml
  7. 113
      samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
  8. 14
      src/Avalonia.Base/Layout/LayoutManager.cs
  9. 19
      src/Avalonia.Base/Media/GlyphRun.cs
  10. 24
      src/Avalonia.Base/Media/PlatformGeometry.cs
  11. 8
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  12. 14
      src/Avalonia.Controls/GridSplitter.cs
  13. 17
      src/Avalonia.Controls/Templates/DataTemplates.cs
  14. 10
      src/Avalonia.Controls/Templates/ITypedDataTemplate.cs
  15. 7
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  16. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  17. 73
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XDataTypeTransformer.cs
  18. 2
      src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
  19. 2
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  20. 35
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  21. 28
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  22. 18
      tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs
  23. 5
      tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
  24. 5
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  25. 7
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
  26. 8
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs
  27. 113
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs
  28. 19
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs
  29. 81
      tests/Avalonia.RenderTests/Media/GlyphRunTests.cs
  30. 7
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  31. BIN
      tests/TestFiles/Direct2D1/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png
  32. BIN
      tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png

17
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 {

27
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@ -9,42 +9,37 @@
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<AndroidPackageFormat>apk</AndroidPackageFormat>
<MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
<RuntimeIdentifiers>android-arm64;android-x64</RuntimeIdentifiers>
</PropertyGroup>
<ItemGroup>
<None Remove="Assets\AboutAssets.txt" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="..\..\build\Assets\Icon.png">
<Link>Resources\drawable\Icon.png</Link>
</AndroidResource>
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release' and '$(TF_BUILD)' == ''">
<DebugSymbols>False</DebugSymbols>
<UseInterpreter>False</UseInterpreter>
<PropertyGroup Condition="'$(RunAOTCompilation)'=='' and '$(Configuration)'=='Release' and '$(TF_BUILD)'==''">
<RunAOTCompilation>True</RunAOTCompilation>
</PropertyGroup>
<PropertyGroup Condition="'$(RunAOTCompilation)'=='True'">
<EnableLLVM>True</EnableLLVM>
<AndroidAotAdditionalArguments>no-write-symbols,nodebug</AndroidAotAdditionalArguments>
<AndroidAotMode>Hybrid</AndroidAotMode>
<AndroidGenerateJniMarshalMethods>True</AndroidGenerateJniMarshalMethods>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<EmbedAssembliesIntoApk>False</EmbedAssembliesIntoApk>
<RunAOTCompilation>False</RunAOTCompilation>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
<PropertyGroup Condition="'$(AndroidEnableProfiler)'=='True'">
<IsEmulator Condition="'$(IsEmulator)' == ''">True</IsEmulator>
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.3.1.3" />
<PackageReference Include="Xamarin.AndroidX.Lifecycle.ViewModel" Version="2.3.1.3" />
<AndroidEnvironment Condition="'$(IsEmulator)'=='True'" Include="environment.emulator.txt" />
<AndroidEnvironment Condition="'$(IsEmulator)'!='True'" Include="environment.device.txt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Android\Avalonia.Android\Avalonia.Android.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
</ItemGroup>
</Project>
</Project>

3
samples/ControlCatalog.Android/Properties/AndroidManifest.xml

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto">
<application android:label="ControlCatalog.Android" android:icon="@drawable/Icon"></application>
<application android:label="ControlCatalog.Android" android:icon="@drawable/Icon"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

1
samples/ControlCatalog.Android/environment.device.txt

@ -0,0 +1 @@
DOTNET_DiagnosticPorts=127.0.0.1:9000,suspend

1
samples/ControlCatalog.Android/environment.emulator.txt

@ -0,0 +1 @@
DOTNET_DiagnosticPorts=10.0.2.2:9001,suspend

12
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">
<Border
<Grid
ColumnDefinitions="*,*"
Background="White">
<Image
x:Name="imageControl"
Stretch="None">
</Image>
</Border>
<local:GlyphRunControl Grid.Column="0"/>
<local:GlyphRunGeometryControl Grid.Column="1"/>
</Grid>
</UserControl>

113
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<Image>("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);
}
}
}

14
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();
}
/// <inheritdoc/>
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);
}
}

19
src/Avalonia.Base/Media/GlyphRun.cs

@ -194,6 +194,25 @@ namespace Avalonia.Media
}
}
/// <summary>
/// Obtains geometry for the glyph run.
/// </summary>
/// <returns>The geometry returned contains the combined geometry of all glyphs in the glyph run.</returns>
public Geometry BuildGeometry()
{
var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
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;
}
/// <summary>
/// Retrieves the offset from the leading edge of the <see cref="GlyphRun"/>
/// to the leading or trailing edge of a caret stop containing the specified character hit.

24
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;
}
}
}

8
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

@ -58,6 +58,14 @@ namespace Avalonia.Platform
/// <returns>A combined geometry.</returns>
IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2);
/// <summary>
/// Created a geometry implementation for the glyph run.
/// </summary>
/// <param name="glyphRun">The glyph run to build a geometry from.</param>
/// <param name="scale">The scaling of the produces geometry.</param>
/// <returns>The geometry returned contains the combined geometry of all glyphs in the glyph run.</returns>
IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale);
/// <summary>
/// Creates a renderer.
/// </summary>

14
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;
}
}

17
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.");
}
}
}
}

10
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; }
}

7
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)

4
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<PropertyReferenceResolver>(
new AvaloniaXamlIlResolveClassesPropertiesTransformer(),
@ -57,6 +56,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer()
);
InsertAfter<TypeReferenceResolver>(
new XDataTypeTransformer());
// After everything else
InsertBefore<NewObjectTransformer>(
new AddNameScopeRegistration(),

73
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";
/// <summary>
/// Converts x:DataType directives to regular DataType assignments if property with Avalonia.Metadata.DataTypeAttribute exists.
/// </summary>
/// <returns></returns>
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<XamlAstXamlPropertyValueNode>()
.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;
}
}
}

2
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; }

2
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; }

35
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);
}
/// <inheritdoc />
public IBitmapImpl LoadBitmap(string fileName)
{

28
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -159,6 +159,34 @@ namespace Avalonia.Direct2D1
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<Geometry> 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);
}
/// <inheritdoc />
public IBitmapImpl LoadBitmap(string fileName)
{

18
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);
}
}
}

5
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();

5
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;

7
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'>
<ItemsControl Items='{CompiledBinding ListProperty}' Name='target'>
<ItemsControl.DataTemplates>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text='{CompiledBinding}' Name='textBlock' />
</DataTemplate>
</ItemsControl.DataTemplates>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>";
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;
}

8
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs

@ -74,18 +74,18 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
<Window xmlns='https://github.com/avaloniaui'>
<DockPanel>
<TabStrip Name='strip' DockPanel.Dock='Top' Items='{Binding Items}' SelectedIndex='0'>
<TabStrip.DataTemplates>
<TabStrip.ItemTemplate>
<DataTemplate>
<TextBlock Text='{Binding Header}'/>
</DataTemplate>
</TabStrip.DataTemplates>
</TabStrip.ItemTemplate>
</TabStrip>
<Carousel Name='carousel' Items='{Binding Items}' SelectedIndex='{Binding #strip.SelectedIndex}'>
<Carousel.DataTemplates>
<Carousel.ItemTemplate>
<DataTemplate>
<TextBlock Text='{Binding Detail}'/>
</DataTemplate>
</Carousel.DataTemplates>
</Carousel.ItemTemplate>
</Carousel>
</DockPanel>
</Window>";

113
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 = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:sys='clr-namespace:System;assembly=netstandard'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.DataTemplates>
<DataTemplate x:DataType='sys:String'>
<Canvas Name='foo'/>
</DataTemplate>
</Window.DataTemplates>
<ContentControl Name='target' Content='Foo'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var target = window.FindControl<ContentControl>("target");
var template = (DataTemplate)window.DataTemplates.First();
window.ApplyTemplate();
target.ApplyTemplate();
((ContentPresenter)target.Presenter).UpdateChild();
Assert.Equal(typeof(string), template.DataType);
Assert.IsType<Canvas>(target.Presenter.Child);
}
}
[Fact]
public void XDataType_Should_Be_Ignored_If_DataType_Already_Set()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:sys='clr-namespace:System;assembly=netstandard'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.DataTemplates>
<DataTemplate DataType='sys:String' x:DataType='UserControl'>
<Canvas Name='foo'/>
</DataTemplate>
</Window.DataTemplates>
<ContentControl Name='target' Content='Foo'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var target = window.FindControl<ContentControl>("target");
window.ApplyTemplate();
target.ApplyTemplate();
((ContentPresenter)target.Presenter).UpdateChild();
Assert.IsType<Canvas>(target.Presenter.Child);
}
}
[Fact]
public void XDataType_Should_Be_Ignored_If_DataType_Has_Non_Standard_Name()
{
// We don't want DataType to be mapped to FancyDataType, avoid possible confusion.
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:sys='clr-namespace:System;assembly=netstandard'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'>
<ContentControl Name='target' Content='Foo'>
<ContentControl.ContentTemplate>
<local:CustomDataTemplate x:DataType='local:TestDataContext'>
<TextBlock Text='{CompiledBinding StringProperty}' Name='textBlock' />
</local:CustomDataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var target = window.FindControl<ContentControl>("target");
window.ApplyTemplate();
target.ApplyTemplate();
((ContentPresenter)target.Presenter).UpdateChild();
var dataTemplate = (CustomDataTemplate)target.ContentTemplate;
Assert.Null(dataTemplate.FancyDataType);
}
}
[Fact]
public void Can_Set_DataContext_In_DataTemplate()
{
@ -132,5 +225,25 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Same(viewModel.Child.Child, canvas.DataContext);
}
}
[Fact]
public void DataTemplates_Without_Type_Should_Throw()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:sys='clr-namespace:System;assembly=netstandard'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.DataTemplates>
<DataTemplate>
<Canvas Name='foo'/>
</DataTemplate>
</Window.DataTemplates>
<ContentControl Name='target' Content='Foo'/>
</Window>";
Assert.Throws<InvalidOperationException>(() => (Window)AvaloniaRuntimeXamlLoader.Load(xaml));
}
}
}
}

19
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs

@ -14,12 +14,29 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
using (UnitTestApplication.Start(TestServices.MockPlatformWrapper))
{
var xaml = "<DataTemplates xmlns='https://github.com/avaloniaui'><TreeDataTemplate ItemsSource='{Binding}'/></DataTemplates>";
var xaml = "<DataTemplates xmlns='https://github.com/avaloniaui'><TreeDataTemplate DataType='Control' ItemsSource='{Binding}'/></DataTemplates>";
var templates = (DataTemplates)AvaloniaRuntimeXamlLoader.Load(xaml);
var template = (TreeDataTemplate)(templates.First());
Assert.IsType<Binding>(template.ItemsSource);
}
}
[Fact]
public void XDataType_Should_Be_Assigned_To_Clr_Property()
{
using (UnitTestApplication.Start(TestServices.MockPlatformWrapper))
{
var xaml = @"
<DataTemplates xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<TreeDataTemplate x:DataType='x:String' />
</DataTemplates>";
var templates = (DataTemplates)AvaloniaRuntimeXamlLoader.Load(xaml);
var template = (TreeDataTemplate)(templates.First());
Assert.Equal(typeof(string), template.DataType);
}
}
}
}

81
tests/Avalonia.RenderTests/Media/GlyphRunTests.cs

@ -0,0 +1,81 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Xunit;
#if AVALONIA_SKIA
namespace Avalonia.Skia.RenderTests
#else
namespace Avalonia.Direct2D1.RenderTests.Media
#endif
{
public class GlyphRunTests : TestBase
{
public GlyphRunTests()
: base(@"Media\GlyphRun")
{
}
[Fact]
public async Task Should_Render_GlyphRun_Geometry()
{
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 200,
Height = 100,
Child = new GlyphRunGeometryControl
{
[TextElement.ForegroundProperty] = new LinearGradientBrush
{
StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative),
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
}
}
}
};
await RenderToFile(target);
CompareImages();
}
public class GlyphRunGeometryControl : Control
{
private readonly Geometry _geometry;
public GlyphRunGeometryControl()
{
var glyphTypeface = new Typeface(TestFontFamily).GlyphTypeface;
var glyphIndices = new[] { glyphTypeface.GetGlyph('A'), glyphTypeface.GetGlyph('B'), glyphTypeface.GetGlyph('C') };
var characters = new[] { 'A', 'B', 'C' };
var glyphRun = new GlyphRun(glyphTypeface, 100, characters, glyphIndices);
_geometry = glyphRun.BuildGeometry();
}
protected override Size MeasureOverride(Size availableSize)
{
return _geometry.Bounds.Size;
}
public override void Render(DrawingContext context)
{
var foreground = TextElement.GetForeground(this);
context.DrawGeometry(foreground, null, _geometry);
}
}
}
}

7
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@ -122,6 +122,13 @@ namespace Avalonia.UnitTests
return Mock.Of<IGlyphRunImpl>();
}
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
{
scale = Matrix.Identity;
return Mock.Of<IGeometryImpl>();
}
public bool SupportsIndividualRoundRects { get; set; }
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;

BIN
tests/TestFiles/Direct2D1/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Loading…
Cancel
Save