Browse Source

Merge branch 'master' into fixes/1096-popup-scene-not-updating

pull/1106/head
Steven Kirk 9 years ago
committed by GitHub
parent
commit
f8141a4b3a
  1. 1
      samples/RenderTest/MainWindow.xaml
  2. 132
      samples/RenderTest/Pages/DrawingPage.xaml
  3. 18
      samples/RenderTest/Pages/DrawingPage.xaml.cs
  4. 8
      samples/RenderTest/RenderTest.csproj
  5. 59
      src/Avalonia.Controls/DrawingPresenter.cs
  6. 28
      src/Avalonia.Controls/Shapes/Shape.cs
  7. 29
      src/Avalonia.Visuals/Matrix.cs
  8. 9
      src/Avalonia.Visuals/Media/Drawing.cs
  9. 58
      src/Avalonia.Visuals/Media/DrawingGroup.cs
  10. 51
      src/Avalonia.Visuals/Media/EllipseGeometry.cs
  11. 43
      src/Avalonia.Visuals/Media/GeometryDrawing.cs
  12. 80
      src/Avalonia.Visuals/Media/LineGeometry.cs
  13. 1
      src/Avalonia.Visuals/Media/PathMarkupParser.cs
  14. 115
      src/Avalonia.Visuals/Media/PolylineGeometry.cs
  15. 51
      src/Avalonia.Visuals/Media/RectangleGeometry.cs
  16. 2
      src/Avalonia.Visuals/Point.cs
  17. 9
      src/Avalonia.Visuals/Points.cs
  18. 26
      src/Avalonia.Visuals/Rect.cs
  19. 2
      src/Avalonia.Visuals/RelativeRect.cs
  20. 2
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  21. 23
      src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs
  22. 23
      src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs
  23. 2
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs
  24. 16
      tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs
  25. 1
      tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs
  26. 16
      tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs

1
samples/RenderTest/MainWindow.xaml

@ -27,6 +27,7 @@
</TabControl.Transition>
<TabItem Header="Animations"><pages:AnimationsPage/></TabItem>
<TabItem Header="Clipping"><pages:ClippingPage/></TabItem>
<TabItem Header="Drawing"><pages:DrawingPage/></TabItem>
</TabControl>
</DockPanel>
</Window>

132
samples/RenderTest/Pages/DrawingPage.xaml

