[Core] Added RoundRectangleGeometry (#11851)
* Implemented RoundRectangleGeometry * Fixed broken unit test * Fix formatting * Fixed formatting issues fixes #11151
This commit is contained in:
Родитель
be202998f3
Коммит
d79d5b77e7
|
@ -0,0 +1,88 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentPage
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Xamarin.Forms.Controls.GalleryPages.ShapesGalleries.ClipCornerRadiusGallery"
|
||||
Title="Clip CornerRadius Gallery">
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
<Style x:Key="ImageContainerStyle" TargetType="Grid">
|
||||
<Setter Property="BackgroundColor" Value="LightGray" />
|
||||
<Setter Property="HorizontalOptions" Value="Start" />
|
||||
<Setter Property="HeightRequest" Value="200" />
|
||||
<Setter Property="WidthRequest" Value="200" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Label">
|
||||
<Setter Property="FontSize" Value="Small" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Image">
|
||||
<Setter Property="Aspect" Value="AspectFill" />
|
||||
<Setter Property="HorizontalOptions" Value="Start" />
|
||||
<Setter Property="HeightRequest" Value="200" />
|
||||
<Setter Property="WidthRequest" Value="200" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Slider">
|
||||
<Setter Property="Minimum" Value="0" />
|
||||
<Setter Property="MinimumTrackColor" Value="LightGray" />
|
||||
<Setter Property="Maximum" Value="60" />
|
||||
<Setter Property="MaximumTrackColor" Value="Gray" />
|
||||
<Setter Property="HorizontalOptions" Value="FillAndExpand" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
</ContentPage.Resources>
|
||||
<ContentPage.Content>
|
||||
<StackLayout
|
||||
Padding="12">
|
||||
<Label
|
||||
Text="Clipped Image using RoundRectangleGeometry"/>
|
||||
<Grid
|
||||
Style="{StaticResource ImageContainerStyle}">
|
||||
<Image
|
||||
Source="crimson.jpg">
|
||||
<Image.Clip>
|
||||
<RoundRectangleGeometry
|
||||
x:Name="RoundRectangleGeometry"
|
||||
Rect="0, 0, 150, 150"/>
|
||||
</Image.Clip>
|
||||
</Image>
|
||||
</Grid>
|
||||
<!-- TOP LEFT CORNER -->
|
||||
<Label
|
||||
FontSize="Medium"
|
||||
Text="Top Left Corner"
|
||||
VerticalTextAlignment="Center" />
|
||||
<Slider
|
||||
x:Name="TopLeftCorner"
|
||||
ValueChanged="OnCornerChanged"/>
|
||||
<!-- TOP RIGHT CORNER -->
|
||||
<Label
|
||||
FontSize="Medium"
|
||||
Text="Top Right Corner"
|
||||
VerticalTextAlignment="Center" />
|
||||
<Slider
|
||||
x:Name="TopRightCorner"
|
||||
ValueChanged="OnCornerChanged"/>
|
||||
<!-- BOTTOM LEFT CORNER -->
|
||||
<Label
|
||||
FontSize="Medium"
|
||||
Text="Bottom Left Corner"
|
||||
VerticalTextAlignment="Center" />
|
||||
<Slider
|
||||
x:Name="BottomLeftCorner"
|
||||
ValueChanged="OnCornerChanged"/>
|
||||
<!-- BOTTOM RIGHT CORNER -->
|
||||
<Label
|
||||
FontSize="Medium"
|
||||
Text="Bottom Right Corner"
|
||||
VerticalTextAlignment="Center" />
|
||||
<Slider
|
||||
x:Name="BottomRightCorner"
|
||||
ValueChanged="OnCornerChanged"/>
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
|
@ -0,0 +1,19 @@
|
|||
namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
|
||||
{
|
||||
public partial class ClipCornerRadiusGallery : ContentPage
|
||||
{
|
||||
public ClipCornerRadiusGallery()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
void OnCornerChanged(object sender, ValueChangedEventArgs e)
|
||||
{
|
||||
RoundRectangleGeometry.CornerRadius = new CornerRadius(
|
||||
TopLeftCorner.Value,
|
||||
TopRightCorner.Value,
|
||||
BottomLeftCorner.Value,
|
||||
BottomRightCorner.Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
|
|||
GalleryBuilder.NavButton("Path Transform using string (TypeConverter) Gallery", () => new PathTransformStringGallery(), Navigation),
|
||||
GalleryBuilder.NavButton("Animate Shape Gallery", () => new AnimateShapeGallery(), Navigation),
|
||||
GalleryBuilder.NavButton("Clip Gallery", () => new ClipGallery(), Navigation),
|
||||
GalleryBuilder.NavButton("Clip CornerRadius Gallery", () => new ClipCornerRadiusGallery(), Navigation),
|
||||
GalleryBuilder.NavButton("Clip Views Gallery", () => new ClipViewsGallery(), Navigation),
|
||||
GalleryBuilder.NavButton("Add/Remove Clip Gallery", () => new AddRemoveClipGallery(), Navigation),
|
||||
GalleryBuilder.NavButton("Clip Performance Gallery", () => new ClipPerformanceGallery(), Navigation)
|
||||
|
|
|
@ -59,5 +59,53 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
|
||||
Assert.AreNotEqual(0, points.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRoundLineGeometryConstruction()
|
||||
{
|
||||
var lineGeometry = new LineGeometry(new Point(0, 0), new Point(100, 100));
|
||||
|
||||
Assert.IsNotNull(lineGeometry);
|
||||
Assert.AreEqual(0, lineGeometry.StartPoint.X);
|
||||
Assert.AreEqual(0, lineGeometry.StartPoint.Y);
|
||||
Assert.AreEqual(100, lineGeometry.EndPoint.X);
|
||||
Assert.AreEqual(100, lineGeometry.EndPoint.Y);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEllipseGeometryConstruction()
|
||||
{
|
||||
var ellipseGeometry = new EllipseGeometry(new Point(50, 50), 10, 20);
|
||||
|
||||
Assert.IsNotNull(ellipseGeometry);
|
||||
Assert.AreEqual(50, ellipseGeometry.Center.X);
|
||||
Assert.AreEqual(50, ellipseGeometry.Center.Y);
|
||||
Assert.AreEqual(10, ellipseGeometry.RadiusX);
|
||||
Assert.AreEqual(20, ellipseGeometry.RadiusY);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRectangleGeometryConstruction()
|
||||
{
|
||||
var rectangleGeometry = new RectangleGeometry(new Rect(0, 0, 150, 150));
|
||||
|
||||
Assert.IsNotNull(rectangleGeometry);
|
||||
Assert.AreEqual(150, rectangleGeometry.Rect.Height);
|
||||
Assert.AreEqual(150, rectangleGeometry.Rect.Width);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRoundRectangleGeometryConstruction()
|
||||
{
|
||||
var roundRectangleGeometry = new RoundRectangleGeometry(new CornerRadius(12, 0, 0, 12), new Rect(0, 0, 150, 150));
|
||||
|
||||
Assert.IsNotNull(roundRectangleGeometry);
|
||||
Assert.AreEqual(12, roundRectangleGeometry.CornerRadius.TopLeft);
|
||||
Assert.AreEqual(0, roundRectangleGeometry.CornerRadius.TopRight);
|
||||
Assert.AreEqual(0, roundRectangleGeometry.CornerRadius.BottomLeft);
|
||||
Assert.AreEqual(12, roundRectangleGeometry.CornerRadius.BottomRight);
|
||||
Assert.AreEqual(150, roundRectangleGeometry.Rect.Height);
|
||||
Assert.AreEqual(150, roundRectangleGeometry.Rect.Width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,6 +99,7 @@ namespace Xamarin.Forms.Markup.UnitTests
|
|||
{ typeof(PolyQuadraticBezierSegment), tbd },
|
||||
{ typeof(QuadraticBezierSegment), tbd },
|
||||
{ typeof(RectangleGeometry), tbd },
|
||||
{ typeof(RoundRectangleGeometry), tbd },
|
||||
{ typeof(RotateTransform), tbd },
|
||||
{ typeof(ScaleTransform), tbd },
|
||||
{ typeof(SkewTransform), tbd },
|
||||
|
|
|
@ -262,6 +262,7 @@
|
|||
<Compile Include="RadialGradientBrushTests.cs" />
|
||||
<Compile Include="SwipeViewTests.cs" />
|
||||
<Compile Include="PathSegmentTests.cs" />
|
||||
<Compile Include="GeometryTests.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Xamarin.Forms.Core\Xamarin.Forms.Core.csproj">
|
||||
|
|
|
@ -2,6 +2,18 @@
|
|||
{
|
||||
public class EllipseGeometry : Geometry
|
||||
{
|
||||
public EllipseGeometry()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public EllipseGeometry(Point center, double radiusX, double radiusY)
|
||||
{
|
||||
Center = center;
|
||||
RadiusX = radiusX;
|
||||
RadiusY = radiusY;
|
||||
}
|
||||
|
||||
public static readonly BindableProperty CenterProperty =
|
||||
BindableProperty.Create(nameof(Center), typeof(Point), typeof(EllipseGeometry), new Point());
|
||||
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
namespace Xamarin.Forms.Shapes
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Xamarin.Forms.Shapes
|
||||
{
|
||||
[ContentProperty("Children")]
|
||||
public sealed class GeometryGroup : Geometry
|
||||
public class GeometryGroup : Geometry
|
||||
{
|
||||
public static readonly BindableProperty ChildrenProperty =
|
||||
BindableProperty.Create(nameof(Children), typeof(GeometryCollection), typeof(GeometryGroup), null);
|
||||
BindableProperty.Create(nameof(Children), typeof(GeometryCollection), typeof(GeometryGroup), null,
|
||||
propertyChanged: OnChildrenChanged);
|
||||
|
||||
static void OnChildrenChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
(bindable as GeometryGroup)?.UpdateChildren(oldValue as GeometryCollection, newValue as GeometryCollection);
|
||||
}
|
||||
|
||||
public static readonly BindableProperty FillRuleProperty =
|
||||
BindableProperty.Create(nameof(FillRule), typeof(FillRule), typeof(GeometryGroup), FillRule.EvenOdd);
|
||||
|
@ -25,5 +35,67 @@
|
|||
set { SetValue(FillRuleProperty, value); }
|
||||
get { return (FillRule)GetValue(FillRuleProperty); }
|
||||
}
|
||||
|
||||
public event EventHandler InvalidateGeometryRequested;
|
||||
|
||||
void UpdateChildren(GeometryCollection oldCollection, GeometryCollection newCollection)
|
||||
{
|
||||
if (oldCollection != null)
|
||||
{
|
||||
oldCollection.CollectionChanged -= OnChildrenCollectionChanged;
|
||||
|
||||
foreach (var oldChildren in oldCollection)
|
||||
{
|
||||
oldChildren.PropertyChanged -= OnChildrenPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
if (newCollection == null)
|
||||
return;
|
||||
|
||||
newCollection.CollectionChanged += OnChildrenCollectionChanged;
|
||||
|
||||
foreach (var newChildren in newCollection)
|
||||
{
|
||||
newChildren.PropertyChanged += OnChildrenPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.OldItems != null)
|
||||
{
|
||||
foreach (var oldItem in e.OldItems)
|
||||
{
|
||||
if (!(oldItem is Geometry oldGeometry))
|
||||
continue;
|
||||
|
||||
oldGeometry.PropertyChanged -= OnChildrenPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.NewItems != null)
|
||||
{
|
||||
foreach (var newItem in e.NewItems)
|
||||
{
|
||||
if (!(newItem is Geometry newGeometry))
|
||||
continue;
|
||||
|
||||
newGeometry.PropertyChanged += OnChildrenPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
void OnChildrenPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
void Invalidate()
|
||||
{
|
||||
InvalidateGeometryRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,17 @@
|
|||
{
|
||||
public class LineGeometry : Geometry
|
||||
{
|
||||
public LineGeometry()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public LineGeometry(Point startPoint, Point endPoint)
|
||||
{
|
||||
StartPoint = startPoint;
|
||||
EndPoint = endPoint;
|
||||
}
|
||||
|
||||
public static readonly BindableProperty StartPointProperty =
|
||||
BindableProperty.Create(nameof(StartPoint), typeof(Point), typeof(LineGeometry), new Point());
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
|
||||
namespace Xamarin.Forms.Shapes
|
||||
namespace Xamarin.Forms.Shapes
|
||||
{
|
||||
[ContentProperty("Figures")]
|
||||
public sealed class PathGeometry : Geometry
|
||||
|
@ -10,6 +8,17 @@ namespace Xamarin.Forms.Shapes
|
|||
Figures = new PathFigureCollection();
|
||||
}
|
||||
|
||||
public PathGeometry(PathFigureCollection figures)
|
||||
{
|
||||
Figures = figures;
|
||||
}
|
||||
|
||||
public PathGeometry(PathFigureCollection figures, FillRule fillRule)
|
||||
{
|
||||
Figures = figures;
|
||||
FillRule = fillRule;
|
||||
}
|
||||
|
||||
public static readonly BindableProperty FiguresProperty =
|
||||
BindableProperty.Create(nameof(Figures), typeof(PathFigureCollection), typeof(PathGeometry), null);
|
||||
|
||||
|
|
|
@ -2,6 +2,16 @@
|
|||
{
|
||||
public class RectangleGeometry : Geometry
|
||||
{
|
||||
public RectangleGeometry()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public RectangleGeometry(Rect rect)
|
||||
{
|
||||
Rect = rect;
|
||||
}
|
||||
|
||||
public static readonly BindableProperty RectProperty =
|
||||
BindableProperty.Create(nameof(Rect), typeof(Rect), typeof(RectangleGeometry), new Rect());
|
||||
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
namespace Xamarin.Forms.Shapes
|
||||
{
|
||||
public class RoundRectangleGeometry : GeometryGroup
|
||||
{
|
||||
public RoundRectangleGeometry()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public RoundRectangleGeometry(CornerRadius cornerRadius, Rect rect)
|
||||
{
|
||||
CornerRadius = cornerRadius;
|
||||
Rect = rect;
|
||||
}
|
||||
|
||||
public static readonly BindableProperty RectProperty =
|
||||
BindableProperty.Create(nameof(Rect), typeof(Rect), typeof(RoundRectangleGeometry), new Rect(),
|
||||
propertyChanged: OnRectChanged);
|
||||
|
||||
static void OnRectChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
(bindable as RoundRectangleGeometry)?.UpdateGeometry();
|
||||
}
|
||||
|
||||
public Rect Rect
|
||||
{
|
||||
set { SetValue(RectProperty, value); }
|
||||
get { return (Rect)GetValue(RectProperty); }
|
||||
}
|
||||
|
||||
public static readonly BindableProperty CornerRadiusProperty =
|
||||
BindableProperty.Create(nameof(CornerRadius), typeof(CornerRadius), typeof(RoundRectangleGeometry), new CornerRadius(),
|
||||
propertyChanged: OnCornerRadiusChanged);
|
||||
|
||||
static void OnCornerRadiusChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
(bindable as RoundRectangleGeometry)?.UpdateGeometry();
|
||||
}
|
||||
|
||||
public CornerRadius CornerRadius
|
||||
{
|
||||
set { SetValue(CornerRadiusProperty, value); }
|
||||
get { return (CornerRadius)GetValue(CornerRadiusProperty); }
|
||||
}
|
||||
|
||||
void UpdateGeometry()
|
||||
{
|
||||
FillRule = FillRule.Nonzero;
|
||||
|
||||
Children.Clear();
|
||||
|
||||
Children.Add(GetRoundRectangleGeometry());
|
||||
}
|
||||
|
||||
Geometry GetRoundRectangleGeometry()
|
||||
{
|
||||
GeometryGroup roundedRectGeometry = new GeometryGroup
|
||||
{
|
||||
FillRule = FillRule.Nonzero
|
||||
};
|
||||
|
||||
if (CornerRadius.TopLeft > 0)
|
||||
roundedRectGeometry.Children.Add(
|
||||
new EllipseGeometry(new Point(Rect.Location.X + CornerRadius.TopLeft, Rect.Location.Y + CornerRadius.TopLeft), Rect.Location.Y + CornerRadius.TopLeft, Rect.Location.Y + CornerRadius.TopLeft));
|
||||
|
||||
if (CornerRadius.TopRight > 0)
|
||||
roundedRectGeometry.Children.Add(
|
||||
new EllipseGeometry(new Point(Rect.Location.X + Rect.Width - CornerRadius.TopRight, Rect.Location.Y + CornerRadius.TopRight), CornerRadius.TopRight, CornerRadius.TopRight));
|
||||
|
||||
if (CornerRadius.BottomRight > 0)
|
||||
roundedRectGeometry.Children.Add(
|
||||
new EllipseGeometry(new Point(Rect.Location.X + Rect.Width - CornerRadius.BottomRight, Rect.Location.Y + Rect.Height - CornerRadius.BottomRight), CornerRadius.BottomRight, CornerRadius.BottomRight));
|
||||
|
||||
if (CornerRadius.BottomLeft > 0)
|
||||
roundedRectGeometry.Children.Add(
|
||||
new EllipseGeometry(new Point(Rect.Location.X + CornerRadius.BottomLeft, Rect.Location.Y + Rect.Height - CornerRadius.BottomLeft), CornerRadius.BottomLeft, CornerRadius.BottomLeft));
|
||||
|
||||
PathFigure pathFigure = new PathFigure
|
||||
{
|
||||
IsClosed = true,
|
||||
StartPoint = new Point(Rect.Location.X + CornerRadius.TopLeft, Rect.Location.Y),
|
||||
Segments = new PathSegmentCollection
|
||||
{
|
||||
new LineSegment { Point = new Point(Rect.Location.X + Rect.Width - CornerRadius.TopRight, Rect.Location.Y) },
|
||||
new LineSegment { Point = new Point(Rect.Location.X + Rect.Width, Rect.Location.Y + CornerRadius.TopRight) },
|
||||
new LineSegment { Point = new Point(Rect.Location.X + Rect.Width, Rect.Location.Y + Rect.Height - CornerRadius.BottomRight) },
|
||||
new LineSegment { Point = new Point(Rect.Location.X + Rect.Width - CornerRadius.BottomRight, Rect.Location.Y + Rect.Height) },
|
||||
new LineSegment { Point = new Point(Rect.Location.X + CornerRadius.BottomLeft, Rect.Location.Y + Rect.Height) },
|
||||
new LineSegment { Point = new Point(Rect.Location.X, Rect.Location.Y + Rect.Height - CornerRadius.BottomLeft) },
|
||||
new LineSegment { Point = new Point(Rect.Location.X, Rect.Location.Y + CornerRadius.TopLeft) }
|
||||
}
|
||||
};
|
||||
|
||||
PathFigureCollection pathFigureCollection = new PathFigureCollection
|
||||
{
|
||||
pathFigure
|
||||
};
|
||||
|
||||
roundedRectGeometry.Children.Add(new PathGeometry(pathFigureCollection, FillRule.Nonzero));
|
||||
|
||||
return roundedRectGeometry;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -58,7 +58,49 @@ namespace Xamarin.Forms
|
|||
|
||||
internal static readonly BindableProperty TransformProperty = BindableProperty.Create("Transform", typeof(string), typeof(VisualElement), null, propertyChanged: OnTransformChanged);
|
||||
|
||||
public static readonly BindableProperty ClipProperty = BindableProperty.Create(nameof(Clip), typeof(Geometry), typeof(VisualElement), null);
|
||||
public static readonly BindableProperty ClipProperty = BindableProperty.Create(nameof(Clip), typeof(Geometry), typeof(VisualElement), null,
|
||||
propertyChanging: (bindable, oldvalue, newvalue) =>
|
||||
{
|
||||
if (oldvalue != null)
|
||||
(bindable as VisualElement)?.StopNotifyingClipChanges();
|
||||
},
|
||||
propertyChanged: (bindable, oldvalue, newvalue) =>
|
||||
{
|
||||
if (newvalue != null)
|
||||
(bindable as VisualElement)?.NotifyClipChanges();
|
||||
});
|
||||
|
||||
void NotifyClipChanges()
|
||||
{
|
||||
if (Clip != null)
|
||||
{
|
||||
Clip.PropertyChanged += OnClipChanged;
|
||||
|
||||
if (Clip is GeometryGroup geometryGroup)
|
||||
geometryGroup.InvalidateGeometryRequested += InvalidateGeometryRequested;
|
||||
}
|
||||
}
|
||||
|
||||
void StopNotifyingClipChanges()
|
||||
{
|
||||
if (Clip != null)
|
||||
{
|
||||
Clip.PropertyChanged -= OnClipChanged;
|
||||
|
||||
if(Clip is GeometryGroup geometryGroup)
|
||||
geometryGroup.InvalidateGeometryRequested -= InvalidateGeometryRequested;
|
||||
}
|
||||
}
|
||||
|
||||
void OnClipChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(Clip));
|
||||
}
|
||||
|
||||
void InvalidateGeometryRequested(object sender, EventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(Clip));
|
||||
}
|
||||
|
||||
public static readonly BindableProperty VisualProperty =
|
||||
BindableProperty.Create(nameof(Visual), typeof(IVisual), typeof(VisualElement), Forms.VisualMarker.MatchParent,
|
||||
|
|
Загрузка…
Ссылка в новой задаче