Browse Source

Merge branch 'master' into multiple-selection

pull/278/head
Steven Kirk 11 years ago
parent
commit
7f9e81b1fc
  1. 72
      src/Gtk/Perspex.Cairo/Media/DrawingContext.cs
  2. 2
      src/Gtk/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs
  3. 2
      src/Gtk/Perspex.Cairo/Media/TileBrushes.cs
  4. 12
      src/Gtk/Perspex.Cairo/RenderTarget.cs
  5. 4
      src/Gtk/Perspex.Gtk/GtkPlatform.cs
  6. 33
      src/Markup/Perspex.Markup.Xaml/Context/PerspexAttachableXamlMember.cs
  7. 30
      src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs
  8. 9
      src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs
  9. 1
      src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
  10. 3
      src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs
  11. 2
      src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs
  12. 1
      src/Perspex.Base/Perspex.Base.csproj
  13. 41
      src/Perspex.Base/PerspexDisposable.cs
  14. 18
      src/Perspex.Base/PerspexObject.cs
  15. 1
      src/Perspex.Base/Platform/IPclPlatformWrapper.cs
  16. 2
      src/Perspex.Base/Platform/IPlatformThreadingInterface.cs
  17. 21
      src/Perspex.Base/Threading/Dispatcher.cs
  18. 2
      src/Perspex.Base/Threading/DispatcherTimer.cs
  19. 4
      src/Perspex.Base/Threading/PerspexSynchronizationContext.cs
  20. 2
      src/Perspex.Base/Threading/SingleThreadDispatcher.cs
  21. 2
      src/Perspex.Controls/Border.cs
  22. 3
      src/Perspex.Controls/Button.cs
  23. 17
      src/Perspex.Controls/Canvas.cs
  24. 9
      src/Perspex.Controls/Generators/ItemContainerGenerator.cs
  25. 6
      src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs
  26. 25
      src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs
  27. 2
      src/Perspex.Controls/Image.cs
  28. 4
      src/Perspex.Controls/ListBox.cs
  29. 4
      src/Perspex.Controls/MenuItem.cs
  30. 4
      src/Perspex.Controls/Panel.cs
  31. 12
      src/Perspex.Controls/PlacementMode.cs
  32. 17
      src/Perspex.Controls/Platform/IPopupImpl.cs
  33. 16
      src/Perspex.Controls/Platform/ISystemDialogImpl.cs
  34. 46
      src/Perspex.Controls/Platform/ITopLevelImpl.cs
  35. 5
      src/Perspex.Controls/Presenters/ContentPresenter.cs
  36. 2
      src/Perspex.Controls/Presenters/TextPresenter.cs
  37. 2
      src/Perspex.Controls/Primitives/AccessText.cs
  38. 2
      src/Perspex.Controls/Shapes/Shape.cs
  39. 2
      src/Perspex.Controls/Templates/DataTemplateExtensions.cs
  40. 86
      src/Perspex.Controls/TextBlock.cs
  41. 4
      src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs
  42. 2
      src/Perspex.HtmlRenderer/HtmlContainer.cs
  43. 2
      src/Perspex.HtmlRenderer/HtmlControl.cs
  44. 4
      src/Perspex.SceneGraph/IVisual.cs
  45. 206
      src/Perspex.SceneGraph/Media/DrawingContext.cs
  46. 22
      src/Perspex.SceneGraph/Media/FormattedText.cs
  47. 19
      src/Perspex.SceneGraph/Media/IDrawingContext.cs
  48. 2
      src/Perspex.SceneGraph/Media/Imaging/RenderTargetBitmap.cs
  49. 76
      src/Perspex.SceneGraph/Media/ValidatingDrawingContext.cs
  50. 2
      src/Perspex.SceneGraph/Perspex.SceneGraph.csproj
  51. 4
      src/Perspex.SceneGraph/Platform/IRenderTarget.cs
  52. 9
      src/Perspex.SceneGraph/Rendering/RendererBase.cs
  53. 4
      src/Perspex.SceneGraph/Visual.cs
  54. BIN
      src/Shared/PlatformSupport/PclPlatformWrapper.cs
  55. 59
      src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs
  56. 2
      src/Windows/Perspex.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs
  57. 2
      src/Windows/Perspex.Direct2D1/Media/VisualBrushImpl.cs
  58. 19
      src/Windows/Perspex.Direct2D1/RenderTarget.cs
  59. 31
      src/Windows/Perspex.Win32/SystemDialogImpl.cs
  60. 4
      src/Windows/Perspex.Win32/Win32Platform.cs
  61. 44
      tests/Perspex.Controls.UnitTests/ListBoxTests.cs
  62. 1
      tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj
  63. 101
      tests/Perspex.Controls.UnitTests/TreeViewTests.cs
  64. 4
      tests/Perspex.Input.UnitTests/Properties/AssemblyInfo.cs
  65. 80
      tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_SetValue.cs
  66. 1
      tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj
  67. 1
      tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj
  68. 37
      tests/Perspex.Markup.Xaml.UnitTests/Templates/DataTemplateTests.cs

72
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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -16,7 +17,7 @@ namespace Perspex.Cairo.Media
/// <summary> /// <summary>
/// Draws using Direct2D1. /// Draws using Direct2D1.
/// </summary> /// </summary>
public class DrawingContext : IDrawingContext, IDisposable public class DrawingContext : IDrawingContextImpl, IDisposable
{ {
/// <summary> /// <summary>
/// The cairo context. /// The cairo context.
@ -30,7 +31,6 @@ namespace Perspex.Cairo.Media
public DrawingContext(Cairo.Surface surface) public DrawingContext(Cairo.Surface surface)
{ {
_context = new Cairo.Context(surface); _context = new Cairo.Context(surface);
CurrentTransform = Matrix.Identity;
} }
/// <summary> /// <summary>
@ -40,15 +40,23 @@ namespace Perspex.Cairo.Media
public DrawingContext(Gdk.Drawable drawable) public DrawingContext(Gdk.Drawable drawable)
{ {
_context = Gdk.CairoHelper.Create(drawable); _context = Gdk.CairoHelper.Create(drawable);
CurrentTransform = Matrix.Identity;
} }
private Matrix _transform = Matrix.Identity;
/// <summary> /// <summary>
/// Gets the current transform of the drawing context. /// Gets the current transform of the drawing context.
/// </summary> /// </summary>
public Matrix CurrentTransform public Matrix Transform
{ {
get; } get { return _transform; }
set
{
_transform = value;
_context.Matrix = value.ToCairo();
}
}
/// <summary> /// <summary>
/// Ends a draw operation. /// Ends a draw operation.
@ -131,28 +139,30 @@ namespace Perspex.Cairo.Media
{ {
var impl = geometry.PlatformImpl as StreamGeometryImpl; 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); _context.AppendPath(impl.Path);
using (var b = SetBrush(brush, geometry.Bounds.Size))
if (brush != null)
{ {
using (var b = SetBrush(brush, geometry.Bounds.Size)) if (pen != null)
{ _context.FillPreserve();
if (pen != null) else
_context.FillPreserve(); _context.Fill();
else
_context.Fill();
}
} }
} }
Transform = oldMatrix;
if (pen != null) if (pen != null)
{ {
using (var p = SetPen(pen, geometry.Bounds.Size)) _context.AppendPath(impl.Path);
{ using (var p = SetPen(pen, geometry.Bounds.Size))
_context.Stroke(); {
} _context.Stroke();
}
} }
} }
@ -208,31 +218,37 @@ namespace Perspex.Cairo.Media
/// </summary> /// </summary>
/// <param name="clip">The clip rectangle.</param> /// <param name="clip">The clip rectangle.</param>
/// <returns>A disposable used to undo the clip rectangle.</returns> /// <returns>A disposable used to undo the clip rectangle.</returns>
public IDisposable PushClip(Rect clip) public void PushClip(Rect clip)
{ {
_context.Save(); _context.Save();
_context.Rectangle(clip.ToCairo()); _context.Rectangle(clip.ToCairo());
_context.Clip(); _context.Clip();
}
return Disposable.Create(() => _context.Restore()); public void PopClip()
{
_context.Restore();
} }
readonly Stack<double> _opacityStack = new Stack<double>();
/// <summary> /// <summary>
/// Pushes an opacity value. /// Pushes an opacity value.
/// </summary> /// </summary>
/// <param name="opacity">The opacity.</param> /// <param name="opacity">The opacity.</param>
/// <returns>A disposable used to undo the opacity.</returns> /// <returns>A disposable used to undo the opacity.</returns>
public IDisposable PushOpacity(double opacity) public void PushOpacity(double opacity)
{ {
var tmp = opacityOverride; _opacityStack.Push(opacityOverride);
if (opacity < 1.0f) if (opacity < 1.0f)
opacityOverride = opacity; opacityOverride *= opacity;
return Disposable.Create(() => }
{
opacityOverride = tmp; public void PopOpacity()
}); {
opacityOverride = _opacityStack.Pop();
} }
/// <summary> /// <summary>

2
src/Gtk/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs

@ -39,7 +39,7 @@ namespace Perspex.Cairo.Media.Imaging
Surface.WriteToPng(fileName); Surface.WriteToPng(fileName);
} }
public IDrawingContext CreateDrawingContext() public Perspex.Media.DrawingContext CreateDrawingContext()
{ {
return _renderTarget.CreateDrawingContext(); return _renderTarget.CreateDrawingContext();
} }

2
src/Gtk/Perspex.Cairo/Media/TileBrushes.cs

@ -113,7 +113,7 @@ namespace Perspex.Cairo.Media
out drawRect); out drawRect);
using (ctx.PushClip(drawRect)) using (ctx.PushClip(drawRect))
using (ctx.PushTransform(transform)) using (ctx.PushPostTransform(transform))
{ {
ctx.Render(visual); ctx.Render(visual);
} }

12
src/Gtk/Perspex.Cairo/RenderTarget.cs

