Browse Source

Merge branch 'master' into master

pull/6392/head
Max Katz 5 years ago
committed by GitHub
parent
commit
0f0eb7718b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 32
      samples/ControlCatalog/Pages/ComboBoxPage.xaml
  2. 34
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  3. 6
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  4. 6
      src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs
  5. 2
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
  6. 1
      src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
  7. 15
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  8. 1
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs
  9. 86
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs
  10. 106
      tests/Avalonia.RenderTests/Media/TextFormatting/TextLayoutTests.cs
  11. BIN
      tests/TestFiles/Direct2D1/Media/TextFormatting/TextLayout/TextLayout_Basic.expected.png
  12. BIN
      tests/TestFiles/Direct2D1/Media/TextFormatting/TextLayout/TextLayout_Rotated.expected.png
  13. BIN
      tests/TestFiles/Skia/Media/TextFormatting/TextLayout/TextLayout_Basic.expected.png
  14. BIN
      tests/TestFiles/Skia/Media/TextFormatting/TextLayout/TextLayout_Rotated.expected.png

32
samples/ControlCatalog/Pages/ComboBoxPage.xaml

@ -1,12 +1,20 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ComboBoxPage"
xmlns:sys="clr-namespace:System;assembly=netstandard">
xmlns:sys="using:System"
xmlns:col="using:System.Collections">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">ComboBox</TextBlock>
<TextBlock Classes="h2">A drop-down list.</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 16 0 0" Spacing="8">
<WrapPanel HorizontalAlignment="Center" Margin="0 16 0 0"
MaxWidth="750">
<WrapPanel.Styles>
<Style Selector="ComboBox">
<Setter Property="Width" Value="250" />
<Setter Property="Margin" Value="10" />
</Style>
</WrapPanel.Styles>
<ComboBox PlaceholderText="Pick an Item">
<ComboBoxItem>Inline Items</ComboBoxItem>
<ComboBoxItem>Inline Item 2</ComboBoxItem>
@ -14,6 +22,24 @@
<ComboBoxItem>Inline Item 4</ComboBoxItem>
</ComboBox>
<ComboBox>
<ComboBox.Items>
<col:ArrayList>
<x:Null />
<sys:String>Hello</sys:String>
<sys:String>World</sys:String>
</col:ArrayList>
</ComboBox.Items>
<ComboBox.ItemTemplate>
<DataTemplate>
<Panel>
<TextBlock Text="{Binding}" />
<TextBlock Text="Null object" IsVisible="{Binding Converter={x:Static ObjectConverters.IsNull}}" />
</Panel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox SelectedIndex="0">
<ComboBoxItem>
<Panel>
@ -46,7 +72,7 @@
<sys:Exception />
</DataValidationErrors.Error>
</ComboBox>
</StackPanel>
</WrapPanel>
</StackPanel>
</UserControl>

34
src/Avalonia.Base/Utilities/TypeUtilities.cs

