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.
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
/// <summary>
/// Draws using Direct2D1.
/// </summary>
public class DrawingContext : IDrawingContext, IDisposable
public class DrawingContext : IDrawingContextImpl, IDisposable
{
/// <summary>
/// The cairo context.
@ -30,7 +31,6 @@ namespace Perspex.Cairo.Media
public DrawingContext(Cairo.Surface surface)
{
_context = new Cairo.Context(surface);
CurrentTransform = Matrix.Identity;
}
/// <summary>
@ -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;
/// <summary>
/// Gets the current transform of the drawing context.
/// </summary>
public Matrix CurrentTransform
public Matrix Transform
{
get; }
get { return _transform; }
set
{
_transform = value;
_context.Matrix = value.ToCairo();
}
}
/// <summary>
/// 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
/// </summary>
/// <param name="clip">The clip rectangle.</param>
/// <returns>A disposable used to undo the clip rectangle.</returns>
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<double> _opacityStack = new Stack<double>();
/// <summary>
/// Pushes an opacity value.
/// </summary>
/// <param name="opacity">The opacity.</param>
/// <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)
opacityOverride = opacity;
opacityOverride *= opacity;
return Disposable.Create(() =>
{
opacityOverride = tmp;
});
}
public void PopOpacity()
{
opacityOverride = _opacityStack.Pop();
}
/// <summary>

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

2
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);
}

12
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
/// <summary>
/// Creates a cairo surface that targets a platform-specific resource.
/// </summary>
/// <returns>A surface wrapped in an <see cref="IDrawingContext"/>.</returns>
public IDrawingContext CreateDrawingContext()
/// <returns>A surface wrapped in an <see cref="Perspex.Media.DrawingContext"/>.</returns>
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();

4
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<IPlatformThreadingInterface>().ToConstant(s_instance)
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogImpl>();
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;
}
}

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.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

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

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

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

2
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)

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

