Browse Source

Merge branch 'master' into devtools

pull/403/head
Steven Kirk 10 years ago
parent
commit
0fc143e117
  1. 5
      samples/TestApplicationShared/App.cs
  2. 9
      samples/TestApplicationShared/MainWindow.cs
  3. 34
      samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs
  4. 11
      samples/XamlTestApplicationPcl/ViewModels/TestNode.cs
  5. 9
      samples/XamlTestApplicationPcl/Views/MainWindow.paml
  6. 16
      samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj
  7. 5
      samples/XamlTestApplicationPcl/packages.config
  8. 12
      src/Markup/Perspex.Markup.Xaml/Data/Binding.cs
  9. 2
      src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  10. 3
      src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
  11. 2
      src/Perspex.Base/Perspex.Base.csproj
  12. 64
      src/Perspex.Base/PerspexObject.cs
  13. 79
      src/Perspex.Base/PerspexObjectExtensions.cs
  14. 16
      src/Perspex.Base/Reactive/AnonymousSubject`1.cs
  15. 49
      src/Perspex.Base/Reactive/AnonymousSubject`2.cs
  16. 1
      src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs
  17. 1
      src/Perspex.Controls/Perspex.Controls.csproj
  18. 15
      src/Perspex.Controls/Primitives/ToggleButton.cs
  19. 36
      src/Perspex.Controls/Shapes/Line.cs
  20. 63
      src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs
  21. 55
      src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs
  22. 7
      src/Perspex.Controls/Templates/ITreeDataTemplate.cs
  23. 47
      src/Perspex.SceneGraph/Media/LineGeometry.cs
  24. 1
      src/Perspex.SceneGraph/Perspex.SceneGraph.csproj
  25. 4
      src/Perspex.Styling/Perspex.Styling.csproj
  26. 74
      src/Perspex.Styling/Styling/ActivatedObservable.cs
  27. 111
      src/Perspex.Styling/Styling/ActivatedSubject.cs
  28. 72
      src/Perspex.Styling/Styling/ActivatedValue.cs
  29. 24
      src/Perspex.Styling/Styling/Setter.cs
  30. 107
      src/Perspex.Styling/Styling/StyleBinding.cs
  31. 30
      tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs
  32. 3
      tests/Perspex.LeakTests/ControlTests.cs
  33. 87
      tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs
  34. 1
      tests/Perspex.RenderTests/Perspex.Cairo.RenderTests.csproj
  35. 1
      tests/Perspex.RenderTests/Perspex.Direct2D1.RenderTests.csproj
  36. 1
      tests/Perspex.RenderTests/Perspex.Skia.RenderTests.csproj
  37. 43
      tests/Perspex.RenderTests/Shapes/LineTests.cs
  38. 70
      tests/Perspex.Styling.UnitTests/ActivatedObservableTests.cs
  39. 98
      tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs
  40. 42
      tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs
  41. 4
      tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj
  42. 79
      tests/Perspex.Styling.UnitTests/StyleBindingTests.cs
  43. BIN
      tests/TestFiles/Cairo/Shapes/Line/Line_1px_Stroke.expected.png
  44. BIN
      tests/TestFiles/Direct2D1/Shapes/Line/Line_1px_Stroke.expected.png
  45. BIN
      tests/TestFiles/Skia/Shapes/Line/Line_1px_Stroke.expected.png

5
samples/TestApplicationShared/App.cs

@ -21,11 +21,8 @@ namespace TestApplication
{
new FuncTreeDataTemplate<Node>(
x => new TextBlock {Text = x.Name},
x => x.Children,
x => true),
x => x.Children),
};
}
}
}

9
samples/TestApplicationShared/MainWindow.cs

@ -689,6 +689,15 @@ namespace TestApplication
[Canvas.LeftProperty] = 130,
[Canvas.TopProperty] = 79,
},
new Line
{
Width = 90,
Height = 70,
Stroke = Brushes.Red,
StrokeThickness = 2,
[Canvas.LeftProperty] = 30,
[Canvas.TopProperty] = 120
}
}
},
}

34
samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs

@ -1,7 +1,9 @@
// 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 ReactiveUI;
namespace XamlTestApplication.ViewModels
{
@ -22,6 +24,7 @@ namespace XamlTestApplication.ViewModels
{
Header = "Root",
SubHeader = "Root Item",
IsExpanded = true,
Children = new[]
{
new TestNode
@ -33,6 +36,7 @@ namespace XamlTestApplication.ViewModels
{
Header = "Child 2",
SubHeader = "Child 2 Value",
IsExpanded = false,
Children = new[]
{
new TestNode
@ -50,9 +54,39 @@ namespace XamlTestApplication.ViewModels
}
}
};
CollapseNodesCommand = ReactiveCommand.Create();
CollapseNodesCommand.Subscribe(_ => ExpandNodes(false));
ExpandNodesCommand = ReactiveCommand.Create();
ExpandNodesCommand.Subscribe(_ => ExpandNodes(true));
}
public List<TestItem> Items { get; }
public List<TestNode> Nodes { get; }
public ReactiveCommand<object> CollapseNodesCommand { get; }
public ReactiveCommand<object> ExpandNodesCommand { get; }
public void ExpandNodes(bool expanded)
{
foreach (var node in Nodes)
{
ExpandNodes(node, expanded);
}
}
private void ExpandNodes(TestNode node, bool expanded)
{
node.IsExpanded = expanded;
if (node.Children != null)
{
foreach (var child in node.Children)
{
ExpandNodes(child, expanded);
}
}
}
}
}

11
samples/XamlTestApplicationPcl/ViewModels/TestNode.cs

@ -2,13 +2,22 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using ReactiveUI;
namespace XamlTestApplication.ViewModels
{
public class TestNode
public class TestNode : ReactiveObject
{
private bool _isExpanded;
public string Header { get; set; }
public string SubHeader { get; set; }
public IEnumerable<TestNode> Children { get; set; }
public bool IsExpanded
{
get { return _isExpanded; }
set { this.RaiseAndSetIfChanged(ref this._isExpanded, value); }
}
}
}

9
samples/XamlTestApplicationPcl/Views/MainWindow.paml