@ -7,6 +7,7 @@ using Perspex.Cairo.Media;
using Perspex.Media; using Perspex.Media;
using Perspex.Platform; using Perspex.Platform;
using Perspex.Rendering; using Perspex.Rendering;
using DrawingContext = Perspex.Media.DrawingContext;
namespace Perspex.Cairo namespace Perspex.Cairo
{ {
@ -50,12 +51,13 @@ namespace Perspex.Cairo
/// <summary> /// <summary>
/// Creates a cairo surface that targets a platform-specific resource. /// Creates a cairo surface that targets a platform-specific resource.
/// </summary> /// </summary>
/// <returns>A surface wrapped in an <see cref="IDrawingContext"/>.</returns> /// <returns>A surface wrapped in an <see cref="Perspex.Media.DrawingContext"/>.</returns>
public IDrawingContext CreateDrawingContext() public DrawingContext CreateDrawingContext()
{ {
if(_surface != null) var ctx = _surface != null
return new DrawingContext(_surface); ? new Media.DrawingContext(_surface)
return new DrawingContext(_window.GdkWindow); : new Media.DrawingContext(_window.GdkWindow);
return new DrawingContext(ctx);
} }
public void Dispose() => _surface?.Dispose(); public void Dispose() => _surface?.Dispose();

4
src/Gtk/Perspex.Gtk/GtkPlatform.cs

@ -17,6 +17,7 @@ namespace Perspex.Gtk
public class GtkPlatform : IPlatformThreadingInterface, IPlatformSettings public class GtkPlatform : IPlatformThreadingInterface, IPlatformSettings
{ {
private static readonly GtkPlatform s_instance = new GtkPlatform(); private static readonly GtkPlatform s_instance = new GtkPlatform();
private static Thread _uiThread;
public GtkPlatform() public GtkPlatform()
{ {
@ -40,6 +41,7 @@ namespace Perspex.Gtk
.Bind<IPlatformThreadingInterface>().ToConstant(s_instance) .Bind<IPlatformThreadingInterface>().ToConstant(s_instance)
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogImpl>(); .Bind<ISystemDialogImpl>().ToSingleton<SystemDialogImpl>();
SharedPlatform.Register(); SharedPlatform.Register();
_uiThread = Thread.CurrentThread;
} }
public bool HasMessages() public bool HasMessages()
@ -79,6 +81,8 @@ namespace Perspex.Gtk
Gtk.Application.Invoke(delegate { Signaled?.Invoke(); }); Gtk.Application.Invoke(delegate { Signaled?.Invoke(); });
} }
public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _uiThread;
public event Action Signaled; public event Action Signaled;
} }
} }

33
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();
}
}
}

30
src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs

@ -5,6 +5,7 @@ using System;
using System.Linq; using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using Glass; using Glass;
using OmniXaml.ObjectAssembler; using OmniXaml.ObjectAssembler;
using OmniXaml.Typing; using OmniXaml.Typing;
@ -16,9 +17,9 @@ namespace Perspex.Markup.Xaml.Context
{ {
public class PerspexXamlMemberValuePlugin : MemberValuePlugin public class PerspexXamlMemberValuePlugin : MemberValuePlugin
{ {
private readonly XamlMember _xamlMember; private readonly MutableXamlMember _xamlMember;
public PerspexXamlMemberValuePlugin(XamlMember xamlMember) public PerspexXamlMemberValuePlugin(MutableXamlMember xamlMember)
: base(xamlMember) : base(xamlMember)
{ {
_xamlMember = xamlMember; _xamlMember = xamlMember;
@ -73,6 +74,7 @@ namespace Perspex.Markup.Xaml.Context
else else
{ {
var perspexObject = instance as PerspexObject; var perspexObject = instance as PerspexObject;
var attached = _xamlMember as PerspexAttachableXamlMember;
if (perspexObject == null) if (perspexObject == null)
{ {
@ -80,13 +82,31 @@ namespace Perspex.Markup.Xaml.Context
$"Cannot bind to an object of type '{instance.GetType()}"); $"Cannot bind to an object of type '{instance.GetType()}");
} }
var property = perspexObject.GetRegisteredProperties() PerspexProperty property;
.FirstOrDefault(x => x.Name == _xamlMember.Name); 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) if (property == null)
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
$"Cannot find '{_xamlMember.Name}' on '{instance.GetType()}"); $"Cannot find '{propertyName}' on '{instance.GetType()}");
} }
var binding = new XamlBinding var binding = new XamlBinding

9
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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Reflection;
using OmniXaml; using OmniXaml;
using OmniXaml.Typing; using OmniXaml.Typing;
using Perspex.Markup.Xaml.Binding; using Perspex.Markup.Xaml.Binding;
@ -22,6 +23,14 @@ namespace Perspex.Markup.Xaml.Context
return new PerspexXamlMember(name, this, TypeRepository, FeatureProvider); 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() public override string ToString()
{ {
return "Perspex XAML Type " + base.ToString(); return "Perspex XAML Type " + base.ToString();

1
src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj

@ -42,6 +42,7 @@
<Compile Include="Binding\TargetBindingEndpoint.cs" /> <Compile Include="Binding\TargetBindingEndpoint.cs" />
<Compile Include="Binding\XamlBinding.cs" /> <Compile Include="Binding\XamlBinding.cs" />
<Compile Include="Binding\XamlBindingDefinition.cs" /> <Compile Include="Binding\XamlBindingDefinition.cs" />
<Compile Include="Context\PerspexAttachableXamlMember.cs" />
<Compile Include="Converters\ColorTypeConverter.cs" /> <Compile Include="Converters\ColorTypeConverter.cs" />
<Compile Include="Converters\KeyGestureConverter.cs" /> <Compile Include="Converters\KeyGestureConverter.cs" />
<Compile Include="Converters\RelativePointTypeConverter.cs" /> <Compile Include="Converters\RelativePointTypeConverter.cs" />

3
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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Reflection;
using OmniXaml.Attributes; using OmniXaml.Attributes;
using Perspex.Controls; using Perspex.Controls;
using Perspex.Controls.Templates; using Perspex.Controls.Templates;
@ -20,7 +21,7 @@ namespace Perspex.Markup.Xaml.Templates
throw new InvalidOperationException("DataTemplate must have a DataType."); throw new InvalidOperationException("DataTemplate must have a DataType.");
} }
return DataType == data.GetType(); return DataType.GetTypeInfo().IsAssignableFrom(data.GetType().GetTypeInfo());
} }
public IControl Build(object data) public IControl Build(object data)

2
src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs

@ -81,6 +81,8 @@ namespace Perspex.Markup.Binding
{ {
inpc.PropertyChanged -= PropertyChanged; inpc.PropertyChanged -= PropertyChanged;
} }
_propertyInfo = null;
} }
private static PropertyInfo FindProperty(object target, string propertyName) private static PropertyInfo FindProperty(object target, string propertyName)

1
src/Perspex.Base/Perspex.Base.csproj

@ -46,6 +46,7 @@
<Compile Include="Diagnostics\PerspexObjectExtensions.cs" /> <Compile Include="Diagnostics\PerspexObjectExtensions.cs" />
<Compile Include="IObservablePropertyBag.cs" /> <Compile Include="IObservablePropertyBag.cs" />
<Compile Include="IPropertyBag.cs" /> <Compile Include="IPropertyBag.cs" />
<Compile Include="PerspexDisposable.cs" />
<Compile Include="PerspexLocator.cs" /> <Compile Include="PerspexLocator.cs" />
<Compile Include="Metadata\XmlnsDefinitionAttribute.cs" /> <Compile Include="Metadata\XmlnsDefinitionAttribute.cs" />
<Compile Include="PerspexObjectExtensions.cs" /> <Compile Include="PerspexObjectExtensions.cs" />

41
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<IPclPlatformWrapper>().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();
}
}

18
src/Perspex.Base/PerspexObject.cs