@ -0,0 +1,132 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Styles>
<Style>
<Style.Resources>
<DrawingGroup x:Key="Bulb">
<DrawingGroup.Transform>
<MatrixTransform Matrix="1,0,0,1,0,-1028.4" />
</DrawingGroup.Transform>
<DrawingGroup>
<DrawingGroup.Transform>
<MatrixTransform Matrix="1,0,0,1.25,-10,1031.4" />
</DrawingGroup.Transform>
<GeometryDrawing Brush="#FF7F8C8D"
Geometry="F1 M24,14 A2,2,0,1,1,20,14 A2,2,0,1,1,24,14 z" />
</DrawingGroup>
<GeometryDrawing Brush="#FFF39C12"
Geometry="F1 M12,1030.4 C8.134,1030.4 5,1033.6 5,1037.6 5,1040.7 8.125,1043.5 9,1045.4 9.875,1047.2 9,1050.4 9,1050.4 L12,1049.9 15,1050.4 C15,1050.4 14.125,1047.2 15,1045.4 15.875,1043.5 19,1040.7 19,1037.6 19,1033.6 15.866,1030.4 12,1030.4 z" />
<GeometryDrawing Brush="#FFF1C40F"
Geometry="F1 M12,1030.4 C15.866,1030.4 19,1033.6 19,1037.6 19,1040.7 15.875,1043.5 15,1045.4 14.125,1047.2 15,1050.4 15,1050.4 L12,1049.9 12,1030.4 z" />
<GeometryDrawing Brush="#FFE67E22"
Geometry="F1 M9,1036.4 L8,1037.4 12,1049.4 16,1037.4 15,1036.4 14,1037.4 13,1036.4 12,1037.4 11,1036.4 10,1037.4 9,1036.4 z M9,1037.4 L10,1038.4 10.5,1037.9 11,1037.4 11.5,1037.9 12,1038.4 12.5,1037.9 13,1037.4 13.5,1037.9 14,1038.4 15,1037.4 15.438,1037.8 12,1048.1 8.5625,1037.8 9,1037.4 z" />
<DrawingGroup>
<DrawingGroup.Transform>
<MatrixTransform Matrix="1,0,0,1,9,1045.4" />
</DrawingGroup.Transform>
<GeometryDrawing Brush="#FFBDC3C7">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,6,5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
<GeometryDrawing Brush="#FF95A5A6"
Geometry="F1 M9,1045.4 L9,1050.4 12,1050.4 12,1049.4 15,1049.4 15,1048.4 12,1048.4 12,1047.4 15,1047.4 15,1046.4 12,1046.4 12,1045.4 9,1045.4 z" />
<GeometryDrawing Brush="#FF7F8C8D"
Geometry="F1 M9,1046.4 L9,1047.4 12,1047.4 12,1046.4 9,1046.4 z M9,1048.4 L9,1049.4 12,1049.4 12,1048.4 9,1048.4 z" />
</DrawingGroup>
</Style.Resources>
</Style>
</UserControl.Styles>
<Grid RowDefinitions="Auto,Auto,Auto"
ColumnDefinitions="Auto,Auto,Auto,Auto">
<TextBlock Text="None"
Margin="3" />
<Border Grid.Column="0"
Grid.Row="1"
VerticalAlignment="Top"
HorizontalAlignment="Left"
BorderThickness="1"
BorderBrush="Gray"
Margin="5">
<DrawingPresenter Drawing="{StyleResource Bulb}" />
</Border>
<TextBlock Text="Fill"
Margin="3"
Grid.Column="1" />
<Border Grid.Column="1"
Grid.Row="1"
VerticalAlignment="Top"
HorizontalAlignment="Left"
BorderThickness="1"
BorderBrush="Gray"
Margin="5">
<DrawingPresenter Drawing="{StyleResource Bulb}"
Width="100"
Height="50"
Stretch="Fill" />
</Border>
<TextBlock Text="Uniform"
Margin="3"
Grid.Column="2" />
<Border Grid.Column="2"
Grid.Row="1"
VerticalAlignment="Top"
HorizontalAlignment="Left"
BorderThickness="1"
BorderBrush="Gray"
Margin="5">
<DrawingPresenter Drawing="{StyleResource Bulb}"
Width="100"
Height="50"
Stretch="Uniform" />
</Border>
<TextBlock Text="UniformToFill"
Margin="3"
Grid.Column="3" />
<Border Grid.Column="3"
Grid.Row="1"
VerticalAlignment="Top"
HorizontalAlignment="Left"
BorderThickness="1"
BorderBrush="Gray"
Margin="5">
<DrawingPresenter Drawing="{StyleResource Bulb}"
Width="100"
Height="50"
Stretch="UniformToFill" />
</Border>
<!-- For comparison -->
<Ellipse Grid.Row="2"
Grid.Column="0"
Width="100"
Height="50"
Stretch="None"
Fill="Blue"
Margin="5"/>
<Ellipse Grid.Row="2"
Grid.Column="1"
Width="100"
Height="50"
Stretch="Fill"
Fill="Blue"
Margin="5" />
<Ellipse Grid.Row="2"
Grid.Column="2"
Width="100"
Height="50"
Stretch="Uniform"
Fill="Blue"
Margin="5" />
<Ellipse Grid.Row="2"
Grid.Column="3"
Width="100"
Height="50"
Stretch="UniformToFill"
Fill="Blue"
Margin="5" />
</Grid>
</UserControl>