@ -147,6 +147,11 @@
</StackPanel>
</DropDown>
<TreeView Items="{Binding Nodes}">
<TreeView.Styles>
<Style Selector="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
</TreeView.Styles>
<TreeView.DataTemplates>
<TreeDataTemplate DataType="vm:TestNode" ItemsSource="{Binding Children}">
<StackPanel>
@ -156,6 +161,10 @@
</TreeDataTemplate>
</TreeView.DataTemplates>
</TreeView>
<StackPanel Orientation="Vertical" Gap="4">
<Button Command="{Binding CollapseNodesCommand}">Collapse Nodes</Button>
<Button Command="{Binding ExpandNodesCommand}">Expand Nodes</Button>
</StackPanel>
</StackPanel>
</TabItem>
<TabItem Header="Layout">

16
samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj

@ -123,6 +123,22 @@
<HintPath>..\..\packages\Splat.1.6.2\lib\Portable-net45+win+wpa81+wp80\Splat.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-Interfaces.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Interfaces.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-Linq.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Linq.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-PlatformServices.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.PlatformServices.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="XamlTestApp.paml">

5
samples/XamlTestApplicationPcl/packages.config

@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Rx-Core" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Rx-Interfaces" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Rx-Linq" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Rx-Main" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Splat" version="1.6.2" targetFramework="portable45-net45+win8" />
</packages>

12
src/Markup/Perspex.Markup.Xaml/Data/Binding.cs

@ -71,21 +71,21 @@ namespace Perspex.Markup.Xaml.Data
if (pathInfo.ElementName != null || ElementName != null)
{
observer = CreateElementSubject(
observer = CreateElementObserver(
(IControl)target,
pathInfo.ElementName ?? ElementName,
pathInfo.Path);
}
else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext)
{
observer = CreateDataContextSubject(
observer = CreateDataContexObserver(
target,
pathInfo.Path,
targetIsDataContext);
}
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
{
observer = CreateTemplatedParentSubject(
observer = CreateTemplatedParentObserver(
target,
pathInfo.Path);
}
@ -148,7 +148,7 @@ namespace Perspex.Markup.Xaml.Data
}
}
private ExpressionObserver CreateDataContextSubject(
private ExpressionObserver CreateDataContexObserver(
IPerspexObject target,
string path,
bool targetIsDataContext)
@ -178,7 +178,7 @@ namespace Perspex.Markup.Xaml.Data
}
}
private ExpressionObserver CreateTemplatedParentSubject(
private ExpressionObserver CreateTemplatedParentObserver(
IPerspexObject target,
string path)
{
@ -196,7 +196,7 @@ namespace Perspex.Markup.Xaml.Data
return result;
}
private ExpressionObserver CreateElementSubject(
private ExpressionObserver CreateElementObserver(
IControl target,
string elementName,
string path)

2
src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -27,6 +27,7 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
ElementName = ElementName,
Mode = Mode,
Path = Path,
Priority = Priority,
};
}
@ -35,5 +36,6 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
public string ElementName { get; set; }
public BindingMode Mode { get; set; }
public string Path { get; set; }
public BindingPriority Priority { get; set; } = BindingPriority.LocalValue;
}
}

3
src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs

@ -25,9 +25,9 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
Converter = Converter,
ElementName = ElementName,
Mode = Mode,
Priority = BindingPriority.TemplatedParent,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Path = Path,
Priority = Priority,
};
}
@ -35,5 +35,6 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
public string ElementName { get; set; }
public BindingMode Mode { get; set; }
public string Path { get; set; }
public BindingPriority Priority { get; set; } = BindingPriority.TemplatedParent;
}
}

2
src/Perspex.Base/Perspex.Base.csproj

@ -76,6 +76,8 @@
<Compile Include="PriorityLevel.cs" />
<Compile Include="PriorityValue.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Reactive\AnonymousSubject`1.cs" />
<Compile Include="Reactive\AnonymousSubject`2.cs" />
<Compile Include="Reactive\PerspexObservable.cs" />
<Compile Include="Threading\Dispatcher.cs" />
<Compile Include="Threading\DispatcherPriority.cs" />

64
src/Perspex.Base/PerspexObject.cs

@ -185,7 +185,8 @@ namespace Perspex
sourceBinding.Source.Bind(sourceBinding.Property, this.GetObservable(binding.Property), binding.Priority);
break;
case BindingMode.TwoWay:
BindTwoWay(binding.Property, sourceBinding.Source, sourceBinding.Property);
var subject = sourceBinding.Source.GetSubject(sourceBinding.Property, sourceBinding.Priority);
this.Bind(binding.Property, subject, BindingMode.TwoWay, sourceBinding.Priority);
break;
}
}
@ -480,67 +481,6 @@ namespace Perspex
}
}
/// <summary>
/// Initiates a two-way binding between <see cref="PerspexProperty"/>s.
/// </summary>
/// <param name="property">The property on this object.</param>
/// <param name="source">The source object.</param>
/// <param name="sourceProperty">The property on the source object.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
/// <remarks>
/// The binding is first carried out from <paramref name="source"/> to this.
/// </remarks>
public IDisposable BindTwoWay(
PerspexProperty property,
PerspexObject source,
PerspexProperty sourceProperty,
BindingPriority priority = BindingPriority.LocalValue)
{
VerifyAccess();
_propertyLog.Verbose(
"Bound two way {Property} to {Binding} with priority {Priority}",
property,
source,
priority);
return new CompositeDisposable(
Bind(property, source.GetObservable(sourceProperty)),
source.Bind(sourceProperty, this.GetObservable(property)));
}
/// <summary>
/// Initiates a two-way binding between a <see cref="PerspexProperty"/> and an
/// <see cref="ISubject{Object}"/>.
/// </summary>
/// <param name="property">The property on this object.</param>
/// <param name="source">The subject to bind to.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
/// <remarks>
/// The binding is first carried out from <paramref name="source"/> to this.
/// </remarks>
public IDisposable BindTwoWay(
PerspexProperty property,
ISubject<object> source,
BindingPriority priority = BindingPriority.LocalValue)
{
VerifyAccess();
_propertyLog.Verbose(
"Bound two way {Property} to {Binding} with priority {Priority}",
property,
GetDescription(source),
priority);
return new CompositeDisposable(
Bind(property, source),
this.GetObservable(property).Subscribe(source));
}
/// <summary>
/// Forces the specified property to be revalidated.
/// </summary>

79
src/Perspex.Base/PerspexObjectExtensions.cs