@ -46,6 +46,7 @@
<Compile Include="Diagnostics\PerspexObjectExtensions.cs" />
<Compile Include="IObservablePropertyBag.cs" />
<Compile Include="IPropertyBag.cs" />
<Compile Include="PerspexDisposable.cs" />
<Compile Include="PerspexLocator.cs" />
<Compile Include="Metadata\XmlnsDefinitionAttribute.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.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();
/// <summary>
/// Clears a <see cref="PerspexProperty"/>'s local value.
/// </summary>
@ -495,7 +500,7 @@ namespace Perspex
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
if (property.IsDirect)
{
property = GetRegistered(property);
@ -556,7 +561,7 @@ namespace Perspex
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
if (property.IsDirect)
{
property = (PerspexProperty<T>)GetRegistered(property);
@ -590,7 +595,7 @@ namespace Perspex
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
if (property.IsDirect)
{
property = GetRegistered(property);
@ -650,7 +655,7 @@ namespace Perspex
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
if (property.IsDirect)
{
property = (PerspexProperty<T>)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<object> source,
BindingPriority priority = BindingPriority.LocalValue)
{
VerifyAccess();
_propertyLog.Verbose(
"Bound two way {Property} to {Binding} with priority {Priority}",
property,
@ -733,6 +740,7 @@ namespace Perspex
/// <param name="property">The property.</param>
public void Revalidate(PerspexProperty property)
{
VerifyAccess();
PriorityValue value;
if (_values.TryGetValue(property, out value))
@ -788,6 +796,7 @@ namespace Perspex
BindingPriority priority)
{
Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
PerspexPropertyChangedEventArgs e = new PerspexPropertyChangedEventArgs(
this,
@ -824,6 +833,7 @@ namespace Perspex
/// </returns>
protected bool SetAndRaise<T>(PerspexProperty<T> property, ref T field, T value)
{
VerifyAccess();
if (!object.Equals(field, value))
{
var old = field;

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

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

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

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

21
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");
}
/// <summary>
/// Runs the dispatcher's main loop.
@ -49,7 +60,7 @@ namespace Perspex.Threading
/// </summary>
public void RunJobs()
{
_jobRunner.RunJobs();
_jobRunner?.RunJobs();
}
/// <summary>
@ -58,9 +69,9 @@ namespace Perspex.Threading
/// <param name="action">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>
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>
@ -68,9 +79,9 @@ namespace Perspex.Threading
/// </summary>
/// <param name="action">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>
private void InternalTick()
{
Dispatcher.UIThread.Post(RaiseTick, _priority);
Dispatcher.UIThread.InvokeAsync(RaiseTick, _priority);
}
/// <summary>

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

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

2
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;
}

2
src/Perspex.Controls/Border.cs

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

3
src/Perspex.Controls/Button.cs

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

17
src/Perspex.Controls/Canvas.cs

@ -6,28 +6,37 @@ using Perspex.Input;
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
{
/// <summary>
/// Defines the <see cref="Left"/> property.
/// Defines the Left attached property.
/// </summary>
public static readonly PerspexProperty<double> LeftProperty =
PerspexProperty.RegisterAttached<StackPanel, Control, double>("Left");
/// <summary>
/// Defines the <see cref="Top"/> property.
/// Defines the Top attached property.
/// </summary>
public static readonly PerspexProperty<double> TopProperty =
PerspexProperty.RegisterAttached<StackPanel, Control, double>("Top");
/// <summary>
/// Defines the <see cref="Right"/> property.
/// Defines the Right attached property.
/// </summary>
public static readonly PerspexProperty<double> RightProperty =
PerspexProperty.RegisterAttached<StackPanel, Control, double>("Right");
/// <summary>
/// Defines the <see cref="Bottom"/> property.
/// Defines the Bottom attached property.
/// </summary>
public static readonly PerspexProperty<double> BottomProperty =
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>
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>

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

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

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

@ -165,22 +165,33 @@ namespace Perspex.Controls.Generators
/// <returns>The created container control.</returns>
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);
result = new T
var result = new T
{
Header = template.Build(item),
Items = template.ItemsSelector(item),
IsExpanded = template.IsExpanded(item),
DataContext = item,
};
}
return result;
if (!(item is IControl))
{
result.DataContext = item;
}
return result;
}
}
/// <summary>

2
src/Perspex.Controls/Image.cs

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

4
src/Perspex.Controls/ListBox.cs

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

4
src/Perspex.Controls/MenuItem.cs

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

4
src/Perspex.Controls/Panel.cs

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

12
src/Perspex.Controls/PlacementMode.cs

@ -1,11 +1,23 @@
// 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.Controls.Primitives;
namespace Perspex.Controls
{
/// <summary>
/// Defines the placement for a <see cref="Popup"/> control.
/// </summary>
public enum PlacementMode
{
/// <summary>
/// The popup is placed at the bottom left of its target.
/// </summary>
Bottom,
/// <summary>
/// The popup is placed at the top right of its target.
/// </summary>
Right,
}
}

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

@ -1,18 +1,27 @@
// 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 Perspex.Controls;
using Perspex.Input.Raw;
namespace Perspex.Platform
{
/// <summary>
/// Defines a platform-specific popup window implementation.
/// </summary>
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);
/// <summary>
/// Shows the popup.
/// </summary>
void Show();
/// <summary>
/// Hides the popup.
/// </summary>
void Hide();
}
}

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

@ -1,14 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
// 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.Threading.Tasks;
using Perspex.Platform;
namespace Perspex.Controls.Platform
{
/// <summary>
/// Defines a platform-specific system dialog implementation.
/// </summary>
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);
}
}

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.
using System;
using Perspex.Controls;
using Perspex.Input;
using Perspex.Input.Raw;
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
{
/// <summary>
/// Gets or sets the client size of the window.
/// </summary>
Size ClientSize { get; set; }
/// <summary>
/// Gets the platform window handle.
/// </summary>
IPlatformHandle Handle { get; }
/// <summary>
/// Gets or sets a method called when the window is activated (receives focus).
/// </summary>
Action Activated { get; set; }
/// <summary>
/// Gets or sets a method called when the window is closed.
/// </summary>
Action Closed { get; set; }
/// <summary>
/// Gets or sets a method called when the window is deactivated (loses focus).
/// </summary>
Action Deactivated { get; set; }
/// <summary>
/// Gets or sets a method called when the window receives input.
/// </summary>
Action<RawInputEventArgs> Input { get; set; }
/// <summary>
/// Gets or sets a method called when the window requires painting.
/// </summary>
Action<Rect> Paint { get; set; }
/// <summary>
/// Gets or sets a method called when the window is resized.
/// </summary>
Action<Size> Resized { get; set; }
/// <summary>
/// Activates the window.
/// </summary>
void Activate();
/// <summary>
/// Invalidates a rect on the window.
/// </summary>
void Invalidate(Rect rect);
/// <summary>
/// Sets the <see cref="IInputRoot"/> for the window.
/// </summary>
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);
/// <summary>

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

