diff --git a/src/Perspex.SceneGraph/Media/Brush.cs b/src/Perspex.SceneGraph/Media/Brush.cs index f86a7d2560..26e5953621 100644 --- a/src/Perspex.SceneGraph/Media/Brush.cs +++ b/src/Perspex.SceneGraph/Media/Brush.cs @@ -9,7 +9,15 @@ namespace Perspex.Media /// /// Describes how an area is painted. /// - public abstract class Brush + public abstract class Brush : PerspexObject { + public static readonly PerspexProperty OpacityProperty = + PerspexProperty.Register(nameof(Opacity), 1.0); + + public double Opacity + { + get { return this.GetValue(OpacityProperty); } + set { this.SetValue(OpacityProperty, value); } + } } } diff --git a/src/Perspex.SceneGraph/Media/BrushMappingMode.cs b/src/Perspex.SceneGraph/Media/BrushMappingMode.cs new file mode 100644 index 0000000000..a453f81d85 --- /dev/null +++ b/src/Perspex.SceneGraph/Media/BrushMappingMode.cs @@ -0,0 +1,14 @@ +namespace Perspex.Media +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + public enum BrushMappingMode + { + Absolute, + RelativeToBoundingBox + } +} \ No newline at end of file diff --git a/src/Perspex.SceneGraph/Media/GradientBrush.cs b/src/Perspex.SceneGraph/Media/GradientBrush.cs new file mode 100644 index 0000000000..5c63b66242 --- /dev/null +++ b/src/Perspex.SceneGraph/Media/GradientBrush.cs @@ -0,0 +1,38 @@ +namespace Perspex.Media +{ + using System.Collections.Generic; + + public abstract class GradientBrush : Brush + { + public static readonly PerspexProperty MappingModeProperty = +PerspexProperty.Register(nameof(MappingMode), BrushMappingMode.RelativeToBoundingBox); + + public static readonly PerspexProperty SpreadMethodProperty = +PerspexProperty.Register(nameof(SpreadMethod), GradientSpreadMethod.Pad); + + public static readonly PerspexProperty> GradientStopsProperty = +PerspexProperty.Register>(nameof(Opacity), new List()); + + public GradientBrush() + { + } + + public BrushMappingMode MappingMode + { + get { return this.GetValue(MappingModeProperty); } + set { this.SetValue(MappingModeProperty, value); } + } + + public GradientSpreadMethod SpreadMethod + { + get { return this.GetValue(SpreadMethodProperty); } + set { this.SetValue(SpreadMethodProperty, value); } + } + + public List GradientStops + { + get { return this.GetValue(GradientStopsProperty); } + set { this.SetValue(GradientStopsProperty, value); } + } + } +} \ No newline at end of file diff --git a/src/Perspex.SceneGraph/Media/GradientSpreadMethod.cs b/src/Perspex.SceneGraph/Media/GradientSpreadMethod.cs new file mode 100644 index 0000000000..78ba87b05a --- /dev/null +++ b/src/Perspex.SceneGraph/Media/GradientSpreadMethod.cs @@ -0,0 +1,15 @@ +namespace Perspex.Media +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + public enum GradientSpreadMethod + { + Pad, + Reflect, + Repeat + } +} \ No newline at end of file diff --git a/src/Perspex.SceneGraph/Media/GradientStop.cs b/src/Perspex.SceneGraph/Media/GradientStop.cs new file mode 100644 index 0000000000..9dbbe610ef --- /dev/null +++ b/src/Perspex.SceneGraph/Media/GradientStop.cs @@ -0,0 +1,36 @@ +namespace Perspex.Media +{ + /// + /// GradientStop + /// + public sealed class GradientStop + { + /// + /// Initializes a new instance of the class. + /// + public GradientStop() { } + + /// + /// Initializes a new instance of the class. + /// + /// The color + /// The offset + public GradientStop(Color color, double offset) + { + this.Color = color; + this.Offset = offset; + } + + // TODO: Make these dependency properties. + + /// + /// The offset + /// + public double Offset { get; set; } + + /// + /// The color + /// + public Color Color { get; set; } + } +} \ No newline at end of file diff --git a/src/Perspex.SceneGraph/Media/LinearGradientBrush.cs b/src/Perspex.SceneGraph/Media/LinearGradientBrush.cs new file mode 100644 index 0000000000..3a63a39506 --- /dev/null +++ b/src/Perspex.SceneGraph/Media/LinearGradientBrush.cs @@ -0,0 +1,23 @@ +namespace Perspex.Media +{ + public class LinearGradientBrush : GradientBrush + { + public static readonly PerspexProperty StartPointProperty = +PerspexProperty.Register(nameof(StartPoint), new Point(0,0)); + + public static readonly PerspexProperty EndPointProperty = +PerspexProperty.Register(nameof(EndPoint), new Point(0, 0)); + + public Point StartPoint + { + get { return this.GetValue(StartPointProperty); } + set { this.SetValue(StartPointProperty, value); } + } + + public Point EndPoint + { + get { return this.GetValue(EndPointProperty); } + set { this.SetValue(EndPointProperty, value); } + } + } +} \ No newline at end of file diff --git a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj index 3b5280b26a..e455b1940a 100644 --- a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -59,8 +59,13 @@ + + + + + diff --git a/src/Windows/Perspex.Direct2D1/Media/BrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/BrushImpl.cs new file mode 100644 index 0000000000..e6114b3764 --- /dev/null +++ b/src/Windows/Perspex.Direct2D1/Media/BrushImpl.cs @@ -0,0 +1,19 @@ +using System; + +namespace Perspex.Direct2D1.Media +{ + public abstract class BrushImpl : IDisposable + { + public SharpDX.Direct2D1.Brush PlatformBrush { get; set; } + + public BrushImpl(Perspex.Media.Brush brush, SharpDX.Direct2D1.RenderTarget target, Size destinationSize) + { + } + + public virtual void Dispose() + { + if (this.PlatformBrush != null) + this.PlatformBrush.Dispose(); + } + } +} diff --git a/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs b/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs index fce32ace29..c7ae3950dd 100644 --- a/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs +++ b/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs @@ -89,13 +89,15 @@ namespace Perspex.Direct2D1.Media { if (pen != null) { - using (var d2dBrush = pen.Brush.ToDirect2D(this.renderTarget)) + var size = new Rect(p1, p2).Size; + + using (var d2dBrush = this.CreateBrush(pen.Brush, size)) using (var d2dStroke = pen.ToDirect2DStrokeStyle(this.renderTarget)) { this.renderTarget.DrawLine( p1.ToSharpDX(), p2.ToSharpDX(), - d2dBrush, + d2dBrush.PlatformBrush, (float)pen.Thickness, d2dStroke); } @@ -112,20 +114,20 @@ namespace Perspex.Direct2D1.Media { if (brush != null) { - using (var d2dBrush = brush.ToDirect2D(this.renderTarget)) + using (var d2dBrush = this.CreateBrush(brush, geometry.Bounds.Size)) { GeometryImpl impl = (GeometryImpl)geometry.PlatformImpl; - this.renderTarget.FillGeometry(impl.Geometry, d2dBrush); + this.renderTarget.FillGeometry(impl.Geometry, d2dBrush.PlatformBrush); } } if (pen != null) { - using (var d2dBrush = pen.Brush.ToDirect2D(this.renderTarget)) + using (var d2dBrush = this.CreateBrush(pen.Brush, geometry.GetRenderBounds(pen.Thickness).Size)) using (var d2dStroke = pen.ToDirect2DStrokeStyle(this.renderTarget)) { GeometryImpl impl = (GeometryImpl)geometry.PlatformImpl; - this.renderTarget.DrawGeometry(impl.Geometry, d2dBrush, (float)pen.Thickness, d2dStroke); + this.renderTarget.DrawGeometry(impl.Geometry, d2dBrush.PlatformBrush, (float)pen.Thickness, d2dStroke); } } } @@ -138,12 +140,12 @@ namespace Perspex.Direct2D1.Media /// The corner radius. public void DrawRectange(Pen pen, Rect rect, float cornerRadius) { - using (var brush = pen.Brush.ToDirect2D(this.renderTarget)) + using (var brush = this.CreateBrush(pen.Brush, rect.Size)) using (var d2dStroke = pen.ToDirect2DStrokeStyle(this.renderTarget)) { this.renderTarget.DrawRoundedRectangle( new RoundedRectangle { Rect = rect.ToDirect2D(), RadiusX = cornerRadius, RadiusY = cornerRadius }, - brush, + brush.PlatformBrush, (float)pen.Thickness, d2dStroke); } @@ -161,7 +163,8 @@ namespace Perspex.Direct2D1.Media { var impl = (FormattedTextImpl)text.PlatformImpl; - using (var renderer = new PerspexTextRenderer(this.renderTarget, foreground.ToDirect2D(this.renderTarget))) + using (var brush = this.CreateBrush(foreground, impl.Measure())) + using (var renderer = new PerspexTextRenderer(this, this.renderTarget, brush.PlatformBrush)) { impl.TextLayout.Draw(renderer, (float)origin.X, (float)origin.Y); } @@ -176,7 +179,7 @@ namespace Perspex.Direct2D1.Media /// The corner radius. public void FillRectange(Perspex.Media.Brush brush, Rect rect, float cornerRadius) { - using (var b = brush.ToDirect2D(this.renderTarget)) + using (var b = this.CreateBrush(brush, rect.Size)) { this.renderTarget.FillRoundedRectangle( new RoundedRectangle @@ -189,7 +192,7 @@ namespace Perspex.Direct2D1.Media RadiusX = cornerRadius, RadiusY = cornerRadius }, - b); + b.PlatformBrush); } } @@ -256,5 +259,24 @@ namespace Perspex.Direct2D1.Media this.renderTarget.Transform = transform * m3x2; }); } + + public BrushImpl CreateBrush(Perspex.Media.Brush brush, Size destinationSize) + { + Perspex.Media.SolidColorBrush solidColorBrush = brush as Perspex.Media.SolidColorBrush; + Perspex.Media.LinearGradientBrush linearGradientBrush = brush as Perspex.Media.LinearGradientBrush; + + if (solidColorBrush != null) + { + return new SolidColorBrushImpl(solidColorBrush, this.renderTarget, destinationSize); + } + else if (linearGradientBrush != null) + { + return new LinearGradientBrushImpl(linearGradientBrush, this.renderTarget, destinationSize); + } + else + { + return new SolidColorBrushImpl(null, this.renderTarget, destinationSize); + } + } } } diff --git a/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs new file mode 100644 index 0000000000..81242c9e0b --- /dev/null +++ b/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs @@ -0,0 +1,43 @@ +namespace Perspex.Direct2D1.Media +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + public class LinearGradientBrushImpl : BrushImpl + { + public LinearGradientBrushImpl(Perspex.Media.LinearGradientBrush brush, SharpDX.Direct2D1.RenderTarget target, Size destinationSize) + : base(brush, target, destinationSize) + { + if (brush != null) + { + var gradientStops = brush.GradientStops.Select(s => new SharpDX.Direct2D1.GradientStop { Color = s.Color.ToDirect2D(), Position = (float)s.Offset }).ToArray(); + + Point startPoint = new Point(0, 0); + Point endPoint = new Point(0, 0); + + switch (brush.MappingMode) + { + case Perspex.Media.BrushMappingMode.Absolute: + // TODO: + + break; + case Perspex.Media.BrushMappingMode.RelativeToBoundingBox: + startPoint = new Point(brush.StartPoint.X * destinationSize.Width, brush.StartPoint.Y * destinationSize.Height); + endPoint = new Point(brush.EndPoint.X * destinationSize.Width, brush.EndPoint.Y * destinationSize.Height); + + break; + } + + this.PlatformBrush = new SharpDX.Direct2D1.LinearGradientBrush( + target, + new SharpDX.Direct2D1.LinearGradientBrushProperties { StartPoint = startPoint.ToSharpDX(), EndPoint = endPoint.ToSharpDX() }, + new SharpDX.Direct2D1.BrushProperties { Opacity = (float)brush.Opacity, Transform = target.Transform }, + new SharpDX.Direct2D1.GradientStopCollection(target, gradientStops, brush.SpreadMethod.ToDirect2D()) + ); + } + } + } +} diff --git a/src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs b/src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs index 3e6888fb64..7723d535d6 100644 --- a/src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs +++ b/src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs @@ -13,14 +13,18 @@ namespace Perspex.Direct2D1.Media internal class PerspexTextRenderer : TextRenderer { + private DrawingContext context; + private RenderTarget renderTarget; private Brush foreground; public PerspexTextRenderer( + DrawingContext context, RenderTarget target, Brush foreground) { + this.context = context; this.renderTarget = target; this.foreground = foreground; } @@ -33,7 +37,6 @@ namespace Perspex.Direct2D1.Media public void Dispose() { - this.foreground.Dispose(); } public Result DrawGlyphRun( @@ -46,9 +49,11 @@ namespace Perspex.Direct2D1.Media ComObject clientDrawingEffect) { var wrapper = clientDrawingEffect as BrushWrapper; + + // TODO: Work out how to get the size below rather than passing new Size(). var brush = (wrapper == null) ? this.foreground : - wrapper.Brush.ToDirect2D(this.renderTarget); + this.context.CreateBrush(wrapper.Brush, new Size()).PlatformBrush; this.renderTarget.DrawGlyphRun( new Vector2(baselineOriginX, baselineOriginY), @@ -94,4 +99,4 @@ namespace Perspex.Direct2D1.Media return false; } } -} +} \ No newline at end of file diff --git a/src/Windows/Perspex.Direct2D1/Media/SolidColorBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/SolidColorBrushImpl.cs new file mode 100644 index 0000000000..949b8e72be --- /dev/null +++ b/src/Windows/Perspex.Direct2D1/Media/SolidColorBrushImpl.cs @@ -0,0 +1,17 @@ +namespace Perspex.Direct2D1.Media +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + public class SolidColorBrushImpl : BrushImpl + { + public SolidColorBrushImpl(Perspex.Media.SolidColorBrush brush, SharpDX.Direct2D1.RenderTarget target, Size destinationSize) + : base(brush, target, destinationSize) + { + this.PlatformBrush = new SharpDX.Direct2D1.SolidColorBrush(target, brush?.Color.ToDirect2D() ?? new SharpDX.Color4()); + } + } +} diff --git a/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj b/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj index 73ce0904a4..c7a62078e8 100644 --- a/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj +++ b/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj @@ -75,11 +75,14 @@ + + + diff --git a/src/Windows/Perspex.Direct2D1/PrimitiveExtensions.cs b/src/Windows/Perspex.Direct2D1/PrimitiveExtensions.cs index ef44de77db..18df70c82c 100644 --- a/src/Windows/Perspex.Direct2D1/PrimitiveExtensions.cs +++ b/src/Windows/Perspex.Direct2D1/PrimitiveExtensions.cs @@ -34,6 +34,15 @@ namespace Perspex.Direct2D1 return new Size2F((float)p.Width, (float)p.Height); } + public static SharpDX.Direct2D1.ExtendMode ToDirect2D(this Perspex.Media.GradientSpreadMethod spreadMethod) + { + if (spreadMethod == Perspex.Media.GradientSpreadMethod.Pad) + return ExtendMode.Clamp; + else if (spreadMethod == Perspex.Media.GradientSpreadMethod.Reflect) + return ExtendMode.Mirror; + else + return ExtendMode.Wrap; + } /// /// Converts a brush to Direct2D. ///