@ -103,6 +103,36 @@ namespace Perspex
GetDescription(o, property));
}
/// <summary>
/// Gets a subject for a <see cref="PerspexProperty"/>.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="property">The property.</param>
/// <param name="priority">
/// The priority with which binding values are written to the object.
/// </param>
/// <returns>
/// An <see cref="ISubject{Object}"/> which can be used for two-way binding to/from the
/// property.
/// </returns>
public static ISubject<object> GetSubject(
this IPerspexObject o,
PerspexProperty property,
BindingPriority priority = BindingPriority.LocalValue)
{
// TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the
// AnonymousSubject classes and use Subject.Create<T>.
var output = new Subject<object>();
var result = new AnonymousSubject<object>(
Observer.Create<object>(
x => output.OnNext(x),
e => output.OnError(e),
() => output.OnCompleted()),
o.GetObservable(property));
o.Bind(property, output, priority);
return result;
}
/// <summary>
/// Gets a subject for a <see cref="PerspexProperty"/>.
/// </summary>
@ -242,54 +272,5 @@ namespace Perspex
handler(target)(e);
}
}
class AnonymousSubject<T, U> : ISubject<T, U>
{
private readonly IObserver<T> _observer;
private readonly IObservable<U> _observable;
public AnonymousSubject(IObserver<T> observer, IObservable<U> observable)
{
_observer = observer;
_observable = observable;
}
public void OnCompleted()
{
_observer.OnCompleted();
}
public void OnError(Exception error)
{
if (error == null)
throw new ArgumentNullException("error");
_observer.OnError(error);
}
public void OnNext(T value)
{
_observer.OnNext(value);
}
public IDisposable Subscribe(IObserver<U> observer)
{
if (observer == null)
throw new ArgumentNullException("observer");
//
// [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence.
//
return _observable.Subscribe/*Unsafe*/(observer);
}
}
class AnonymousSubject<T> : AnonymousSubject<T, T>, ISubject<T>
{
public AnonymousSubject(IObserver<T> observer, IObservable<T> observable)
: base(observer, observable)
{
}
}
}
}

16
src/Perspex.Base/Reactive/AnonymousSubject`1.cs

@ -0,0 +1,16 @@
// 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.Reactive.Subjects;
namespace Perspex.Reactive
{
public class AnonymousSubject<T> : AnonymousSubject<T, T>, ISubject<T>
{
public AnonymousSubject(IObserver<T> observer, IObservable<T> observable)
: base(observer, observable)
{
}
}
}

49
src/Perspex.Base/Reactive/AnonymousSubject`2.cs

@ -0,0 +1,49 @@
// 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.Reactive.Subjects;
namespace Perspex.Reactive
{
public class AnonymousSubject<T, U> : ISubject<T, U>
{
private readonly IObserver<T> _observer;
private readonly IObservable<U> _observable;
public AnonymousSubject(IObserver<T> observer, IObservable<U> observable)
{
_observer = observer;
_observable = observable;
}
public void OnCompleted()
{
_observer.OnCompleted();
}
public void OnError(Exception error)
{
if (error == null)
throw new ArgumentNullException("error");
_observer.OnError(error);
}
public void OnNext(T value)
{
_observer.OnNext(value);
}
public IDisposable Subscribe(IObserver<U> observer)
{
if (observer == null)
throw new ArgumentNullException("observer");
//
// [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence.
//
return _observable.Subscribe/*Unsafe*/(observer);
}
}
}

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