@ -93,13 +93,25 @@ namespace Avalonia.Utilities
return !type.IsValueType || IsNullableType(type);
}
/// <summary>
/// Returns a value indicating whether value can be casted to the specified type.
/// If value is null, checks if instances of that type can be null.
/// </summary>
/// <typeparam name="T">The type to cast to</typeparam>
/// <param name="value">The value to check if cast possible</param>
/// <returns>True if the cast is possible, otherwise false.</returns>
public static bool CanCast<T>(object value)
{
return value is T || (value is null && AcceptsNull(typeof(T)));
}
/// <summary>
/// Try to convert a value to a type by any means possible.
/// </summary>
/// <param name="to">The type to cast to.</param>
/// <param name="value">The value to cast.</param>
/// <param name="to">The type to convert to.</param>
/// <param name="value">The value to convert.</param>
/// <param name="culture">The culture to use.</param>
/// <param name="result">If successful, contains the cast value.</param>
/// <param name="result">If successful, contains the convert value.</param>
/// <returns>True if the cast was successful, otherwise false.</returns>
public static bool TryConvert(Type to, object value, CultureInfo culture, out object result)
{
@ -216,10 +228,10 @@ namespace Avalonia.Utilities
/// Try to convert a value to a type using the implicit conversions allowed by the C#
/// language.
/// </summary>
/// <param name="to">The type to cast to.</param>
/// <param name="value">The value to cast.</param>
/// <param name="result">If successful, contains the cast value.</param>
/// <returns>True if the cast was successful, otherwise false.</returns>
/// <param name="to">The type to convert to.</param>
/// <param name="value">The value to convert.</param>
/// <param name="result">If successful, contains the converted value.</param>
/// <returns>True if the convert was successful, otherwise false.</returns>
public static bool TryConvertImplicit(Type to, object value, out object result)
{
if (value == null)
@ -278,8 +290,8 @@ namespace Avalonia.Utilities
/// Convert a value to a type by any means possible, returning the default for that type
/// if the value could not be converted.
/// </summary>
/// <param name="value">The value to cast.</param>
/// <param name="type">The type to cast to..</param>
/// <param name="value">The value to convert.</param>
/// <param name="type">The type to convert to..</param>
/// <param name="culture">The culture to use.</param>
/// <returns>A value of <paramref name="type"/>.</returns>
public static object ConvertOrDefault(object value, Type type, CultureInfo culture)
@ -291,8 +303,8 @@ namespace Avalonia.Utilities
/// Convert a value to a type using the implicit conversions allowed by the C# language or
/// return the default for the type if the value could not be converted.
/// </summary>
/// <param name="value">The value to cast.</param>
/// <param name="type">The type to cast to..</param>
/// <param name="value">The value to convert.</param>
/// <param name="type">The type to convert to.</param>
/// <returns>A value of <paramref name="type"/>.</returns>
public static object ConvertImplicitOrDefault(object value, Type type)
{

6
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -327,7 +327,11 @@ namespace Avalonia.Controls.Presenters
var oldChild = Child;
var newChild = content as IControl;
if (content != null && newChild == null)
// We want to allow creating Child from the Template, if Content is null.
// But it's important to not use DataTemplates, otherwise we will break content presenters in many places,
// otherwise it will blow up every ContentPresenter without Content set.
if (newChild == null
&& (content != null || ContentTemplate != null))
{
var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ??
(

6
src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs

@ -1,5 +1,7 @@
using System;
using Avalonia.Utilities;
namespace Avalonia.Controls.Templates
{
/// <summary>
@ -16,7 +18,7 @@ namespace Avalonia.Controls.Templates
/// </param>
/// <param name="supportsRecycling">Whether the control can be recycled.</param>
public FuncDataTemplate(Func<T, INameScope, IControl> build, bool supportsRecycling = false)
: base(typeof(T), CastBuild(build), supportsRecycling)
: base(o => TypeUtilities.CanCast<T>(o), CastBuild(build), supportsRecycling)
{
}
@ -63,7 +65,7 @@ namespace Avalonia.Controls.Templates
/// <returns>The weakly typed function.</returns>
private static Func<object, bool> CastMatch(Func<T, bool> f)
{
return o => (o is T) && f((T)o);
return o => TypeUtilities.CanCast<T>(o) && f((T)o);
}
/// <summary>

2
src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs

@ -47,7 +47,7 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/>
public override void Draw(DrawingContext drawingContext, Point origin)
{
using (drawingContext.PushPostTransform(Matrix.CreateTranslation(origin)))
using (drawingContext.PushPreTransform(Matrix.CreateTranslation(origin)))
{
if (GlyphRun.GlyphIndices.Length == 0)
{

1
src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs

@ -9,7 +9,6 @@ namespace Avalonia.Markup.Xaml.Templates
{
public Type DataType { get; set; }
//we need content to be object otherwise portable.xaml is crashing
[Content]
[TemplateContent]
public object Content { get; set; }

15
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -534,11 +534,7 @@ namespace Avalonia.Skia
public void PushOpacityMask(IBrush mask, Rect bounds)
{
// TODO: This should be disposed
var paint = new SKPaint()
{
IsAntialias = true,
Style = SKPaintStyle.StrokeAndFill
};
var paint = new SKPaint();
Canvas.SaveLayer(paint);
_maskStack.Push(CreatePaint(paint, mask, bounds, true));
@ -547,14 +543,7 @@ namespace Avalonia.Skia
/// <inheritdoc />
public void PopOpacityMask()
{
using (var paint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.StrokeAndFill,
BlendMode = SKBlendMode.DstIn,
Color = new SKColor(0, 0, 0, 255),
ColorFilter = SKColorFilter.CreateLumaColor()
})
using (var paint = new SKPaint { BlendMode = SKBlendMode.DstIn })
{
Canvas.SaveLayer(paint);
using (var paintWrapper = _maskStack.Pop())

1
src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs

@ -144,6 +144,7 @@ namespace Avalonia.Win32.WinRT.Composition
try
{
TryCreateAndRegisterCore(angle);
return;
}
catch (Exception e)
{

86
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs

@ -264,5 +264,91 @@ namespace Avalonia.Controls.UnitTests.Presenters
// InheritanceParent is exposed via StylingParent.
Assert.Same(logicalParent, ((IStyledElement)child).StylingParent);
}
[Fact]
public void Should_Create_Child_Even_With_Null_Content_When_ContentTemplate_Is_Set()
{
var target = new ContentPresenter
{
ContentTemplate = new FuncDataTemplate<object>(_ => true, (_, __) => new TextBlock
{
Text = "Hello World"
}),
Content = null
};
target.UpdateChild();
var textBlock = Assert.IsType<TextBlock>(target.Child);
Assert.Equal("Hello World", textBlock.Text);
}
[Fact]
public void Should_Not_Create_Child_Even_With_Null_Content_And_DataTemplates_InsteadOf_ContentTemplate()
{
var target = new ContentPresenter
{
DataTemplates =
{
new FuncDataTemplate<object>(_ => true, (_, __) => new TextBlock
{
Text = "Hello World"
})
},
Content = null
};
target.UpdateChild();
Assert.Null(target.Child);
}
[Fact]
public void Should_Not_Create_Child_When_Content_And_Template_Are_Null()
{
var target = new ContentPresenter
{
ContentTemplate = null,
Content = null
};
target.UpdateChild();
Assert.Null(target.Child);
}
[Fact]
public void Should_Not_Create_When_Child_Content_Is_Null_But_Expected_ValueType_With_FuncDataTemplate()
{
var target = new ContentPresenter
{
ContentTemplate = new FuncDataTemplate<int>(_ => true, (_, __) => new TextBlock
{
Text = "Hello World"
}),
Content = null
};
target.UpdateChild();
Assert.Null(target.Child);
}
[Fact]
public void Should_Create_Child_When_Content_Is_Null_And_Expected_NullableValueType_With_FuncDataTemplate()
{
var target = new ContentPresenter
{
ContentTemplate = new FuncDataTemplate<int?>(_ => true, (_, __) => new TextBlock
{
Text = "Hello World"
}),
Content = null
};
target.UpdateChild();
Assert.NotNull(target.Child);
}
}
}

106
tests/Avalonia.RenderTests/Media/TextFormatting/TextLayoutTests.cs

@ -0,0 +1,106 @@
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Platform;
using Avalonia.Utilities;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using System.Runtime.InteropServices;
#if AVALONIA_SKIA
namespace Avalonia.Skia.RenderTests
#else
using Avalonia.Direct2D1.RenderTests;
namespace Avalonia.Direct2D1.RenderTests.Media
#endif
{
public class TextLayoutTests : TestBase
{
public TextLayoutTests()
: base(@"Media\TextFormatting\TextLayout")
{
}
[Fact]
public async Task TextLayout_Basic()
{
// Skip test on OSX: text rendering is subtly different.
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return;
var t = new TextLayout(
"Avalonia!",
new Typeface(TestFontFamily),
24,
Brushes.Black);
var target = new Border
{
Width = 200,
Height = 200,
Background = Brushes.White,
Child = new DrawnControl(c =>
{
var textRect = new Rect(t.Size);
var bounds = new Rect(0, 0, 200, 200);
var rect = bounds.CenterRect(textRect);
c.DrawRectangle(Brushes.Yellow, null, rect);
t.Draw(c, rect.Position);
}),
};
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task TextLayout_Rotated()
{
// Skip test on OSX: text rendering is subtly different.
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return;
var t = new TextLayout(
"Avalonia!",
new Typeface(TestFontFamily),
24,
Brushes.Black);
var target = new Border
{
Width = 200,
Height = 200,
Background = Brushes.White,
Child = new DrawnControl(c =>
{
var textRect = new Rect(t.Size);
var bounds = new Rect(0, 0, 200, 200);
var rect = bounds.CenterRect(textRect);
var rotate = Matrix.CreateTranslation(-100, -100) *
Matrix.CreateRotation(MathUtilities.Deg2Rad(90)) *
Matrix.CreateTranslation(100, 100);
using var transform = c.PushPreTransform(rotate);
c.DrawRectangle(Brushes.Yellow, null, rect);
t.Draw(c, rect.Position);
}),
};
await RenderToFile(target);
CompareImages();
}
private class DrawnControl : Control
{
private readonly Action<DrawingContext> _render;
public DrawnControl(Action<DrawingContext> render) => _render = render;
public override void Render(DrawingContext context) => _render(context);
}
}
}

BIN
tests/TestFiles/Direct2D1/Media/TextFormatting/TextLayout/TextLayout_Basic.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
tests/TestFiles/Direct2D1/Media/TextFormatting/TextLayout/TextLayout_Rotated.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
tests/TestFiles/Skia/Media/TextFormatting/TextLayout/TextLayout_Basic.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
tests/TestFiles/Skia/Media/TextFormatting/TextLayout/TextLayout_Rotated.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Loading…
Cancel
Save