diff --git a/Stubs/Xamarin.Forms.Platform.cs b/Stubs/Xamarin.Forms.Platform.cs index 7fc36aed8a5..b5e8ee170b0 100644 --- a/Stubs/Xamarin.Forms.Platform.cs +++ b/Stubs/Xamarin.Forms.Platform.cs @@ -189,7 +189,6 @@ internal class _RefreshViewRenderer { } [RenderWith(typeof(SwipeViewRenderer))] internal class _SwipeViewRenderer { } -#if !TIZEN4_0 [RenderWith(typeof(PathRenderer))] internal class _PathRenderer { } @@ -207,7 +206,6 @@ internal class _PolygonRenderer { } [RenderWith(typeof(RectangleRenderer))] internal class _RectangleRenderer { } -#endif } diff --git a/Xamarin.Forms.Platform.Tizen/Extensions/BrushExtensions.cs b/Xamarin.Forms.Platform.Tizen/Extensions/BrushExtensions.cs index fab995e95c0..5e040f6c165 100644 --- a/Xamarin.Forms.Platform.Tizen/Extensions/BrushExtensions.cs +++ b/Xamarin.Forms.Platform.Tizen/Extensions/BrushExtensions.cs @@ -52,26 +52,37 @@ public static SKPaint GetBackgroundPaint(this VisualElement element, SKRectI bou Style = SKPaintStyle.Fill }; - SKShader backgroundShader = null; if (brush is GradientBrush fillGradientBrush) { - if (fillGradientBrush is LinearGradientBrush linearGradientBrush) - backgroundShader = CreateLinearGradient(linearGradientBrush, bounds); + paint.Shader = fillGradientBrush.CreateShader(bounds); + } + else if (brush is SolidColorBrush solidColorBrush) + { + paint.Color = solidColorBrush.ToSolidColor(); + } + return paint; + } - if (fillGradientBrush is RadialGradientBrush radialGradientBrush) - backgroundShader = CreateRadialGradient(radialGradientBrush, bounds); + public static SKShader CreateShader(this GradientBrush gradientBrush, SKRect bounds) + { + SKShader shader = null; - paint.Shader = backgroundShader; - } - else + if (gradientBrush is LinearGradientBrush linearGradientBrush) { - SKColor fillColor = Color.Default.ToNative().ToSKColor(); - if (brush is SolidColorBrush solidColorBrush && solidColorBrush.Color != Color.Default) - fillColor = solidColorBrush.Color.ToNative().ToSKColor(); + shader = CreateLinearGradient(linearGradientBrush, bounds); + } - paint.Color = fillColor; + if (gradientBrush is RadialGradientBrush radialGradientBrush) + { + shader = CreateRadialGradient(radialGradientBrush, bounds); } - return paint; + + return shader; + } + + public static SKColor ToSolidColor(this SolidColorBrush solidColorBrush) + { + return solidColorBrush.Color != Color.Default ? solidColorBrush.Color.ToNative().ToSKColor() : SKColor.Empty; } static SKShader CreateLinearGradient(LinearGradientBrush linearGradientBrush, SKRect pathBounds) diff --git a/Xamarin.Forms.Platform.Tizen/Extensions/GeometryExtensions.cs b/Xamarin.Forms.Platform.Tizen/Extensions/GeometryExtensions.cs new file mode 100644 index 00000000000..4f84467df0a --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Extensions/GeometryExtensions.cs @@ -0,0 +1,205 @@ +using System.Collections.Generic; +using Xamarin.Forms.Shapes; +using SkiaSharp; +using FormsRectangle = Xamarin.Forms.Rectangle; + +namespace Xamarin.Forms.Platform.Tizen +{ + public static class GeometryExtensions + { + public static SKPath ToSKPath(this Geometry geometry) + { + return MakePath((dynamic)geometry); + } + + static SKPath MakePath(Geometry geometry) + { + return new SKPath(); + } + + static SKPath MakePath(LineGeometry lineGeometry) + { + var path = new SKPath(); + path.MoveTo( + Forms.ConvertToScaledPixel(lineGeometry.StartPoint.X), + Forms.ConvertToScaledPixel(lineGeometry.StartPoint.Y)); + + path.LineTo( + Forms.ConvertToScaledPixel(lineGeometry.EndPoint.X), + Forms.ConvertToScaledPixel(lineGeometry.EndPoint.Y)); + + return path; + } + + static SKPath MakePath(RectangleGeometry rectangleGeometry) + { + var path = new SKPath(); + FormsRectangle rect = rectangleGeometry.Rect; + + path.AddRect(new SKRect( + Forms.ConvertToScaledPixel(rect.Left), + Forms.ConvertToScaledPixel(rect.Top), + Forms.ConvertToScaledPixel(rect.Right), + Forms.ConvertToScaledPixel(rect.Bottom)), + SKPathDirection.Clockwise); + + return path; + } + + static SKPath MakePath(EllipseGeometry ellipseGeometry) + { + var path = new SKPath(); + path.AddOval(new SKRect( + Forms.ConvertToScaledPixel(ellipseGeometry.Center.X - ellipseGeometry.RadiusX), + Forms.ConvertToScaledPixel(ellipseGeometry.Center.Y - ellipseGeometry.RadiusY), + Forms.ConvertToScaledPixel(ellipseGeometry.Center.X + ellipseGeometry.RadiusX), + Forms.ConvertToScaledPixel(ellipseGeometry.Center.Y + ellipseGeometry.RadiusY)), + SKPathDirection.Clockwise); + + return path; + } + + static SKPath MakePath(GeometryGroup geometryGroup) + { + var path = new SKPath(); + path.FillType = geometryGroup.FillRule == FillRule.Nonzero ? SKPathFillType.Winding : SKPathFillType.EvenOdd; + + foreach (Geometry child in geometryGroup.Children) + { + SKPath childPath = MakePath((dynamic)child); + path.AddPath(childPath); + } + + return path; + } + + static SKPath MakePath(PathGeometry pathGeometry) + { + var path = new SKPath(); + path.FillType = pathGeometry.FillRule == FillRule.Nonzero ? SKPathFillType.Winding : SKPathFillType.EvenOdd; + + foreach (PathFigure pathFigure in pathGeometry.Figures) + { + path.MoveTo( + Forms.ConvertToScaledPixel(pathFigure.StartPoint.X), + Forms.ConvertToScaledPixel(pathFigure.StartPoint.Y)); + + Point lastPoint = pathFigure.StartPoint; + + foreach (PathSegment pathSegment in pathFigure.Segments) + { + // LineSegment + if (pathSegment is LineSegment) + { + LineSegment lineSegment = pathSegment as LineSegment; + + path.LineTo( + Forms.ConvertToScaledPixel(lineSegment.Point.X), + Forms.ConvertToScaledPixel(lineSegment.Point.Y)); + lastPoint = lineSegment.Point; + } + // PolylineSegment + else if (pathSegment is PolyLineSegment) + { + PolyLineSegment polylineSegment = pathSegment as PolyLineSegment; + PointCollection points = polylineSegment.Points; + + for (int i = 0; i < points.Count; i++) + { + path.LineTo( + Forms.ConvertToScaledPixel(points[i].X), + Forms.ConvertToScaledPixel(points[i].Y)); + } + lastPoint = points[points.Count - 1]; + } + // BezierSegment + else if (pathSegment is BezierSegment) + { + BezierSegment bezierSegment = pathSegment as BezierSegment; + + path.CubicTo( + Forms.ConvertToScaledPixel(bezierSegment.Point1.X), Forms.ConvertToScaledPixel(bezierSegment.Point1.Y), + Forms.ConvertToScaledPixel(bezierSegment.Point2.X), Forms.ConvertToScaledPixel(bezierSegment.Point2.Y), + Forms.ConvertToScaledPixel(bezierSegment.Point3.X), Forms.ConvertToScaledPixel(bezierSegment.Point3.Y)); + + lastPoint = bezierSegment.Point3; + } + // PolyBezierSegment + else if (pathSegment is PolyBezierSegment) + { + PolyBezierSegment polyBezierSegment = pathSegment as PolyBezierSegment; + PointCollection points = polyBezierSegment.Points; + + for (int i = 0; i < points.Count; i += 3) + { + path.CubicTo( + Forms.ConvertToScaledPixel(points[i + 0].X), Forms.ConvertToScaledPixel(points[i + 0].Y), + Forms.ConvertToScaledPixel(points[i + 1].X), Forms.ConvertToScaledPixel(points[i + 1].Y), + Forms.ConvertToScaledPixel(points[i + 2].X), Forms.ConvertToScaledPixel(points[i + 2].Y)); + } + + lastPoint = points[points.Count - 1]; + } + // QuadraticBezierSegment + else if (pathSegment is QuadraticBezierSegment) + { + QuadraticBezierSegment bezierSegment = pathSegment as QuadraticBezierSegment; + + path.QuadTo( + Forms.ConvertToScaledPixel(bezierSegment.Point1.X), Forms.ConvertToScaledPixel(bezierSegment.Point1.Y), + Forms.ConvertToScaledPixel(bezierSegment.Point2.X), Forms.ConvertToScaledPixel(bezierSegment.Point2.Y)); + + lastPoint = bezierSegment.Point2; + } + // PolyQuadraticBezierSegment + else if (pathSegment is PolyQuadraticBezierSegment) + { + PolyQuadraticBezierSegment polyBezierSegment = pathSegment as PolyQuadraticBezierSegment; + PointCollection points = polyBezierSegment.Points; + + for (int i = 0; i < points.Count; i += 2) + { + path.QuadTo( + Forms.ConvertToScaledPixel(points[i + 0].X), Forms.ConvertToScaledPixel(points[i + 0].Y), + Forms.ConvertToScaledPixel(points[i + 1].X), Forms.ConvertToScaledPixel(points[i + 1].Y)); + } + + lastPoint = points[points.Count - 1]; + } + // ArcSegment + else if (pathSegment is ArcSegment) + { + ArcSegment arcSegment = pathSegment as ArcSegment; + + List points = new List(); + + GeometryHelper.FlattenArc(points, + lastPoint, + arcSegment.Point, + arcSegment.Size.Width, + arcSegment.Size.Height, + arcSegment.RotationAngle, + arcSegment.IsLargeArc, + arcSegment.SweepDirection == SweepDirection.CounterClockwise, + 1); + + for (int i = 0; i < points.Count; i++) + { + path.LineTo( + Forms.ConvertToScaledPixel(points[i].X), + Forms.ConvertToScaledPixel(points[i].Y)); + } + + if (points.Count > 0) + lastPoint = points[points.Count - 1]; + } + } + + if (pathFigure.IsClosed) + path.Close(); + } + + return path; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Extensions/TransformExtensions.cs b/Xamarin.Forms.Platform.Tizen/Extensions/TransformExtensions.cs new file mode 100644 index 00000000000..82398bf147d --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Extensions/TransformExtensions.cs @@ -0,0 +1,31 @@ +using SkiaSharp; +using Xamarin.Forms.Shapes; + +namespace Xamarin.Forms.Platform.Tizen +{ + public static class TransformExtensions + { + public static SKMatrix ToSkia(this Transform transform) + { + SKMatrix skMatrix = SKMatrix.CreateIdentity(); + + if (transform == null) + return skMatrix; + + Matrix matrix = transform.Value; + + skMatrix.Values = new float[] { + (float)matrix.M11, + (float)matrix.M21, + Forms.ConvertToScaledPixel(matrix.OffsetX), + (float)matrix.M12, + (float)matrix.M22, + Forms.ConvertToScaledPixel(matrix.OffsetY), + 0, + 0, + 1 }; + + return skMatrix; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Forms.cs b/Xamarin.Forms.Platform.Tizen/Forms.cs index 8d5a2917b3f..d0ca3a63a13 100644 --- a/Xamarin.Forms.Platform.Tizen/Forms.cs +++ b/Xamarin.Forms.Platform.Tizen/Forms.cs @@ -7,6 +7,7 @@ using ElmSharp; using ElmSharp.Wearable; using Tizen.Applications; +using Xamarin.Forms.Shapes; using Xamarin.Forms.Internals; using Xamarin.Forms.Platform.Tizen; using DeviceOrientation = Xamarin.Forms.Internals.DeviceOrientation; @@ -581,6 +582,13 @@ static void RegisterSkiaSharpRenderers() Registrar.Registered.Register(typeof(Frame), typeof(Platform.Tizen.SkiaSharp.FrameRenderer)); Registrar.Registered.Register(typeof(BoxView), typeof(Platform.Tizen.SkiaSharp.BoxViewRenderer)); Registrar.Registered.Register(typeof(Image), typeof(Platform.Tizen.SkiaSharp.ImageRenderer)); + + Registrar.Registered.Register(typeof(Ellipse), typeof(Platform.Tizen.SkiaSharp.EllipseRenderer)); + Registrar.Registered.Register(typeof(Line), typeof(Platform.Tizen.SkiaSharp.LineRenderer)); + Registrar.Registered.Register(typeof(Path), typeof(Platform.Tizen.SkiaSharp.PathRenderer)); + Registrar.Registered.Register(typeof(Shapes.Polygon), typeof(Platform.Tizen.SkiaSharp.PolygonRenderer)); + Registrar.Registered.Register(typeof(Polyline), typeof(Platform.Tizen.SkiaSharp.PolylineRenderer)); + Registrar.Registered.Register(typeof(Shapes.Rectangle), typeof(Platform.Tizen.SkiaSharp.RectangleRenderer)); } static Color GetAccentColor(string profile) diff --git a/Xamarin.Forms.Platform.Tizen/Properties/AssemblyInfo.cs b/Xamarin.Forms.Platform.Tizen/Properties/AssemblyInfo.cs index a81899d88ed..af12a805ad6 100644 --- a/Xamarin.Forms.Platform.Tizen/Properties/AssemblyInfo.cs +++ b/Xamarin.Forms.Platform.Tizen/Properties/AssemblyInfo.cs @@ -1,5 +1,6 @@ using Xamarin.Forms; using Xamarin.Forms.Platform.Tizen; +using Xamarin.Forms.Shapes; [assembly: Dependency(typeof(ResourcesProvider))] [assembly: Dependency(typeof(Deserializer))] @@ -65,4 +66,11 @@ [assembly: ExportHandler(typeof(PanGestureRecognizer), typeof(PanGestureHandler))] [assembly: ExportHandler(typeof(SwipeGestureRecognizer), typeof(SwipeGestureHandler))] -[assembly: ExportRenderer(typeof(Shell), typeof(Xamarin.Forms.Platform.Tizen.Watch.ShellRenderer), TargetIdiom.Watch)] \ No newline at end of file +[assembly: ExportRenderer(typeof(Shell), typeof(Xamarin.Forms.Platform.Tizen.Watch.ShellRenderer), TargetIdiom.Watch)] + +[assembly: ExportRenderer(typeof(Ellipse), typeof(EllipseRenderer))] +[assembly: ExportRenderer(typeof(Line), typeof(LineRenderer))] +[assembly: ExportRenderer(typeof(Path), typeof(PathRenderer))] +[assembly: ExportRenderer(typeof(Polygon), typeof(PolygonRenderer))] +[assembly: ExportRenderer(typeof(Polyline), typeof(PolylineRenderer))] +[assembly: ExportRenderer(typeof(Xamarin.Forms.Shapes.Rectangle), typeof(RectangleRenderer))] diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/DefaultRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/DefaultRenderer.cs index f921756cc30..e1905e96c0f 100644 --- a/Xamarin.Forms.Platform.Tizen/Renderers/DefaultRenderer.cs +++ b/Xamarin.Forms.Platform.Tizen/Renderers/DefaultRenderer.cs @@ -1,3 +1,4 @@ +using Xamarin.Forms.Shapes; using ElmSharp; using ELayout = ElmSharp.Layout; @@ -15,4 +16,30 @@ protected override void OnElementChanged(ElementChangedEventArgs base.OnElementChanged(e); } } -} \ No newline at end of file + + public class EllipseRenderer : ShapeRenderer { } + + public class LineRenderer : ShapeRenderer { } + + public class PathRenderer : ShapeRenderer { } + + public class PolygonRenderer : ShapeRenderer { } + + public class PolylineRenderer : ShapeRenderer { } + + public class RectangleRenderer : ShapeRenderer { } + + public class ShapeRenderer : VisualElementRenderer + { + protected override void OnElementChanged(ElementChangedEventArgs e) + { + Log.Info("Use skia render mode (InitializationOptions.UseSkiaSharp=true) to use Shape."); + if (NativeView == null) + { + var control = new ELayout(Forms.NativeParent); + SetNativeView(control); + } + base.OnElementChanged(e); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/PageRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/PageRenderer.cs index a73e196434b..9af39bfe8ba 100644 --- a/Xamarin.Forms.Platform.Tizen/Renderers/PageRenderer.cs +++ b/Xamarin.Forms.Platform.Tizen/Renderers/PageRenderer.cs @@ -88,13 +88,13 @@ protected override void Dispose(bool disposing) _moreOption.Value.Items.Clear(); _moreOption.Value.Unrealize(); } + } - if (_backgroundCanvas.IsValueCreated) - { - BackgroundCanvas.PaintSurface -= OnBackgroundPaint; - BackgroundCanvas.Unrealize(); - _backgroundCanvas = null; - } + if (Forms.UseSkiaSharp && _backgroundCanvas.IsValueCreated) + { + BackgroundCanvas.PaintSurface -= OnBackgroundPaint; + BackgroundCanvas.Unrealize(); + _backgroundCanvas = null; } } base.Dispose(disposing); diff --git a/Xamarin.Forms.Platform.Tizen/Shapes/EllipseRenderer.cs b/Xamarin.Forms.Platform.Tizen/Shapes/EllipseRenderer.cs new file mode 100644 index 00000000000..f2895773680 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Shapes/EllipseRenderer.cs @@ -0,0 +1,33 @@ +using SkiaSharp; +using Xamarin.Forms.Shapes; + +namespace Xamarin.Forms.Platform.Tizen.SkiaSharp +{ + public class EllipseRenderer : ShapeRenderer + { + protected override void OnElementChanged(ElementChangedEventArgs e) + { + if (Control == null) + { + SetNativeControl(new EllipseView()); + } + + base.OnElementChanged(e); + } + } + + public class EllipseView : ShapeView + { + public EllipseView() : base() + { + UpdateShape(); + } + + void UpdateShape() + { + var path = new SKPath(); + path.AddCircle(0, 0, 1); + UpdateShape(path); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Tizen/Shapes/LineRenderer.cs b/Xamarin.Forms.Platform.Tizen/Shapes/LineRenderer.cs new file mode 100644 index 00000000000..ef2e0babbfb --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Shapes/LineRenderer.cs @@ -0,0 +1,87 @@ +using SkiaSharp; +using Xamarin.Forms.Shapes; + +namespace Xamarin.Forms.Platform.Tizen.SkiaSharp +{ + public class LineRenderer : ShapeRenderer + { + public LineRenderer() : base() + { + RegisterPropertyHandler(Line.X1Property, UpdateX1); + RegisterPropertyHandler(Line.Y1Property, UpdateY1); + RegisterPropertyHandler(Line.X2Property, UpdateX2); + RegisterPropertyHandler(Line.Y2Property, UpdateY2); + } + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + if (Control == null) + { + SetNativeControl(new LineView()); + } + + base.OnElementChanged(e); + } + + void UpdateX1() + { + Control.UpdateX1((float)Element.X1); + } + + void UpdateY1() + { + Control.UpdateY1((float)Element.Y1); + } + + void UpdateX2() + { + Control.UpdateX2((float)Element.X2); + } + + void UpdateY2() + { + Control.UpdateY2((float)Element.Y2); + } + } + + public class LineView : ShapeView + { + float _x1, _y1, _x2, _y2; + + public LineView() : base() + { + } + + void UpdateShape() + { + var path = new SKPath(); + path.MoveTo(_x1, _y1); + path.LineTo(_x2, _y2); + UpdateShape(path); + } + + public void UpdateX1(float x1) + { + _x1 = Forms.ConvertToScaledPixel(x1); + UpdateShape(); + } + + public void UpdateY1(float y1) + { + _y1 = Forms.ConvertToScaledPixel(y1); + UpdateShape(); + } + + public void UpdateX2(float x2) + { + _x2 = Forms.ConvertToScaledPixel(x2); + UpdateShape(); + } + + public void UpdateY2(float y2) + { + _y2 = Forms.ConvertToScaledPixel(y2); + UpdateShape(); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Shapes/PathRenderer.cs b/Xamarin.Forms.Platform.Tizen/Shapes/PathRenderer.cs new file mode 100644 index 00000000000..e2ca8b5ee6a --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Shapes/PathRenderer.cs @@ -0,0 +1,55 @@ +using SkiaSharp; +using Path = Xamarin.Forms.Shapes.Path; + +namespace Xamarin.Forms.Platform.Tizen.SkiaSharp +{ + public class PathRenderer : ShapeRenderer + { + public PathRenderer() : base() + { + RegisterPropertyHandler(Path.DataProperty, UpdateData); + RegisterPropertyHandler(Path.RenderTransformProperty, UpdateRenderTransform); + } + protected override void OnElementChanged(ElementChangedEventArgs e) + { + if (Control == null) + { + SetNativeControl(new PathView()); + } + + base.OnElementChanged(e); + } + + void UpdateData() + { + Control.UpdateData(Element.Data.ToSKPath()); + } + + void UpdateRenderTransform() + { + UpdateData(); + + if (Element.RenderTransform != null) + { + Control.UpdateTransform(Element.RenderTransform.ToSkia()); + } + } + } + + public class PathView : ShapeView + { + public PathView() : base() + { + } + + public void UpdateData(SKPath path) + { + UpdateShape(path); + } + + public void UpdateTransform(SKMatrix transform) + { + UpdateShapeTransform(transform); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Shapes/PolygonRenderer.cs b/Xamarin.Forms.Platform.Tizen/Shapes/PolygonRenderer.cs new file mode 100644 index 00000000000..8183b7ef687 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Shapes/PolygonRenderer.cs @@ -0,0 +1,86 @@ +using System.Collections.Specialized; +using SkiaSharp; +using Xamarin.Forms.Shapes; + +namespace Xamarin.Forms.Platform.Tizen.SkiaSharp +{ + public class PolygonRenderer : ShapeRenderer + { + public PolygonRenderer() : base() + { + RegisterPropertyHandler(Polygon.PointsProperty, UpdatePoints); + RegisterPropertyHandler(Polygon.FillRuleProperty, UpdateFillRule); + } + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + if (Control == null) + { + SetNativeControl(new PolygonView()); + } + + base.OnElementChanged(e); + + if (e.NewElement != null) + { + var points = e.NewElement.Points; + points.CollectionChanged += OnCollectionChanged; + } + } + + void UpdatePoints() + { + Control.UpdatePoints(Element.Points); + } + + void UpdateFillRule() + { + Control.UpdateFillMode(Element.FillRule == FillRule.Nonzero); + } + + void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + UpdatePoints(); + } + } + + public class PolygonView : ShapeView + { + PointCollection _points; + bool _fillMode; + + public PolygonView() : base() + { + } + + void UpdateShape() + { + if (_points != null && _points.Count > 1) + { + SKPath path = new SKPath(); + path.FillType = _fillMode ? SKPathFillType.Winding : SKPathFillType.EvenOdd; + + path.MoveTo(Forms.ConvertToScaledPixel(_points[0].X), Forms.ConvertToScaledPixel(_points[0].Y)); + for (int index = 1; index < _points.Count; index++) + { + path.LineTo(Forms.ConvertToScaledPixel(_points[index].X), Forms.ConvertToScaledPixel(_points[index].Y)); + } + path.Close(); + + UpdateShape(path); + } + } + + public void UpdatePoints(PointCollection points) + { + _points = points; + UpdateShape(); + } + + public void UpdateFillMode(bool fillMode) + { + _fillMode = fillMode; + UpdateShape(); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Shapes/PolylineRenderer.cs b/Xamarin.Forms.Platform.Tizen/Shapes/PolylineRenderer.cs new file mode 100644 index 00000000000..10d4478695f --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Shapes/PolylineRenderer.cs @@ -0,0 +1,83 @@ +using System.ComponentModel; +using SkiaSharp; +using Xamarin.Forms.Shapes; + +namespace Xamarin.Forms.Platform.Tizen.SkiaSharp +{ + public class PolylineRenderer : ShapeRenderer + { + public PolylineRenderer() : base() + { + RegisterPropertyHandler(Polyline.PointsProperty, UpdatePoints); + RegisterPropertyHandler(Polyline.FillRuleProperty, UpdateFillRule); + } + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + if (Control == null) + { + SetNativeControl(new PolylineView()); + } + + base.OnElementChanged(e); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == Polyline.PointsProperty.PropertyName) + UpdatePoints(); + else if (e.PropertyName == Polyline.FillRuleProperty.PropertyName) + UpdateFillRule(); + } + + void UpdatePoints() + { + Control.UpdatePoints(Element.Points); + } + + void UpdateFillRule() + { + Control.UpdateFillMode(Element.FillRule == FillRule.Nonzero); + } + } + + public class PolylineView : ShapeView + { + PointCollection _points; + bool _fillMode; + + public PolylineView() : base() + { + } + + void UpdateShape() + { + if (_points != null && _points.Count > 1) + { + SKPath path = new SKPath(); + path.FillType = _fillMode ? SKPathFillType.Winding : SKPathFillType.EvenOdd; + + path.MoveTo(Forms.ConvertToScaledPixel(_points[0].X), Forms.ConvertToScaledPixel(_points[0].Y)); + + for (int index = 1; index < _points.Count; index++) + path.LineTo(Forms.ConvertToScaledPixel(_points[index].X), Forms.ConvertToScaledPixel(_points[index].Y)); + + UpdateShape(path); + } + } + + public void UpdatePoints(PointCollection points) + { + _points = points; + UpdateShape(); + } + + public void UpdateFillMode(bool fillMode) + { + _fillMode = fillMode; + UpdateShape(); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Shapes/RectangleRenderer.cs b/Xamarin.Forms.Platform.Tizen/Shapes/RectangleRenderer.cs new file mode 100644 index 00000000000..40c2b34dfd7 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Shapes/RectangleRenderer.cs @@ -0,0 +1,66 @@ +using SkiaSharp; +using FormsRectangle = Xamarin.Forms.Shapes.Rectangle; + + +namespace Xamarin.Forms.Platform.Tizen.SkiaSharp +{ + public class RectangleRenderer : ShapeRenderer + { + public RectangleRenderer() : base() + { + RegisterPropertyHandler(FormsRectangle.RadiusXProperty, UpdateRadiusX); + RegisterPropertyHandler(FormsRectangle.RadiusYProperty, UpdateRadiusY); + } + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + if (Control == null) + { + SetNativeControl(new RectView()); + } + + base.OnElementChanged(e); + } + + void UpdateRadiusX() + { + Control.UpdateRadiusX(Element.RadiusX / Element.WidthRequest); + } + + void UpdateRadiusY() + { + Control.UpdateRadiusY(Element.RadiusY / Element.HeightRequest); + } + } + + public class RectView : ShapeView + { + public RectView() : base() + { + UpdateShape(); + } + + public float RadiusX { set; get; } + + public float RadiusY { set; get; } + + void UpdateShape() + { + var path = new SKPath(); + path.AddRoundRect(new SKRect(0, 0, 1, 1), RadiusX, RadiusY, SKPathDirection.Clockwise); + UpdateShape(path); + } + + public void UpdateRadiusX(double radiusX) + { + RadiusX = (float)radiusX; + UpdateShape(); + } + + public void UpdateRadiusY(double radiusY) + { + RadiusY = (float)radiusY; + UpdateShape(); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Shapes/ShapeRenderer.cs b/Xamarin.Forms.Platform.Tizen/Shapes/ShapeRenderer.cs new file mode 100644 index 00000000000..957179ffdb8 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Shapes/ShapeRenderer.cs @@ -0,0 +1,99 @@ +using System.Linq; +using SkiaSharp; +using Xamarin.Forms.Shapes; +using Shape = Xamarin.Forms.Shapes.Shape; + +namespace Xamarin.Forms.Platform.Tizen.SkiaSharp +{ + public class ShapeRenderer : ViewRenderer + where TShape : Shape + where TNativeShape : ShapeView + { + + public ShapeRenderer() + { + RegisterPropertyHandler(Shape.AspectProperty, UpdateAspect); + RegisterPropertyHandler(Shape.FillProperty, UpdateFill); + RegisterPropertyHandler(Shape.StrokeProperty, UpdateStroke); + RegisterPropertyHandler(Shape.StrokeThicknessProperty, UpdateStrokeThickness); + RegisterPropertyHandler(Shape.StrokeDashArrayProperty, UpdateStrokeDashArray); + RegisterPropertyHandler(Shape.StrokeDashOffsetProperty, UpdateStrokeDashOffset); + RegisterPropertyHandler(Shape.StrokeLineCapProperty, UpdateStrokeLineCap); + RegisterPropertyHandler(Shape.StrokeLineJoinProperty, UpdateStrokeLineJoin); + RegisterPropertyHandler(Shape.StrokeMiterLimitProperty, UpdateStrokeMiterLimit); + } + + void UpdateAspect() + { + Control.UpdateAspect(Element.Aspect); + } + + void UpdateFill() + { + Control.UpdateFill(Element.Fill); + } + + void UpdateStroke() + { + Control.UpdateStroke(Element.Stroke); + } + + void UpdateStrokeThickness() + { + Control.UpdateStrokeThickness(Element.StrokeThickness); + } + + void UpdateStrokeDashArray() + { + Control.UpdateStrokeDashArray(Element.StrokeDashArray.Select(x => (float)x).ToArray()); + } + + void UpdateStrokeDashOffset() + { + Control.UpdateStrokeDashOffset((float)Element.StrokeDashOffset); + } + + void UpdateStrokeLineCap() + { + PenLineCap lineCap = Element.StrokeLineCap; + SKStrokeCap skStrokeCap = SKStrokeCap.Butt; + switch (lineCap) + { + case PenLineCap.Flat: + skStrokeCap = SKStrokeCap.Butt; + break; + case PenLineCap.Square: + skStrokeCap = SKStrokeCap.Square; + break; + case PenLineCap.Round: + skStrokeCap = SKStrokeCap.Round; + break; + } + Control.UpdateStrokeLineCap(skStrokeCap); + } + + void UpdateStrokeLineJoin() + { + PenLineJoin lineJoin = Element.StrokeLineJoin; + SKStrokeJoin skStrokeJoin = SKStrokeJoin.Miter; + switch (lineJoin) + { + case PenLineJoin.Miter: + skStrokeJoin = SKStrokeJoin.Miter; + break; + case PenLineJoin.Bevel: + skStrokeJoin = SKStrokeJoin.Bevel; + break; + case PenLineJoin.Round: + skStrokeJoin = SKStrokeJoin.Round; + break; + } + Control.UpdateStrokeLineJoin(skStrokeJoin); + } + + void UpdateStrokeMiterLimit() + { + Control.UpdateStrokeMiterLimit((float)Element.StrokeMiterLimit); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Shapes/ShapeView.cs b/Xamarin.Forms.Platform.Tizen/Shapes/ShapeView.cs new file mode 100644 index 00000000000..2f31ead7dd4 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Shapes/ShapeView.cs @@ -0,0 +1,288 @@ +using System; +using SkiaSharp; +using SkiaSharp.Views.Tizen; +using Xamarin.Forms.Platform.Tizen.Native; + +namespace Xamarin.Forms.Platform.Tizen.SkiaSharp +{ + public class ShapeView : Canvas, IMeasurable + { + SKCanvasView _skCanvasView; + SKPath _skPath; + SKPaint _skPaint; + SKRect _drawableBounds; + SKRect _pathFillBounds; + SKRect _pathStrokeBounds; + SKMatrix _transform; + + Brush _stroke; + Brush _fill; + Stretch _stretch; + + float _strokeWidth; + float[] _strokeDash; + float _strokeDashOffset; + + public ShapeView() : base(Forms.NativeParent) + { + _skPaint = new SKPaint(); + _skPaint.IsAntialias = true; + _skCanvasView = new SKCanvasView(Forms.NativeParent); + _skCanvasView.PaintSurface += OnPaintSurface; + _skCanvasView.Show(); + Children.Add(_skCanvasView); + LayoutUpdated += OnLayoutUpdated; + + _pathFillBounds = new SKRect(); + _pathStrokeBounds = new SKRect(); + + _stretch = Stretch.None; + } + + void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e) + { + var canvas = e.Surface.Canvas; + _drawableBounds = e.Info.Rect; + canvas.Clear(); + + if (_skPath == null) + return; + + SKMatrix transformMatrix = CreateMatrix(); + SKPath transformedSkPath = new SKPath(); + _skPath.Transform(transformMatrix, transformedSkPath); + SKRect fillBounds = transformMatrix.MapRect(_pathFillBounds); + SKRect strokeBounds; + using (SKPath strokePath = new SKPath()) + { + _skPaint.GetFillPath(transformedSkPath, strokePath); + strokeBounds = strokePath.Bounds; + } + + if (_fill != null) + { + _skPaint.Style = SKPaintStyle.Fill; + + if (_fill is GradientBrush fillGradientBrush) + { + _skPaint.Shader = fillGradientBrush.CreateShader(fillBounds); + } + else if (_fill is SolidColorBrush fillSolidColorBrush) + { + _skPaint.Color = fillSolidColorBrush.ToSolidColor(); + } + + canvas.DrawPath(transformedSkPath, _skPaint); + _skPaint.Shader = null; + } + + if (_stroke != null) + { + _skPaint.Style = SKPaintStyle.Stroke; + + if (_stroke is GradientBrush strokeGradientBrush) + { + UpdatePathStrokeBounds(); + _skPaint.Shader = strokeGradientBrush.CreateShader(strokeBounds); + } + else if (_stroke is SolidColorBrush strokeSolidColorBrush) + { + _skPaint.Color = strokeSolidColorBrush.ToSolidColor(); + } + + canvas.DrawPath(transformedSkPath, _skPaint); + _skPaint.Shader = null; + } + } + + void OnLayoutUpdated(object sender, LayoutEventArgs e) + { + _skCanvasView.Geometry = Geometry; + } + + public void UpdateShape(SKPath sKPath) + { + _skPath = sKPath; + UpdatePathShape(); + } + + public void UpdateShapeTransform(SKMatrix matrix) + { + _transform = matrix; + _skPath.Transform(_transform); + _skCanvasView.Invalidate(); + } + + public void UpdateAspect(Stretch stretch) + { + _stretch = stretch; + _skCanvasView.Invalidate(); + } + + public void UpdateFill(Brush fill) + { + _fill = fill; + _skCanvasView.Invalidate(); + } + + public void UpdateStroke(Brush stroke) + { + _stroke = stroke; + _skCanvasView.Invalidate(); + } + + public void UpdateStrokeThickness(double strokeWidth) + { + _strokeWidth = Forms.ConvertToScaledPixel(strokeWidth); + _skPaint.StrokeWidth = _strokeWidth; + UpdateStrokeDash(); + } + + public void UpdateStrokeDashArray(float[] dash) + { + _strokeDash = dash; + UpdateStrokeDash(); + } + + public void UpdateStrokeDashOffset(float strokeDashOffset) + { + _strokeDashOffset = strokeDashOffset; + UpdateStrokeDash(); + } + + public void UpdateStrokeDash() + { + if (_strokeDash != null && _strokeDash.Length > 1) + { + float[] strokeDash = new float[_strokeDash.Length]; + + for (int i = 0; i < _strokeDash.Length; i++) + strokeDash[i] = _strokeDash[i] * _strokeWidth; + _skPaint.PathEffect = SKPathEffect.CreateDash(strokeDash, _strokeDashOffset * _strokeWidth); + } + else + { + _skPaint.PathEffect = null; + } + UpdatePathStrokeBounds(); + } + + public void UpdateStrokeLineCap(SKStrokeCap strokeCap) + { + _skPaint.StrokeCap = strokeCap; + UpdatePathStrokeBounds(); + } + public void UpdateStrokeLineJoin(SKStrokeJoin strokeJoin) + { + _skPaint.StrokeJoin = strokeJoin; + _skCanvasView.Invalidate(); + } + + public void UpdateStrokeMiterLimit(float strokeMiterLimit) + { + _skPaint.StrokeMiter = strokeMiterLimit * 2; + UpdatePathStrokeBounds(); + } + + protected void UpdatePathShape() + { + if (_skPath != null) + { + using (SKPath fillPath = new SKPath()) + { + _skPaint.StrokeWidth = 0.01f; + _skPaint.Style = SKPaintStyle.Stroke; + _skPaint.GetFillPath(_skPath, fillPath); + _pathFillBounds = fillPath.Bounds; + _skPaint.StrokeWidth = _strokeWidth; + } + } + else + { + _pathFillBounds = SKRect.Empty; + } + + UpdatePathStrokeBounds(); + } + + SKMatrix CreateMatrix() + { + SKMatrix matrix = SKMatrix.CreateIdentity(); + + SKRect drawableBounds = _drawableBounds; + float halfStrokeWidth = _skPaint.StrokeWidth / 2; + + drawableBounds.Left += halfStrokeWidth; + drawableBounds.Top += halfStrokeWidth; + drawableBounds.Right -= halfStrokeWidth; + drawableBounds.Bottom -= halfStrokeWidth; + + float widthScale = drawableBounds.Width / _pathFillBounds.Width; + float heightScale = drawableBounds.Height / _pathFillBounds.Height; + + switch (_stretch) + { + case Stretch.None: + drawableBounds = _drawableBounds; + float adjustX = Math.Min(0, _pathStrokeBounds.Left); + float adjustY = Math.Min(0, _pathStrokeBounds.Top); + if (adjustX < 0 || adjustY < 0) + { + matrix = SKMatrix.CreateTranslation(-adjustX, -adjustY); + } + break; + case Stretch.Fill: + matrix = SKMatrix.CreateScale(widthScale, heightScale); + matrix = matrix.PostConcat( + SKMatrix.CreateTranslation(drawableBounds.Left - widthScale * _pathFillBounds.Left, + drawableBounds.Top - heightScale * _pathFillBounds.Top)); + break; + case Stretch.Uniform: + float minScale = Math.Min(widthScale, heightScale); + matrix = SKMatrix.CreateScale(minScale, minScale); + matrix = matrix.PostConcat( + SKMatrix.CreateTranslation(drawableBounds.Left - (minScale * _pathFillBounds.Left) + (drawableBounds.Width - (minScale * _pathFillBounds.Width)) / 2, + drawableBounds.Top - (minScale * _pathFillBounds.Top) + (drawableBounds.Height - (minScale * _pathFillBounds.Height)) / 2)); + break; + case Stretch.UniformToFill: + float maxScale = Math.Max(widthScale, heightScale); + matrix = SKMatrix.CreateScale(maxScale, maxScale); + matrix = matrix.PostConcat( + SKMatrix.CreateTranslation(drawableBounds.Left - (maxScale * _pathFillBounds.Left), + drawableBounds.Top - (maxScale * _pathFillBounds.Top))); + break; + } + + return matrix; + } + + void UpdatePathStrokeBounds() + { + if (_skPath != null) + { + using (SKPath strokePath = new SKPath()) + { + _skPaint.Style = SKPaintStyle.Stroke; + _skPaint.GetFillPath(_skPath, strokePath); + _pathStrokeBounds = strokePath.Bounds; + } + } + else + { + _pathStrokeBounds = SKRect.Empty; + } + + _skCanvasView.Invalidate(); + } + + public ElmSharp.Size Measure(int availableWidth, int availableHeight) + { + if (_skPath != null) + { + return new ElmSharp.Size((int)Math.Max(_pathStrokeBounds.Right - Math.Min(0, _pathStrokeBounds.Left), _strokeWidth), + (int)Math.Max(_pathStrokeBounds.Bottom - Math.Min(0, _pathStrokeBounds.Top), _strokeWidth)); + } + return new ElmSharp.Size(MinimumWidth, MinimumHeight); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/StaticRegistrar.cs b/Xamarin.Forms.Platform.Tizen/StaticRegistrar.cs index 0834d350735..189a360b5df 100644 --- a/Xamarin.Forms.Platform.Tizen/StaticRegistrar.cs +++ b/Xamarin.Forms.Platform.Tizen/StaticRegistrar.cs @@ -4,6 +4,7 @@ using Xamarin.Forms.Internals; using Xamarin.Forms.Platform.Tizen.Native; using Xamarin.Forms.PlatformConfiguration.TizenSpecific; +using Xamarin.Forms.Shapes; using Xamarin.Forms.Xaml.Internals; namespace Xamarin.Forms.Platform.Tizen @@ -145,6 +146,13 @@ public static void RegisterHandlers(Dictionary> custom Registered.Register(typeof(Frame), () => new SkiaSharp.FrameRenderer()); Registered.Register(typeof(BoxView), () => new SkiaSharp.BoxViewRenderer()); Registered.Register(typeof(Image), () => new SkiaSharp.ImageRenderer()); + + Registered.Register(typeof(Ellipse), () => new SkiaSharp.EllipseRenderer()); + Registered.Register(typeof(Line), () => new SkiaSharp.LineRenderer()); + Registered.Register(typeof(Path), () => new SkiaSharp.PathRenderer()); + Registered.Register(typeof(Polygon), () => new SkiaSharp.PolygonRenderer()); + Registered.Register(typeof(Polyline), () => new SkiaSharp.PolylineRenderer()); + Registered.Register(typeof(Shapes.Rectangle), () => new SkiaSharp.RectangleRenderer()); } //Custom Handlers