@ -77,7 +77,6 @@ namespace Perspex.Controls.Generators
result.SetValue(ContentProperty, template.Build(item));
result.SetValue(ItemsProperty, template.ItemsSelector(item));
//result.SetValue(IsExpandedProperty, template.IsExpanded(item));
if (!(item is IControl))
{

1
src/Perspex.Controls/Perspex.Controls.csproj

@ -67,6 +67,7 @@
<Compile Include="SelectionChangedEventArgs.cs" />
<Compile Include="SelectionMode.cs" />
<Compile Include="Separator.cs" />
<Compile Include="Shapes\Line.cs" />
<Compile Include="SystemDialog.cs" />
<Compile Include="Generators\ITreeItemContainerGenerator.cs" />
<Compile Include="Generators\ItemContainerEventArgs.cs" />

15
src/Perspex.Controls/Primitives/ToggleButton.cs

@ -9,21 +9,22 @@ namespace Perspex.Controls.Primitives
public class ToggleButton : Button
{
public static readonly PerspexProperty<bool> IsCheckedProperty =
PerspexProperty.Register<ToggleButton, bool>("IsChecked");
PerspexProperty.RegisterDirect<ToggleButton, bool>(
"IsChecked",
o => o.IsChecked,
(o,v) => o.IsChecked = v);
private bool _isChecked;
static ToggleButton()
{
PseudoClass(IsCheckedProperty, ":checked");
}
public ToggleButton()
{
}
public bool IsChecked
{
get { return GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
get { return _isChecked; }
set { SetAndRaise(IsCheckedProperty, ref _isChecked, value); }
}
protected override void OnClick(RoutedEventArgs e)

36
src/Perspex.Controls/Shapes/Line.cs

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Perspex.Media;
namespace Perspex.Controls.Shapes
{
public class Line : Shape
{
private Geometry _geometry;
private Size _geometrySize;
public override Geometry DefiningGeometry
{
get
{
if (_geometry == null || _geometrySize != Bounds.Size)
{
var rect = new Rect(Bounds.Size).Deflate(StrokeThickness);
_geometry = new LineGeometry(rect.TopLeft, rect.BottomRight);
_geometrySize = Bounds.Size;
}
return _geometry;
}
}
protected override Size MeasureOverride(Size availableSize)
{
return new Size(StrokeThickness, StrokeThickness);
}
}
}

63
src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs

@ -14,8 +14,6 @@ namespace Perspex.Controls.Templates
{
private readonly Func<object, IEnumerable> _itemsSelector;
private readonly Func<object, bool> _isExpanded;
/// <summary>
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate"/> class.
/// </summary>
@ -35,30 +33,6 @@ namespace Perspex.Controls.Templates
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate"/> class.
/// </summary>
/// <param name="type">The type of data which the data template matches.</param>
/// <param name="build">
/// A function which when passed an object of <paramref name="type"/> returns a control.
/// </param>
/// <param name="itemsSelector">
/// A function which when passed an object of <paramref name="type"/> returns the child
/// items.
/// </param>
/// <param name="isExpanded">
/// A function which when passed an object of <paramref name="type"/> returns the the
/// initial expanded state of the node.
/// </param>
public FuncTreeDataTemplate(
Type type,
Func<object, IControl> build,
Func<object, IEnumerable> itemsSelector,
Func<object, bool> isExpanded)
: this(o => IsInstance(o, type), build, itemsSelector, isExpanded)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate"/> class.
/// </summary>
@ -75,46 +49,9 @@ namespace Perspex.Controls.Templates
Func<object, bool> match,
Func<object, IControl> build,
Func<object, IEnumerable> itemsSelector)
: this(match, build, itemsSelector, _ => false)
{
_itemsSelector = itemsSelector;
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate"/> class.
/// </summary>
/// <param name="match">
/// A function which determines whether the data template matches the specified data.
/// </param>
/// <param name="build">
/// A function which when passed a matching object returns a control.
/// </param>
/// <param name="itemsSelector">
/// A function which when passed a matching object returns the child items.
/// </param>
/// <param name="isExpanded">
/// A function which when passed a matching object returns the the initial expanded state
/// of the node.
/// </param>
public FuncTreeDataTemplate(
Func<object, bool> match,
Func<object, IControl> build,
Func<object, IEnumerable> itemsSelector,
Func<object, bool> isExpanded)
: base(match, build)
{
_itemsSelector = itemsSelector;
_isExpanded = isExpanded;
}
/// <summary>
/// Checks to see if the item should be initially expanded.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>True if the item should be initially expanded, otherwise false.</returns>
public bool IsExpanded(object item)
{
return this?._isExpanded(item) ?? false;
}
/// <summary>

55
src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs

@ -32,32 +32,6 @@ namespace Perspex.Controls.Templates
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate{T}"/> class.
/// </summary>
/// <param name="build">
/// A function which when passed an object of <typeparamref name="T"/> returns a control.
/// </param>
/// <param name="itemsSelector">
/// A function which when passed an object of <typeparamref name="T"/> returns the child
/// items.
/// </param>
/// <param name="isExpanded">
/// A function which when passed an object of <typeparamref name="T"/> returns the the
/// initial expanded state of the node.
/// </param>
public FuncTreeDataTemplate(
Func<T, Control> build,
Func<T, IEnumerable> itemsSelector,
Func<T, bool> isExpanded)
: base(
typeof(T),
Cast(build),
Cast(itemsSelector),
Cast(isExpanded))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate{T}"/> class.
/// </summary>
@ -81,35 +55,6 @@ namespace Perspex.Controls.Templates
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate{T}"/> class.
/// </summary>
/// <param name="match">
/// A function which determines whether the data template matches the specified data.
/// </param>
/// <param name="build">
/// A function which when passed a matching object returns a control.
/// </param>
/// <param name="itemsSelector">
/// A function which when passed a matching object returns the child items.
/// </param>
/// <param name="isExpanded">
/// A function which when passed a matching object returns the the initial expanded state
/// of the node.
/// </param>
public FuncTreeDataTemplate(
Func<T, bool> match,
Func<T, Control> build,
Func<T, IEnumerable> itemsSelector,
Func<T, bool> isExpanded)
: base(
CastMatch(match),
Cast(build),
Cast(itemsSelector),
Cast(isExpanded))
{
}
/// <summary>
/// Casts a typed match function to an untyped match function.
/// </summary>

7
src/Perspex.Controls/Templates/ITreeDataTemplate.cs

@ -10,13 +10,6 @@ namespace Perspex.Controls.Templates
/// </summary>
public interface ITreeDataTemplate : IDataTemplate
{
/// <summary>
/// Checks to see if the item should be initially expanded.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>True if the item should be initially expanded, otherwise false.</returns>
bool IsExpanded(object item);
/// <summary>
/// Selects the child items of an item.
/// </summary>

47
src/Perspex.SceneGraph/Media/LineGeometry.cs

@ -0,0 +1,47 @@
// 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.Platform;
namespace Perspex.Media
{
/// <summary>
/// Represents the geometry of a line.
/// </summary>
public class LineGeometry : Geometry
{
private Point _startPoint;
private Point _endPoint;
/// <summary>
/// Initializes a new instance of the <see cref="LineGeometry"/> class.
/// </summary>
/// <param name="startPoint">The start point.</param>
/// <param name="endPoint">The end point.</param>
public LineGeometry(Point startPoint, Point endPoint)
{
_startPoint = startPoint;
_endPoint = endPoint;
IPlatformRenderInterface factory = PerspexLocator.Current.GetService<IPlatformRenderInterface>();
IStreamGeometryImpl impl = factory.CreateStreamGeometry();
using (IStreamGeometryContextImpl context = impl.Open())
{
context.BeginFigure(startPoint, false);
context.LineTo(endPoint);
context.EndFigure(false);
}
PlatformImpl = impl;
}
/// <inheritdoc/>
public override Rect Bounds => new Rect(_startPoint, _endPoint);
/// <inheritdoc/>
public override Geometry Clone()
{
return new LineGeometry(Bounds.TopLeft, Bounds.BottomRight);
}
}
}

1
src/Perspex.SceneGraph/Perspex.SceneGraph.csproj

@ -70,6 +70,7 @@
<Compile Include="Media\GradientBrush.cs" />
<Compile Include="Media\GradientSpreadMethod.cs" />
<Compile Include="Media\GradientStop.cs" />
<Compile Include="Media\LineGeometry.cs" />
<Compile Include="Media\RadialGradientBrush.cs" />
<Compile Include="Media\LinearGradientBrush.cs" />
<Compile Include="Media\MediaExtensions.cs" />

4
src/Perspex.Styling/Perspex.Styling.csproj

@ -45,6 +45,8 @@
<Compile Include="ILogical.cs" />
<Compile Include="LogicalTree\LogicalExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Styling\ActivatedSubject.cs" />
<Compile Include="Styling\ActivatedValue.cs" />
<Compile Include="Styling\IGlobalStyles.cs" />
<Compile Include="Styling\ISetter.cs" />
<Compile Include="Styling\IStyle.cs" />
@ -59,7 +61,7 @@
<Compile Include="Styling\Setter.cs" />
<Compile Include="Styling\Style.cs" />
<Compile Include="Styling\StyleActivator.cs" />
<Compile Include="Styling\StyleBinding.cs" />
<Compile Include="Styling\ActivatedObservable.cs" />
<Compile Include="Styling\Styler.cs" />
<Compile Include="Styling\Styles.cs" />
</ItemGroup>

74
src/Perspex.Styling/Styling/ActivatedObservable.cs

@ -0,0 +1,74 @@
// 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.Reactive;
using System.Reactive.Linq;
namespace Perspex.Styling
{
/// <summary>
/// An observable which is switched on or off according to an activator observable.
/// </summary>
/// <remarks>
/// An <see cref="ActivatedObservable"/> has two inputs: an activator observable a
/// <see cref="Source"/> observable which produces the activated value. When the activator
/// produces true, the <see cref="ActivatedObservable"/> will produce the current activated
/// value. When the activator produces false it will produce
/// <see cref="PerspexProperty.UnsetValue"/>.
/// </remarks>
internal class ActivatedObservable : ObservableBase<object>, IDescription
{
/// <summary>
/// Initializes a new instance of the <see cref="ActivatedObservable"/> class.
/// </summary>
/// <param name="activator">The activator.</param>
/// <param name="source">An observable that produces the activated value.</param>
/// <param name="description">The binding description.</param>
public ActivatedObservable(
IObservable<bool> activator,
IObservable<object> source,
string description)
{
Activator = activator;
Description = description;
Source = source;
}
/// <summary>
/// Gets the activator observable.
/// </summary>
public IObservable<bool> Activator { get; }
/// <summary>
/// Gets a description of the binding.
/// </summary>
public string Description { get; }
/// <summary>
/// Gets an observable which produces the <see cref="ActivatedValue"/>.
/// </summary>
public IObservable<object> Source { get; }
/// <summary>
/// Notifies the provider that an observer is to receive notifications.
/// </summary>
/// <param name="observer">The observer.</param>
/// <returns>IDisposable object used to unsubscribe from the observable sequence.</returns>
protected override IDisposable SubscribeCore(IObserver<object> observer)
{
Contract.Requires<ArgumentNullException>(observer != null);
var sourceCompleted = Source.TakeLast(1).Select(_ => Unit.Default);
var activatorCompleted = Activator.TakeLast(1).Select(_ => Unit.Default);
var completed = sourceCompleted.Merge(activatorCompleted);
return Activator
.CombineLatest(Source, (x, y) => new { Active = x, Value = y })
.Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue)
.DistinctUntilChanged()
.TakeUntil(completed)
.Subscribe(observer);
}
}
}

111
src/Perspex.Styling/Styling/ActivatedSubject.cs

@ -0,0 +1,111 @@
// 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.Reactive.Linq;
using System.Reactive.Subjects;
namespace Perspex.Styling
{
/// <summary>
/// A subject which is switched on or off according to an activator observable.
/// </summary>
/// <remarks>
/// An <see cref="ActivatedSubject"/> has two inputs: an activator observable and either an
/// <see cref="ActivatedValue"/> or a <see cref="Source"/> observable which produces the
/// activated value. When the activator produces true, the <see cref="ActivatedObservable"/> will
/// produce the current activated value. When the activator produces false it will produce
/// <see cref="PerspexProperty.UnsetValue"/>.
/// </remarks>
internal class ActivatedSubject : ActivatedObservable, ISubject<object>, IDescription
{
private bool? _active;
private bool _completed;
private object _value;
/// <summary>
/// Initializes a new instance of the <see cref="ActivatedSubject"/> class.
/// </summary>
/// <param name="activator">The activator.</param>
/// <param name="source">An observable that produces the activated value.</param>
/// <param name="description">The binding description.</param>
public ActivatedSubject(
IObservable<bool> activator,
ISubject<object> source,
string description)
: base(activator, source, description)
{
Activator.Subscribe(ActivatorChanged, ActivatorError, ActivatorCompleted);
}
/// <summary>
/// Gets the underlying subject.
/// </summary>
public new ISubject<object> Source
{
get { return (ISubject<object>)base.Source; }
}
/// <summary>
/// Notifies all subscribed observers about the end of the sequence.
/// </summary>
public void OnCompleted()
{
if (_active.Value && !_completed)
{
Source.OnCompleted();
}
}
/// <summary>
/// Notifies all subscribed observers with the exception.
/// </summary>
/// <param name="error">The exception to send to all subscribed observers.</param>
/// <exception cref="ArgumentNullException"><paramref name="error"/> is null.</exception>
public void OnError(Exception error)
{
if (_active.Value && !_completed)
{
Source.OnError(error);
}
}
/// <summary>
/// Notifies all subscribed observers with the value.
/// </summary>
/// <param name="value">The value to send to all subscribed observers.</param>
public void OnNext(object value)
{
_value = value;
if (_active.Value && !_completed)
{
Source.OnNext(value);
}
}
private void ActivatorChanged(bool active)
{
bool first = !_active.HasValue;
_active = active;
if (!first)
{
Source.OnNext(active ? _value : PerspexProperty.UnsetValue);
}
}
private void ActivatorCompleted()
{
_completed = true;
Source.OnCompleted();
}
private void ActivatorError(Exception e)
{
_completed = true;
Source.OnError(e);
}
}
}

72
src/Perspex.Styling/Styling/ActivatedValue.cs

@ -0,0 +1,72 @@
// 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.Reactive;
using System.Reactive.Linq;
namespace Perspex.Styling
{
/// <summary>
/// An value which is switched on or off according to an activator observable.
/// </summary>
/// <remarks>
/// An <see cref="ActivatedValue"/> has two inputs: an activator observable and an
/// <see cref="Value"/>. When the activator produces true, the
/// <see cref="ActivatedValue"/> will produce the current value. When the activator
/// produces false it will produce <see cref="PerspexProperty.UnsetValue"/>.
/// </remarks>
internal class ActivatedValue : ObservableBase<object>, IDescription
{
/// <summary>
/// The activator.
/// </summary>
private readonly IObservable<bool> _activator;
/// <summary>
/// Initializes a new instance of the <see cref="ActivatedObservable"/> class.
/// </summary>
/// <param name="activator">The activator.</param>
/// <param name="value">The activated value.</param>
/// <param name="description">The binding description.</param>
public ActivatedValue(
IObservable<bool> activator,
object value,
string description)
{
_activator = activator;
Value = value;
Description = description;
}
/// <summary>
/// Gets the activated value.
/// </summary>
public object Value
{
get;
}
/// <summary>
/// Gets a description of the binding.
/// </summary>
public string Description
{
get;
}
/// <summary>
/// Notifies the provider that an observer is to receive notifications.
/// </summary>
/// <param name="observer">The observer.</param>
/// <returns>IDisposable object used to unsubscribe from the observable sequence.</returns>
protected override IDisposable SubscribeCore(IObserver<object> observer)
{
Contract.Requires<ArgumentNullException>(observer != null);
return _activator
.Select(active => active ? Value : PerspexProperty.UnsetValue)
.Subscribe(observer);
}
}
}

24
src/Perspex.Styling/Styling/Setter.cs

@ -2,8 +2,10 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Subjects;
using Perspex.Data;
using Perspex.Metadata;
using Perspex.Reactive;
namespace Perspex.Styling
{
@ -62,6 +64,10 @@ namespace Perspex.Styling
/// <param name="activator">An optional activator.</param>
public void Apply(IStyle style, IStyleable control, IObservable<bool> activator)
{
Contract.Requires<ArgumentNullException>(control != null);
var description = style?.ToString();
if (Property == null)
{
throw new InvalidOperationException("Setter.Property must be set.");
@ -77,8 +83,9 @@ namespace Perspex.Styling
}
else
{
throw new NotSupportedException(
"Setter bindings with activators not yet supported.");
var subject = binding.CreateSubject(control, Property);
var activated = new ActivatedSubject(activator, subject, description);
Bind(control, Property, binding, activated);
}
}
else
@ -89,13 +96,22 @@ namespace Perspex.Styling
}
else
{
var activated = new StyleBinding(activator, Value, style.ToString());
var activated = new ActivatedValue(activator, Value, description);
control.Bind(Property, activated, BindingPriority.StyleTrigger);
}
}
}
private void Bind(IStyleable control, PerspexProperty property, IBinding binding)
{
Bind(control, property, binding, binding.CreateSubject(control, property));
}
private void Bind(
IStyleable control,
PerspexProperty property,
IBinding binding,
ISubject<object> subject)
{
var mode = binding.Mode;
@ -106,7 +122,7 @@ namespace Perspex.Styling
control.Bind(
property,
binding.CreateSubject(control, property),
subject,
mode,
binding.Priority);
}

107
src/Perspex.Styling/Styling/StyleBinding.cs

@ -1,107 +0,0 @@
// 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.Reactive;
using System.Reactive.Linq;
namespace Perspex.Styling
{
/// <summary>
/// Provides an observable for a style.
/// </summary>
/// <remarks>
/// A <see cref="StyleBinding"/> has two inputs: an activator observable and either an
/// <see cref="ActivatedValue"/> or a <see cref="Source"/> observable which produces the
/// activated value. When the activator produces true, the <see cref="StyleBinding"/> will
/// produce the current activated value. When the activator produces false it will produce
/// <see cref="PerspexProperty.UnsetValue"/>.
/// </remarks>
internal class StyleBinding : ObservableBase<object>, IDescription
{
/// <summary>
/// The activator.
/// </summary>
private readonly IObservable<bool> _activator;
/// <summary>
/// Initializes a new instance of the <see cref="StyleBinding"/> class.
/// </summary>
/// <param name="activator">The activator.</param>
/// <param name="activatedValue">The activated value.</param>
/// <param name="description">The binding description.</param>
public StyleBinding(
IObservable<bool> activator,
object activatedValue,
string description)
{
_activator = activator;
ActivatedValue = activatedValue;
Description = description;
}
/// <summary>
/// Initializes a new instance of the <see cref="StyleBinding"/> class.
/// </summary>
/// <param name="activator">The activator.</param>
/// <param name="source">An observable that produces the activated value.</param>
/// <param name="description">The binding description.</param>
public StyleBinding(
IObservable<bool> activator,
IObservable<object> source,
string description)
{
_activator = activator;
Description = description;
Source = source;
}
/// <summary>
/// Gets the activated value.
/// </summary>
public object ActivatedValue
{
get;
}
/// <summary>
/// Gets a description of the binding.
/// </summary>
public string Description
{
get;
}
/// <summary>
/// Gets an observable which produces the <see cref="ActivatedValue"/>.
/// </summary>
public IObservable<object> Source
{
get;
}
/// <summary>
/// Notifies the provider that an observer is to receive notifications.
/// </summary>
/// <param name="observer">The observer.</param>
/// <returns>IDisposable object used to unsubscribe from the observable sequence.</returns>
protected override IDisposable SubscribeCore(IObserver<object> observer)
{
Contract.Requires<ArgumentNullException>(observer != null);
if (Source == null)
{
return _activator
.Select(active => active ? ActivatedValue : PerspexProperty.UnsetValue)
.Subscribe(observer);
}
else
{
return _activator
.CombineLatest(Source, (x, y) => new { Active = x, Value = y })
.Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue)
.Subscribe(observer);
}
}
}
}

30
tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs

@ -82,7 +82,7 @@ namespace Perspex.Base.UnitTests
}
[Fact]
public void Two_Way_Binding_Works()
public void Two_Way_Separate_Binding_Works()
{
Class1 obj1 = new Class1();
Class1 obj2 = new Class1();
@ -143,34 +143,6 @@ namespace Perspex.Base.UnitTests
Assert.Equal("third", obj2.GetValue(Class1.FooProperty));
}
[Fact]
public void BindTwoWay_Gets_Initial_Value_From_Source()
{
Class1 source = new Class1();
Class1 target = new Class1();
source.SetValue(Class1.FooProperty, "initial");
target.BindTwoWay(Class1.FooProperty, source, Class1.FooProperty);
Assert.Equal("initial", target.GetValue(Class1.FooProperty));
}
[Fact]
public void BindTwoWay_Updates_Values()
{
Class1 source = new Class1();
Class1 target = new Class1();
source.SetValue(Class1.FooProperty, "first");
target.BindTwoWay(Class1.FooProperty, source, Class1.FooProperty);
Assert.Equal("first", target.GetValue(Class1.FooProperty));
source.SetValue(Class1.FooProperty, "second");
Assert.Equal("second", target.GetValue(Class1.FooProperty));
target.SetValue(Class1.FooProperty, "third");
Assert.Equal("third", source.GetValue(Class1.FooProperty));
}
[Fact]
public void Local_Binding_Overwrites_Local_Value()
{

3
tests/Perspex.LeakTests/ControlTests.cs

@ -273,8 +273,7 @@ namespace Perspex.LeakTests
{
new FuncTreeDataTemplate<Node>(
x => new TextBlock { Text = x.Name },
x => x.Children,
x => true)
x => x.Children)
},
Items = nodes
}