18
samples/RenderTest/Pages/DrawingPage.xaml.cs

@ -0,0 +1,18 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace RenderTest.Pages
{
public class DrawingPage : UserControl
{
public DrawingPage()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

8
samples/RenderTest/RenderTest.csproj

@ -51,6 +51,9 @@
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\DrawingPage.xaml.cs">
<DependentUpon>DrawingPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ClippingPage.xaml.cs">
<DependentUpon>ClippingPage.xaml</DependentUpon>
</Compile>
@ -178,6 +181,11 @@
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\DrawingPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Serilog.Sinks.Trace.props" />

59
src/Avalonia.Controls/DrawingPresenter.cs

@ -0,0 +1,59 @@
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.Metadata;
namespace Avalonia.Controls
{
public class DrawingPresenter : Control
{
static DrawingPresenter()
{
AffectsMeasure(DrawingProperty);
AffectsRender(DrawingProperty);
}
public static readonly StyledProperty<Drawing> DrawingProperty =
AvaloniaProperty.Register<DrawingPresenter, Drawing>(nameof(Drawing));
public static readonly StyledProperty<Stretch> StretchProperty =
AvaloniaProperty.Register<DrawingPresenter, Stretch>(nameof(Stretch), Stretch.Uniform);
[Content]
public Drawing Drawing
{
get => GetValue(DrawingProperty);
set => SetValue(DrawingProperty, value);
}
public Stretch Stretch
{
get => GetValue(StretchProperty);
set => SetValue(StretchProperty, value);
}
private Matrix _transform = Matrix.Identity;
protected override Size MeasureOverride(Size availableSize)
{
if (Drawing == null) return new Size();
var (size, transform) = Shape.CalculateSizeAndTransform(availableSize, Drawing.GetBounds(), Stretch);
_transform = transform;
return size;
}
public override void Render(DrawingContext context)
{
if (Drawing != null)
{
using (context.PushPreTransform(_transform))
using (context.PushClip(Bounds))
{
Drawing.Draw(context);
}
}
}
}
}

28
src/Avalonia.Controls/Shapes/Shape.cs

@ -155,11 +155,21 @@ namespace Avalonia.Controls.Shapes
{
// This should probably use GetRenderBounds(strokeThickness) but then the calculations
// will multiply the stroke thickness as well, which isn't correct.
Rect shapeBounds = DefiningGeometry.Bounds;
var (size, transform) = CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch);
if (_transform != transform)
{
_transform = transform;
_renderedGeometry = null;
}
return size;
}
internal static (Size, Matrix) CalculateSizeAndTransform(Size availableSize, Rect shapeBounds, Stretch Stretch)
{
Size shapeSize = new Size(shapeBounds.Right, shapeBounds.Bottom);
Matrix translate = Matrix.Identity;
double width = Width;
double height = Height;
double desiredX = availableSize.Width;
double desiredY = availableSize.Height;
double sx = 0.0;
@ -226,15 +236,9 @@ namespace Avalonia.Controls.Shapes
break;
}
var t = translate * Matrix.CreateScale(sx, sy);
if (_transform != t)
{
_transform = t;
_renderedGeometry = null;
}
return new Size(shapeSize.Width * sx, shapeSize.Height * sy);
var transform = translate * Matrix.CreateScale(sx, sy);
var size = new Size(shapeSize.Width * sx, shapeSize.Height * sy);
return (size, transform);
}
private static void AffectsGeometryInvalidate(AvaloniaPropertyChangedEventArgs e)

29
src/Avalonia.Visuals/Matrix.cs