@ -115,6 +115,11 @@ namespace Perspex.Controls.Presenters
if (result != null)
{
if (!(content is IControl))
{
result.DataContext = content;
}
AddVisualChild(result);
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);
}
public override void Render(IDrawingContext context)
public override void Render(DrawingContext context)
{
var selectionStart = SelectionStart;
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.
/// </summary>
/// <param name="context">The drawing context.</param>
public override void Render(IDrawingContext context)
public override void Render(DrawingContext context)
{
base.Render(context);

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

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

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

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

86
src/Perspex.Controls/TextBlock.cs

@ -210,11 +210,95 @@ namespace Perspex.Controls
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>
/// Renders the <see cref="TextBlock"/> to a drawing context.
/// </summary>
/// <param name="context">The drawing context.</param>
public override void Render(IDrawingContext context)
public override void Render(DrawingContext context)
{
Brush background = Background;

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

@ -32,7 +32,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
/// <summary>
/// The wrapped Perspex graphics object
/// </summary>
private readonly IDrawingContext _g;
private readonly DrawingContext _g;
/// <summary>
/// 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="initialClip">the initial clip of the graphics</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)
{
ArgChecker.AssertArgNotNull(g, "g");

2
src/Perspex.HtmlRenderer/HtmlContainer.cs

@ -360,7 +360,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex
/// </summary>
/// <param name="g">the device to use to render</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");

2
src/Perspex.HtmlRenderer/HtmlControl.cs

@ -327,7 +327,7 @@ namespace Perspex.Controls.Html
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));

4
src/Perspex.SceneGraph/IVisual.cs

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

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

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

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

@ -47,7 +47,7 @@ namespace Perspex.Media.Imaging
return factory.CreateRenderTargetBitmap(width, height);
}
public IDrawingContext CreateDrawingContext() => PlatformImpl.CreateDrawingContext();
public DrawingContext CreateDrawingContext() => PlatformImpl.CreateDrawingContext();
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\Colors.cs" />
<Compile Include="Media\DashStyle.cs" />
<Compile Include="Media\DrawingContext.cs" />
<Compile Include="Media\GradientBrush.cs" />
<Compile Include="Media\GradientSpreadMethod.cs" />
<Compile Include="Media\GradientStop.cs" />
@ -99,7 +100,6 @@
<Compile Include="Media\Transform.cs" />
<Compile Include="Media\TileBrush.cs" />
<Compile Include="Media\ImageBush.cs" />
<Compile Include="Media\ValidatingDrawingContext.cs" />
<Compile Include="Media\VisualBrush.cs" />
<Compile Include="RelativePoint.cs" />
<Compile Include="Platform\IFormattedTextImpl.cs" />

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

@ -15,9 +15,9 @@ namespace Perspex.Platform
public interface IRenderTarget : IDisposable
{
/// <summary>
/// Creates an <see cref="IDrawingContext"/> for a rendering session.
/// Creates an <see cref="DrawingContext"/> for a rendering session.
/// </summary>
IDrawingContext CreateDrawingContext();
DrawingContext CreateDrawingContext();
/// <summary>
/// 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="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;
if (visual.IsVisible && opacity > 0)
@ -50,11 +50,12 @@ namespace Perspex.Rendering
var offset = Matrix.CreateTranslation(origin);
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 (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);
foreach (var child in visual.VisualChildren.OrderBy(x => x.ZIndex))

4
src/Perspex.SceneGraph/Visual.cs

@ -234,10 +234,10 @@ namespace Perspex
}
/// <summary>
/// Renders the visual to a <see cref="IDrawingContext"/>.
/// Renders the visual to a <see cref="DrawingContext"/>.
/// </summary>
/// <param name="context">The drawing context.</param>
public virtual void Render(IDrawingContext context)
public virtual void Render(DrawingContext context)
{
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.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reactive.Disposables;
using Perspex.Media;
using SharpDX;
@ -13,7 +15,7 @@ namespace Perspex.Direct2D1.Media
/// <summary>
/// Draws using Direct2D1.
/// </summary>
public class DrawingContext : IDrawingContext, IDisposable
public class DrawingContext : IDrawingContextImpl, IDisposable
{
/// <summary>
/// The Direct2D1 render target.
@ -42,10 +44,10 @@ namespace Perspex.Direct2D1.Media
/// <summary>
/// Gets the current transform of the drawing context.
/// </summary>
public Matrix CurrentTransform
public Matrix Transform
{
get { return _renderTarget.Transform.ToPerspex(); }
private set { _renderTarget.Transform = value.ToDirect2D(); }
set { _renderTarget.Transform = value.ToDirect2D(); }
}
/// <summary>
@ -53,6 +55,8 @@ namespace Perspex.Direct2D1.Media
/// </summary>
public void Dispose()
{
foreach (var layer in _layerPool)
layer.Dispose();
_renderTarget.EndDraw();
}
@ -233,22 +237,24 @@ namespace Perspex.Direct2D1.Media
/// </summary>
/// <param name="clip">The clip rectangle.</param>
/// <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);
}
return Disposable.Create(() =>
{
_renderTarget.PopAxisAlignedClip();
});
public void PopClip()
{
_renderTarget.PopAxisAlignedClip();
}
Stack<Layer> _layers = new Stack<Layer>();
private readonly Stack<Layer> _layerPool = new Stack<Layer>();
/// <summary>
/// Pushes an opacity value.
/// </summary>
/// <param name="opacity">The opacity.</param>
/// <returns>A disposable used to undo the opacity.</returns>
public IDisposable PushOpacity(double opacity)
public void PushOpacity(double opacity)
{
if (opacity < 1)
{
@ -256,41 +262,26 @@ namespace Perspex.Direct2D1.Media
{
ContentBounds = RectangleF.Infinite,
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);
return Disposable.Create(() =>
{
_renderTarget.PopLayer();
layer.Dispose();
});
_layers.Push(layer);
}
else
{
return Disposable.Empty;
}
_layers.Push(null);
}
/// <summary>
/// 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)
public void PopOpacity()
{
Matrix3x2 m3x2 = matrix.ToDirect2D();
Matrix3x2 transform = _renderTarget.Transform * m3x2;
_renderTarget.Transform = transform;
return Disposable.Create(() =>
var layer = _layers.Pop();
if (layer != null)
{
m3x2.Invert();
_renderTarget.Transform = transform * m3x2;
});
_renderTarget.PopLayer();
_layerPool.Push(layer);
}
}
/// <summary>

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

@ -38,7 +38,7 @@ namespace Perspex.Direct2D1.Media
// TODO:
}
public IDrawingContext CreateDrawingContext() => new RenderTarget(_target).CreateDrawingContext();
public Perspex.Media.DrawingContext CreateDrawingContext() => new RenderTarget(_target).CreateDrawingContext();
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 (ctx.PushClip(drawRect))
using (ctx.PushTransform(transform))
using (ctx.PushPostTransform(transform))
{
ctx.Render(visual);
}

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