87
tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs

@ -2,7 +2,11 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Linq;
using System.Reactive.Linq;
using Moq;
using Perspex.Controls;
using Perspex.Controls.Primitives;
using Perspex.Data;
using Perspex.Markup.Xaml.Data;
using Perspex.Platform;
using Perspex.Styling;
@ -29,5 +33,88 @@ namespace Perspex.Markup.Xaml.UnitTests
Assert.IsType<Binding>(setter.Value);
}
}
[Fact]
public void Setter_With_TwoWay_Binding_Should_Update_Source()
{
using (PerspexLocator.EnterScope())
{
PerspexLocator.CurrentMutable
.Bind<IPlatformThreadingInterface>()
.ToConstant(Mock.Of<IPlatformThreadingInterface>(x =>
x.CurrentThreadIsLoopThread == true));
var data = new Data
{
Foo = "foo",
};
var control = new TextBox
{
DataContext = data,
};
var setter = new Setter
{
Property = TextBox.TextProperty,
Value = new Binding
{
Path = "Foo",
Mode = BindingMode.TwoWay
}
};
setter.Apply(null, control, null);
Assert.Equal("foo", control.Text);
control.Text = "bar";
Assert.Equal("bar", data.Foo);
}
}
[Fact]
public void Setter_With_TwoWay_Binding_And_Activator_Should_Update_Source()
{
using (PerspexLocator.EnterScope())
{
PerspexLocator.CurrentMutable
.Bind<IPlatformThreadingInterface>()
.ToConstant(Mock.Of<IPlatformThreadingInterface>(x =>
x.CurrentThreadIsLoopThread == true));
var data = new Data
{
Foo = "foo",
};
var control = new TextBox
{
DataContext = data,
};
var setter = new Setter
{
Property = TextBox.TextProperty,
Value = new Binding
{
Path = "Foo",
Mode = BindingMode.TwoWay
}
};
var activator = Observable.Never<bool>().StartWith(true);
setter.Apply(null, control, activator);
Assert.Equal("foo", control.Text);
control.Text = "bar";
Assert.Equal("bar", data.Foo);
}
}
private class Data
{
public string Foo { get; set; }
}
}
}