@ -3,6 +3,7 @@
using System;
using System.Globalization;
using System.Linq;
namespace Avalonia
{
@ -295,5 +296,33 @@ namespace Avalonia
((_m21 * _m32) - (_m22 * _m31)) / d,
((_m12 * _m31) - (_m11 * _m32)) / d);
}
/// <summary>
/// Parses a <see cref="Matrix"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The <see cref="Matrix"/>.</returns>
public static Matrix Parse(string s, CultureInfo culture)
{
var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToArray();
if (parts.Length == 6)
{
return new Matrix(
double.Parse(parts[0], culture),
double.Parse(parts[1], culture),
double.Parse(parts[2], culture),
double.Parse(parts[3], culture),
double.Parse(parts[4], culture),
double.Parse(parts[5], culture));
}
else
{
throw new FormatException("Invalid Matrix.");
}
}
}
}

9
src/Avalonia.Visuals/Media/Drawing.cs

@ -0,0 +1,9 @@
namespace Avalonia.Media
{
public abstract class Drawing : AvaloniaObject
{
public abstract void Draw(DrawingContext context);
public abstract Rect GetBounds();
}
}

58
src/Avalonia.Visuals/Media/DrawingGroup.cs

@ -0,0 +1,58 @@
using Avalonia.Collections;
using Avalonia.Metadata;
namespace Avalonia.Media
{
public class DrawingGroup : Drawing
{
public static readonly StyledProperty<double> OpacityProperty =
AvaloniaProperty.Register<DrawingGroup, double>(nameof(Opacity), 1);
public static readonly StyledProperty<Transform> TransformProperty =
AvaloniaProperty.Register<DrawingGroup, Transform>(nameof(Transform));
public double Opacity
{
get => GetValue(OpacityProperty);
set => SetValue(OpacityProperty, value);
}
public Transform Transform
{
get => GetValue(TransformProperty);
set => SetValue(TransformProperty, value);
}
[Content]
public AvaloniaList<Drawing> Children { get; } = new AvaloniaList<Drawing>();
public override void Draw(DrawingContext context)
{
using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity))
using (context.PushOpacity(Opacity))
{
foreach (var drawing in Children)
{
drawing.Draw(context);
}
}
}
public override Rect GetBounds()
{
var rect = new Rect();
foreach (var drawing in Children)
{
rect = rect.Union(drawing.GetBounds());
}
if (Transform != null)
{
rect = rect.TransformToAABB(Transform.Value);
}
return rect;
}
}
}

51
src/Avalonia.Visuals/Media/EllipseGeometry.cs

@ -11,16 +11,51 @@ namespace Avalonia.Media
/// </summary>
public class EllipseGeometry : Geometry
{
/// <summary>
/// Defines the <see cref="Rect"/> property.
/// </summary>
public static readonly StyledProperty<Rect> RectProperty =
AvaloniaProperty.Register<EllipseGeometry, Rect>(nameof(Rect));
public Rect Rect
{
get => GetValue(RectProperty);
set => SetValue(RectProperty, value);
}
static EllipseGeometry()
{
RectProperty.Changed.AddClassHandler<EllipseGeometry>(x => x.RectChanged);
}
/// <summary>
/// Initializes a new instance of the <see cref="EllipseGeometry"/> class.
/// </summary>
/// <param name="rect">The rectangle that the ellipse should fill.</param>
public EllipseGeometry(Rect rect)
public EllipseGeometry()
{
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
IStreamGeometryImpl impl = factory.CreateStreamGeometry();
PlatformImpl = factory.CreateStreamGeometry();
}
/// <summary>
/// Initializes a new instance of the <see cref="EllipseGeometry"/> class.
/// </summary>
/// <param name="rect">The rectangle that the ellipse should fill.</param>
public EllipseGeometry(Rect rect) : this()
{
Rect = rect;
}
/// <inheritdoc/>
public override Geometry Clone()
{
return new EllipseGeometry(Rect);
}
using (IStreamGeometryContextImpl ctx = impl.Open())
private void RectChanged(AvaloniaPropertyChangedEventArgs e)
{
var rect = (Rect)e.NewValue;
using (var ctx = ((IStreamGeometryImpl)PlatformImpl).Open())
{
double controlPointRatio = (Math.Sqrt(2) - 1) * 4 / 3;
var center = rect.Center;
@ -45,14 +80,6 @@ namespace Avalonia.Media
ctx.CubicBezierTo(new Point(x0, y1), new Point(x1, y0), new Point(x2, y0));
ctx.EndFigure(true);
}
PlatformImpl = impl;
}
/// <inheritdoc/>
public override Geometry Clone()
{
return new EllipseGeometry(Bounds);
}
}
}