@ -8,6 +8,7 @@ using Perspex.Platform;
using Perspex.Rendering;
using SharpDX;
using SharpDX.Direct2D1;
using DrawingContext = Perspex.Media.DrawingContext;
using DwFactory = SharpDX.DirectWrite.Factory;
namespace Perspex.Direct2D1
@ -91,24 +92,12 @@ namespace Perspex.Direct2D1
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>
/// Creates a drawing context for a rendering session.
/// </summary>
/// <returns>An <see cref="IDrawingContext"/>.</returns>
public IDrawingContext CreateDrawingContext()
{
return Wrap(new DrawingContext(_renderTarget, DirectWriteFactory));
}
/// <returns>An <see cref="Perspex.Media.DrawingContext"/>.</returns>
public DrawingContext CreateDrawingContext()
=> new DrawingContext(new Media.DrawingContext(_renderTarget, DirectWriteFactory));
public void Dispose()
{

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

@ -40,10 +40,12 @@ namespace Perspex.Win32
filters.CopyTo(0, filterBuffer, 0, filterBuffer.Length);
var defExt = (dialog as SaveFileDialog)?.DefaultExtension;
var buffer = new char[256];
dialog.InitialFileName?.CopyTo(0, buffer, 0, dialog.InitialFileName.Length);
var fileBuffer = new char[256];
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* pDefExt = defExt)
fixed (char* pInitDir = dialog.InitialDirectory)
@ -60,13 +62,13 @@ namespace Perspex.Win32
UnmanagedMethods.OpenFileNameFlags.OFN_EXPLORER |
UnmanagedMethods.OpenFileNameFlags.OFN_HIDEREADONLY,
nMaxCustFilter = 0,
nMaxFile = buffer.Length - 1,
nMaxFile = fileBuffer.Length - 1,
nMaxFileTitle = 0,
lpTemplateName = IntPtr.Zero,
lpfnHook = IntPtr.Zero,
lpstrCustomFilter = IntPtr.Zero,
lpstrDefExt = new IntPtr(pDefExt),
lpstrFile = new IntPtr(pBuffer),
lpstrFile = new IntPtr(pFileBuffer),
lpstrFileTitle = IntPtr.Zero,
lpstrFilter = new IntPtr(pFilterBuffer),
lpstrInitialDir = new IntPtr(pInitDir),
@ -88,20 +90,21 @@ namespace Perspex.Win32
: UnmanagedMethods.GetSaveFileName(new IntPtr(pofn));
if (!res)
return null;
if (dialog?.Filters.Count > 0)
userSelectedExt = dialog.Filters[ofn.nFilterIndex - 1].Extensions.FirstOrDefault();
}
var cStart = 0;
string dir = null;
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
if (cStart == c)
break;
var s = new string(buffer, cStart, c - cStart);
var s = new string(fileBuffer, cStart, c - cStart);
if (dir == null)
dir = s;
else
@ -110,7 +113,17 @@ namespace Perspex.Win32
}
}
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 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
{
private static readonly Win32Platform s_instance = new Win32Platform();
private static Thread _uiThread;
private UnmanagedMethods.WndProc _wndProcDelegate;
@ -52,6 +53,7 @@ namespace Perspex.Win32
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogImpl>();
SharedPlatform.Register();
_uiThread = Thread.CurrentThread;
}
public static void Initialize()
@ -124,6 +126,8 @@ namespace Perspex.Win32
new IntPtr(SignalL));
}
public bool CurrentThreadIsLoopThread => _uiThread == Thread.CurrentThread;
public event Action Signaled;
[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);
}
[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)
{
return new ScrollViewer
@ -70,5 +104,15 @@ namespace Perspex.Controls.UnitTests
[~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="ContentPresenterTests.cs" />
<Compile Include="BorderTests.cs" />
<Compile Include="TreeViewTests.cs" />
<Compile Include="Mixins\SelectableMixinTests.cs" />
<Compile Include="StackPanelTests.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.
using System.Reflection;
using Xunit;
[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>
<Compile Include="Binding\ExpressionNodeBuilderTests_Errors.cs" />
<Compile Include="Binding\ExpressionObserverTests_Observable.cs" />
<Compile Include="Binding\ExpressionObserverTests_SetValue.cs" />
<Compile Include="Binding\ExpressionObserverTests_Task.cs" />
<Compile Include="Binding\ExpressionObserverTests_Indexer.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\UserRepositoriesViewModel.cs" />
<Compile Include="SamplePerspexObject.cs" />
<Compile Include="Templates\DataTemplateTests.cs" />
<Compile Include="TypeProviderMock.cs" />
<Compile Include="XamlBindingTest.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