From e2198a0c67aabd240be8baaf6824a020fc315c6e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 3 Sep 2015 09:38:57 +0300 Subject: [PATCH 1/6] Added RunJobs to Dispatcher (needed for designer) --- src/Perspex.Base/Threading/Dispatcher.cs | 8 ++++ src/Perspex.Base/Threading/MainLoop.cs | 54 ++++++++++++++---------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/src/Perspex.Base/Threading/Dispatcher.cs b/src/Perspex.Base/Threading/Dispatcher.cs index af6f1d58af..31191d587c 100644 --- a/src/Perspex.Base/Threading/Dispatcher.cs +++ b/src/Perspex.Base/Threading/Dispatcher.cs @@ -50,6 +50,14 @@ namespace Perspex.Threading this.mainLoop.Run(cancellationToken); } + /// + /// Runs continuations pushed on the loop. + /// + public void RunJobs() + { + this.mainLoop.RunJobs(); + } + /// /// Invokes a method on the dispatcher thread. /// diff --git a/src/Perspex.Base/Threading/MainLoop.cs b/src/Perspex.Base/Threading/MainLoop.cs index e7efefacc1..009d331b28 100644 --- a/src/Perspex.Base/Threading/MainLoop.cs +++ b/src/Perspex.Base/Threading/MainLoop.cs @@ -42,37 +42,45 @@ namespace Perspex.Win32.Threading { while (!cancellationToken.IsCancellationRequested) { - Job job = null; + RunJobs(); - while (job != null || this.queue.Count > 0) - { - if (job == null) - { - lock (this.queue) - { - job = this.queue.Dequeue(); - } - } + platform.ProcessMessage(); + } + } - if (job.Priority < DispatcherPriority.Input && platform.HasMessages()) - { - break; - } + /// + /// Runs continuations pushed on the loop. + /// + public void RunJobs() + { + Job job = null; - try - { - job.Action(); - job.TaskCompletionSource.SetResult(null); - } - catch (Exception e) + while (job != null || this.queue.Count > 0) + { + if (job == null) + { + lock (this.queue) { - job.TaskCompletionSource.SetException(e); + job = this.queue.Dequeue(); } + } - job = null; + if (job.Priority < DispatcherPriority.Input && platform.HasMessages()) + { + break; } - platform.ProcessMessage(); + try + { + job.Action(); + job.TaskCompletionSource.SetResult(null); + } + catch (Exception e) + { + job.TaskCompletionSource.SetException(e); + } + + job = null; } } From 99af4bfdb3c59a2174441361d85c2e7f49fffcc8 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 3 Sep 2015 21:50:52 +0300 Subject: [PATCH 2/6] Added SynchronizationContext support --- src/Perspex.Application/Application.cs | 2 +- src/Perspex.Base/Perspex.Base.csproj | 1 + src/Perspex.Base/PerspexObject.cs | 3 ++ src/Perspex.Base/Threading/Dispatcher.cs | 9 ++++ src/Perspex.Base/Threading/MainLoop.cs | 40 ++++++++++++---- .../PerspexSynchronizationContext.cs | 47 +++++++++++++++++++ 6 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 src/Perspex.Base/Threading/PerspexSynchronizationContext.cs diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs index 1b94ecaa51..d3d8119af8 100644 --- a/src/Perspex.Application/Application.cs +++ b/src/Perspex.Application/Application.cs @@ -55,7 +55,7 @@ namespace Perspex { throw new InvalidOperationException("Cannot create more than one Application instance."); } - + PerspexSynchronizationContext.InstallIfNeeded(); Current = this; } diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index 654fbb3926..d5723e5a6d 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -72,6 +72,7 @@ + diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index 0c0e402c36..ffa0b89f13 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -4,6 +4,8 @@ // // ----------------------------------------------------------------------- +using Perspex.Threading; + namespace Perspex { using System; @@ -100,6 +102,7 @@ namespace Perspex /// public PerspexObject() { + PerspexSynchronizationContext.InstallIfNeeded(); this.propertyLog = Log.ForContext(new[] { new PropertyEnricher("Area", "Property"), diff --git a/src/Perspex.Base/Threading/Dispatcher.cs b/src/Perspex.Base/Threading/Dispatcher.cs index 31191d587c..e9b91f9ab9 100644 --- a/src/Perspex.Base/Threading/Dispatcher.cs +++ b/src/Perspex.Base/Threading/Dispatcher.cs @@ -68,5 +68,14 @@ namespace Perspex.Threading { return this.mainLoop.InvokeAsync(action, priority); } + + /// + /// Post action that will be invoked on main thread + /// + /// The method. + internal void Post(Action action) + { + this.mainLoop.Post(action); + } } } \ No newline at end of file diff --git a/src/Perspex.Base/Threading/MainLoop.cs b/src/Perspex.Base/Threading/MainLoop.cs index 009d331b28..b4941118ae 100644 --- a/src/Perspex.Base/Threading/MainLoop.cs +++ b/src/Perspex.Base/Threading/MainLoop.cs @@ -70,14 +70,21 @@ namespace Perspex.Win32.Threading break; } - try + if (job.TaskCompletionSource == null) { job.Action(); - job.TaskCompletionSource.SetResult(null); } - catch (Exception e) + else { - job.TaskCompletionSource.SetException(e); + try + { + job.Action(); + job.TaskCompletionSource.SetResult(null); + } + catch (Exception e) + { + job.TaskCompletionSource.SetException(e); + } } job = null; @@ -92,15 +99,27 @@ namespace Perspex.Win32.Threading /// A task that can be used to track the method's execution. public Task InvokeAsync(Action action, DispatcherPriority priority) { - var job = new Job(action, priority); + var job = new Job(action, priority, false); + this.AddJob(job); + return job.TaskCompletionSource.Task; + } + /// + /// Post action that will be invoked on main thread + /// + /// The method. + internal void Post(Action action) + { + this.AddJob(new Job(action, DispatcherPriority.Normal, true)); + } + + private void AddJob(Job job) + { lock (this.queue) { - this.queue.Add(job, priority); + this.queue.Add(job, job.Priority); } - platform.Wake(); - return job.TaskCompletionSource.Task; } /// @@ -113,11 +132,12 @@ namespace Perspex.Win32.Threading /// /// The method to call. /// The job priority. - public Job(Action action, DispatcherPriority priority) + /// Do not wrap excepption in TaskCompletionSource + public Job(Action action, DispatcherPriority priority, bool throwOnUiThread) { this.Action = action; this.Priority = priority; - this.TaskCompletionSource = new TaskCompletionSource(); + this.TaskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource(); } /// diff --git a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs new file mode 100644 index 0000000000..5c0540ef62 --- /dev/null +++ b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs @@ -0,0 +1,47 @@ +using System.Threading; + +namespace Perspex.Threading +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + /// + /// SynchronizationContext to be used on main thread + /// + public class PerspexSynchronizationContext : SynchronizationContext + { + /// + /// Controls if SynchronizationContext should be installed in InstallIfNeeded + /// + public static bool AutoInstall { get; set; } = true; + + /// + /// Installs synchronization context in current thread + /// + public static void InstallIfNeeded() + { + if (!AutoInstall || Current is PerspexSynchronizationContext) + { + return; + } + + SetSynchronizationContext(new PerspexSynchronizationContext()); + } + + /// + public override void Post(SendOrPostCallback d, object state) + { + Dispatcher.UIThread.Post(() => d(state)); + } + + /// + public override void Send(SendOrPostCallback d, object state) + { + // TODO: Add check for being on the main thread, we should invoke the method immediately in this case + Dispatcher.UIThread.InvokeAsync(() => d(state)).Wait(); + } + } +} \ No newline at end of file From 7923159c6123c05d3bb15370df834d0cd9f8044e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 3 Sep 2015 22:05:42 +0300 Subject: [PATCH 3/6] DispatcherTimer should now swallow exceptions --- src/Perspex.Base/Threading/Dispatcher.cs | 5 +++-- src/Perspex.Base/Threading/DispatcherTimer.cs | 2 +- src/Perspex.Base/Threading/MainLoop.cs | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Perspex.Base/Threading/Dispatcher.cs b/src/Perspex.Base/Threading/Dispatcher.cs index e9b91f9ab9..d959151543 100644 --- a/src/Perspex.Base/Threading/Dispatcher.cs +++ b/src/Perspex.Base/Threading/Dispatcher.cs @@ -73,9 +73,10 @@ namespace Perspex.Threading /// Post action that will be invoked on main thread /// /// The method. - internal void Post(Action action) + /// The priority with which to invoke the method. + internal void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { - this.mainLoop.Post(action); + this.mainLoop.Post(action, priority); } } } \ No newline at end of file diff --git a/src/Perspex.Base/Threading/DispatcherTimer.cs b/src/Perspex.Base/Threading/DispatcherTimer.cs index 73335ea408..0d49bb21f8 100644 --- a/src/Perspex.Base/Threading/DispatcherTimer.cs +++ b/src/Perspex.Base/Threading/DispatcherTimer.cs @@ -203,7 +203,7 @@ namespace Perspex.Threading /// private void InternalTick() { - this.Dispatcher.InvokeAsync(this.RaiseTick, this.priority); + this.Dispatcher.Post(this.RaiseTick, this.priority); } /// diff --git a/src/Perspex.Base/Threading/MainLoop.cs b/src/Perspex.Base/Threading/MainLoop.cs index b4941118ae..3a99133d15 100644 --- a/src/Perspex.Base/Threading/MainLoop.cs +++ b/src/Perspex.Base/Threading/MainLoop.cs @@ -108,9 +108,11 @@ namespace Perspex.Win32.Threading /// Post action that will be invoked on main thread /// /// The method. - internal void Post(Action action) + /// + /// The priority with which to invoke the method. + internal void Post(Action action, DispatcherPriority priority) { - this.AddJob(new Job(action, DispatcherPriority.Normal, true)); + this.AddJob(new Job(action, priority, true)); } private void AddJob(Job job) From 7d416715fe73b84833f1f8ac82b2d32c746f3d77 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 3 Sep 2015 22:29:31 +0300 Subject: [PATCH 4/6] Register SyncContext in RegisterServices --- src/Perspex.Application/Application.cs | 3 ++- src/Perspex.Base/PerspexObject.cs | 3 --- .../Threading/PerspexSynchronizationContext.cs | 8 ++++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs index d3d8119af8..40c3542d8c 100644 --- a/src/Perspex.Application/Application.cs +++ b/src/Perspex.Application/Application.cs @@ -55,7 +55,7 @@ namespace Perspex { throw new InvalidOperationException("Cannot create more than one Application instance."); } - PerspexSynchronizationContext.InstallIfNeeded(); + Current = this; } @@ -150,6 +150,7 @@ namespace Perspex /// protected virtual void RegisterServices() { + PerspexSynchronizationContext.InstallIfNeeded(); this.FocusManager = new FocusManager(); this.InputManager = new InputManager(); diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index ffa0b89f13..0c0e402c36 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -4,8 +4,6 @@ // // ----------------------------------------------------------------------- -using Perspex.Threading; - namespace Perspex { using System; @@ -102,7 +100,6 @@ namespace Perspex /// public PerspexObject() { - PerspexSynchronizationContext.InstallIfNeeded(); this.propertyLog = Log.ForContext(new[] { new PropertyEnricher("Area", "Property"), diff --git a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs index 5c0540ef62..40bbfc801c 100644 --- a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs +++ b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs @@ -1,12 +1,12 @@ -using System.Threading; - -namespace Perspex.Threading +namespace Perspex.Threading { using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; + using System.Threading; + /// /// SynchronizationContext to be used on main thread @@ -14,7 +14,7 @@ namespace Perspex.Threading public class PerspexSynchronizationContext : SynchronizationContext { /// - /// Controls if SynchronizationContext should be installed in InstallIfNeeded + /// Controls if SynchronizationContext should be installed in InstallIfNeeded. Used by Designer. /// public static bool AutoInstall { get; set; } = true; From 13e811f74bf08ad36f6ff901d9c2d72b09f2de13 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 3 Sep 2015 22:32:22 +0300 Subject: [PATCH 5/6] Fixed RXUI threading issue --- src/Perspex.ReactiveUI/Registrations.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Perspex.ReactiveUI/Registrations.cs b/src/Perspex.ReactiveUI/Registrations.cs index b6b09fb7f2..699d02d87f 100644 --- a/src/Perspex.ReactiveUI/Registrations.cs +++ b/src/Perspex.ReactiveUI/Registrations.cs @@ -1,4 +1,6 @@ using System; +using System.Reactive.Concurrency; +using System.Threading; namespace ReactiveUI @@ -12,7 +14,7 @@ namespace ReactiveUI { public void Register(Action, Type> registerFunction) { - + RxApp.MainThreadScheduler = new SynchronizationContextScheduler(SynchronizationContext.Current); } } } From 454b1547ee2c680a15669b85aec87f8512fd2523 Mon Sep 17 00:00:00 2001 From: Nelson Carrillo Date: Thu, 3 Sep 2015 13:27:11 -0400 Subject: [PATCH 6/6] Implemented LinearGradientBrush --- src/Perspex.SceneGraph/Media/Brush.cs | 10 ++++- .../Media/BrushMappingMode.cs | 14 ++++++ src/Perspex.SceneGraph/Media/GradientBrush.cs | 38 ++++++++++++++++ .../Media/GradientSpreadMethod.cs | 15 +++++++ src/Perspex.SceneGraph/Media/GradientStop.cs | 36 +++++++++++++++ .../Media/LinearGradientBrush.cs | 23 ++++++++++ .../Perspex.SceneGraph.csproj | 5 +++ .../Perspex.Direct2D1/Media/BrushImpl.cs | 19 ++++++++ .../Perspex.Direct2D1/Media/DrawingContext.cs | 44 ++++++++++++++----- .../Media/LinearGradientBrushImpl.cs | 43 ++++++++++++++++++ .../Media/PerspexTextRenderer.cs | 11 +++-- .../Media/SolidColorBrushImpl.cs | 17 +++++++ .../Perspex.Direct2D1.csproj | 3 ++ .../Perspex.Direct2D1/PrimitiveExtensions.cs | 9 ++++ 14 files changed, 272 insertions(+), 15 deletions(-) create mode 100644 src/Perspex.SceneGraph/Media/BrushMappingMode.cs create mode 100644 src/Perspex.SceneGraph/Media/GradientBrush.cs create mode 100644 src/Perspex.SceneGraph/Media/GradientSpreadMethod.cs create mode 100644 src/Perspex.SceneGraph/Media/GradientStop.cs create mode 100644 src/Perspex.SceneGraph/Media/LinearGradientBrush.cs create mode 100644 src/Windows/Perspex.Direct2D1/Media/BrushImpl.cs create mode 100644 src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs create mode 100644 src/Windows/Perspex.Direct2D1/Media/SolidColorBrushImpl.cs 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. ///