43
src/Avalonia.Visuals/Media/GeometryDrawing.cs

@ -0,0 +1,43 @@
namespace Avalonia.Media
{
public class GeometryDrawing : Drawing
{
public static readonly StyledProperty<Geometry> GeometryProperty =
AvaloniaProperty.Register<GeometryDrawing, Geometry>(nameof(Geometry));
public Geometry Geometry
{
get => GetValue(GeometryProperty);
set => SetValue(GeometryProperty, value);
}
public static readonly StyledProperty<IBrush> BrushProperty =
AvaloniaProperty.Register<GeometryDrawing, IBrush>(nameof(Brush), Brushes.Transparent);
public IBrush Brush
{
get => GetValue(BrushProperty);
set => SetValue(BrushProperty, value);
}
public static readonly StyledProperty<Pen> PenProperty =
AvaloniaProperty.Register<GeometryDrawing, Pen>(nameof(Pen));
public Pen Pen
{
get => GetValue(PenProperty);
set => SetValue(PenProperty, value);
}
public override void Draw(DrawingContext context)
{
context.DrawGeometry(Brush, Pen, Geometry);
}
public override Rect GetBounds()
{
// adding the Pen's stroke thickness here could yield wrong results due to transforms
return Geometry?.GetRenderBounds(0) ?? new Rect();
}
}
}

80
src/Avalonia.Visuals/Media/LineGeometry.cs

@ -10,35 +10,89 @@ namespace Avalonia.Media
/// </summary>
public class LineGeometry : Geometry
{
private Point _startPoint;
private Point _endPoint;
/// <summary>
/// Defines the <see cref="StartPoint"/> property.
/// </summary>
public static readonly StyledProperty<Point> StartPointProperty =
AvaloniaProperty.Register<LineGeometry, Point>(nameof(StartPoint));
public Point StartPoint
{
get => GetValue(StartPointProperty);
set => SetValue(StartPointProperty, value);
}
/// <summary>
/// Defines the <see cref="EndPoint"/> property.
/// </summary>
public static readonly StyledProperty<Point> EndPointProperty =
AvaloniaProperty.Register<LineGeometry, Point>(nameof(EndPoint));
private bool _isDirty;
public Point EndPoint
{
get => GetValue(EndPointProperty);
set => SetValue(EndPointProperty, value);
}
static LineGeometry()
{
StartPointProperty.Changed.AddClassHandler<LineGeometry>(x => x.PointsChanged);
EndPointProperty.Changed.AddClassHandler<LineGeometry>(x => x.PointsChanged);
}
/// <summary>
/// Initializes a new instance of the <see cref="LineGeometry"/> class.
/// </summary>
public LineGeometry()
{
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
PlatformImpl = factory.CreateStreamGeometry();
}
/// <summary>
/// Initializes a new instance of the <see cref="LineGeometry"/> class.
/// </summary>
/// <param name="startPoint">The start point.</param>
/// <param name="endPoint">The end point.</param>
public LineGeometry(Point startPoint, Point endPoint)
public LineGeometry(Point startPoint, Point endPoint) : this()
{
_startPoint = startPoint;
_endPoint = endPoint;
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
IStreamGeometryImpl impl = factory.CreateStreamGeometry();
StartPoint = startPoint;
EndPoint = endPoint;
}
using (IStreamGeometryContextImpl context = impl.Open())
public override IGeometryImpl PlatformImpl
{
get
{
context.BeginFigure(_startPoint, false);
context.LineTo(_endPoint);
context.EndFigure(false);
PrepareIfNeeded();
return base.PlatformImpl;
}
protected set => base.PlatformImpl = value;
}
PlatformImpl = impl;
public void PrepareIfNeeded()
{
if (_isDirty)
{
_isDirty = false;
using (var context = ((IStreamGeometryImpl)PlatformImpl).Open())
{
context.BeginFigure(StartPoint, false);
context.LineTo(EndPoint);
context.EndFigure(false);
}
}
}
/// <inheritdoc/>
public override Geometry Clone()
{
return new LineGeometry(_startPoint, _endPoint);
PrepareIfNeeded();
return new LineGeometry(StartPoint, EndPoint);
}
private void PointsChanged(AvaloniaPropertyChangedEventArgs e) => _isDirty = true;
}
}