1
tests/Perspex.RenderTests/Perspex.Cairo.RenderTests.csproj

@ -71,6 +71,7 @@
<Compile Include="Media\ImageBrushTests.cs" />
<Compile Include="Media\LinearGradientBrushTests.cs" />
<Compile Include="Media\VisualBrushTests.cs" />
<Compile Include="Shapes\LineTests.cs" />
<Compile Include="Shapes\PathTests.cs" />
<Compile Include="Shapes\EllipseTests.cs" />
<Compile Include="Shapes\RectangleTests.cs" />

1
tests/Perspex.RenderTests/Perspex.Direct2D1.RenderTests.csproj

@ -76,6 +76,7 @@
<Compile Include="Controls\ImageTests.cs" />
<Compile Include="Controls\BorderTests.cs" />
<Compile Include="Media\LinearGradientBrushTests.cs" />
<Compile Include="Shapes\LineTests.cs" />
<Compile Include="Shapes\PathTests.cs" />
<Compile Include="Shapes\EllipseTests.cs" />
<Compile Include="Shapes\RectangleTests.cs" />

1
tests/Perspex.RenderTests/Perspex.Skia.RenderTests.csproj

@ -68,6 +68,7 @@
<Compile Include="Media\LinearGradientBrushTests.cs" />
<Compile Include="Media\VisualBrushTests.cs" />
<Compile Include="Shapes\EllipseTests.cs" />
<Compile Include="Shapes\LineTests.cs" />
<Compile Include="Shapes\PathTests.cs" />
<Compile Include="Shapes\RectangleTests.cs" />
<Compile Include="TestBase.cs" />