@ -10,6 +10,7 @@ using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using System.Reflection; using System.Reflection;
using Perspex.Reactive; using Perspex.Reactive;
using Perspex.Threading;
using Perspex.Utilities; using Perspex.Utilities;
using Serilog; using Serilog;
using Serilog.Core.Enrichers; using Serilog.Core.Enrichers;
@ -300,6 +301,10 @@ namespace Perspex
} }
} }
public bool CheckAccess() => Dispatcher.UIThread.CheckAccess();
public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess();
/// <summary> /// <summary>
/// Clears a <see cref="PerspexProperty"/>'s local value. /// Clears a <see cref="PerspexProperty"/>'s local value.
/// </summary> /// </summary>
@ -495,7 +500,7 @@ namespace Perspex
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
{ {
Contract.Requires<ArgumentNullException>(property != null); Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
if (property.IsDirect) if (property.IsDirect)
{ {
property = GetRegistered(property); property = GetRegistered(property);
@ -556,7 +561,7 @@ namespace Perspex
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
{ {
Contract.Requires<ArgumentNullException>(property != null); Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
if (property.IsDirect) if (property.IsDirect)
{ {
property = (PerspexProperty<T>)GetRegistered(property); property = (PerspexProperty<T>)GetRegistered(property);
@ -590,7 +595,7 @@ namespace Perspex
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
{ {
Contract.Requires<ArgumentNullException>(property != null); Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
if (property.IsDirect) if (property.IsDirect)
{ {
property = GetRegistered(property); property = GetRegistered(property);
@ -650,7 +655,7 @@ namespace Perspex
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
{ {
Contract.Requires<ArgumentNullException>(property != null); Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
if (property.IsDirect) if (property.IsDirect)
{ {
property = (PerspexProperty<T>)GetRegistered(property); property = (PerspexProperty<T>)GetRegistered(property);
@ -687,6 +692,7 @@ namespace Perspex
PerspexProperty sourceProperty, PerspexProperty sourceProperty,
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
{ {
VerifyAccess();
_propertyLog.Verbose( _propertyLog.Verbose(
"Bound two way {Property} to {Binding} with priority {Priority}", "Bound two way {Property} to {Binding} with priority {Priority}",
property, property,
@ -716,6 +722,7 @@ namespace Perspex
ISubject<object> source, ISubject<object> source,
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
{ {
VerifyAccess();
_propertyLog.Verbose( _propertyLog.Verbose(
"Bound two way {Property} to {Binding} with priority {Priority}", "Bound two way {Property} to {Binding} with priority {Priority}",
property, property,
@ -733,6 +740,7 @@ namespace Perspex
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
public void Revalidate(PerspexProperty property) public void Revalidate(PerspexProperty property)
{ {
VerifyAccess();
PriorityValue value; PriorityValue value;
if (_values.TryGetValue(property, out value)) if (_values.TryGetValue(property, out value))
@ -788,6 +796,7 @@ namespace Perspex
BindingPriority priority) BindingPriority priority)
{ {
Contract.Requires<ArgumentNullException>(property != null); Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
PerspexPropertyChangedEventArgs e = new PerspexPropertyChangedEventArgs( PerspexPropertyChangedEventArgs e = new PerspexPropertyChangedEventArgs(
this, this,
@ -824,6 +833,7 @@ namespace Perspex
/// </returns> /// </returns>
protected bool SetAndRaise<T>(PerspexProperty<T> property, ref T field, T value) protected bool SetAndRaise<T>(PerspexProperty<T> property, ref T field, T value)
{ {
VerifyAccess();
if (!object.Equals(field, value)) if (!object.Equals(field, value))
{ {
var old = field; var old = field;

1
src/Perspex.Base/Platform/IPclPlatformWrapper.cs

@ -12,5 +12,6 @@ namespace Perspex.Platform
Assembly[] GetLoadedAssemblies(); Assembly[] GetLoadedAssemblies();
void PostThreadPoolItem(Action cb); void PostThreadPoolItem(Action cb);
IDisposable StartSystemTimer(TimeSpan interval, Action tick); IDisposable StartSystemTimer(TimeSpan interval, Action tick);
string GetStackTrace();
} }
} }

2
src/Perspex.Base/Platform/IPlatformThreadingInterface.cs

@ -26,6 +26,8 @@ namespace Perspex.Platform
/// </summary> /// </summary>
void Signal(); void Signal();
bool CurrentThreadIsLoopThread { get; }
event Action Signaled; event Action Signaled;
} }

21
src/Perspex.Base/Threading/Dispatcher.cs

@ -26,10 +26,21 @@ namespace Perspex.Threading
public Dispatcher(IPlatformThreadingInterface platform) public Dispatcher(IPlatformThreadingInterface platform)
{ {
_platform = platform; _platform = platform;
if(_platform == null)
//TODO: Unit test mode, fix that somehow
return;
_jobRunner = new JobRunner(platform); _jobRunner = new JobRunner(platform);
_platform.Signaled += _jobRunner.RunJobs; _platform.Signaled += _jobRunner.RunJobs;
} }
public bool CheckAccess() => _platform?.CurrentThreadIsLoopThread() ?? true;
public void VerifyAccess()
{
if (!CheckAccess())
throw new InvalidOperationException("Call from invalid thread");
}
/// <summary> /// <summary>
/// Runs the dispatcher's main loop. /// Runs the dispatcher's main loop.
@ -49,7 +60,7 @@ namespace Perspex.Threading
/// </summary> /// </summary>
public void RunJobs() public void RunJobs()
{ {
_jobRunner.RunJobs(); _jobRunner?.RunJobs();
} }
/// <summary> /// <summary>
@ -58,9 +69,9 @@ namespace Perspex.Threading
/// <param name="action">The method.</param> /// <param name="action">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param> /// <param name="priority">The priority with which to invoke the method.</param>
/// <returns>A task that can be used to track the method's execution.</returns> /// <returns>A task that can be used to track the method's execution.</returns>
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);
} }
/// <summary> /// <summary>
@ -68,9 +79,9 @@ namespace Perspex.Threading
/// </summary> /// </summary>
/// <param name="action">The method.</param> /// <param name="action">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param> /// <param name="priority">The priority with which to invoke the method.</param>
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);
} }
} }
} }

2
src/Perspex.Base/Threading/DispatcherTimer.cs

@ -177,7 +177,7 @@ namespace Perspex.Threading
/// </summary> /// </summary>
private void InternalTick() private void InternalTick()
{ {
Dispatcher.UIThread.Post(RaiseTick, _priority); Dispatcher.UIThread.InvokeAsync(RaiseTick, _priority);
} }
/// <summary> /// <summary>

4
src/Perspex.Base/Threading/PerspexSynchronizationContext.cs

@ -36,14 +36,14 @@ namespace Perspex.Threading
/// <inheritdoc/> /// <inheritdoc/>
public override void Post(SendOrPostCallback d, object state) public override void Post(SendOrPostCallback d, object state)
{ {
Dispatcher.UIThread.Post(() => d(state)); Dispatcher.UIThread.InvokeAsync(() => d(state));
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void Send(SendOrPostCallback d, object 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 // 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();
} }
} }
} }

2
src/Perspex.Base/Threading/SingleThreadDispatcher.cs

@ -37,6 +37,8 @@ namespace Perspex.Threading
() => _timerJobRunner.Post(tick, DispatcherPriority.Normal)); () => _timerJobRunner.Post(tick, DispatcherPriority.Normal));
public void Signal() => _evnt.Set(); public void Signal() => _evnt.Set();
//TODO: Actually perform a check
public bool CurrentThreadIsLoopThread => true;
public event Action Signaled; public event Action Signaled;
} }

2
src/Perspex.Controls/Border.cs

@ -83,7 +83,7 @@ namespace Perspex.Controls
/// Renders the control. /// Renders the control.
/// </summary> /// </summary>
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
public override void Render(IDrawingContext context) public override void Render(DrawingContext context)
{ {
var background = Background; var background = Background;
var borderBrush = BorderBrush; var borderBrush = BorderBrush;

3
src/Perspex.Controls/Button.cs

@ -44,6 +44,9 @@ namespace Perspex.Controls
public static readonly PerspexProperty<ICommand> CommandProperty = public static readonly PerspexProperty<ICommand> CommandProperty =
PerspexProperty.Register<Button, ICommand>(nameof(Command)); PerspexProperty.Register<Button, ICommand>(nameof(Command));
/// <summary>
/// Defines the <see cref="HotKey"/> property.
/// </summary>
public static readonly PerspexProperty<KeyGesture> HotKeyProperty = public static readonly PerspexProperty<KeyGesture> HotKeyProperty =
HotKeyManager.HotKeyProperty.AddOwner<Button>(); HotKeyManager.HotKeyProperty.AddOwner<Button>();

17
src/Perspex.Controls/Canvas.cs

@ -6,28 +6,37 @@ using Perspex.Input;
namespace Perspex.Controls namespace Perspex.Controls
{ {
/// <summary>
/// A panel that displays child controls at arbitrary locations.
/// </summary>
/// <remarks>
/// Unlike other <see cref="Panel"/> implementations, the <see cref="Canvas"/> doesn't lay out
/// its children in any particular layout. Instead, the positioning of each child control is
/// defined by the <code>Canvas.Left</code>, <code>Canvas.Top</code>, <code>Canvas.Right</code>
/// and <code>Canvas.Bottom</code> attached properties.
/// </remarks>
public class Canvas : Panel, INavigableContainer public class Canvas : Panel, INavigableContainer
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Left"/> property. /// Defines the Left attached property.
/// </summary> /// </summary>
public static readonly PerspexProperty<double> LeftProperty = public static readonly PerspexProperty<double> LeftProperty =
PerspexProperty.RegisterAttached<StackPanel, Control, double>("Left"); PerspexProperty.RegisterAttached<StackPanel, Control, double>("Left");
/// <summary> /// <summary>
/// Defines the <see cref="Top"/> property. /// Defines the Top attached property.
/// </summary> /// </summary>
public static readonly PerspexProperty<double> TopProperty = public static readonly PerspexProperty<double> TopProperty =
PerspexProperty.RegisterAttached<StackPanel, Control, double>("Top"); PerspexProperty.RegisterAttached<StackPanel, Control, double>("Top");
/// <summary> /// <summary>
/// Defines the <see cref="Right"/> property. /// Defines the Right attached property.
/// </summary> /// </summary>
public static readonly PerspexProperty<double> RightProperty = public static readonly PerspexProperty<double> RightProperty =
PerspexProperty.RegisterAttached<StackPanel, Control, double>("Right"); PerspexProperty.RegisterAttached<StackPanel, Control, double>("Right");
/// <summary> /// <summary>
/// Defines the <see cref="Bottom"/> property. /// Defines the Bottom attached property.
/// </summary> /// </summary>
public static readonly PerspexProperty<double> BottomProperty = public static readonly PerspexProperty<double> BottomProperty =
PerspexProperty.RegisterAttached<StackPanel, Control, double>("Bottom"); PerspexProperty.RegisterAttached<StackPanel, Control, double>("Bottom");

9
src/Perspex.Controls/Generators/ItemContainerGenerator.cs

@ -145,7 +145,14 @@ namespace Perspex.Controls.Generators
/// <returns>The created container control.</returns> /// <returns>The created container control.</returns>
protected virtual IControl CreateContainer(object item) protected virtual IControl CreateContainer(object item)
{ {
return Owner.MaterializeDataTemplate(item); var result = Owner.MaterializeDataTemplate(item);
if (result != null && !(item is IControl))
{
result.DataContext = item;
}
return result;
} }
/// <summary> /// <summary>

6
src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs

@ -37,6 +37,12 @@ namespace Perspex.Controls.Generators
{ {
var result = new T(); var result = new T();
result.Content = Owner.MaterializeDataTemplate(item); result.Content = Owner.MaterializeDataTemplate(item);
if (!(item is IControl))
{
result.DataContext = item;
}
return result; return result;
} }
} }

25
src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs

@ -165,22 +165,33 @@ namespace Perspex.Controls.Generators
/// <returns>The created container control.</returns> /// <returns>The created container control.</returns>
protected virtual T CreateContainer(object item) protected virtual T CreateContainer(object item)
{ {
T result = item as T; var container = item as T;
if (result == null) if (item == null)
{
return null;
}
else if (container != null)
{
return container;
}
else
{ {
var template = GetTreeDataTemplate(item); var template = GetTreeDataTemplate(item);
var result = new T
result = new T
{ {
Header = template.Build(item), Header = template.Build(item),
Items = template.ItemsSelector(item), Items = template.ItemsSelector(item),
IsExpanded = template.IsExpanded(item), IsExpanded = template.IsExpanded(item),
DataContext = item,
}; };
}
return result; if (!(item is IControl))
{
result.DataContext = item;
}
return result;
}
} }
/// <summary> /// <summary>

2
src/Perspex.Controls/Image.cs

@ -46,7 +46,7 @@ namespace Perspex.Controls
/// Renders the control. /// Renders the control.
/// </summary> /// </summary>
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
public override void Render(IDrawingContext context) public override void Render(DrawingContext context)
{ {
Bitmap source = Source; Bitmap source = Source;

4
src/Perspex.Controls/ListBox.cs

@ -6,8 +6,12 @@ using Perspex.Controls.Primitives;
namespace Perspex.Controls namespace Perspex.Controls
{ {
/// <summary>
/// An <see cref="ItemsControl"/> in which individual items can be selected.
/// </summary>
public class ListBox : SelectingItemsControl public class ListBox : SelectingItemsControl
{ {
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator() protected override IItemContainerGenerator CreateItemContainerGenerator()
{ {
return new ItemContainerGenerator<ListBoxItem>(this); return new ItemContainerGenerator<ListBoxItem>(this);

4
src/Perspex.Controls/MenuItem.cs

@ -28,6 +28,9 @@ namespace Perspex.Controls
public static readonly PerspexProperty<ICommand> CommandProperty = public static readonly PerspexProperty<ICommand> CommandProperty =
Button.CommandProperty.AddOwner<MenuItem>(); Button.CommandProperty.AddOwner<MenuItem>();
/// <summary>
/// Defines the <see cref="HotKey"/> property.
/// </summary>
public static readonly PerspexProperty<KeyGesture> HotKeyProperty = public static readonly PerspexProperty<KeyGesture> HotKeyProperty =
HotKeyManager.HotKeyProperty.AddOwner<MenuItem>(); HotKeyManager.HotKeyProperty.AddOwner<MenuItem>();
@ -519,7 +522,6 @@ namespace Perspex.Controls
/// Gets a service of the specified type. /// Gets a service of the specified type.
/// </summary> /// </summary>
/// <param name="serviceType">The service type.</param> /// <param name="serviceType">The service type.</param>
/// <param name="contract">An optional contract.</param>
/// <returns>A service of the requested type.</returns> /// <returns>A service of the requested type.</returns>
public object GetService(Type serviceType) public object GetService(Type serviceType)
{ {

4
src/Perspex.Controls/Panel.cs

@ -172,10 +172,10 @@ namespace Perspex.Controls
} }
/// <summary> /// <summary>
/// Renders the visual to a <see cref="IDrawingContext"/>. /// Renders the visual to a <see cref="DrawingContext"/>.
/// </summary> /// </summary>
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
public override void Render(IDrawingContext context) public override void Render(DrawingContext context)
{ {
Brush background = Background; Brush background = Background;
if (background != null) if (background != null)

12
src/Perspex.Controls/PlacementMode.cs

@ -1,11 +1,23 @@
// Copyright (c) The Perspex Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Controls.Primitives;
namespace Perspex.Controls namespace Perspex.Controls
{ {
/// <summary>
/// Defines the placement for a <see cref="Popup"/> control.
/// </summary>
public enum PlacementMode public enum PlacementMode
{ {
/// <summary>
/// The popup is placed at the bottom left of its target.
/// </summary>
Bottom, Bottom,
/// <summary>
/// The popup is placed at the top right of its target.
/// </summary>
Right, Right,
} }
} }

17
src/Perspex.Controls/Platform/IPopupImpl.cs

@ -1,18 +1,27 @@
// Copyright (c) The Perspex Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Controls;
using Perspex.Input.Raw;
namespace Perspex.Platform namespace Perspex.Platform
{ {
/// <summary>
/// Defines a platform-specific popup window implementation.
/// </summary>
public interface IPopupImpl : ITopLevelImpl public interface IPopupImpl : ITopLevelImpl
{ {
/// <summary>
/// Sets the position of the popup.
/// </summary>
/// <param name="p">The position, in screen coordinates.</param>
void SetPosition(Point p); void SetPosition(Point p);
/// <summary>
/// Shows the popup.
/// </summary>
void Show(); void Show();
/// <summary>
/// Hides the popup.
/// </summary>
void Hide(); void Hide();
} }
} }

16
src/Perspex.Controls/Platform/ISystemDialogImpl.cs

@ -1,14 +1,22 @@
using System; // Copyright (c) The Perspex Project. All rights reserved.
using System.Collections.Generic; // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Perspex.Platform; using Perspex.Platform;
namespace Perspex.Controls.Platform namespace Perspex.Controls.Platform
{ {
/// <summary>
/// Defines a platform-specific system dialog implementation.
/// </summary>
public interface ISystemDialogImpl public interface ISystemDialogImpl
{ {
/// <summary>
/// Shows a file dialog.
/// </summary>
/// <param name="dialog">The details of the file dialog to show.</param>
/// <param name="parent">The parent window.</param>
/// <returns>A task returning the selected filenames.</returns>
Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent); Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent);
} }
} }

46
src/Perspex.Controls/Platform/ITopLevelImpl.cs

@ -2,36 +2,80 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using Perspex.Controls;
using Perspex.Input; using Perspex.Input;
using Perspex.Input.Raw; using Perspex.Input.Raw;
namespace Perspex.Platform namespace Perspex.Platform
{ {
/// <summary>
/// Defines a platform-specific top-level window implementation.
/// </summary>
/// <remarks>
/// This interface is the common interface to <see cref="IWindowImpl"/> and
/// <see cref="IPopupImpl"/>.
/// </remarks>
public interface ITopLevelImpl : IDisposable public interface ITopLevelImpl : IDisposable
{ {
/// <summary>
/// Gets or sets the client size of the window.
/// </summary>
Size ClientSize { get; set; } Size ClientSize { get; set; }
/// <summary>
/// Gets the platform window handle.
/// </summary>
IPlatformHandle Handle { get; } IPlatformHandle Handle { get; }
/// <summary>
/// Gets or sets a method called when the window is activated (receives focus).
/// </summary>
Action Activated { get; set; } Action Activated { get; set; }
/// <summary>
/// Gets or sets a method called when the window is closed.
/// </summary>
Action Closed { get; set; } Action Closed { get; set; }
/// <summary>
/// Gets or sets a method called when the window is deactivated (loses focus).
/// </summary>
Action Deactivated { get; set; } Action Deactivated { get; set; }
/// <summary>
/// Gets or sets a method called when the window receives input.
/// </summary>
Action<RawInputEventArgs> Input { get; set; } Action<RawInputEventArgs> Input { get; set; }
/// <summary>
/// Gets or sets a method called when the window requires painting.
/// </summary>
Action<Rect> Paint { get; set; } Action<Rect> Paint { get; set; }
/// <summary>
/// Gets or sets a method called when the window is resized.
/// </summary>
Action<Size> Resized { get; set; } Action<Size> Resized { get; set; }
/// <summary>
/// Activates the window.
/// </summary>
void Activate(); void Activate();
/// <summary>
/// Invalidates a rect on the window.
/// </summary>
void Invalidate(Rect rect); void Invalidate(Rect rect);
/// <summary>
/// Sets the <see cref="IInputRoot"/> for the window.
/// </summary>
void SetInputRoot(IInputRoot inputRoot); void SetInputRoot(IInputRoot inputRoot);
/// <summary>
/// Converts a point from client to screen coordinates.
/// </summary>
/// <param name="point">The point in client coordinates.</param>
/// <returns>The point in screen coordinates.</returns>
Point PointToScreen(Point point); Point PointToScreen(Point point);
/// <summary> /// <summary>

5
src/Perspex.Controls/Presenters/ContentPresenter.cs

@ -115,6 +115,11 @@ namespace Perspex.Controls.Presenters
if (result != null) if (result != null)
{ {
if (!(content is IControl))
{
result.DataContext = content;
}
AddVisualChild(result); AddVisualChild(result);
if (result.Parent == null) if (result.Parent == null)

2
src/Perspex.Controls/Presenters/TextPresenter.cs

@ -69,7 +69,7 @@ namespace Perspex.Controls.Presenters
return hit.TextPosition + (hit.IsTrailing ? 1 : 0); return hit.TextPosition + (hit.IsTrailing ? 1 : 0);
} }
public override void Render(IDrawingContext context) public override void Render(DrawingContext context)
{ {
var selectionStart = SelectionStart; var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd; var selectionEnd = SelectionEnd;

2
src/Perspex.Controls/Primitives/AccessText.cs

@ -62,7 +62,7 @@ namespace Perspex.Controls.Primitives
/// Renders the <see cref="AccessText"/> to a drawing context. /// Renders the <see cref="AccessText"/> to a drawing context.
/// </summary> /// </summary>
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
public override void Render(IDrawingContext context) public override void Render(DrawingContext context)
{ {
base.Render(context); base.Render(context);

2
src/Perspex.Controls/Shapes/Shape.cs

@ -90,7 +90,7 @@ namespace Perspex.Controls.Shapes
set { SetValue(StrokeThicknessProperty, value); } set { SetValue(StrokeThicknessProperty, value); }
} }
public override void Render(IDrawingContext context) public override void Render(DrawingContext context)
{ {
var geometry = RenderedGeometry; var geometry = RenderedGeometry;

2
src/Perspex.Controls/Templates/DataTemplateExtensions.cs

@ -45,8 +45,6 @@ namespace Perspex.Controls.Templates
result = FuncDataTemplate.Default.Build(data); result = FuncDataTemplate.Default.Build(data);
} }
result.DataContext = data;
return result; return result;
} }
} }

86
src/Perspex.Controls/TextBlock.cs

@ -210,11 +210,95 @@ namespace Perspex.Controls
set { SetValue(TextAlignmentProperty, value); } set { SetValue(TextAlignmentProperty, value); }
} }
/// <summary>
/// Gets the value of the attached <see cref="FontFamilyProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font family.</returns>
public static string GetFontFamily(Control control)
{
return control.GetValue(FontFamilyProperty);
}
/// <summary>
/// Gets the value of the attached <see cref="FontSizeProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font family.</returns>
public static double GetFontSize(Control control)
{
return control.GetValue(FontSizeProperty);
}
/// <summary>
/// Gets the value of the attached <see cref="FontStyleProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font family.</returns>
public static FontStyle GetFontStyle(Control control)
{
return control.GetValue(FontStyleProperty);
}
/// <summary>
/// Gets the value of the attached <see cref="FontWeightProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font family.</returns>
public static FontWeight GetFontWeight(Control control)
{
return control.GetValue(FontWeightProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FontFamilyProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
/// <returns>The font family.</returns>
public static void SetFontFamily(Control control, string value)
{
control.SetValue(FontFamilyProperty, value);
}
/// <summary>
/// Sets the value of the attached <see cref="FontSizeProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
/// <returns>The font family.</returns>
public static void SetFontSize(Control control, double value)
{
control.SetValue(FontSizeProperty, value);
}
/// <summary>
/// Sets the value of the attached <see cref="FontStyleProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
/// <returns>The font family.</returns>
public static void SetFontStyle(Control control, FontStyle value)
{
control.SetValue(FontStyleProperty, value);
}
/// <summary>
/// Sets the value of the attached <see cref="FontWeightProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
/// <returns>The font family.</returns>
public static void SetFontWeight(Control control, FontWeight value)
{
control.SetValue(FontWeightProperty, value);
}
/// <summary> /// <summary>
/// Renders the <see cref="TextBlock"/> to a drawing context. /// Renders the <see cref="TextBlock"/> to a drawing context.
/// </summary> /// </summary>
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
public override void Render(IDrawingContext context) public override void Render(DrawingContext context)
{ {
Brush background = Background; Brush background = Background;

4
src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs

@ -32,7 +32,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
/// <summary> /// <summary>
/// The wrapped Perspex graphics object /// The wrapped Perspex graphics object
/// </summary> /// </summary>
private readonly IDrawingContext _g; private readonly DrawingContext _g;
/// <summary> /// <summary>
/// if to release the graphics object on dispose /// if to release the graphics object on dispose
@ -51,7 +51,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
/// <param name="g">the Perspex graphics object to use</param> /// <param name="g">the Perspex graphics object to use</param>
/// <param name="initialClip">the initial clip of the graphics</param> /// <param name="initialClip">the initial clip of the graphics</param>
/// <param name="releaseGraphics">optional: if to release the graphics object on dispose (default - false)</param> /// <param name="releaseGraphics">optional: if to release the graphics object on dispose (default - false)</param>
public GraphicsAdapter(IDrawingContext g, RRect initialClip, bool releaseGraphics = false) public GraphicsAdapter(DrawingContext g, RRect initialClip, bool releaseGraphics = false)
: base(PerspexAdapter.Instance, initialClip) : base(PerspexAdapter.Instance, initialClip)
{ {
ArgChecker.AssertArgNotNull(g, "g"); ArgChecker.AssertArgNotNull(g, "g");

2
src/Perspex.HtmlRenderer/HtmlContainer.cs

@ -360,7 +360,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex
/// </summary> /// </summary>
/// <param name="g">the device to use to render</param> /// <param name="g">the device to use to render</param>
/// <param name="clip">the clip rectangle of the html container</param> /// <param name="clip">the clip rectangle of the html container</param>
public void PerformPaint(IDrawingContext g, Rect clip) public void PerformPaint(DrawingContext g, Rect clip)
{ {
ArgChecker.AssertArgNotNull(g, "g"); ArgChecker.AssertArgNotNull(g, "g");

2
src/Perspex.HtmlRenderer/HtmlControl.cs

@ -327,7 +327,7 @@ namespace Perspex.Controls.Html
private Size RenderSize => new Size(Bounds.Width, Bounds.Height); private Size RenderSize => new Size(Bounds.Width, Bounds.Height);
public override void Render(IDrawingContext context) public override void Render(DrawingContext context)
{ {
context.FillRectangle(Background, new Rect(RenderSize)); context.FillRectangle(Background, new Rect(RenderSize));

4
src/Perspex.SceneGraph/IVisual.cs

@ -76,10 +76,10 @@ namespace Perspex
int ZIndex { get; set; } int ZIndex { get; set; }
/// <summary> /// <summary>
/// Renders the scene graph node to a <see cref="IDrawingContext"/>. /// Renders the scene graph node to a <see cref="DrawingContext"/>.
/// </summary> /// </summary>
/// <param name="context">The context.</param> /// <param name="context">The context.</param>
void Render(IDrawingContext context); void Render(DrawingContext context);
/// <summary> /// <summary>
/// Returns a transform that transforms the visual's coordinates into the coordinates /// Returns a transform that transforms the visual's coordinates into the coordinates

206
src/Perspex.SceneGraph/Media/DrawingContext.cs

@ -0,0 +1,206 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Perspex.Media.Imaging;
namespace Perspex.Media
{
public sealed class DrawingContext : IDisposable
{
private readonly IDrawingContextImpl _impl;
private int _currentLevel;
private Stack<TransformContainer> _transformContainers = new Stack<TransformContainer>();
struct TransformContainer
{
public Matrix LocalTransform;
public Matrix ContainerTransform;
public TransformContainer(Matrix localTransform, Matrix containerTransform)
{
LocalTransform = localTransform;
ContainerTransform = containerTransform;
}
}
public DrawingContext(IDrawingContextImpl impl)
{
_impl = impl;
}
private Matrix _currentTransform = Matrix.Identity;
/// <summary>
/// Gets the current transform of the drawing context.
/// </summary>
public Matrix CurrentTransform
{
get { return _currentTransform; }
private set
{
_currentTransform = value;
_impl.Transform = _currentTransform*_currentContainerTransform;
}
}
private Matrix _currentContainerTransform = Matrix.Identity;
/// <summary>
/// Draws a bitmap image.
/// </summary>
/// <param name="source">The bitmap image.</param>
/// <param name="opacity">The opacity to draw with.</param>
/// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect)
=> _impl.DrawImage(source, opacity, sourceRect, destRect);
/// <summary>
/// Draws a line.
/// </summary>
/// <param name="pen">The stroke pen.</param>
/// <param name="p1">The first point of the line.</param>
/// <param name="p2">The second point of the line.</param>
public void DrawLine(Pen pen, Point p1, Point p2) => _impl.DrawLine(pen, p1, p2);
/// <summary>
/// Draws a geometry.
/// </summary>
/// <param name="brush">The fill brush.</param>
/// <param name="pen">The stroke pen.</param>
/// <param name="geometry">The geometry.</param>
public void DrawGeometry(Brush brush, Pen pen, Geometry geometry) => _impl.DrawGeometry(brush, pen, geometry);
/// <summary>
/// Draws the outline of a rectangle.
/// </summary>
/// <param name="pen">The pen.</param>
/// <param name="rect">The rectangle bounds.</param>
/// <param name="cornerRadius">The corner radius.</param>
public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0.0f)
=> _impl.DrawRectangle(pen, rect, cornerRadius);
/// <summary>
/// Draws text.
/// </summary>
/// <param name="foreground">The foreground brush.</param>
/// <param name="origin">The upper-left corner of the text.</param>
/// <param name="text">The text.</param>
public void DrawText(Brush foreground, Point origin, FormattedText text)
=> _impl.DrawText(foreground, origin, text);
/// <summary>
/// Draws a filled rectangle.
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="rect">The rectangle bounds.</param>
/// <param name="cornerRadius">The corner radius.</param>
public void FillRectangle(Brush brush, Rect rect, float cornerRadius = 0.0f)
=> _impl.FillRectangle(brush, rect, cornerRadius);
public struct PushedState : IDisposable
{
private readonly int _level;
private readonly DrawingContext _context;
private readonly Matrix _matrix;
private readonly PushedStateType _type;
public enum PushedStateType
{
None,
Matrix,
Opacity,
Clip,
MatrixContainer
}
public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default(Matrix))
{
_level = context._currentLevel += 1;
_context = context;
_type = type;
_matrix = matrix;
}
public void Dispose()
{
if(_type == PushedStateType.None)
return;
if (_context._currentLevel != _level)
throw new InvalidOperationException("Wrong Push/Pop state order");
_context._currentLevel--;
if (_type == PushedStateType.Matrix)
_context.CurrentTransform = _matrix;
else if(_type == PushedStateType.Clip)
_context._impl.PopClip();
else if(_type == PushedStateType.Opacity)
_context._impl.PopOpacity();
else if (_type == PushedStateType.MatrixContainer)
{
var cont = _context._transformContainers.Pop();
_context._currentContainerTransform = cont.ContainerTransform;
_context.CurrentTransform = cont.LocalTransform;
}
}
}
/// <summary>
/// Pushes a clip rectange.
/// </summary>
/// <param name="clip">The clip rectangle.</param>
/// <returns>A disposable used to undo the clip rectangle.</returns>
public PushedState PushClip(Rect clip)
{
_impl.PushClip(clip);
return new PushedState(this, PushedState.PushedStateType.Clip);
}
/// <summary>
/// Pushes an opacity value.
/// </summary>
/// <param name="opacity">The opacity.</param>
/// <returns>A disposable used to undo the opacity.</returns>
public PushedState PushOpacity(double opacity)
//TODO: Elimintate platform-specific push opacity call
{
_impl.PushOpacity(opacity);
return new PushedState(this, PushedState.PushedStateType.Opacity);
}
/// <summary>
/// Pushes a matrix transformation.
/// </summary>
/// <param name="matrix">The matrix</param>
/// <returns>A disposable used to undo the transformation.</returns>
public PushedState PushPostTransform(Matrix matrix) => PushSetTransform(CurrentTransform*matrix);
public PushedState PushPreTransform(Matrix matrix) => PushSetTransform(matrix*CurrentTransform);
PushedState PushSetTransform(Matrix matrix)
{
var oldMatrix = CurrentTransform;
CurrentTransform = matrix;
return new PushedState(this, PushedState.PushedStateType.Matrix, oldMatrix);
}
public PushedState PushTransformContainer()
{
_transformContainers.Push(new TransformContainer(CurrentTransform, _currentContainerTransform));
_currentContainerTransform = CurrentTransform*_currentContainerTransform;
_currentTransform = Matrix.Identity;
return new PushedState(this, PushedState.PushedStateType.MatrixContainer);
}
public void Dispose() => _impl.Dispose();
}
}

22
src/Perspex.SceneGraph/Media/FormattedText.cs

@ -10,7 +10,7 @@ namespace Perspex.Media
/// <summary> /// <summary>
/// Represents a piece of text with formatting. /// Represents a piece of text with formatting.
/// </summary> /// </summary>
public class FormattedText : IDisposable public class FormattedText : PerspexDisposable
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FormattedText"/> class. /// Initializes a new instance of the <see cref="FormattedText"/> class.
@ -56,8 +56,16 @@ namespace Perspex.Media
/// </summary> /// </summary>
public Size Constraint public Size Constraint
{ {
get { return PlatformImpl.Constraint; } get
set { PlatformImpl.Constraint = value; } {
CheckDisposed();
return PlatformImpl.Constraint;
}
set
{
CheckDisposed();
PlatformImpl.Constraint = value;
}
} }
/// <summary> /// <summary>
@ -124,7 +132,7 @@ namespace Perspex.Media
/// <summary> /// <summary>
/// Disposes of unmanaged resources associated with the formatted text. /// Disposes of unmanaged resources associated with the formatted text.
/// </summary> /// </summary>
public void Dispose() protected override void DoDispose()
{ {
PlatformImpl.Dispose(); PlatformImpl.Dispose();
} }
@ -137,6 +145,7 @@ namespace Perspex.Media
/// </returns> /// </returns>
public IEnumerable<FormattedTextLine> GetLines() public IEnumerable<FormattedTextLine> GetLines()
{ {
CheckDisposed();
return PlatformImpl.GetLines(); return PlatformImpl.GetLines();
} }
@ -149,6 +158,7 @@ namespace Perspex.Media
/// </returns> /// </returns>
public TextHitTestResult HitTestPoint(Point point) public TextHitTestResult HitTestPoint(Point point)
{ {
CheckDisposed();
return PlatformImpl.HitTestPoint(point); return PlatformImpl.HitTestPoint(point);
} }
@ -159,6 +169,7 @@ namespace Perspex.Media
/// <returns>The character bounds.</returns> /// <returns>The character bounds.</returns>
public Rect HitTestTextPosition(int index) public Rect HitTestTextPosition(int index)
{ {
CheckDisposed();
return PlatformImpl.HitTestTextPosition(index); return PlatformImpl.HitTestTextPosition(index);
} }
@ -170,6 +181,7 @@ namespace Perspex.Media
/// <returns>The character bounds.</returns> /// <returns>The character bounds.</returns>
public IEnumerable<Rect> HitTestTextRange(int index, int length) public IEnumerable<Rect> HitTestTextRange(int index, int length)
{ {
CheckDisposed();
return PlatformImpl.HitTestTextRange(index, length); return PlatformImpl.HitTestTextRange(index, length);
} }
@ -179,6 +191,7 @@ namespace Perspex.Media
/// <returns>The bounds box of the text.</returns> /// <returns>The bounds box of the text.</returns>
public Size Measure() public Size Measure()
{ {
CheckDisposed();
return PlatformImpl.Measure(); return PlatformImpl.Measure();
} }
@ -190,6 +203,7 @@ namespace Perspex.Media
/// <param name="length">The length of the text range.</param> /// <param name="length">The length of the text range.</param>
public void SetForegroundBrush(Brush brush, int startIndex, int length) public void SetForegroundBrush(Brush brush, int startIndex, int length)
{ {
CheckDisposed();
PlatformImpl.SetForegroundBrush(brush, startIndex, length); PlatformImpl.SetForegroundBrush(brush, startIndex, length);
} }
} }

19
src/Perspex.SceneGraph/Media/IDrawingContext.cs

@ -9,12 +9,12 @@ namespace Perspex.Media
/// <summary> /// <summary>
/// Defines the interface through which drawing occurs. /// Defines the interface through which drawing occurs.
/// </summary> /// </summary>
public interface IDrawingContext : IDisposable public interface IDrawingContextImpl : IDisposable
{ {
/// <summary> /// <summary>
/// Gets the current transform of the drawing context. /// Gets or sets the current transform of the drawing context.
/// </summary> /// </summary>
Matrix CurrentTransform { get; } Matrix Transform { get; set; }
/// <summary> /// <summary>
/// Draws a bitmap image. /// Draws a bitmap image.
@ -70,20 +70,17 @@ namespace Perspex.Media
/// </summary> /// </summary>
/// <param name="clip">The clip rectangle.</param> /// <param name="clip">The clip rectangle.</param>
/// <returns>A disposable used to undo the clip rectangle.</returns> /// <returns>A disposable used to undo the clip rectangle.</returns>
IDisposable PushClip(Rect clip); void PushClip(Rect clip);
void PopClip();
/// <summary> /// <summary>
/// Pushes an opacity value. /// Pushes an opacity value.
/// </summary> /// </summary>
/// <param name="opacity">The opacity.</param> /// <param name="opacity">The opacity.</param>
/// <returns>A disposable used to undo the opacity.</returns> /// <returns>A disposable used to undo the opacity.</returns>
IDisposable PushOpacity(double opacity); void PushOpacity(double opacity);
/// <summary> void PopOpacity();
/// Pushes a matrix transformation.
/// </summary>
/// <param name="matrix">The matrix</param>
/// <returns>A disposable used to undo the transformation.</returns>
IDisposable PushTransform(Matrix matrix);
} }
} }

2
src/Perspex.SceneGraph/Media/Imaging/RenderTargetBitmap.cs

@ -47,7 +47,7 @@ namespace Perspex.Media.Imaging
return factory.CreateRenderTargetBitmap(width, height); return factory.CreateRenderTargetBitmap(width, height);
} }
public IDrawingContext CreateDrawingContext() => PlatformImpl.CreateDrawingContext(); public DrawingContext CreateDrawingContext() => PlatformImpl.CreateDrawingContext();
void IRenderTarget.Resize(int width, int height) void IRenderTarget.Resize(int width, int height)
{ {

76
src/Perspex.SceneGraph/Media/ValidatingDrawingContext.cs

@ -1,76 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Reactive.Disposables;
using Perspex.Media.Imaging;
namespace Perspex.Media
{
public class ValidatingDrawingContext : IDrawingContext
{
private readonly IDrawingContext _base;
public ValidatingDrawingContext(IDrawingContext @base)
{
_base = @base;
}
public void Dispose()
{
_base.Dispose();
}
public Matrix CurrentTransform => _base.CurrentTransform;
public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect)
{
_base.DrawImage(source, opacity, sourceRect, destRect);
}
public void DrawLine(Pen pen, Point p1, Point p2)
{
_base.DrawLine(pen, p1, p2);
}
public void DrawGeometry(Brush brush, Pen pen, Geometry geometry)
{
_base.DrawGeometry(brush, pen, geometry);
}
public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0)
{
_base.DrawRectangle(pen, rect, cornerRadius);
}
public void DrawText(Brush foreground, Point origin, FormattedText text)
{
_base.DrawText(foreground, origin, text);
}
public void FillRectangle(Brush brush, Rect rect, float cornerRadius = 0)
{
_base.FillRectangle(brush, rect, cornerRadius);
}
Stack<IDisposable> _stateStack = new Stack<IDisposable>();
IDisposable Transform(IDisposable disposable)
{
_stateStack.Push(disposable);
return Disposable.Create(() =>
{
var current = _stateStack.Peek();
if (current != disposable)
throw new InvalidOperationException("Invalid push/pop order");
current.Dispose();
_stateStack.Pop();
});
}
public IDisposable PushClip(Rect clip) => Transform(_base.PushClip(clip));
public IDisposable PushOpacity(double opacity) => Transform(_base.PushOpacity(opacity));
public IDisposable PushTransform(Matrix matrix) => Transform(_base.PushTransform(matrix));
}
}

2
src/Perspex.SceneGraph/Perspex.SceneGraph.csproj

@ -64,6 +64,7 @@
<Compile Include="Media\Color.cs" /> <Compile Include="Media\Color.cs" />
<Compile Include="Media\Colors.cs" /> <Compile Include="Media\Colors.cs" />
<Compile Include="Media\DashStyle.cs" /> <Compile Include="Media\DashStyle.cs" />
<Compile Include="Media\DrawingContext.cs" />
<Compile Include="Media\GradientBrush.cs" /> <Compile Include="Media\GradientBrush.cs" />
<Compile Include="Media\GradientSpreadMethod.cs" /> <Compile Include="Media\GradientSpreadMethod.cs" />
<Compile Include="Media\GradientStop.cs" /> <Compile Include="Media\GradientStop.cs" />
@ -99,7 +100,6 @@
<Compile Include="Media\Transform.cs" /> <Compile Include="Media\Transform.cs" />
<Compile Include="Media\TileBrush.cs" /> <Compile Include="Media\TileBrush.cs" />
<Compile Include="Media\ImageBush.cs" /> <Compile Include="Media\ImageBush.cs" />
<Compile Include="Media\ValidatingDrawingContext.cs" />
<Compile Include="Media\VisualBrush.cs" /> <Compile Include="Media\VisualBrush.cs" />
<Compile Include="RelativePoint.cs" /> <Compile Include="RelativePoint.cs" />
<Compile Include="Platform\IFormattedTextImpl.cs" /> <Compile Include="Platform\IFormattedTextImpl.cs" />

4
src/Perspex.SceneGraph/Platform/IRenderTarget.cs

@ -15,9 +15,9 @@ namespace Perspex.Platform
public interface IRenderTarget : IDisposable public interface IRenderTarget : IDisposable
{ {
/// <summary> /// <summary>
/// Creates an <see cref="IDrawingContext"/> for a rendering session. /// Creates an <see cref="DrawingContext"/> for a rendering session.
/// </summary> /// </summary>
IDrawingContext CreateDrawingContext(); DrawingContext CreateDrawingContext();
/// <summary> /// <summary>
/// Resizes the rendered viewport. /// Resizes the rendered viewport.

9
src/Perspex.SceneGraph/Rendering/RendererBase.cs

@ -35,7 +35,7 @@ namespace Perspex.Rendering
/// <param name="visual">The visual to render.</param> /// <param name="visual">The visual to render.</param>
/// ///
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
public static void Render(this IDrawingContext context, IVisual visual) public static void Render(this DrawingContext context, IVisual visual)
{ {
var opacity = visual.Opacity; var opacity = visual.Opacity;
if (visual.IsVisible && opacity > 0) if (visual.IsVisible && opacity > 0)
@ -50,11 +50,12 @@ namespace Perspex.Rendering
var offset = Matrix.CreateTranslation(origin); var offset = Matrix.CreateTranslation(origin);
renderTransform = (-offset)*visual.RenderTransform.Value*(offset); renderTransform = (-offset)*visual.RenderTransform.Value*(offset);
} }
m = context.CurrentTransform.Invert()*renderTransform*m*context.CurrentTransform; m = renderTransform*m;
using (context.PushTransform(m)) using (context.PushPostTransform(m))
using (context.PushOpacity(opacity)) using (context.PushOpacity(opacity))
using (visual.ClipToBounds ? context.PushClip(new Rect(visual.Bounds.Size)) : null) using (visual.ClipToBounds ? context.PushClip(new Rect(visual.Bounds.Size)) : default(DrawingContext.PushedState))
using (context.PushTransformContainer())
{ {
visual.Render(context); visual.Render(context);
foreach (var child in visual.VisualChildren.OrderBy(x => x.ZIndex)) foreach (var child in visual.VisualChildren.OrderBy(x => x.ZIndex))

4
src/Perspex.SceneGraph/Visual.cs

@ -234,10 +234,10 @@ namespace Perspex
} }
/// <summary> /// <summary>
/// Renders the visual to a <see cref="IDrawingContext"/>. /// Renders the visual to a <see cref="DrawingContext"/>.
/// </summary> /// </summary>
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
public virtual void Render(IDrawingContext context) public virtual void Render(DrawingContext context)
{ {
Contract.Requires<ArgumentNullException>(context != null); Contract.Requires<ArgumentNullException>(context != null);
} }

BIN
src/Shared/PlatformSupport/PclPlatformWrapper.cs

Binary file not shown.

59
src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs

@ -2,6 +2,8 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections;
using System.Collections.Generic;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Perspex.Media; using Perspex.Media;
using SharpDX; using SharpDX;
@ -13,7 +15,7 @@ namespace Perspex.Direct2D1.Media
/// <summary> /// <summary>
/// Draws using Direct2D1. /// Draws using Direct2D1.
/// </summary> /// </summary>
public class DrawingContext : IDrawingContext, IDisposable public class DrawingContext : IDrawingContextImpl, IDisposable
{ {
/// <summary> /// <summary>
/// The Direct2D1 render target. /// The Direct2D1 render target.
@ -42,10 +44,10 @@ namespace Perspex.Direct2D1.Media
/// <summary> /// <summary>
/// Gets the current transform of the drawing context. /// Gets the current transform of the drawing context.
/// </summary> /// </summary>
public Matrix CurrentTransform public Matrix Transform
{ {
get { return _renderTarget.Transform.ToPerspex(); } get { return _renderTarget.Transform.ToPerspex(); }
private set { _renderTarget.Transform = value.ToDirect2D(); } set { _renderTarget.Transform = value.ToDirect2D(); }
} }
/// <summary> /// <summary>
@ -53,6 +55,8 @@ namespace Perspex.Direct2D1.Media
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
foreach (var layer in _layerPool)
layer.Dispose();
_renderTarget.EndDraw(); _renderTarget.EndDraw();
} }
@ -233,22 +237,24 @@ namespace Perspex.Direct2D1.Media
/// </summary> /// </summary>
/// <param name="clip">The clip rectangle.</param> /// <param name="clip">The clip rectangle.</param>
/// <returns>A disposable used to undo the clip rectangle.</returns> /// <returns>A disposable used to undo the clip rectangle.</returns>
public IDisposable PushClip(Rect clip) public void PushClip(Rect clip)
{ {
_renderTarget.PushAxisAlignedClip(clip.ToSharpDX(), AntialiasMode.PerPrimitive); _renderTarget.PushAxisAlignedClip(clip.ToSharpDX(), AntialiasMode.PerPrimitive);
}
return Disposable.Create(() => public void PopClip()
{ {
_renderTarget.PopAxisAlignedClip(); _renderTarget.PopAxisAlignedClip();
});
} }
Stack<Layer> _layers = new Stack<Layer>();
private readonly Stack<Layer> _layerPool = new Stack<Layer>();
/// <summary> /// <summary>
/// Pushes an opacity value. /// Pushes an opacity value.
/// </summary> /// </summary>
/// <param name="opacity">The opacity.</param> /// <param name="opacity">The opacity.</param>
/// <returns>A disposable used to undo the opacity.</returns> /// <returns>A disposable used to undo the opacity.</returns>
public IDisposable PushOpacity(double opacity) public void PushOpacity(double opacity)
{ {
if (opacity < 1) if (opacity < 1)
{ {
@ -256,41 +262,26 @@ namespace Perspex.Direct2D1.Media
{ {
ContentBounds = RectangleF.Infinite, ContentBounds = RectangleF.Infinite,
MaskTransform = Matrix3x2.Identity, MaskTransform = Matrix3x2.Identity,
Opacity = (float)opacity, Opacity = (float) opacity,
}; };
var layer = new Layer(_renderTarget); var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget);
_renderTarget.PushLayer(ref parameters, layer); _renderTarget.PushLayer(ref parameters, layer);
return Disposable.Create(() => _layers.Push(layer);
{
_renderTarget.PopLayer();
layer.Dispose();
});
} }
else else
{ _layers.Push(null);
return Disposable.Empty;
}
} }
/// <summary> public void PopOpacity()
/// Pushes a matrix transformation.
/// </summary>
/// <param name="matrix">The matrix</param>
/// <returns>A disposable used to undo the transformation.</returns>
public IDisposable PushTransform(Matrix matrix)
{ {
Matrix3x2 m3x2 = matrix.ToDirect2D(); var layer = _layers.Pop();
Matrix3x2 transform = _renderTarget.Transform * m3x2; if (layer != null)
_renderTarget.Transform = transform;
return Disposable.Create(() =>
{ {
m3x2.Invert(); _renderTarget.PopLayer();
_renderTarget.Transform = transform * m3x2; _layerPool.Push(layer);
}); }
} }
/// <summary> /// <summary>

2
src/Windows/Perspex.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs

@ -38,7 +38,7 @@ namespace Perspex.Direct2D1.Media
// TODO: // TODO:
} }
public IDrawingContext CreateDrawingContext() => new RenderTarget(_target).CreateDrawingContext(); public Perspex.Media.DrawingContext CreateDrawingContext() => new RenderTarget(_target).CreateDrawingContext();
void IRenderTarget.Resize(int width, int height) void IRenderTarget.Resize(int width, int height)
{ {

2
src/Windows/Perspex.Direct2D1/Media/VisualBrushImpl.cs

@ -55,7 +55,7 @@ namespace Perspex.Direct2D1.Media
using (var ctx = renderer.CreateDrawingContext()) using (var ctx = renderer.CreateDrawingContext())
using (ctx.PushClip(drawRect)) using (ctx.PushClip(drawRect))
using (ctx.PushTransform(transform)) using (ctx.PushPostTransform(transform))
{ {
ctx.Render(visual); ctx.Render(visual);
} }

19
src/Windows/Perspex.Direct2D1/RenderTarget.cs

@ -8,6 +8,7 @@ using Perspex.Platform;
using Perspex.Rendering; using Perspex.Rendering;
using SharpDX; using SharpDX;
using SharpDX.Direct2D1; using SharpDX.Direct2D1;
using DrawingContext = Perspex.Media.DrawingContext;
using DwFactory = SharpDX.DirectWrite.Factory; using DwFactory = SharpDX.DirectWrite.Factory;
namespace Perspex.Direct2D1 namespace Perspex.Direct2D1
@ -91,24 +92,12 @@ namespace Perspex.Direct2D1
window.Resize(new Size2(width, height)); window.Resize(new Size2(width, height));
} }
IDrawingContext Wrap(IDrawingContext ctx)
{
#if DEBUG
return new ValidatingDrawingContext(ctx);
#endif
#pragma warning disable 162
return ctx;
#pragma warning restore 162
}
/// <summary> /// <summary>
/// Creates a drawing context for a rendering session. /// Creates a drawing context for a rendering session.
/// </summary> /// </summary>
/// <returns>An <see cref="IDrawingContext"/>.</returns> /// <returns>An <see cref="Perspex.Media.DrawingContext"/>.</returns>
public IDrawingContext CreateDrawingContext() public DrawingContext CreateDrawingContext()
{ => new DrawingContext(new Media.DrawingContext(_renderTarget, DirectWriteFactory));
return Wrap(new DrawingContext(_renderTarget, DirectWriteFactory));
}
public void Dispose() public void Dispose()
{ {

31
src/Windows/Perspex.Win32/SystemDialogImpl.cs

@ -40,10 +40,12 @@ namespace Perspex.Win32
filters.CopyTo(0, filterBuffer, 0, filterBuffer.Length); filters.CopyTo(0, filterBuffer, 0, filterBuffer.Length);
var defExt = (dialog as SaveFileDialog)?.DefaultExtension; var defExt = (dialog as SaveFileDialog)?.DefaultExtension;
var buffer = new char[256]; var fileBuffer = new char[256];
dialog.InitialFileName?.CopyTo(0, buffer, 0, dialog.InitialFileName.Length); dialog.InitialFileName?.CopyTo(0, fileBuffer, 0, dialog.InitialFileName.Length);
fixed (char* pBuffer = buffer) string userSelectedExt = null;
fixed (char* pFileBuffer = fileBuffer)
fixed (char* pFilterBuffer = filterBuffer) fixed (char* pFilterBuffer = filterBuffer)
fixed (char* pDefExt = defExt) fixed (char* pDefExt = defExt)
fixed (char* pInitDir = dialog.InitialDirectory) fixed (char* pInitDir = dialog.InitialDirectory)
@ -60,13 +62,13 @@ namespace Perspex.Win32
UnmanagedMethods.OpenFileNameFlags.OFN_EXPLORER | UnmanagedMethods.OpenFileNameFlags.OFN_EXPLORER |
UnmanagedMethods.OpenFileNameFlags.OFN_HIDEREADONLY, UnmanagedMethods.OpenFileNameFlags.OFN_HIDEREADONLY,
nMaxCustFilter = 0, nMaxCustFilter = 0,
nMaxFile = buffer.Length - 1, nMaxFile = fileBuffer.Length - 1,
nMaxFileTitle = 0, nMaxFileTitle = 0,
lpTemplateName = IntPtr.Zero, lpTemplateName = IntPtr.Zero,
lpfnHook = IntPtr.Zero, lpfnHook = IntPtr.Zero,
lpstrCustomFilter = IntPtr.Zero, lpstrCustomFilter = IntPtr.Zero,
lpstrDefExt = new IntPtr(pDefExt), lpstrDefExt = new IntPtr(pDefExt),
lpstrFile = new IntPtr(pBuffer), lpstrFile = new IntPtr(pFileBuffer),
lpstrFileTitle = IntPtr.Zero, lpstrFileTitle = IntPtr.Zero,
lpstrFilter = new IntPtr(pFilterBuffer), lpstrFilter = new IntPtr(pFilterBuffer),
lpstrInitialDir = new IntPtr(pInitDir), lpstrInitialDir = new IntPtr(pInitDir),
@ -88,20 +90,21 @@ namespace Perspex.Win32
: UnmanagedMethods.GetSaveFileName(new IntPtr(pofn)); : UnmanagedMethods.GetSaveFileName(new IntPtr(pofn));
if (!res) if (!res)
return null; return null;
if (dialog?.Filters.Count > 0)
userSelectedExt = dialog.Filters[ofn.nFilterIndex - 1].Extensions.FirstOrDefault();
} }
var cStart = 0; var cStart = 0;
string dir = null; string dir = null;
var files = new List<string>(); var files = new List<string>();
for (var c = 0; c < buffer.Length; c++) for (var c = 0; c < fileBuffer.Length; c++)
{ {
if (buffer[c] == 0) if (fileBuffer[c] == 0)
{ {
//Encountered double zero char //Encountered double zero char
if (cStart == c) if (cStart == c)
break; break;
var s = new string(buffer, cStart, c - cStart); var s = new string(fileBuffer, cStart, c - cStart);
if (dir == null) if (dir == null)
dir = s; dir = s;
else else
@ -110,7 +113,17 @@ namespace Perspex.Win32
} }
} }
if (files.Count == 0) if (files.Count == 0)
{
if (dialog is SaveFileDialog)
{
if (string.IsNullOrWhiteSpace(Path.GetExtension(dir)) &&
!string.IsNullOrWhiteSpace(userSelectedExt) &&
!userSelectedExt.Contains("*"))
dir = Path.ChangeExtension(dir, userSelectedExt);
}
return new[] {dir}; return new[] {dir};
}
return files.Select(f => Path.Combine(dir, f)).ToArray(); return files.Select(f => Path.Combine(dir, f)).ToArray();
}); });

4
src/Windows/Perspex.Win32/Win32Platform.cs

@ -21,6 +21,7 @@ namespace Perspex.Win32
public class Win32Platform : IPlatformThreadingInterface, IPlatformSettings public class Win32Platform : IPlatformThreadingInterface, IPlatformSettings
{ {
private static readonly Win32Platform s_instance = new Win32Platform(); private static readonly Win32Platform s_instance = new Win32Platform();
private static Thread _uiThread;
private UnmanagedMethods.WndProc _wndProcDelegate; private UnmanagedMethods.WndProc _wndProcDelegate;
@ -52,6 +53,7 @@ namespace Perspex.Win32
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogImpl>(); .Bind<ISystemDialogImpl>().ToSingleton<SystemDialogImpl>();
SharedPlatform.Register(); SharedPlatform.Register();
_uiThread = Thread.CurrentThread;
} }
public static void Initialize() public static void Initialize()
@ -124,6 +126,8 @@ namespace Perspex.Win32
new IntPtr(SignalL)); new IntPtr(SignalL));
} }
public bool CurrentThreadIsLoopThread => _uiThread == Thread.CurrentThread;
public event Action Signaled; public event Action Signaled;
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]

44
tests/Perspex.Controls.UnitTests/ListBoxTests.cs

@ -50,6 +50,40 @@ namespace Perspex.Controls.UnitTests
Assert.Equal(1, target.SelectedIndex); Assert.Equal(1, target.SelectedIndex);
} }
[Fact]
public void DataContexts_Should_Be_Correctly_Set()
{
var items = new object[]
{
"Foo",
new Item("Bar"),
new TextBlock { Text = "Baz" },
new ListBoxItem { Content = "Qux" },
};
var target = new ListBox
{
Template = new ControlTemplate(CreateListBoxTemplate),
DataContext = "Base",
DataTemplates = new DataTemplates
{
new FuncDataTemplate<Item>(x => new Button { Content = x })
},
Items = items,
};
target.ApplyTemplate();
var dataContexts = target.Presenter.Panel.Children
.Cast<Control>()
.Select(x => x.DataContext)
.ToList();
Assert.Equal(
new object[] { items[0], items[1], "Base", "Base" },
dataContexts);
}
private Control CreateListBoxTemplate(ITemplatedControl parent) private Control CreateListBoxTemplate(ITemplatedControl parent)
{ {
return new ScrollViewer return new ScrollViewer
@ -70,5 +104,15 @@ namespace Perspex.Controls.UnitTests
[~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty), [~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty),
}; };
} }
private class Item
{
public Item(string value)
{
Value = value;
}
public string Value { get; }
}
} }
} }

1
tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj

@ -84,6 +84,7 @@
<Compile Include="GridLengthTests.cs" /> <Compile Include="GridLengthTests.cs" />
<Compile Include="ContentPresenterTests.cs" /> <Compile Include="ContentPresenterTests.cs" />
<Compile Include="BorderTests.cs" /> <Compile Include="BorderTests.cs" />
<Compile Include="TreeViewTests.cs" />
<Compile Include="Mixins\SelectableMixinTests.cs" /> <Compile Include="Mixins\SelectableMixinTests.cs" />
<Compile Include="StackPanelTests.cs" /> <Compile Include="StackPanelTests.cs" />
<Compile Include="Primitives\TrackTests.cs" /> <Compile Include="Primitives\TrackTests.cs" />

101
tests/Perspex.Controls.UnitTests/TreeViewTests.cs

@ -0,0 +1,101 @@
// 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 System;
using System.Linq;
using Perspex.Controls;
using Perspex.Controls.Presenters;
using Perspex.Controls.Templates;
using Perspex.LogicalTree;
using Perspex.Styling;
using Xunit;
namespace Perspex.Controls.UnitTests
{
public class TreeViewTests
{
[Fact]
public void LogicalChildren_Should_Be_Set()
{
var target = new TreeView
{
Template = new ControlTemplate(CreateTreeViewTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
};
target.ApplyTemplate();
Assert.Equal(3, target.GetLogicalChildren().Count());
foreach (var child in target.GetLogicalChildren())
{
Assert.IsType<TreeViewItem>(child);
}
}
[Fact]
public void DataContexts_Should_Be_Correctly_Set()
{
var items = new object[]
{
"Foo",
new Item("Bar"),
new TextBlock { Text = "Baz" },
new TreeViewItem { Header = "Qux" },
};
var target = new TreeView
{
Template = new ControlTemplate(CreateTreeViewTemplate),
DataContext = "Base",
DataTemplates = new DataTemplates
{
new FuncDataTemplate<Item>(x => new Button { Content = x })
},
Items = items,
};
target.ApplyTemplate();
var dataContexts = target.Presenter.Panel.Children
.Cast<Control>()
.Select(x => x.DataContext)
.ToList();
Assert.Equal(
new object[] { items[0], items[1], "Base", "Base" },
dataContexts);
}
private Control CreateTreeViewTemplate(ITemplatedControl parent)
{
return new ScrollViewer
{
Template = new ControlTemplate(CreateScrollViewerTemplate),
Content = new ItemsPresenter
{
Name = "itemsPresenter",
[~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty),
}
};
}
private Control CreateScrollViewerTemplate(ITemplatedControl parent)
{
return new ScrollContentPresenter
{
[~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty),
};
}
private class Item
{
public Item(string value)
{
Value = value;
}
public string Value { get; }
}
}
}

4
tests/Perspex.Input.UnitTests/Properties/AssemblyInfo.cs

@ -2,5 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Reflection; using System.Reflection;
using Xunit;
[assembly: AssemblyTitle("Perspex.Input.UnitTests")] [assembly: AssemblyTitle("Perspex.Input.UnitTests")]
// Don't run tests in parallel.
[assembly: CollectionBehavior(DisableTestParallelization = true)]

80
tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_SetValue.cs

@ -0,0 +1,80 @@
// 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 System;
using System.Collections.Generic;
using System.Reactive.Linq;
using Perspex.Markup.Binding;
using Xunit;
namespace Perspex.Markup.UnitTests.Binding
{
public class ExpressionObserverTests_SetValue
{
[Fact]
public void Should_Set_Simple_Property_Value()
{
var data = new { Foo = "foo" };
var target = new ExpressionObserver(data, "Foo");
target.SetValue("bar");
Assert.Equal("foo", data.Foo);
}
[Fact]
public void Should_Set_Value_On_Simple_Property_Chain()
{
var data = new Class1 { Foo = new Class2 { Bar = "bar" } };
var target = new ExpressionObserver(data, "Foo.Bar");
target.SetValue("foo");
Assert.Equal("foo", data.Foo.Bar);
}
[Fact]
public void Should_Not_Try_To_Set_Value_On_Broken_Chain()
{
var data = new Class1 { Foo = new Class2 { Bar = "bar" } };
var target = new ExpressionObserver(data, "Foo.Bar");
// Ensure the ExpressionObserver's subscriptions are kept active.
target.OfType<string>().Subscribe(x => { });
data.Foo = null;
Assert.False(target.SetValue("foo"));
}
private class Class1 : NotifyingBase
{
private Class2 _foo;
public Class2 Foo
{
get { return _foo; }
set
{
_foo = value;
RaisePropertyChanged(nameof(Foo));
}
}
}
private class Class2 : NotifyingBase
{
private string _bar;
public string Bar
{
get { return _bar; }
set
{
_bar = value;
RaisePropertyChanged(nameof(Bar));
}
}
}
}
}

1
tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj

@ -74,6 +74,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="Binding\ExpressionNodeBuilderTests_Errors.cs" /> <Compile Include="Binding\ExpressionNodeBuilderTests_Errors.cs" />
<Compile Include="Binding\ExpressionObserverTests_Observable.cs" /> <Compile Include="Binding\ExpressionObserverTests_Observable.cs" />
<Compile Include="Binding\ExpressionObserverTests_SetValue.cs" />
<Compile Include="Binding\ExpressionObserverTests_Task.cs" /> <Compile Include="Binding\ExpressionObserverTests_Task.cs" />
<Compile Include="Binding\ExpressionObserverTests_Indexer.cs" /> <Compile Include="Binding\ExpressionObserverTests_Indexer.cs" />
<Compile Include="Binding\ExpressionObserverTests_Negation.cs" /> <Compile Include="Binding\ExpressionObserverTests_Negation.cs" />

1
tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj

@ -101,6 +101,7 @@
<Compile Include="SampleModel\Repository.cs" /> <Compile Include="SampleModel\Repository.cs" />
<Compile Include="SampleModel\UserRepositoriesViewModel.cs" /> <Compile Include="SampleModel\UserRepositoriesViewModel.cs" />
<Compile Include="SamplePerspexObject.cs" /> <Compile Include="SamplePerspexObject.cs" />
<Compile Include="Templates\DataTemplateTests.cs" />
<Compile Include="TypeProviderMock.cs" /> <Compile Include="TypeProviderMock.cs" />
<Compile Include="XamlBindingTest.cs" /> <Compile Include="XamlBindingTest.cs" />
<Compile Include="ViewModelMock.cs" /> <Compile Include="ViewModelMock.cs" />

37
tests/Perspex.Markup.Xaml.UnitTests/Templates/DataTemplateTests.cs

@ -0,0 +1,37 @@
// 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 System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Perspex.Markup.Xaml.Templates;
using Xunit;
namespace Perspex.Markup.Xaml.UnitTests.Templates
{
public class DataTemplateTests
{
[Fact]
public void DataTemplate_Should_Match_Data_Of_Type()
{
var target = new DataTemplate { DataType = typeof(Class1) };
var data = new Class1();
Assert.True(target.Match(data));
}
[Fact]
public void DataTemplate_Should_Match_Data_Of_Derived_Type()
{
var target = new DataTemplate { DataType = typeof(Class1) };
var data = new Class2();
Assert.True(target.Match(data));
}
private class Class1 { }
private class Class2 : Class1 { }
}
}
Loading…
Cancel
Save