1
src/Avalonia.Visuals/Media/PathMarkupParser.cs

@ -141,6 +141,7 @@ namespace Avalonia.Media
bool isLargeArc = ReadBool(reader);
ReadSeparator(reader);
SweepDirection sweepDirection = ReadBool(reader) ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
ReadSeparator(reader);
point = ReadPoint(reader, point, relative);
_context.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection);

115
src/Avalonia.Visuals/Media/PolylineGeometry.cs

@ -3,10 +3,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Platform;
using Avalonia.Metadata;
using Avalonia.Collections;
namespace Avalonia.Media
{
@ -15,36 +14,118 @@ namespace Avalonia.Media
/// </summary>
public class PolylineGeometry : Geometry
{
private IList<Point> _points;
private bool _isFilled;
/// <summary>
/// Defines the <see cref="Points"/> property.
/// </summary>
public static readonly DirectProperty<PolylineGeometry, Points> PointsProperty =
AvaloniaProperty.RegisterDirect<PolylineGeometry, Points>(nameof(Points), g => g.Points, (g, f) => g.Points = f);
public PolylineGeometry(IList<Point> points, bool isFilled)
/// <summary>
/// Defines the <see cref="IsFilled"/> property.
/// </summary>
public static readonly AvaloniaProperty<bool> IsFilledProperty =
AvaloniaProperty.Register<PolylineGeometry, bool>(nameof(IsFilled));
private Points _points;
private bool _isDirty;
private IDisposable _pointsObserver;
static PolylineGeometry()
{
PointsProperty.Changed.AddClassHandler<PolylineGeometry>((s, e) =>
s.OnPointsChanged(e.OldValue as Points, e.NewValue as Points));
IsFilledProperty.Changed.AddClassHandler<PolylineGeometry>((s, _) => s.NotifyChanged());
}
/// <summary>
/// Initializes a new instance of the <see cref="PolylineGeometry"/> class.
/// </summary>
public PolylineGeometry()
{
_points = points;
_isFilled = isFilled;
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
IStreamGeometryImpl impl = factory.CreateStreamGeometry();
PlatformImpl = factory.CreateStreamGeometry();
Points = new Points();
}
using (IStreamGeometryContextImpl context = impl.Open())
/// <summary>
/// Initializes a new instance of the <see cref="PolylineGeometry"/> class.
/// </summary>
public PolylineGeometry(IEnumerable<Point> points, bool isFilled) : this()
{
Points.AddRange(points);
IsFilled = isFilled;
}
public void PrepareIfNeeded()
{
if (_isDirty)
{
if (points.Count > 0)
_isDirty = false;
using (var context = ((IStreamGeometryImpl)PlatformImpl).Open())
{
context.BeginFigure(points[0], isFilled);
for (int i = 1; i < points.Count; i++)
var points = Points;
var isFilled = IsFilled;
if (points.Count > 0)
{
context.LineTo(points[i]);
context.BeginFigure(points[0], isFilled);
for (int i = 1; i < points.Count; i++)
{
context.LineTo(points[i]);
}
context.EndFigure(isFilled);
}
context.EndFigure(isFilled);
}
}
}
PlatformImpl = impl;
/// <summary>
/// Gets or sets the figures.
/// </summary>
/// <value>
/// The points.
/// </value>
[Content]
public Points Points
{
get => _points;
set => SetAndRaise(PointsProperty, ref _points, value);
}
public bool IsFilled
{
get => GetValue(IsFilledProperty);
set => SetValue(IsFilledProperty, value);
}
public override IGeometryImpl PlatformImpl
{
get
{
PrepareIfNeeded();
return base.PlatformImpl;
}
protected set => base.PlatformImpl = value;
}
/// <inheritdoc/>
public override Geometry Clone()
{
return new PolylineGeometry(new List<Point>(_points), _isFilled);
PrepareIfNeeded();
return new PolylineGeometry(Points, IsFilled);
}
private void OnPointsChanged(Points oldValue, Points newValue)
{
_pointsObserver?.Dispose();
_pointsObserver = newValue?.ForEachItem(f => NotifyChanged(), f => NotifyChanged(), () => NotifyChanged());
}
internal void NotifyChanged()
{
_isDirty = true;
}
}
}

