diff --git a/samples/TestApplicationShared/App.cs b/samples/TestApplicationShared/App.cs index f8b64146d4..6429b76b32 100644 --- a/samples/TestApplicationShared/App.cs +++ b/samples/TestApplicationShared/App.cs @@ -21,11 +21,8 @@ namespace TestApplication { new FuncTreeDataTemplate( x => new TextBlock {Text = x.Name}, - x => x.Children, - x => true), + x => x.Children), }; } - - } } diff --git a/samples/TestApplicationShared/MainWindow.cs b/samples/TestApplicationShared/MainWindow.cs index 4650df1667..0a6ebeae2c 100644 --- a/samples/TestApplicationShared/MainWindow.cs +++ b/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 + } } }, } diff --git a/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs b/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs index 1523299d41..9ac24a622e 100644 --- a/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs +++ b/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 Items { get; } public List Nodes { get; } + + public ReactiveCommand CollapseNodesCommand { get; } + + public ReactiveCommand 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); + } + } + } } } diff --git a/samples/XamlTestApplicationPcl/ViewModels/TestNode.cs b/samples/XamlTestApplicationPcl/ViewModels/TestNode.cs index 953bfd0f58..c2e0f4f15c 100644 --- a/samples/XamlTestApplicationPcl/ViewModels/TestNode.cs +++ b/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 Children { get; set; } + + public bool IsExpanded + { + get { return _isExpanded; } + set { this.RaiseAndSetIfChanged(ref this._isExpanded, value); } + } } } \ No newline at end of file diff --git a/samples/XamlTestApplicationPcl/Views/MainWindow.paml b/samples/XamlTestApplicationPcl/Views/MainWindow.paml index 98633354b1..584c43c3af 100644 --- a/samples/XamlTestApplicationPcl/Views/MainWindow.paml +++ b/samples/XamlTestApplicationPcl/Views/MainWindow.paml @@ -147,6 +147,11 @@ + + + @@ -156,6 +161,10 @@ + + + + diff --git a/samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj b/samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj index a32ae5eec6..c7fb0e8484 100644 --- a/samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj +++ b/samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj @@ -123,6 +123,22 @@ ..\..\packages\Splat.1.6.2\lib\Portable-net45+win+wpa81+wp80\Splat.dll True + + ..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll + True + + + ..\..\packages\Rx-Interfaces.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Interfaces.dll + True + + + ..\..\packages\Rx-Linq.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Linq.dll + True + + + ..\..\packages\Rx-PlatformServices.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.PlatformServices.dll + True + diff --git a/samples/XamlTestApplicationPcl/packages.config b/samples/XamlTestApplicationPcl/packages.config index 2115574cc5..749731a770 100644 --- a/samples/XamlTestApplicationPcl/packages.config +++ b/samples/XamlTestApplicationPcl/packages.config @@ -1,4 +1,9 @@  + + + + + \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs index 55b80f5d40..8a254cbf6e 100644 --- a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs +++ b/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) diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs index e43ba9cb03..d8a668da08 100644 --- a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/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; } } \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs index 361b04c915..cec5146c71 100644 --- a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs +++ b/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; } } \ No newline at end of file diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index 1ece57e81d..3a61fbc996 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -76,6 +76,8 @@ + + diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index 5c4271a57d..5310651514 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/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 } } - /// - /// Initiates a two-way binding between s. - /// - /// The property on this object. - /// The source object. - /// The property on the source object. - /// The priority of the binding. - /// - /// A disposable which can be used to terminate the binding. - /// - /// - /// The binding is first carried out from to this. - /// - 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))); - } - - /// - /// Initiates a two-way binding between a and an - /// . - /// - /// The property on this object. - /// The subject to bind to. - /// The priority of the binding. - /// - /// A disposable which can be used to terminate the binding. - /// - /// - /// The binding is first carried out from to this. - /// - public IDisposable BindTwoWay( - PerspexProperty property, - ISubject 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)); - } - /// /// Forces the specified property to be revalidated. /// diff --git a/src/Perspex.Base/PerspexObjectExtensions.cs b/src/Perspex.Base/PerspexObjectExtensions.cs index d7b2d5a748..6cd75b691d 100644 --- a/src/Perspex.Base/PerspexObjectExtensions.cs +++ b/src/Perspex.Base/PerspexObjectExtensions.cs @@ -103,6 +103,36 @@ namespace Perspex GetDescription(o, property)); } + /// + /// Gets a subject for a . + /// + /// The object. + /// The property. + /// + /// The priority with which binding values are written to the object. + /// + /// + /// An which can be used for two-way binding to/from the + /// property. + /// + public static ISubject GetSubject( + this IPerspexObject o, + PerspexProperty property, + BindingPriority priority = BindingPriority.LocalValue) + { + // TODO: Subject.Create is not yet in stable Rx : once it is, remove the + // AnonymousSubject classes and use Subject.Create. + var output = new Subject(); + var result = new AnonymousSubject( + Observer.Create( + x => output.OnNext(x), + e => output.OnError(e), + () => output.OnCompleted()), + o.GetObservable(property)); + o.Bind(property, output, priority); + return result; + } + /// /// Gets a subject for a . /// @@ -242,54 +272,5 @@ namespace Perspex handler(target)(e); } } - - class AnonymousSubject : ISubject - { - private readonly IObserver _observer; - private readonly IObservable _observable; - - public AnonymousSubject(IObserver observer, IObservable 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 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 : AnonymousSubject, ISubject - { - public AnonymousSubject(IObserver observer, IObservable observable) - : base(observer, observable) - { - } - } } } diff --git a/src/Perspex.Base/Reactive/AnonymousSubject`1.cs b/src/Perspex.Base/Reactive/AnonymousSubject`1.cs new file mode 100644 index 0000000000..3bbf2295ff --- /dev/null +++ b/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 : AnonymousSubject, ISubject + { + public AnonymousSubject(IObserver observer, IObservable observable) + : base(observer, observable) + { + } + } +} diff --git a/src/Perspex.Base/Reactive/AnonymousSubject`2.cs b/src/Perspex.Base/Reactive/AnonymousSubject`2.cs new file mode 100644 index 0000000000..04e58585b3 --- /dev/null +++ b/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 : ISubject + { + private readonly IObserver _observer; + private readonly IObservable _observable; + + public AnonymousSubject(IObserver observer, IObservable 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 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); + } + } +} diff --git a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs index 046d018321..9feaab3d25 100644 --- a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs +++ b/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)) { diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index b563cdbe82..cf44ab2997 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -67,6 +67,7 @@ + diff --git a/src/Perspex.Controls/Primitives/ToggleButton.cs b/src/Perspex.Controls/Primitives/ToggleButton.cs index 601792ba1f..744af915c0 100644 --- a/src/Perspex.Controls/Primitives/ToggleButton.cs +++ b/src/Perspex.Controls/Primitives/ToggleButton.cs @@ -9,21 +9,22 @@ namespace Perspex.Controls.Primitives public class ToggleButton : Button { public static readonly PerspexProperty IsCheckedProperty = - PerspexProperty.Register("IsChecked"); + PerspexProperty.RegisterDirect( + "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) diff --git a/src/Perspex.Controls/Shapes/Line.cs b/src/Perspex.Controls/Shapes/Line.cs new file mode 100644 index 0000000000..785ea7bbd1 --- /dev/null +++ b/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); + } + } +} diff --git a/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs b/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs index d7ed589135..3d17cf5659 100644 --- a/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs +++ b/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs @@ -14,8 +14,6 @@ namespace Perspex.Controls.Templates { private readonly Func _itemsSelector; - private readonly Func _isExpanded; - /// /// Initializes a new instance of the class. /// @@ -35,30 +33,6 @@ namespace Perspex.Controls.Templates { } - /// - /// Initializes a new instance of the class. - /// - /// The type of data which the data template matches. - /// - /// A function which when passed an object of returns a control. - /// - /// - /// A function which when passed an object of returns the child - /// items. - /// - /// - /// A function which when passed an object of returns the the - /// initial expanded state of the node. - /// - public FuncTreeDataTemplate( - Type type, - Func build, - Func itemsSelector, - Func isExpanded) - : this(o => IsInstance(o, type), build, itemsSelector, isExpanded) - { - } - /// /// Initializes a new instance of the class. /// @@ -75,46 +49,9 @@ namespace Perspex.Controls.Templates Func match, Func build, Func itemsSelector) - : this(match, build, itemsSelector, _ => false) - { - _itemsSelector = itemsSelector; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A function which determines whether the data template matches the specified data. - /// - /// - /// A function which when passed a matching object returns a control. - /// - /// - /// A function which when passed a matching object returns the child items. - /// - /// - /// A function which when passed a matching object returns the the initial expanded state - /// of the node. - /// - public FuncTreeDataTemplate( - Func match, - Func build, - Func itemsSelector, - Func isExpanded) : base(match, build) { _itemsSelector = itemsSelector; - _isExpanded = isExpanded; - } - - /// - /// Checks to see if the item should be initially expanded. - /// - /// The item. - /// True if the item should be initially expanded, otherwise false. - public bool IsExpanded(object item) - { - return this?._isExpanded(item) ?? false; } /// diff --git a/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs b/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs index d55cfcf095..481eb673d7 100644 --- a/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs +++ b/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs @@ -32,32 +32,6 @@ namespace Perspex.Controls.Templates { } - /// - /// Initializes a new instance of the class. - /// - /// - /// A function which when passed an object of returns a control. - /// - /// - /// A function which when passed an object of returns the child - /// items. - /// - /// - /// A function which when passed an object of returns the the - /// initial expanded state of the node. - /// - public FuncTreeDataTemplate( - Func build, - Func itemsSelector, - Func isExpanded) - : base( - typeof(T), - Cast(build), - Cast(itemsSelector), - Cast(isExpanded)) - { - } - /// /// Initializes a new instance of the class. /// @@ -81,35 +55,6 @@ namespace Perspex.Controls.Templates { } - /// - /// Initializes a new instance of the class. - /// - /// - /// A function which determines whether the data template matches the specified data. - /// - /// - /// A function which when passed a matching object returns a control. - /// - /// - /// A function which when passed a matching object returns the child items. - /// - /// - /// A function which when passed a matching object returns the the initial expanded state - /// of the node. - /// - public FuncTreeDataTemplate( - Func match, - Func build, - Func itemsSelector, - Func isExpanded) - : base( - CastMatch(match), - Cast(build), - Cast(itemsSelector), - Cast(isExpanded)) - { - } - /// /// Casts a typed match function to an untyped match function. /// diff --git a/src/Perspex.Controls/Templates/ITreeDataTemplate.cs b/src/Perspex.Controls/Templates/ITreeDataTemplate.cs index ca967f60a4..6415ed37b2 100644 --- a/src/Perspex.Controls/Templates/ITreeDataTemplate.cs +++ b/src/Perspex.Controls/Templates/ITreeDataTemplate.cs @@ -10,13 +10,6 @@ namespace Perspex.Controls.Templates /// public interface ITreeDataTemplate : IDataTemplate { - /// - /// Checks to see if the item should be initially expanded. - /// - /// The item. - /// True if the item should be initially expanded, otherwise false. - bool IsExpanded(object item); - /// /// Selects the child items of an item. /// diff --git a/src/Perspex.SceneGraph/Media/LineGeometry.cs b/src/Perspex.SceneGraph/Media/LineGeometry.cs new file mode 100644 index 0000000000..196fdc40eb --- /dev/null +++ b/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 +{ + /// + /// Represents the geometry of a line. + /// + public class LineGeometry : Geometry + { + private Point _startPoint; + private Point _endPoint; + + /// + /// Initializes a new instance of the class. + /// + /// The start point. + /// The end point. + public LineGeometry(Point startPoint, Point endPoint) + { + _startPoint = startPoint; + _endPoint = endPoint; + IPlatformRenderInterface factory = PerspexLocator.Current.GetService(); + IStreamGeometryImpl impl = factory.CreateStreamGeometry(); + + using (IStreamGeometryContextImpl context = impl.Open()) + { + context.BeginFigure(startPoint, false); + context.LineTo(endPoint); + context.EndFigure(false); + } + + PlatformImpl = impl; + } + + /// + public override Rect Bounds => new Rect(_startPoint, _endPoint); + + /// + public override Geometry Clone() + { + return new LineGeometry(Bounds.TopLeft, Bounds.BottomRight); + } + } +} diff --git a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj index a7be9919f3..f3079729e3 100644 --- a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -70,6 +70,7 @@ + diff --git a/src/Perspex.Styling/Perspex.Styling.csproj b/src/Perspex.Styling/Perspex.Styling.csproj index 5f79ca5820..df3c17278d 100644 --- a/src/Perspex.Styling/Perspex.Styling.csproj +++ b/src/Perspex.Styling/Perspex.Styling.csproj @@ -45,6 +45,8 @@ + + @@ -59,7 +61,7 @@ - + diff --git a/src/Perspex.Styling/Styling/ActivatedObservable.cs b/src/Perspex.Styling/Styling/ActivatedObservable.cs new file mode 100644 index 0000000000..97c1b73106 --- /dev/null +++ b/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 +{ + /// + /// An observable which is switched on or off according to an activator observable. + /// + /// + /// An has two inputs: an activator observable a + /// observable which produces the activated value. When the activator + /// produces true, the will produce the current activated + /// value. When the activator produces false it will produce + /// . + /// + internal class ActivatedObservable : ObservableBase, IDescription + { + /// + /// Initializes a new instance of the class. + /// + /// The activator. + /// An observable that produces the activated value. + /// The binding description. + public ActivatedObservable( + IObservable activator, + IObservable source, + string description) + { + Activator = activator; + Description = description; + Source = source; + } + + /// + /// Gets the activator observable. + /// + public IObservable Activator { get; } + + /// + /// Gets a description of the binding. + /// + public string Description { get; } + + /// + /// Gets an observable which produces the . + /// + public IObservable Source { get; } + + /// + /// Notifies the provider that an observer is to receive notifications. + /// + /// The observer. + /// IDisposable object used to unsubscribe from the observable sequence. + protected override IDisposable SubscribeCore(IObserver observer) + { + Contract.Requires(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); + } + } +} diff --git a/src/Perspex.Styling/Styling/ActivatedSubject.cs b/src/Perspex.Styling/Styling/ActivatedSubject.cs new file mode 100644 index 0000000000..3ccbfac6c0 --- /dev/null +++ b/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 +{ + /// + /// A subject which is switched on or off according to an activator observable. + /// + /// + /// An has two inputs: an activator observable and either an + /// or a observable which produces the + /// activated value. When the activator produces true, the will + /// produce the current activated value. When the activator produces false it will produce + /// . + /// + internal class ActivatedSubject : ActivatedObservable, ISubject, IDescription + { + private bool? _active; + private bool _completed; + private object _value; + + /// + /// Initializes a new instance of the class. + /// + /// The activator. + /// An observable that produces the activated value. + /// The binding description. + public ActivatedSubject( + IObservable activator, + ISubject source, + string description) + : base(activator, source, description) + { + Activator.Subscribe(ActivatorChanged, ActivatorError, ActivatorCompleted); + } + + /// + /// Gets the underlying subject. + /// + public new ISubject Source + { + get { return (ISubject)base.Source; } + } + + /// + /// Notifies all subscribed observers about the end of the sequence. + /// + public void OnCompleted() + { + if (_active.Value && !_completed) + { + Source.OnCompleted(); + } + } + + /// + /// Notifies all subscribed observers with the exception. + /// + /// The exception to send to all subscribed observers. + /// is null. + public void OnError(Exception error) + { + if (_active.Value && !_completed) + { + Source.OnError(error); + } + } + + /// + /// Notifies all subscribed observers with the value. + /// + /// The value to send to all subscribed observers. + 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); + } + } +} diff --git a/src/Perspex.Styling/Styling/ActivatedValue.cs b/src/Perspex.Styling/Styling/ActivatedValue.cs new file mode 100644 index 0000000000..0c02598086 --- /dev/null +++ b/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 +{ + /// + /// An value which is switched on or off according to an activator observable. + /// + /// + /// An has two inputs: an activator observable and an + /// . When the activator produces true, the + /// will produce the current value. When the activator + /// produces false it will produce . + /// + internal class ActivatedValue : ObservableBase, IDescription + { + /// + /// The activator. + /// + private readonly IObservable _activator; + + /// + /// Initializes a new instance of the class. + /// + /// The activator. + /// The activated value. + /// The binding description. + public ActivatedValue( + IObservable activator, + object value, + string description) + { + _activator = activator; + Value = value; + Description = description; + } + + /// + /// Gets the activated value. + /// + public object Value + { + get; + } + + /// + /// Gets a description of the binding. + /// + public string Description + { + get; + } + + /// + /// Notifies the provider that an observer is to receive notifications. + /// + /// The observer. + /// IDisposable object used to unsubscribe from the observable sequence. + protected override IDisposable SubscribeCore(IObserver observer) + { + Contract.Requires(observer != null); + + return _activator + .Select(active => active ? Value : PerspexProperty.UnsetValue) + .Subscribe(observer); + } + } +} diff --git a/src/Perspex.Styling/Styling/Setter.cs b/src/Perspex.Styling/Styling/Setter.cs index e93729cf6e..b224382f4a 100644 --- a/src/Perspex.Styling/Styling/Setter.cs +++ b/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 /// An optional activator. public void Apply(IStyle style, IStyleable control, IObservable activator) { + Contract.Requires(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 subject) { var mode = binding.Mode; @@ -106,7 +122,7 @@ namespace Perspex.Styling control.Bind( property, - binding.CreateSubject(control, property), + subject, mode, binding.Priority); } diff --git a/src/Perspex.Styling/Styling/StyleBinding.cs b/src/Perspex.Styling/Styling/StyleBinding.cs deleted file mode 100644 index b2ddbeee1b..0000000000 --- a/src/Perspex.Styling/Styling/StyleBinding.cs +++ /dev/null @@ -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 -{ - /// - /// Provides an observable for a style. - /// - /// - /// A has two inputs: an activator observable and either an - /// or a observable which produces the - /// activated value. When the activator produces true, the will - /// produce the current activated value. When the activator produces false it will produce - /// . - /// - internal class StyleBinding : ObservableBase, IDescription - { - /// - /// The activator. - /// - private readonly IObservable _activator; - - /// - /// Initializes a new instance of the class. - /// - /// The activator. - /// The activated value. - /// The binding description. - public StyleBinding( - IObservable activator, - object activatedValue, - string description) - { - _activator = activator; - ActivatedValue = activatedValue; - Description = description; - } - - /// - /// Initializes a new instance of the class. - /// - /// The activator. - /// An observable that produces the activated value. - /// The binding description. - public StyleBinding( - IObservable activator, - IObservable source, - string description) - { - _activator = activator; - Description = description; - Source = source; - } - - /// - /// Gets the activated value. - /// - public object ActivatedValue - { - get; - } - - /// - /// Gets a description of the binding. - /// - public string Description - { - get; - } - - /// - /// Gets an observable which produces the . - /// - public IObservable Source - { - get; - } - - /// - /// Notifies the provider that an observer is to receive notifications. - /// - /// The observer. - /// IDisposable object used to unsubscribe from the observable sequence. - protected override IDisposable SubscribeCore(IObserver observer) - { - Contract.Requires(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); - } - } - } -} diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs index 6788e509e2..3e7ee6f86d 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs +++ b/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() { diff --git a/tests/Perspex.LeakTests/ControlTests.cs b/tests/Perspex.LeakTests/ControlTests.cs index a6275f452c..24bcda978b 100644 --- a/tests/Perspex.LeakTests/ControlTests.cs +++ b/tests/Perspex.LeakTests/ControlTests.cs @@ -273,8 +273,7 @@ namespace Perspex.LeakTests { new FuncTreeDataTemplate( x => new TextBlock { Text = x.Name }, - x => x.Children, - x => true) + x => x.Children) }, Items = nodes } diff --git a/tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs index 63c8751d82..a496f26fc5 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs +++ b/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(setter.Value); } } + + [Fact] + public void Setter_With_TwoWay_Binding_Should_Update_Source() + { + using (PerspexLocator.EnterScope()) + { + PerspexLocator.CurrentMutable + .Bind() + .ToConstant(Mock.Of(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() + .ToConstant(Mock.Of(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().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; } + } } } diff --git a/tests/Perspex.RenderTests/Perspex.Cairo.RenderTests.csproj b/tests/Perspex.RenderTests/Perspex.Cairo.RenderTests.csproj index 60b3b02b11..dd6953d355 100644 --- a/tests/Perspex.RenderTests/Perspex.Cairo.RenderTests.csproj +++ b/tests/Perspex.RenderTests/Perspex.Cairo.RenderTests.csproj @@ -71,6 +71,7 @@ + diff --git a/tests/Perspex.RenderTests/Perspex.Direct2D1.RenderTests.csproj b/tests/Perspex.RenderTests/Perspex.Direct2D1.RenderTests.csproj index eb948aec81..2abf92fdb5 100644 --- a/tests/Perspex.RenderTests/Perspex.Direct2D1.RenderTests.csproj +++ b/tests/Perspex.RenderTests/Perspex.Direct2D1.RenderTests.csproj @@ -76,6 +76,7 @@ + diff --git a/tests/Perspex.RenderTests/Perspex.Skia.RenderTests.csproj b/tests/Perspex.RenderTests/Perspex.Skia.RenderTests.csproj index 14dd170a8f..08bba41fce 100644 --- a/tests/Perspex.RenderTests/Perspex.Skia.RenderTests.csproj +++ b/tests/Perspex.RenderTests/Perspex.Skia.RenderTests.csproj @@ -68,6 +68,7 @@ + diff --git a/tests/Perspex.RenderTests/Shapes/LineTests.cs b/tests/Perspex.RenderTests/Shapes/LineTests.cs new file mode 100644 index 0000000000..80eb7c75ca --- /dev/null +++ b/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(); + } + } +} diff --git a/tests/Perspex.Styling.UnitTests/ActivatedObservableTests.cs b/tests/Perspex.Styling.UnitTests/ActivatedObservableTests.cs new file mode 100644 index 0000000000..e1beb992c2 --- /dev/null +++ b/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(false); + var source = new BehaviorSubject(1); + var target = new ActivatedObservable(activator, source, string.Empty); + var result = new List(); + + 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(false); + var source = new BehaviorSubject(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(false); + var source = new BehaviorSubject(1); + var target = new ActivatedObservable(activator, source, string.Empty); + var completed = false; + + target.Subscribe(_ => { }, () => completed = true); + activator.OnCompleted(); + + Assert.True(completed); + } + } +} diff --git a/tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs b/tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs new file mode 100644 index 0000000000..58b7d9fd46 --- /dev/null +++ b/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(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(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(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 FooProperty = + PerspexProperty.Register("Foo", "foodefault"); + + public string Foo + { + get { return GetValue(FooProperty); } + set { SetValue(FooProperty, value); } + } + } + + private class TestSubject : ISubject + { + private IObserver _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 observer) + { + _observer = observer; + return Disposable.Empty; + } + } + } +} diff --git a/tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs b/tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs new file mode 100644 index 0000000000..4921aeaa7b --- /dev/null +++ b/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(false); + var target = new ActivatedValue(activator, 1, string.Empty); + var result = new List(); + + 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(false); + var target = new ActivatedValue(activator, 1, string.Empty); + var completed = false; + + target.Subscribe(_ => { }, () => completed = true); + activator.OnCompleted(); + + Assert.True(completed); + } + } +} diff --git a/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj b/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj index b9e82d589d..af640ebdf4 100644 --- a/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj +++ b/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj @@ -80,6 +80,8 @@ + + @@ -90,7 +92,7 @@ - + diff --git a/tests/Perspex.Styling.UnitTests/StyleBindingTests.cs b/tests/Perspex.Styling.UnitTests/StyleBindingTests.cs deleted file mode 100644 index 82a7f66d07..0000000000 --- a/tests/Perspex.Styling.UnitTests/StyleBindingTests.cs +++ /dev/null @@ -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(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(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(false); - var target = new StyleBinding(activator, 1, string.Empty); - var result = new List(); - - 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(false); - var source = new BehaviorSubject(1); - var target = new StyleBinding(activator, source, string.Empty); - var result = new List(); - - 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(false); - var target = new StyleBinding(activator, 1, string.Empty); - var completed = false; - - target.Subscribe(_ => { }, () => completed = true); - activator.OnCompleted(); - - Assert.True(completed); - } - } -} diff --git a/tests/TestFiles/Cairo/Shapes/Line/Line_1px_Stroke.expected.png b/tests/TestFiles/Cairo/Shapes/Line/Line_1px_Stroke.expected.png new file mode 100644 index 0000000000..18ade2da0f Binary files /dev/null and b/tests/TestFiles/Cairo/Shapes/Line/Line_1px_Stroke.expected.png differ diff --git a/tests/TestFiles/Direct2D1/Shapes/Line/Line_1px_Stroke.expected.png b/tests/TestFiles/Direct2D1/Shapes/Line/Line_1px_Stroke.expected.png new file mode 100644 index 0000000000..18ade2da0f Binary files /dev/null and b/tests/TestFiles/Direct2D1/Shapes/Line/Line_1px_Stroke.expected.png differ diff --git a/tests/TestFiles/Skia/Shapes/Line/Line_1px_Stroke.expected.png b/tests/TestFiles/Skia/Shapes/Line/Line_1px_Stroke.expected.png new file mode 100644 index 0000000000..18ade2da0f Binary files /dev/null and b/tests/TestFiles/Skia/Shapes/Line/Line_1px_Stroke.expected.png differ