43
tests/Perspex.RenderTests/Shapes/LineTests.cs

@ -0,0 +1,43 @@
// 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;
using Perspex.Controls.Shapes;
using Perspex.Media;
using Xunit;
#if PERSPEX_CAIRO
namespace Perspex.Cairo.RenderTests.Shapes
#elif PERSPEX_SKIA
namespace Perspex.Skia.RenderTests
#else
namespace Perspex.Direct2D1.RenderTests.Shapes
#endif
{
public class LineTests : TestBase
{
public LineTests()
: base(@"Shapes\Line")
{
}
[Fact]
public void Line_1px_Stroke()
{
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Line
{
Stroke = Brushes.Black,
StrokeThickness = 1,
}
};
RenderToFile(target);
CompareImages();
}
}
}

70
tests/Perspex.Styling.UnitTests/ActivatedObservableTests.cs

@ -0,0 +1,70 @@
// 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 System.Reactive.Subjects;
using Xunit;
namespace Perspex.Styling.UnitTests
{
public class ActivatedObservableTests
{
[Fact]
public void Should_Produce_Correct_Values()
{
var activator = new BehaviorSubject<bool>(false);
var source = new BehaviorSubject<object>(1);
var target = new ActivatedObservable(activator, source, string.Empty);
var result = new List<object>();
target.Subscribe(x => result.Add(x));
activator.OnNext(true);
source.OnNext(2);
activator.OnNext(false);
source.OnNext(3);
activator.OnNext(true);
Assert.Equal(
new[]
{
PerspexProperty.UnsetValue,
1,
2,
PerspexProperty.UnsetValue,
3,
},
result);
}
[Fact]
public void Should_Complete_When_Source_Completes()
{
var activator = new BehaviorSubject<bool>(false);
var source = new BehaviorSubject<object>(1);
var target = new ActivatedObservable(activator, source, string.Empty);
var completed = false;
target.Subscribe(_ => { }, () => completed = true);
source.OnCompleted();
Assert.True(completed);
}
[Fact]
public void Should_Complete_When_Activator_Completes()
{
var activator = new BehaviorSubject<bool>(false);
var source = new BehaviorSubject<object>(1);
var target = new ActivatedObservable(activator, source, string.Empty);
var completed = false;
target.Subscribe(_ => { }, () => completed = true);
activator.OnCompleted();
Assert.True(completed);
}
}
}