51
src/Avalonia.Visuals/Media/RectangleGeometry.cs

@ -10,16 +10,51 @@ namespace Avalonia.Media
/// </summary>
public class RectangleGeometry : Geometry
{
/// <summary>
/// Defines the <see cref="Rect"/> property.
/// </summary>
public static readonly StyledProperty<Rect> RectProperty =
AvaloniaProperty.Register<RectangleGeometry, Rect>(nameof(Rect));
public Rect Rect
{
get => GetValue(RectProperty);
set => SetValue(RectProperty, value);
}
static RectangleGeometry()
{
RectProperty.Changed.AddClassHandler<RectangleGeometry>(x => x.RectChanged);
}
/// <summary>
/// Initializes a new instance of the <see cref="RectangleGeometry"/> class.
/// </summary>
/// <param name="rect">The rectangle bounds.</param>
public RectangleGeometry(Rect rect)
public RectangleGeometry()
{
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
IStreamGeometryImpl impl = factory.CreateStreamGeometry();
PlatformImpl = factory.CreateStreamGeometry();
}
/// <summary>
/// Initializes a new instance of the <see cref="RectangleGeometry"/> class.
/// </summary>
/// <param name="rect">The rectangle bounds.</param>
public RectangleGeometry(Rect rect) : this()
{
Rect = rect;
}
/// <inheritdoc/>
public override Geometry Clone()
{
return new RectangleGeometry(Rect);
}
using (IStreamGeometryContextImpl context = impl.Open())
private void RectChanged(AvaloniaPropertyChangedEventArgs e)
{
var rect = (Rect)e.NewValue;
using (var context = ((IStreamGeometryImpl)PlatformImpl).Open())
{
context.BeginFigure(rect.TopLeft, true);
context.LineTo(rect.TopRight);
@ -27,14 +62,6 @@ namespace Avalonia.Media
context.LineTo(rect.BottomLeft);
context.EndFigure(true);
}
PlatformImpl = impl;
}
/// <inheritdoc/>
public override Geometry Clone()
{
return new RectangleGeometry(Bounds);
}
}
}

2
src/Avalonia.Visuals/Point.cs

@ -183,7 +183,7 @@ namespace Avalonia
}
else
{
throw new FormatException("Invalid Thickness.");
throw new FormatException("Invalid Point.");
}
}

9
src/Avalonia.Visuals/Points.cs

@ -0,0 +1,9 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Collections;
namespace Avalonia
{
public sealed class Points : AvaloniaList<Point> { }
}

26
src/Avalonia.Visuals/Rect.cs

