diff --git a/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs b/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs
index f8c74f3f8f..a1fee1a2e3 100644
--- a/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs
+++ b/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
@@ -16,7 +17,7 @@ namespace Perspex.Cairo.Media
///
/// Draws using Direct2D1.
///
- public class DrawingContext : IDrawingContext, IDisposable
+ public class DrawingContext : IDrawingContextImpl, IDisposable
{
///
/// The cairo context.
@@ -30,7 +31,6 @@ namespace Perspex.Cairo.Media
public DrawingContext(Cairo.Surface surface)
{
_context = new Cairo.Context(surface);
- CurrentTransform = Matrix.Identity;
}
///
@@ -40,15 +40,23 @@ namespace Perspex.Cairo.Media
public DrawingContext(Gdk.Drawable drawable)
{
_context = Gdk.CairoHelper.Create(drawable);
- CurrentTransform = Matrix.Identity;
}
+
+ private Matrix _transform = Matrix.Identity;
///
/// Gets the current transform of the drawing context.
///
- public Matrix CurrentTransform
+ public Matrix Transform
{
- get; }
+ get { return _transform; }
+ set
+ {
+ _transform = value;
+ _context.Matrix = value.ToCairo();
+
+ }
+ }
///
/// Ends a draw operation.
@@ -131,28 +139,30 @@ namespace Perspex.Cairo.Media
{
var impl = geometry.PlatformImpl as StreamGeometryImpl;
- using (var pop = PushTransform(impl.Transform))
+ var oldMatrix = Transform;
+ Transform = impl.Transform * Transform;
+
+
+ if (brush != null)
{
_context.AppendPath(impl.Path);
-
- if (brush != null)
+ using (var b = SetBrush(brush, geometry.Bounds.Size))
{
- using (var b = SetBrush(brush, geometry.Bounds.Size))
- {
- if (pen != null)
- _context.FillPreserve();
- else
- _context.Fill();
- }
+ if (pen != null)
+ _context.FillPreserve();
+ else
+ _context.Fill();
}
}
+ Transform = oldMatrix;
if (pen != null)
{
- using (var p = SetPen(pen, geometry.Bounds.Size))
- {
- _context.Stroke();
- }
+ _context.AppendPath(impl.Path);
+ using (var p = SetPen(pen, geometry.Bounds.Size))
+ {
+ _context.Stroke();
+ }
}
}
@@ -208,31 +218,37 @@ namespace Perspex.Cairo.Media
///
/// The clip rectangle.
/// A disposable used to undo the clip rectangle.
- public IDisposable PushClip(Rect clip)
+ public void PushClip(Rect clip)
{
_context.Save();
_context.Rectangle(clip.ToCairo());
_context.Clip();
+ }
- return Disposable.Create(() => _context.Restore());
+ public void PopClip()
+ {
+ _context.Restore();
}
+ readonly Stack _opacityStack = new Stack();
+
///
/// Pushes an opacity value.
///
/// The opacity.
/// A disposable used to undo the opacity.
- public IDisposable PushOpacity(double opacity)
+ public void PushOpacity(double opacity)
{
- var tmp = opacityOverride;
+ _opacityStack.Push(opacityOverride);
if (opacity < 1.0f)
- opacityOverride = opacity;
+ opacityOverride *= opacity;
- return Disposable.Create(() =>
- {
- opacityOverride = tmp;
- });
+ }
+
+ public void PopOpacity()
+ {
+ opacityOverride = _opacityStack.Pop();
}
///
diff --git a/src/Gtk/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs b/src/Gtk/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs
index 0b9183d928..4d8f05848a 100644
--- a/src/Gtk/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs
+++ b/src/Gtk/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs
@@ -39,7 +39,7 @@ namespace Perspex.Cairo.Media.Imaging
Surface.WriteToPng(fileName);
}
- public IDrawingContext CreateDrawingContext()
+ public Perspex.Media.DrawingContext CreateDrawingContext()
{
return _renderTarget.CreateDrawingContext();
}
diff --git a/src/Gtk/Perspex.Cairo/Media/TileBrushes.cs b/src/Gtk/Perspex.Cairo/Media/TileBrushes.cs
index 94fb19ea0b..98361427f0 100644
--- a/src/Gtk/Perspex.Cairo/Media/TileBrushes.cs
+++ b/src/Gtk/Perspex.Cairo/Media/TileBrushes.cs
@@ -113,7 +113,7 @@ namespace Perspex.Cairo.Media
out drawRect);
using (ctx.PushClip(drawRect))
- using (ctx.PushTransform(transform))
+ using (ctx.PushPostTransform(transform))
{
ctx.Render(visual);
}
diff --git a/src/Gtk/Perspex.Cairo/RenderTarget.cs b/src/Gtk/Perspex.Cairo/RenderTarget.cs
index 333c558e8a..2daab33fbe 100644
--- a/src/Gtk/Perspex.Cairo/RenderTarget.cs
+++ b/src/Gtk/Perspex.Cairo/RenderTarget.cs
@@ -7,6 +7,7 @@ using Perspex.Cairo.Media;
using Perspex.Media;
using Perspex.Platform;
using Perspex.Rendering;
+using DrawingContext = Perspex.Media.DrawingContext;
namespace Perspex.Cairo
{
@@ -50,12 +51,13 @@ namespace Perspex.Cairo
///
/// Creates a cairo surface that targets a platform-specific resource.
///
- /// A surface wrapped in an .
- public IDrawingContext CreateDrawingContext()
+ /// A surface wrapped in an .
+ public DrawingContext CreateDrawingContext()
{
- if(_surface != null)
- return new DrawingContext(_surface);
- return new DrawingContext(_window.GdkWindow);
+ var ctx = _surface != null
+ ? new Media.DrawingContext(_surface)
+ : new Media.DrawingContext(_window.GdkWindow);
+ return new DrawingContext(ctx);
}
public void Dispose() => _surface?.Dispose();
diff --git a/src/Gtk/Perspex.Gtk/GtkPlatform.cs b/src/Gtk/Perspex.Gtk/GtkPlatform.cs
index 5b52ac84d1..f6190b3330 100644
--- a/src/Gtk/Perspex.Gtk/GtkPlatform.cs
+++ b/src/Gtk/Perspex.Gtk/GtkPlatform.cs
@@ -17,6 +17,7 @@ namespace Perspex.Gtk
public class GtkPlatform : IPlatformThreadingInterface, IPlatformSettings
{
private static readonly GtkPlatform s_instance = new GtkPlatform();
+ private static Thread _uiThread;
public GtkPlatform()
{
@@ -40,6 +41,7 @@ namespace Perspex.Gtk
.Bind().ToConstant(s_instance)
.Bind().ToSingleton();
SharedPlatform.Register();
+ _uiThread = Thread.CurrentThread;
}
public bool HasMessages()
@@ -79,6 +81,8 @@ namespace Perspex.Gtk
Gtk.Application.Invoke(delegate { Signaled?.Invoke(); });
}
+ public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _uiThread;
+
public event Action Signaled;
}
}
\ No newline at end of file
diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexAttachableXamlMember.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexAttachableXamlMember.cs
new file mode 100644
index 0000000000..117f7768f1
--- /dev/null
+++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexAttachableXamlMember.cs
@@ -0,0 +1,33 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Perspex.Markup.Xaml.Binding;
+using OmniXaml;
+using OmniXaml.Typing;
+using System.Reflection;
+
+namespace Perspex.Markup.Xaml.Context
+{
+ public class PerspexAttachableXamlMember : AttachableXamlMember
+ {
+ public PerspexAttachableXamlMember(string name,
+ XamlType owner,
+ MethodInfo getter,
+ MethodInfo setter,
+ IXamlTypeRepository xamlTypeRepository,
+ ITypeFeatureProvider featureProvider)
+ : base(name, getter, setter, xamlTypeRepository, featureProvider)
+ {
+ }
+
+ protected override IXamlMemberValuePlugin LookupXamlMemberValueConnector()
+ {
+ return new PerspexXamlMemberValuePlugin(this);
+ }
+
+ public override string ToString()
+ {
+ return "Perspex Attachable XAML Member " + base.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs
index fd7a786649..36e1d03ea8 100644
--- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs
@@ -5,6 +5,7 @@ using System;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
+using System.Runtime.CompilerServices;
using Glass;
using OmniXaml.ObjectAssembler;
using OmniXaml.Typing;
@@ -16,9 +17,9 @@ namespace Perspex.Markup.Xaml.Context
{
public class PerspexXamlMemberValuePlugin : MemberValuePlugin
{
- private readonly XamlMember _xamlMember;
+ private readonly MutableXamlMember _xamlMember;
- public PerspexXamlMemberValuePlugin(XamlMember xamlMember)
+ public PerspexXamlMemberValuePlugin(MutableXamlMember xamlMember)
: base(xamlMember)
{
_xamlMember = xamlMember;
@@ -73,6 +74,7 @@ namespace Perspex.Markup.Xaml.Context
else
{
var perspexObject = instance as PerspexObject;
+ var attached = _xamlMember as PerspexAttachableXamlMember;
if (perspexObject == null)
{
@@ -80,13 +82,31 @@ namespace Perspex.Markup.Xaml.Context
$"Cannot bind to an object of type '{instance.GetType()}");
}
- var property = perspexObject.GetRegisteredProperties()
- .FirstOrDefault(x => x.Name == _xamlMember.Name);
+ PerspexProperty property;
+ string propertyName;
+
+ if (attached == null)
+ {
+ propertyName = _xamlMember.Name;
+ property = perspexObject.GetRegisteredProperties()
+ .FirstOrDefault(x => x.Name == propertyName);
+ }
+ else
+ {
+ // Ensure the OwnerType's static ctor has been run.
+ RuntimeHelpers.RunClassConstructor(attached.DeclaringType.UnderlyingType.TypeHandle);
+
+ propertyName = attached.DeclaringType.UnderlyingType.Name + '.' + _xamlMember.Name;
+
+ property = perspexObject.GetRegisteredProperties()
+ .Where(x => x.IsAttached && x.OwnerType == attached.DeclaringType.UnderlyingType)
+ .FirstOrDefault(x => x.Name == _xamlMember.Name);
+ }
if (property == null)
{
throw new InvalidOperationException(
- $"Cannot find '{_xamlMember.Name}' on '{instance.GetType()}");
+ $"Cannot find '{propertyName}' on '{instance.GetType()}");
}
var binding = new XamlBinding
diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs
index 0d1fb218b1..dc6b7398f4 100644
--- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Reflection;
using OmniXaml;
using OmniXaml.Typing;
using Perspex.Markup.Xaml.Binding;
@@ -22,6 +23,14 @@ namespace Perspex.Markup.Xaml.Context
return new PerspexXamlMember(name, this, TypeRepository, FeatureProvider);
}
+ protected override AttachableXamlMember LookupAttachableMember(string name)
+ {
+ // OmniXAML seems to require a getter and setter even though we don't use them.
+ var getter = UnderlyingType.GetTypeInfo().GetDeclaredMethod("Get" + name);
+ var setter = UnderlyingType.GetTypeInfo().GetDeclaredMethod("Set" + name);
+ return new PerspexAttachableXamlMember(name, this, getter, setter, TypeRepository, FeatureProvider);
+ }
+
public override string ToString()
{
return "Perspex XAML Type " + base.ToString();
diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
index 987b630907..d34c97c50f 100644
--- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
+++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
@@ -42,6 +42,7 @@
+
diff --git a/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs
index da22cafae2..243ef9ef96 100644
--- a/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Reflection;
using OmniXaml.Attributes;
using Perspex.Controls;
using Perspex.Controls.Templates;
@@ -20,7 +21,7 @@ namespace Perspex.Markup.Xaml.Templates
throw new InvalidOperationException("DataTemplate must have a DataType.");
}
- return DataType == data.GetType();
+ return DataType.GetTypeInfo().IsAssignableFrom(data.GetType().GetTypeInfo());
}
public IControl Build(object data)
diff --git a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs
index 1015bcfa0a..b4efbc324c 100644
--- a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs
+++ b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs
@@ -81,6 +81,8 @@ namespace Perspex.Markup.Binding
{
inpc.PropertyChanged -= PropertyChanged;
}
+
+ _propertyInfo = null;
}
private static PropertyInfo FindProperty(object target, string propertyName)
diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj
index abe7582049..b7227d831b 100644
--- a/src/Perspex.Base/Perspex.Base.csproj
+++ b/src/Perspex.Base/Perspex.Base.csproj
@@ -46,6 +46,7 @@
+
diff --git a/src/Perspex.Base/PerspexDisposable.cs b/src/Perspex.Base/PerspexDisposable.cs
new file mode 100644
index 0000000000..d3017e4b44
--- /dev/null
+++ b/src/Perspex.Base/PerspexDisposable.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Perspex.Platform;
+
+namespace Perspex
+{
+ public abstract class PerspexDisposable : IDisposable
+ {
+#if DEBUG_DISPOSE
+ public string DisposedAt { get; private set; }
+#endif
+
+
+ public bool IsDisposed { get; private set; }
+
+ public void Dispose()
+ {
+ IsDisposed = true;
+#if DEBUG_DISPOSE
+ DisposedAt = PerspexLocator.Current.GetService().GetStackTrace();
+#endif
+ DoDispose();
+ }
+
+ protected void CheckDisposed()
+ {
+ if (IsDisposed)
+ throw new ObjectDisposedException(GetType().FullName
+#if DEBUG_DISPOSE
+ , "Disposed at: \n" + DisposedAt
+#endif
+
+ );
+ }
+
+ protected abstract void DoDispose();
+ }
+}
diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs
index 696d01893a..27476c881d 100644
--- a/src/Perspex.Base/PerspexObject.cs
+++ b/src/Perspex.Base/PerspexObject.cs
@@ -10,6 +10,7 @@ using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Reflection;
using Perspex.Reactive;
+using Perspex.Threading;
using Perspex.Utilities;
using Serilog;
using Serilog.Core.Enrichers;
@@ -300,6 +301,10 @@ namespace Perspex
}
}
+ public bool CheckAccess() => Dispatcher.UIThread.CheckAccess();
+
+ public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess();
+
///
/// Clears a 's local value.
///
@@ -495,7 +500,7 @@ namespace Perspex
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires(property != null);
-
+ VerifyAccess();
if (property.IsDirect)
{
property = GetRegistered(property);
@@ -556,7 +561,7 @@ namespace Perspex
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires(property != null);
-
+ VerifyAccess();
if (property.IsDirect)
{
property = (PerspexProperty)GetRegistered(property);
@@ -590,7 +595,7 @@ namespace Perspex
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires(property != null);
-
+ VerifyAccess();
if (property.IsDirect)
{
property = GetRegistered(property);
@@ -650,7 +655,7 @@ namespace Perspex
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires(property != null);
-
+ VerifyAccess();
if (property.IsDirect)
{
property = (PerspexProperty)GetRegistered(property);
@@ -687,6 +692,7 @@ namespace Perspex
PerspexProperty sourceProperty,
BindingPriority priority = BindingPriority.LocalValue)
{
+ VerifyAccess();
_propertyLog.Verbose(
"Bound two way {Property} to {Binding} with priority {Priority}",
property,
@@ -716,6 +722,7 @@ namespace Perspex
ISubject
void Signal();
+ bool CurrentThreadIsLoopThread { get; }
+
event Action Signaled;
}
diff --git a/src/Perspex.Base/Threading/Dispatcher.cs b/src/Perspex.Base/Threading/Dispatcher.cs
index ee9555552c..9634eeb4a4 100644
--- a/src/Perspex.Base/Threading/Dispatcher.cs
+++ b/src/Perspex.Base/Threading/Dispatcher.cs
@@ -26,10 +26,21 @@ namespace Perspex.Threading
public Dispatcher(IPlatformThreadingInterface platform)
{
_platform = platform;
+ if(_platform == null)
+ //TODO: Unit test mode, fix that somehow
+ return;
_jobRunner = new JobRunner(platform);
_platform.Signaled += _jobRunner.RunJobs;
}
+ public bool CheckAccess() => _platform?.CurrentThreadIsLoopThread() ?? true;
+
+ public void VerifyAccess()
+ {
+ if (!CheckAccess())
+ throw new InvalidOperationException("Call from invalid thread");
+ }
+
///
/// Runs the dispatcher's main loop.
@@ -49,7 +60,7 @@ namespace Perspex.Threading
///
public void RunJobs()
{
- _jobRunner.RunJobs();
+ _jobRunner?.RunJobs();
}
///
@@ -58,9 +69,9 @@ namespace Perspex.Threading
/// The method.
/// The priority with which to invoke the method.
/// A task that can be used to track the method's execution.
- public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
+ public Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
- return _jobRunner.InvokeAsync(action, priority);
+ return _jobRunner?.InvokeAsync(action, priority);
}
///
@@ -68,9 +79,9 @@ namespace Perspex.Threading
///
/// The method.
/// The priority with which to invoke the method.
- internal void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
+ public void InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
- _jobRunner.Post(action, priority);
+ _jobRunner?.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 688cf57605..159151dc79 100644
--- a/src/Perspex.Base/Threading/DispatcherTimer.cs
+++ b/src/Perspex.Base/Threading/DispatcherTimer.cs
@@ -177,7 +177,7 @@ namespace Perspex.Threading
///
private void InternalTick()
{
- Dispatcher.UIThread.Post(RaiseTick, _priority);
+ Dispatcher.UIThread.InvokeAsync(RaiseTick, _priority);
}
///
diff --git a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs
index ac7b509740..15941557d4 100644
--- a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs
+++ b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs
@@ -36,14 +36,14 @@ namespace Perspex.Threading
///
public override void Post(SendOrPostCallback d, object state)
{
- Dispatcher.UIThread.Post(() => d(state));
+ Dispatcher.UIThread.InvokeAsync(() => 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();
+ Dispatcher.UIThread.InvokeTaskAsync(() => d(state)).Wait();
}
}
}
\ No newline at end of file
diff --git a/src/Perspex.Base/Threading/SingleThreadDispatcher.cs b/src/Perspex.Base/Threading/SingleThreadDispatcher.cs
index 7f1ca48593..5e751833f9 100644
--- a/src/Perspex.Base/Threading/SingleThreadDispatcher.cs
+++ b/src/Perspex.Base/Threading/SingleThreadDispatcher.cs
@@ -37,6 +37,8 @@ namespace Perspex.Threading
() => _timerJobRunner.Post(tick, DispatcherPriority.Normal));
public void Signal() => _evnt.Set();
+ //TODO: Actually perform a check
+ public bool CurrentThreadIsLoopThread => true;
public event Action Signaled;
}
diff --git a/src/Perspex.Controls/Border.cs b/src/Perspex.Controls/Border.cs
index 8a70987cb0..bcb1235f25 100644
--- a/src/Perspex.Controls/Border.cs
+++ b/src/Perspex.Controls/Border.cs
@@ -83,7 +83,7 @@ namespace Perspex.Controls
/// Renders the control.
///
/// The drawing context.
- public override void Render(IDrawingContext context)
+ public override void Render(DrawingContext context)
{
var background = Background;
var borderBrush = BorderBrush;
diff --git a/src/Perspex.Controls/Button.cs b/src/Perspex.Controls/Button.cs
index 41736206c9..29eb4c3628 100644
--- a/src/Perspex.Controls/Button.cs
+++ b/src/Perspex.Controls/Button.cs
@@ -44,6 +44,9 @@ namespace Perspex.Controls
public static readonly PerspexProperty CommandProperty =
PerspexProperty.Register