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