@ -481,5 +481,31 @@ namespace Avalonia
_width,
_height);
}
/// <summary>
/// Parses a <see cref="Rect"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The parsed <see cref="Rect"/>.</returns>
public static Rect Parse(string s, CultureInfo culture)
{
var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToList();
if (parts.Count == 4)
{
return new Rect(
double.Parse(parts[0], culture),
double.Parse(parts[1], culture),
double.Parse(parts[2], culture),
double.Parse(parts[3], culture));
}
else
{
throw new FormatException("Invalid Rect.");
}
}
}
}

2
src/Avalonia.Visuals/RelativeRect.cs

@ -203,7 +203,7 @@ namespace Avalonia
}
else
{
throw new FormatException("Invalid Rect.");
throw new FormatException("Invalid RelativeRect.");
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -31,6 +31,8 @@
</Compile>
<Compile Include="AvaloniaXamlLoaderPortableXaml.cs" />
<Compile Include="AvaloniaXamlLoader.cs" />
<Compile Include="Converters\MatrixTypeConverter.cs" />
<Compile Include="Converters\RectTypeConverter.cs" />
<Compile Include="Converters\SetterValueTypeConverter.cs" />
<Compile Include="MarkupExtensions\StyleIncludeExtension.cs" />
<Compile Include="PortableXaml\AvaloniaXamlContext.cs" />

23
src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs

@ -0,0 +1,23 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Globalization;
namespace Avalonia.Markup.Xaml.Converters
{
using System.ComponentModel;
public class MatrixTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return Matrix.Parse((string)value, culture);
}
}
}

23
src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs

@ -0,0 +1,23 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Globalization;
namespace Avalonia.Markup.Xaml.Converters
{
using System.ComponentModel;
public class RectTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return Rect.Parse((string)value, culture);
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs

@ -32,12 +32,14 @@ namespace Avalonia.Markup.Xaml.PortableXaml
{ typeof(AvaloniaList<double>), typeof(AvaloniaListTypeConverter<double>) },
{ typeof(IMemberSelector), typeof(MemberSelectorTypeConverter) },
{ typeof(Point), typeof(PointTypeConverter) },
{ typeof(Matrix), typeof(MatrixTypeConverter) },
{ typeof(IList<Point>), typeof(PointsListTypeConverter) },
{ typeof(AvaloniaProperty), typeof(AvaloniaPropertyTypeConverter) },
{ typeof(RelativePoint), typeof(RelativePointTypeConverter) },
{ typeof(RelativeRect), typeof(RelativeRectTypeConverter) },
{ typeof(RowDefinitions), typeof(RowDefinitionsTypeConverter) },
{ typeof(Size), typeof(SizeTypeConverter) },
{ typeof(Rect), typeof(RectTypeConverter) },
{ typeof(Selector), typeof(SelectorTypeConverter)},
{ typeof(SolidColorBrush), typeof(BrushTypeConverter) },
{ typeof(Thickness), typeof(ThicknessTypeConverter) },

16
tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs

@ -0,0 +1,16 @@
using System.Globalization;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Media
{
public class MatrixTests
{
[Fact]
public void Parse_Parses()
{
var matrix = Matrix.Parse("1,2,3,-4,5 6", CultureInfo.CurrentCulture);
var expected = new Matrix(1, 2, 3, -4, 5, 6);
Assert.Equal(expected, matrix);
}
}
}

1
tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs

@ -56,6 +56,7 @@ namespace Avalonia.Visuals.UnitTests.Media
}
[Theory]
[InlineData("F1 M24,14 A2,2,0,1,1,20,14 A2,2,0,1,1,24,14 z")] // issue #1107
[InlineData("M0 0L10 10z")]
[InlineData("M50 50 L100 100 L150 50")]
[InlineData("M50 50L100 100L150 50")]

16
tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs

@ -0,0 +1,16 @@
using System.Globalization;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Media
{
public class RectTests
{
[Fact]
public void Parse_Parses()
{
var rect = Rect.Parse("1,2 3,-4", CultureInfo.CurrentCulture);
var expected = new Rect(1, 2, 3, -4);
Assert.Equal(expected, rect);
}
}
}
Loading…
Cancel
Save