98
tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs

@ -0,0 +1,98 @@
// 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.Reactive.Disposables;
using System.Reactive.Subjects;
using Xunit;
namespace Perspex.Styling.UnitTests
{
public class ActivatedSubjectTests
{
[Fact]
public void Should_Set_Values()
{
var activator = new BehaviorSubject<bool>(false);
var source = new TestSubject();
var target = new ActivatedSubject(activator, source, string.Empty);
target.OnNext("bar");
Assert.Equal(PerspexProperty.UnsetValue, source.Value);
activator.OnNext(true);
target.OnNext("baz");
Assert.Equal("baz", source.Value);
activator.OnNext(false);
Assert.Equal(PerspexProperty.UnsetValue, source.Value);
target.OnNext("bax");
activator.OnNext(true);
Assert.Equal("bax", source.Value);
}
[Fact]
public void Should_Invoke_OnCompleted_On_Activator_Completed()
{
var activator = new BehaviorSubject<bool>(false);
var source = new TestSubject();
var target = new ActivatedSubject(activator, source, string.Empty);
activator.OnCompleted();
Assert.True(source.Completed);
}
[Fact]
public void Should_Invoke_OnError_On_Activator_Error()
{
var activator = new BehaviorSubject<bool>(false);
var source = new TestSubject();
var target = new ActivatedSubject(activator, source, string.Empty);
activator.OnError(new Exception());
Assert.NotNull(source.Error);
}
private class Class1 : PerspexObject
{
public static readonly PerspexProperty<string> FooProperty =
PerspexProperty.Register<Class1, string>("Foo", "foodefault");
public string Foo
{
get { return GetValue(FooProperty); }
set { SetValue(FooProperty, value); }
}
}
private class TestSubject : ISubject<object>
{
private IObserver<object> _observer;
public bool Completed { get; set; }
public Exception Error { get; set; }
public object Value { get; set; } = PerspexProperty.UnsetValue;
public void OnCompleted()
{
Completed = true;
}
public void OnError(Exception error)
{
Error = error;
}
public void OnNext(object value)
{
Value = value;
}
public IDisposable Subscribe(IObserver<object> observer)
{
_observer = observer;
return Disposable.Empty;
}
}
}
}

42
tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs

@ -0,0 +1,42 @@
// 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 System.Reactive.Subjects;
using Xunit;
namespace Perspex.Styling.UnitTests
{
public class ActivatedValueTests
{
[Fact]
public void Should_Produce_Correct_Values()
{
var activator = new BehaviorSubject<bool>(false);
var target = new ActivatedValue(activator, 1, string.Empty);
var result = new List<object>();
target.Subscribe(x => result.Add(x));
activator.OnNext(true);
activator.OnNext(false);
Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, PerspexProperty.UnsetValue }, result);
}
[Fact]
public void Should_Complete_When_Activator_Completes()
{
var activator = new BehaviorSubject<bool>(false);
var target = new ActivatedValue(activator, 1, string.Empty);
var completed = false;
target.Subscribe(_ => { }, () => completed = true);
activator.OnCompleted();
Assert.True(completed);
}
}
}

4
tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj

@ -80,6 +80,8 @@
<Otherwise />
</Choose>
<ItemGroup>
<Compile Include="ActivatedValueTests.cs" />
<Compile Include="ActivatedSubjectTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SelectorTests_Class.cs" />
<Compile Include="SelectorTests_Child.cs" />
@ -90,7 +92,7 @@
<Compile Include="SelectorTests_OfType.cs" />
<Compile Include="SelectorTests_Template.cs" />
<Compile Include="StyleActivatorTests.cs" />
<Compile Include="StyleBindingTests.cs" />
<Compile Include="ActivatedObservableTests.cs" />
<Compile Include="SetterTests.cs" />
<Compile Include="StyleTests.cs" />
<Compile Include="TestControlBase.cs" />

79
tests/Perspex.Styling.UnitTests/StyleBindingTests.cs

@ -1,79 +0,0 @@
// 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 System.Reactive.Subjects;
using Xunit;
namespace Perspex.Styling.UnitTests
{
public class StyleBindingTests
{
[Fact]
public async void Should_Produce_UnsetValue_On_Activator_False()
{
var activator = new BehaviorSubject<bool>(false);
var target = new StyleBinding(activator, 1, string.Empty);
var result = await target.Take(1);
Assert.Equal(PerspexProperty.UnsetValue, result);
}
[Fact]
public async void Should_Produce_Value_On_Activator_True()
{
var activator = new BehaviorSubject<bool>(true);
var target = new StyleBinding(activator, 1, string.Empty);
var result = await target.Take(1);
Assert.Equal(1, result);
}
[Fact]
public void Should_Change_Value_On_Activator_Change()
{
var activator = new BehaviorSubject<bool>(false);
var target = new StyleBinding(activator, 1, string.Empty);
var result = new List<object>();
target.Subscribe(x => result.Add(x));
activator.OnNext(true);
activator.OnNext(false);
Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, PerspexProperty.UnsetValue }, result);
}
[Fact]
public void Should_Change_Value_With_Source_Observable()
{
var activator = new BehaviorSubject<bool>(false);
var source = new BehaviorSubject<object>(1);
var target = new StyleBinding(activator, source, string.Empty);
var result = new List<object>();
target.Subscribe(x => result.Add(x));
activator.OnNext(true);
source.OnNext(2);
activator.OnNext(false);
Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, 2, PerspexProperty.UnsetValue }, result);
}
[Fact]
public void Should_Complete_When_Activator_Completes()
{
var activator = new BehaviorSubject<bool>(false);
var target = new StyleBinding(activator, 1, string.Empty);
var completed = false;
target.Subscribe(_ => { }, () => completed = true);
activator.OnCompleted();
Assert.True(completed);
}
}
}

BIN
tests/TestFiles/Cairo/Shapes/Line/Line_1px_Stroke.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

BIN
tests/TestFiles/Direct2D1/Shapes/Line/Line_1px_Stroke.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

BIN
tests/TestFiles/Skia/Shapes/Line/Line_1px_Stroke.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Loading…
Cancel
Save