diff --git a/src/Avalonia.SceneGraph/Media/DrawingContext.cs b/src/Avalonia.SceneGraph/Media/DrawingContext.cs index 90a9749e2a..da33347215 100644 --- a/src/Avalonia.SceneGraph/Media/DrawingContext.cs +++ b/src/Avalonia.SceneGraph/Media/DrawingContext.cs @@ -127,7 +127,8 @@ namespace Avalonia.Media Matrix, Opacity, Clip, - MatrixContainer + MatrixContainer, + GeometryClip } public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default(Matrix)) @@ -149,10 +150,12 @@ namespace Avalonia.Media _context._states.Pop(); if (_type == PushedStateType.Matrix) _context.CurrentTransform = _matrix; - else if(_type == PushedStateType.Clip) + else if (_type == PushedStateType.Clip) _context._impl.PopClip(); - else if(_type == PushedStateType.Opacity) + else if (_type == PushedStateType.Opacity) _context._impl.PopOpacity(); + else if (_type == PushedStateType.GeometryClip) + _context._impl.PopGeometryClip(); else if (_type == PushedStateType.MatrixContainer) { var cont = _context._transformContainers.Pop(); @@ -174,6 +177,18 @@ namespace Avalonia.Media return new PushedState(this, PushedState.PushedStateType.Clip); } + /// + /// Pushes a clip geometry. + /// + /// The clip geometry. + /// A disposable used to undo the clip geometry. + public PushedState PushGeometryClip(Geometry clip) + { + Contract.Requires(clip != null); + _impl.PushGeometryClip(clip); + return new PushedState(this, PushedState.PushedStateType.GeometryClip); + } + /// /// Pushes an opacity value. /// diff --git a/src/Avalonia.SceneGraph/Media/IDrawingContext.cs b/src/Avalonia.SceneGraph/Media/IDrawingContext.cs index 31f5b0d38a..a798726cca 100644 --- a/src/Avalonia.SceneGraph/Media/IDrawingContext.cs +++ b/src/Avalonia.SceneGraph/Media/IDrawingContext.cs @@ -69,7 +69,6 @@ namespace Avalonia.Media /// Pushes a clip rectange. /// /// The clip rectangle. - /// A disposable used to undo the clip rectangle. void PushClip(Rect clip); void PopClip(); @@ -78,9 +77,16 @@ namespace Avalonia.Media /// Pushes an opacity value. /// /// The opacity. - /// A disposable used to undo the opacity. void PushOpacity(double opacity); void PopOpacity(); + + /// + /// Pushes a clip geometry. + /// + /// The clip geometry. + void PushGeometryClip(Geometry clip); + + void PopGeometryClip(); } } diff --git a/src/Avalonia.SceneGraph/Media/PathMarkupParser.cs b/src/Avalonia.SceneGraph/Media/PathMarkupParser.cs index a089d71127..70f999c860 100644 --- a/src/Avalonia.SceneGraph/Media/PathMarkupParser.cs +++ b/src/Avalonia.SceneGraph/Media/PathMarkupParser.cs @@ -34,6 +34,12 @@ namespace Avalonia.Media { 'z', Command.Close }, }; + private static readonly Dictionary FillRules = new Dictionary + { + {'0', FillRule.EvenOdd }, + {'1', FillRule.NonZero } + }; + private StreamGeometry _geometry; private readonly StreamGeometryContext _context; @@ -90,8 +96,7 @@ namespace Avalonia.Media switch (command) { case Command.FillRule: - // TODO: Implement. - reader.Read(); + _context.SetFillRule(ReadFillRule(reader)); break; case Command.Move: @@ -226,6 +231,24 @@ namespace Avalonia.Media } } + private static FillRule ReadFillRule(StringReader reader) + { + int i = reader.Read(); + if (i == -1) + { + throw new InvalidDataException("Invalid fill rule"); + } + char c = (char)i; + FillRule rule; + + if (!FillRules.TryGetValue(c, out rule)) + { + throw new InvalidDataException("Invalid fill rule"); + } + + return rule; + } + private static double ReadDouble(StringReader reader) { ReadWhitespace(reader); diff --git a/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs b/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs index a8899c7dcd..b26d825064 100644 --- a/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs +++ b/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs @@ -119,6 +119,7 @@ namespace Avalonia.Rendering using (context.PushPostTransform(m)) using (context.PushOpacity(opacity)) using (clipToBounds ? context.PushClip(bounds) : default(DrawingContext.PushedState)) + using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default(DrawingContext.PushedState)) using (context.PushTransformContainer()) { visual.Render(context); diff --git a/src/Avalonia.SceneGraph/Visual.cs b/src/Avalonia.SceneGraph/Visual.cs index 9cf97c2767..8bdbfecb4a 100644 --- a/src/Avalonia.SceneGraph/Visual.cs +++ b/src/Avalonia.SceneGraph/Visual.cs @@ -39,6 +39,12 @@ namespace Avalonia public static readonly StyledProperty ClipToBoundsProperty = AvaloniaProperty.Register(nameof(ClipToBounds)); + /// + /// Defines the property. + /// + public static readonly StyledProperty ClipProperty = + AvaloniaProperty.Register(nameof(Clip)); + /// /// Defines the property. /// @@ -127,6 +133,15 @@ namespace Avalonia set { SetValue(ClipToBoundsProperty, value); } } + /// + /// Gets or sets the geometry clip for this visual. + /// + public Geometry Clip + { + get { return GetValue(ClipProperty); } + set { SetValue(ClipProperty, value); } + } + /// /// Gets a value indicating whether this scene graph node and all its parents are visible. /// diff --git a/src/Avalonia.SceneGraph/VisualTree/IVisual.cs b/src/Avalonia.SceneGraph/VisualTree/IVisual.cs index 4d6a33b99c..fb4416f78e 100644 --- a/src/Avalonia.SceneGraph/VisualTree/IVisual.cs +++ b/src/Avalonia.SceneGraph/VisualTree/IVisual.cs @@ -41,6 +41,11 @@ namespace Avalonia.VisualTree /// bool ClipToBounds { get; set; } + /// + /// Gets or sets the geometry clip for this visual. + /// + Geometry Clip { get; set; } + /// /// Gets a value indicating whether this scene graph node is attached to a visual root. /// diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 6abe2ac971..00b54e8c5a 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -288,6 +288,17 @@ namespace Avalonia.Skia { } + public void PushGeometryClip(Geometry clip) + { + Canvas.Save(); + Canvas.ClipPath(((StreamGeometryImpl)clip.PlatformImpl).EffectivePath); + } + + public void PopGeometryClip() + { + Canvas.Restore(); + } + private Matrix _currentTransform = Matrix.Identity; public Matrix Transform