From b26573656a5fdd2b87b0096181b4a15cdacee5eb Mon Sep 17 00:00:00 2001 From: donandren Date: Wed, 9 Mar 2016 15:56:40 +0200 Subject: [PATCH 001/104] HasInverse Property property of Matrix --- src/Perspex.SceneGraph/Matrix.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Perspex.SceneGraph/Matrix.cs b/src/Perspex.SceneGraph/Matrix.cs index baf38fb767..121ad1667d 100644 --- a/src/Perspex.SceneGraph/Matrix.cs +++ b/src/Perspex.SceneGraph/Matrix.cs @@ -53,6 +53,11 @@ namespace Perspex /// public bool IsIdentity => Equals(Identity); + /// + /// HasInverse Property - returns true if this matrix is invertable, false otherwise. + /// + public bool HasInverse => GetDeterminant() != 0; + /// /// The first element of the first row /// @@ -209,6 +214,7 @@ namespace Perspex return (_m11 * _m22) - (_m12 * _m21); } + /// /// Returns a boolean indicating whether the matrix is equal to the other given matrix. /// From d36aae900cb565dba415510173a8bd85a2f3a4db Mon Sep 17 00:00:00 2001 From: donandren Date: Wed, 9 Mar 2016 16:00:20 +0200 Subject: [PATCH 002/104] LayoutTransformControll added --- .../LayoutTransformControl.cs | 407 ++++++++++++++++++ src/Perspex.Controls/Perspex.Controls.csproj | 1 + 2 files changed, 408 insertions(+) create mode 100644 src/Perspex.Controls/LayoutTransformControl.cs diff --git a/src/Perspex.Controls/LayoutTransformControl.cs b/src/Perspex.Controls/LayoutTransformControl.cs new file mode 100644 index 0000000000..f92437ad03 --- /dev/null +++ b/src/Perspex.Controls/LayoutTransformControl.cs @@ -0,0 +1,407 @@ +// 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. +// +// Idea got from and adapted to work in perspex +// http://silverlight.codeplex.com/SourceControl/changeset/view/74775#Release/Silverlight4/Source/Controls.Layout.Toolkit/LayoutTransformer/LayoutTransformer.cs +// + +using Perspex.Controls.Presenters; +using Perspex.Controls.Primitives; +using Perspex.Controls.Templates; +using Perspex.Media; +using Perspex.VisualTree; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reactive.Linq; + +namespace Perspex.Controls +{ + public class LayoutTransformControl : ContentControl + { + public static readonly PerspexProperty LayoutTransformProperty = + PerspexProperty.Register(nameof(LayoutTransform)); + + static LayoutTransformControl() + { + LayoutTransformProperty.Changed + .AddClassHandler(x => x.OnLayoutTransformChanged); + TemplateProperty.OverrideDefaultValue(_defaultTemplate); + } + + public Transform LayoutTransform + { + get { return GetValue(LayoutTransformProperty); } + set { SetValue(LayoutTransformProperty, value); } + } + + /// + /// Acceptable difference between two doubles. + /// + private const double AcceptableDelta = 0.0001; + + /// + /// Number of decimals to round the Matrix to. + /// + private const int DecimalsAfterRound = 4; + + private static readonly FuncControlTemplate _defaultTemplate = + new FuncControlTemplate(control => + { + return new Decorator() + { + Child = new ContentPresenter() + { + [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty] + } + }; + }); + + /// + /// RenderTransform/MatrixTransform applied to TransformRoot. + /// + private MatrixTransform _matrixTransform; + + /// + /// Transformation matrix corresponding to _matrixTransform. + /// + private Matrix _transformation; + + /// + /// Actual DesiredSize of Child element (the value it returned from its MeasureOverride method). + /// + private Size _childActualSize = Size.Empty; + + private Control _transformRoot; + + public Control TransformRoot => _transformRoot ?? + (_transformRoot = this.GetVisualChildren().OfType().FirstOrDefault()); + + private IDisposable _transformChangedEvent = null; + + private void OnLayoutTransformChanged(PerspexPropertyChangedEventArgs e) + { + var newTransform = e.NewValue as Transform; + + if (_transformChangedEvent != null) + { + _transformChangedEvent.Dispose(); + _transformChangedEvent = null; + } + + if (newTransform != null) + { + _transformChangedEvent = Observable.FromEventPattern( + v => newTransform.Changed += v, v => newTransform.Changed -= v) + .Subscribe(onNext: v => ApplyLayoutTransform()); + } + + ApplyLayoutTransform(); + } + + /// + /// Builds the visual tree for the LayoutTransformerControl when a new + /// template is applied. + /// + protected override void OnTemplateApplied(TemplateAppliedEventArgs e) + { + base.OnTemplateApplied(e); + + _matrixTransform = new MatrixTransform(); + + if (null != TransformRoot) + { + TransformRoot.RenderTransform = _matrixTransform; + TransformRoot.TransformOrigin = new RelativePoint(0, 0, RelativeUnit.Absolute); + } + + ApplyLayoutTransform(); + } + + /// + /// Applies the layout transform on the LayoutTransformerControl content. + /// + /// + /// Only used in advanced scenarios (like animating the LayoutTransform). + /// Should be used to notify the LayoutTransformer control that some aspect + /// of its Transform property has changed. + /// + public void ApplyLayoutTransform() + { + if (LayoutTransform == null) return; + + // Get the transform matrix and apply it + _transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound); + + if (null != _matrixTransform) + { + _matrixTransform.Matrix = _transformation; + } + + // New transform means re-layout is necessary + InvalidateMeasure(); + } + + /// + /// Provides the behavior for the "Measure" pass of layout. + /// + /// The available size that this element can give to child elements. + /// The size that this element determines it needs during layout, based on its calculations of child element sizes. + protected override Size MeasureOverride(Size availableSize) + { + if (TransformRoot == null || LayoutTransform == null) + { + return base.MeasureOverride(availableSize); + } + + Size measureSize; + if (_childActualSize == Size.Empty) + { + // Determine the largest size after the transformation + measureSize = ComputeLargestTransformedSize(availableSize); + } + else + { + // Previous measure/arrange pass determined that Child.DesiredSize was larger than believed + measureSize = _childActualSize; + } + + // Perform a measure on the TransformRoot (containing Child) + TransformRoot.Measure(measureSize); + + var desiredSize = TransformRoot.DesiredSize; + + // Transform DesiredSize to find its width/height + Rect transformedDesiredRect = new Rect(0, 0, desiredSize.Width, desiredSize.Height).TransformToAABB(_transformation); + Size transformedDesiredSize = new Size(transformedDesiredRect.Width, transformedDesiredRect.Height); + + // Return result to allocate enough space for the transformation + return transformedDesiredSize; + } + + /// + /// Provides the behavior for the "Arrange" pass of layout. + /// + /// The final area within the parent that this element should use to arrange itself and its children. + /// The actual size used. + protected override Size ArrangeOverride(Size finalSize) + { + if (TransformRoot == null || LayoutTransform == null) + { + return base.ArrangeOverride(finalSize); + } + + // Determine the largest available size after the transformation + Size finalSizeTransformed = ComputeLargestTransformedSize(finalSize); + if (IsSizeSmaller(finalSizeTransformed, TransformRoot.DesiredSize)) + { + // Some elements do not like being given less space than they asked for (ex: TextBlock) + // Bump the working size up to do the right thing by them + finalSizeTransformed = TransformRoot.DesiredSize; + } + + // Transform the working size to find its width/height + Rect transformedRect = new Rect(0, 0, finalSizeTransformed.Width, finalSizeTransformed.Height).TransformToAABB(_transformation); + // Create the Arrange rect to center the transformed content + Rect finalRect = new Rect( + -transformedRect.X + ((finalSize.Width - transformedRect.Width) / 2), + -transformedRect.Y + ((finalSize.Height - transformedRect.Height) / 2), + finalSizeTransformed.Width, + finalSizeTransformed.Height); + + // Perform an Arrange on TransformRoot (containing Child) + Size arrangedsize; + TransformRoot.Arrange(finalRect); + arrangedsize = TransformRoot.Bounds.Size; + + // This is the first opportunity under Silverlight to find out the Child's true DesiredSize + if (IsSizeSmaller(finalSizeTransformed, arrangedsize) && (Size.Empty == _childActualSize)) + { + //// Unfortunately, all the work so far is invalid because the wrong DesiredSize was used + //// Make a note of the actual DesiredSize + //_childActualSize = arrangedsize; + //// Force a new measure/arrange pass + //InvalidateMeasure(); + } + else + { + // Clear the "need to measure/arrange again" flag + _childActualSize = Size.Empty; + } + + // Return result to perform the transformation + return finalSize; + } + + /// + /// Compute the largest usable size (greatest area) after applying the transformation to the specified bounds. + /// + /// Arrange bounds. + /// Largest Size possible. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Closely corresponds to WPF's FrameworkElement.FindMaximalAreaLocalSpaceRect.")] + private Size ComputeLargestTransformedSize(Size arrangeBounds) + { + // Computed largest transformed size + Size computedSize = Size.Empty; + + // Detect infinite bounds and constrain the scenario + bool infiniteWidth = double.IsInfinity(arrangeBounds.Width); + if (infiniteWidth) + { + // arrangeBounds.Width = arrangeBounds.Height; + arrangeBounds = arrangeBounds.WithWidth(arrangeBounds.Height); + } + bool infiniteHeight = double.IsInfinity(arrangeBounds.Height); + if (infiniteHeight) + { + //arrangeBounds.Height = arrangeBounds.Width; + arrangeBounds = arrangeBounds.WithHeight(arrangeBounds.Width); + } + + // Capture the matrix parameters + double a = _transformation.M11; + double b = _transformation.M12; + double c = _transformation.M21; + double d = _transformation.M22; + + // Compute maximum possible transformed width/height based on starting width/height + // These constraints define two lines in the positive x/y quadrant + double maxWidthFromWidth = Math.Abs(arrangeBounds.Width / a); + double maxHeightFromWidth = Math.Abs(arrangeBounds.Width / c); + double maxWidthFromHeight = Math.Abs(arrangeBounds.Height / b); + double maxHeightFromHeight = Math.Abs(arrangeBounds.Height / d); + + // The transformed width/height that maximize the area under each segment is its midpoint + // At most one of the two midpoints will satisfy both constraints + double idealWidthFromWidth = maxWidthFromWidth / 2; + double idealHeightFromWidth = maxHeightFromWidth / 2; + double idealWidthFromHeight = maxWidthFromHeight / 2; + double idealHeightFromHeight = maxHeightFromHeight / 2; + + // Compute slope of both constraint lines + double slopeFromWidth = -(maxHeightFromWidth / maxWidthFromWidth); + double slopeFromHeight = -(maxHeightFromHeight / maxWidthFromHeight); + + if ((0 == arrangeBounds.Width) || (0 == arrangeBounds.Height)) + { + // Check for empty bounds + computedSize = new Size(arrangeBounds.Width, arrangeBounds.Height); + } + else if (infiniteWidth && infiniteHeight) + { + // Check for completely unbound scenario + computedSize = new Size(double.PositiveInfinity, double.PositiveInfinity); + } + else if (!_transformation.HasInverse) + { + // Check for singular matrix + computedSize = new Size(0, 0); + } + else if ((0 == b) || (0 == c)) + { + // Check for 0/180 degree special cases + double maxHeight = (infiniteHeight ? double.PositiveInfinity : maxHeightFromHeight); + double maxWidth = (infiniteWidth ? double.PositiveInfinity : maxWidthFromWidth); + if ((0 == b) && (0 == c)) + { + // No constraints + computedSize = new Size(maxWidth, maxHeight); + } + else if (0 == b) + { + // Constrained by width + double computedHeight = Math.Min(idealHeightFromWidth, maxHeight); + computedSize = new Size( + maxWidth - Math.Abs((c * computedHeight) / a), + computedHeight); + } + else if (0 == c) + { + // Constrained by height + double computedWidth = Math.Min(idealWidthFromHeight, maxWidth); + computedSize = new Size( + computedWidth, + maxHeight - Math.Abs((b * computedWidth) / d)); + } + } + else if ((0 == a) || (0 == d)) + { + // Check for 90/270 degree special cases + double maxWidth = (infiniteHeight ? double.PositiveInfinity : maxWidthFromHeight); + double maxHeight = (infiniteWidth ? double.PositiveInfinity : maxHeightFromWidth); + if ((0 == a) && (0 == d)) + { + // No constraints + computedSize = new Size(maxWidth, maxHeight); + } + else if (0 == a) + { + // Constrained by width + double computedHeight = Math.Min(idealHeightFromHeight, maxHeight); + computedSize = new Size( + maxWidth - Math.Abs((d * computedHeight) / b), + computedHeight); + } + else if (0 == d) + { + // Constrained by height + double computedWidth = Math.Min(idealWidthFromWidth, maxWidth); + computedSize = new Size( + computedWidth, + maxHeight - Math.Abs((a * computedWidth) / c)); + } + } + else if (idealHeightFromWidth <= ((slopeFromHeight * idealWidthFromWidth) + maxHeightFromHeight)) + { + // Check the width midpoint for viability (by being below the height constraint line) + computedSize = new Size(idealWidthFromWidth, idealHeightFromWidth); + } + else if (idealHeightFromHeight <= ((slopeFromWidth * idealWidthFromHeight) + maxHeightFromWidth)) + { + // Check the height midpoint for viability (by being below the width constraint line) + computedSize = new Size(idealWidthFromHeight, idealHeightFromHeight); + } + else + { + // Neither midpoint is viable; use the intersection of the two constraint lines instead + // Compute width by setting heights equal (m1*x+c1=m2*x+c2) + double computedWidth = (maxHeightFromHeight - maxHeightFromWidth) / (slopeFromWidth - slopeFromHeight); + // Compute height from width constraint line (y=m*x+c; using height would give same result) + computedSize = new Size( + computedWidth, + (slopeFromWidth * computedWidth) + maxHeightFromWidth); + } + + // Return result + return computedSize; + } + + /// + /// Returns true if Size a is smaller than Size b in either dimension. + /// + /// Second Size. + /// First Size. + /// True if Size a is smaller than Size b in either dimension. + private static bool IsSizeSmaller(Size a, Size b) + { + return (a.Width + AcceptableDelta < b.Width) || (a.Height + AcceptableDelta < b.Height); + } + + /// + /// Rounds the non-offset elements of a Matrix to avoid issues due to floating point imprecision. + /// + /// Matrix to round. + /// Number of decimal places to round to. + /// Rounded Matrix. + private static Matrix RoundMatrix(Matrix matrix, int decimals) + { + return new Matrix( + Math.Round(matrix.M11, decimals), + Math.Round(matrix.M12, decimals), + Math.Round(matrix.M21, decimals), + Math.Round(matrix.M22, decimals), + matrix.M31, + matrix.M32); + } + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index ee90633945..c249a901f6 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -54,6 +54,7 @@ + From 454d1b744cf6b468e9590ecf58ff88b9253353b3 Mon Sep 17 00:00:00 2001 From: donandren Date: Wed, 9 Mar 2016 16:12:39 +0200 Subject: [PATCH 003/104] ScaleTransform Added --- .../Media/ScaleTransform.cs | 69 +++++++++++++++++++ .../Perspex.SceneGraph.csproj | 1 + 2 files changed, 70 insertions(+) create mode 100644 src/Perspex.SceneGraph/Media/ScaleTransform.cs diff --git a/src/Perspex.SceneGraph/Media/ScaleTransform.cs b/src/Perspex.SceneGraph/Media/ScaleTransform.cs new file mode 100644 index 0000000000..9e0f1ecb3a --- /dev/null +++ b/src/Perspex.SceneGraph/Media/ScaleTransform.cs @@ -0,0 +1,69 @@ +// 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; + +namespace Perspex.Media +{ + /// + /// Scale an . + /// + public class ScaleTransform : Transform + { + /// + /// Defines the property. + /// + public static readonly StyledProperty ScaleXProperty = + PerspexProperty.Register(nameof(ScaleX), 1); + + /// + /// Defines the property. + /// + public static readonly StyledProperty ScaleYProperty = + PerspexProperty.Register(nameof(ScaleY), 1); + + /// + /// Initializes a new instance of the class. + /// + public ScaleTransform() + { + this.GetObservable(ScaleXProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(ScaleYProperty).Subscribe(_ => RaiseChanged()); + } + + /// + /// Initializes a new instance of the class. + /// + /// ScaleX + /// ScaleY + public ScaleTransform(double scaleX, double scaleY) + : this() + { + ScaleX = scaleX; + ScaleY = scaleY; + } + + /// + /// Gets or sets the ScaleX property. + /// + public double ScaleX + { + get { return GetValue(ScaleXProperty); } + set { SetValue(ScaleXProperty, value); } + } + + /// + /// Gets or sets the ScaleY property. + /// + public double ScaleY + { + get { return GetValue(ScaleYProperty); } + set { SetValue(ScaleYProperty, value); } + } + + /// + /// Gets the tranform's . + /// + public override Matrix Value => Matrix.CreateScale(ScaleX, ScaleY); + } +} \ No newline at end of file diff --git a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj index 17ee3e98db..be199ab423 100644 --- a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -80,6 +80,7 @@ + From 4e2513b3ffc9f2a3e8f8f242f938b86857cf8d6a Mon Sep 17 00:00:00 2001 From: donandren Date: Wed, 9 Mar 2016 16:16:50 +0200 Subject: [PATCH 004/104] LayoutTransformControl Unit tests --- .../LayoutTransformControlTests.cs | 248 ++++++++++++++++++ .../Perspex.Controls.UnitTests.csproj | 1 + 2 files changed, 249 insertions(+) create mode 100644 tests/Perspex.Controls.UnitTests/LayoutTransformControlTests.cs diff --git a/tests/Perspex.Controls.UnitTests/LayoutTransformControlTests.cs b/tests/Perspex.Controls.UnitTests/LayoutTransformControlTests.cs new file mode 100644 index 0000000000..946fb9e8b9 --- /dev/null +++ b/tests/Perspex.Controls.UnitTests/LayoutTransformControlTests.cs @@ -0,0 +1,248 @@ +using Perspex.Controls.Presenters; +using Perspex.Controls.Shapes; +using Perspex.Controls.Templates; +using Perspex.Media; +using Xunit; + +namespace Perspex.Controls.UnitTests +{ + public class LayoutTransformControlTests + { + [Fact] + public void Measure_On_Scale_x2_Is_Correct() + { + double scale = 2; + + TransformMeasureSizeTest( + new Size(100, 50), + new ScaleTransform() { ScaleX = scale, ScaleY = scale }, + new Size(200, 100)); + } + + [Fact] + public void Measure_On_Scale_x0_5_Is_Correct() + { + double scale = 0.5; + + TransformMeasureSizeTest( + new Size(100, 50), + new ScaleTransform() { ScaleX = scale, ScaleY = scale }, + new Size(50, 25)); + } + + [Fact] + public void Measure_On_Rotate_90_degrees_Is_Correct() + { + TransformMeasureSizeTest( + new Size(100, 25), + new RotateTransform() { Angle = 90 }, + new Size(25, 100)); + } + + [Fact] + public void Measure_On_Rotate_minus_90_degrees_Is_Correct() + { + TransformMeasureSizeTest( + new Size(100, 25), + new RotateTransform() { Angle = -90 }, + new Size(25, 100)); + } + + [Fact] + public void Measure_On_Rotate_0_degrees_Is_Correct() + { + TransformMeasureSizeTest( + new Size(100, 25), + new RotateTransform() { Angle = 0 }, + new Size(100, 25)); + } + + [Fact] + public void Measure_On_Rotate_180_degrees_Is_Correct() + { + TransformMeasureSizeTest( + new Size(100, 25), + new RotateTransform() { Angle = 180 }, + new Size(100, 25)); + } + + [Fact] + public void Bounds_On_Scale_x2_Are_correct() + { + double scale = 2; + + TransformRootBoundsTest( + new Size(100, 50), + new ScaleTransform() { ScaleX = scale, ScaleY = scale }, + new Rect(0, 0, 100, 50)); + } + + [Fact] + public void Bounds_On_Scale_x0_5_Are_correct() + { + double scale = 0.5; + + TransformRootBoundsTest( + new Size(100, 50), + new ScaleTransform() { ScaleX = scale, ScaleY = scale }, + new Rect(0, 0, 100, 50)); + } + + [Fact] + public void Bounds_On_Rotate_180_degrees_Are_correct() + { + TransformRootBoundsTest( + new Size(100, 25), + new RotateTransform() { Angle = 180 }, + new Rect(100, 25, 100, 25)); + } + + [Fact] + public void Bounds_On_Rotate_0_degrees_Are_correct() + { + TransformRootBoundsTest( + new Size(100, 25), + new RotateTransform() { Angle = 0 }, + new Rect(0, 0, 100, 25)); + } + + [Fact] + public void Bounds_On_Rotate_90_degrees_Are_correct() + { + TransformRootBoundsTest( + new Size(100, 25), + new RotateTransform() { Angle = 90 }, + new Rect(25, 0, 100, 25)); + } + + [Fact] + public void Bounds_On_Rotate_minus_90_degrees_Are_correct() + { + TransformRootBoundsTest( + new Size(100, 25), + new RotateTransform() { Angle = -90 }, + new Rect(0, 100, 100, 25)); + } + + [Fact] + public void Should_Generate_RenderTransform_90_degrees() + { + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform( + 100, + 25, + new RotateTransform() { Angle = 90 }); + + Assert.NotNull(lt.TransformRoot.RenderTransform); + + Matrix m = lt.TransformRoot.RenderTransform.Value; + + Matrix res = Matrix.CreateRotation(Matrix.ToRadians(90)); + + Assert.Equal(m.M11, res.M11, 3); + Assert.Equal(m.M12, res.M12, 3); + Assert.Equal(m.M21, res.M21, 3); + Assert.Equal(m.M22, res.M22, 3); + Assert.Equal(m.M31, res.M31, 3); + Assert.Equal(m.M32, res.M32, 3); + } + + [Fact] + public void Should_Generate_RenderTransform_minus_90_degrees() + { + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform( + 100, + 25, + new RotateTransform() { Angle = -90 }); + + Assert.NotNull(lt.TransformRoot.RenderTransform); + + var m = lt.TransformRoot.RenderTransform.Value; + + var res = Matrix.CreateRotation(Matrix.ToRadians(-90)); + + Assert.Equal(m.M11, res.M11, 3); + Assert.Equal(m.M12, res.M12, 3); + Assert.Equal(m.M21, res.M21, 3); + Assert.Equal(m.M22, res.M22, 3); + Assert.Equal(m.M31, res.M31, 3); + Assert.Equal(m.M32, res.M32, 3); + } + + [Fact] + public void Should_Generate_ScaleTransform_x2() + { + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform( + 100, + 50, + new ScaleTransform() { ScaleX = 2, ScaleY = 2 }); + + Assert.NotNull(lt.TransformRoot.RenderTransform); + + Matrix m = lt.TransformRoot.RenderTransform.Value; + Matrix res = Matrix.CreateScale(2, 2); + + Assert.Equal(m.M11, res.M11, 3); + Assert.Equal(m.M12, res.M12, 3); + Assert.Equal(m.M21, res.M21, 3); + Assert.Equal(m.M22, res.M22, 3); + Assert.Equal(m.M31, res.M31, 3); + Assert.Equal(m.M32, res.M32, 3); + } + + private static void TransformMeasureSizeTest(Size size, Transform transform, Size expectedSize) + { + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform( + size.Width, + size.Height, + transform); + + Size outSize = lt.DesiredSize; + + Assert.Equal(outSize.Width, expectedSize.Width); + Assert.Equal(outSize.Height, expectedSize.Height); + } + + private static void TransformRootBoundsTest(Size size, Transform transform, Rect expectedBounds) + { + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(size.Width, size.Height, transform); + + Rect outBounds = lt.TransformRoot.Bounds; + + Assert.Equal(outBounds.X, expectedBounds.X); + Assert.Equal(outBounds.Y, expectedBounds.Y); + Assert.Equal(outBounds.Width, expectedBounds.Width); + Assert.Equal(outBounds.Height, expectedBounds.Height); + } + + private static LayoutTransformControl CreateWithChildAndMeasureAndTransform( + double width, + double height, + Transform transform) + { + var lt = new LayoutTransformControl() + { + LayoutTransform = transform, + Template = new FuncControlTemplate( + p => + { + var c = new ContentPresenter() { Content = p.Content }; + //we need to force create visual child + //so the measure after is correct + c.UpdateChild(); + return c; + }) + }; + + lt.Content = new Rectangle() { Width = width, Height = height }; + + lt.ApplyTemplate(); + + Assert.NotNull(lt.Presenter?.Child); + + lt.Measure(Size.Infinity); + lt.Arrange(new Rect(lt.DesiredSize)); + + return lt; + } + } +} \ No newline at end of file diff --git a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index 57a6b16868..2888640b07 100644 --- a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -90,6 +90,7 @@ + From 92035ffdba2aebef5c6da7b81cff79e54117f48a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 9 Mar 2016 19:52:08 +0100 Subject: [PATCH 005/104] Don't CombineLatest if no need. --- src/Perspex.Styling/Styling/StyleActivator.cs | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Perspex.Styling/Styling/StyleActivator.cs b/src/Perspex.Styling/Styling/StyleActivator.cs index dd2601e0bb..05b97a11ec 100644 --- a/src/Perspex.Styling/Styling/StyleActivator.cs +++ b/src/Perspex.Styling/Styling/StyleActivator.cs @@ -17,18 +17,40 @@ namespace Perspex.Styling public static class StyleActivator { - public static IObservable And(IEnumerable> inputs) + public static IObservable And(IList> inputs) { - return inputs.CombineLatest() - .Select(values => values.All(x => x)) - .DistinctUntilChanged(); + if (inputs.Count == 0) + { + throw new ArgumentException("StyleActivator.And inputs may not be empty."); + } + else if (inputs.Count == 1) + { + return inputs[0]; + } + else + { + return inputs.CombineLatest() + .Select(values => values.All(x => x)) + .DistinctUntilChanged(); + } } - public static IObservable Or(IEnumerable> inputs) + public static IObservable Or(IList> inputs) { - return inputs.CombineLatest() + if (inputs.Count == 0) + { + throw new ArgumentException("StyleActivator.Or inputs may not be empty."); + } + else if (inputs.Count == 1) + { + return inputs[0]; + } + else + { + return inputs.CombineLatest() .Select(values => values.Any(x => x)) .DistinctUntilChanged(); + } } } } From e7363e5fd818d60ff8d3ec6fb6b37676419e3b46 Mon Sep 17 00:00:00 2001 From: donandren Date: Wed, 9 Mar 2016 21:12:03 +0200 Subject: [PATCH 006/104] ApplyLayoutTransform made private --- src/Perspex.Controls/LayoutTransformControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perspex.Controls/LayoutTransformControl.cs b/src/Perspex.Controls/LayoutTransformControl.cs index f92437ad03..cb865c4cbb 100644 --- a/src/Perspex.Controls/LayoutTransformControl.cs +++ b/src/Perspex.Controls/LayoutTransformControl.cs @@ -126,7 +126,7 @@ namespace Perspex.Controls /// Should be used to notify the LayoutTransformer control that some aspect /// of its Transform property has changed. /// - public void ApplyLayoutTransform() + private void ApplyLayoutTransform() { if (LayoutTransform == null) return; From 20cb202232a80c433609571e01a2fa2d1d2fb4b8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 9 Mar 2016 20:21:11 +0100 Subject: [PATCH 007/104] Fix bindings not being unsubscribed on Dispose. --- src/Perspex.Base/PriorityLevel.cs | 1 + .../PerspexObjectTests_Binding.cs | 17 +++++++++++++++++ tests/Perspex.Base.UnitTests/packages.config | 3 +++ 3 files changed, 21 insertions(+) diff --git a/src/Perspex.Base/PriorityLevel.cs b/src/Perspex.Base/PriorityLevel.cs index b7ffcce971..3c47199525 100644 --- a/src/Perspex.Base/PriorityLevel.cs +++ b/src/Perspex.Base/PriorityLevel.cs @@ -140,6 +140,7 @@ namespace Perspex return Disposable.Create(() => { Bindings.Remove(node); + entry.Dispose(); if (entry.Index >= ActiveBindingIndex) { diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs index 40f542ae76..caa36f38a8 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs @@ -4,6 +4,7 @@ using System; using System.Reactive.Linq; using System.Reactive.Subjects; +using Microsoft.Reactive.Testing; using Perspex.Data; using Xunit; @@ -81,6 +82,22 @@ namespace Perspex.Base.UnitTests Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); } + [Fact] + public void Observable_Is_Unsubscribed_When_Subscription_Disposed() + { + var scheduler = new TestScheduler(); + var source = scheduler.CreateColdObservable(); + var target = new Class1(); + + var subscription = target.Bind(Class1.FooProperty, source); + Assert.Equal(1, source.Subscriptions.Count); + Assert.Equal(Subscription.Infinite, source.Subscriptions[0].Unsubscribe); + + subscription.Dispose(); + Assert.Equal(1, source.Subscriptions.Count); + Assert.Equal(0, source.Subscriptions[0].Unsubscribe); + } + [Fact] public void Two_Way_Separate_Binding_Works() { diff --git a/tests/Perspex.Base.UnitTests/packages.config b/tests/Perspex.Base.UnitTests/packages.config index 3047aefeea..164dada787 100644 --- a/tests/Perspex.Base.UnitTests/packages.config +++ b/tests/Perspex.Base.UnitTests/packages.config @@ -3,6 +3,9 @@ + + + From 1b8e0faa7f8338ba9a9de63a6e53fb51f0450b59 Mon Sep 17 00:00:00 2001 From: donandren Date: Wed, 9 Mar 2016 21:26:53 +0200 Subject: [PATCH 008/104] Default template in code of LayoutTransformControl removed and added default template in theme --- src/Perspex.Controls/LayoutTransformControl.cs | 13 ------------- src/Perspex.Themes.Default/DefaultTheme.xaml | 1 + .../LayoutTransformControl.xaml | 12 ++++++++++++ .../Perspex.Themes.Default.csproj | 5 +++++ 4 files changed, 18 insertions(+), 13 deletions(-) create mode 100644 src/Perspex.Themes.Default/LayoutTransformControl.xaml diff --git a/src/Perspex.Controls/LayoutTransformControl.cs b/src/Perspex.Controls/LayoutTransformControl.cs index cb865c4cbb..49169b5895 100644 --- a/src/Perspex.Controls/LayoutTransformControl.cs +++ b/src/Perspex.Controls/LayoutTransformControl.cs @@ -26,7 +26,6 @@ namespace Perspex.Controls { LayoutTransformProperty.Changed .AddClassHandler(x => x.OnLayoutTransformChanged); - TemplateProperty.OverrideDefaultValue(_defaultTemplate); } public Transform LayoutTransform @@ -45,18 +44,6 @@ namespace Perspex.Controls /// private const int DecimalsAfterRound = 4; - private static readonly FuncControlTemplate _defaultTemplate = - new FuncControlTemplate(control => - { - return new Decorator() - { - Child = new ContentPresenter() - { - [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty] - } - }; - }); - /// /// RenderTransform/MatrixTransform applied to TransformRoot. /// diff --git a/src/Perspex.Themes.Default/DefaultTheme.xaml b/src/Perspex.Themes.Default/DefaultTheme.xaml index b25c85bbd1..f536a523f3 100644 --- a/src/Perspex.Themes.Default/DefaultTheme.xaml +++ b/src/Perspex.Themes.Default/DefaultTheme.xaml @@ -8,6 +8,7 @@ + diff --git a/src/Perspex.Themes.Default/LayoutTransformControl.xaml b/src/Perspex.Themes.Default/LayoutTransformControl.xaml new file mode 100644 index 0000000000..e393eedf83 --- /dev/null +++ b/src/Perspex.Themes.Default/LayoutTransformControl.xaml @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj b/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj index e954fd0419..c26bb74ef0 100644 --- a/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj +++ b/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj @@ -200,6 +200,11 @@ Designer + + + Designer + + + + + Header + + +## DataTemplates + +As styles aren't stored in `Resources`, neither are `DataTemplates` ([in fact +there is no `Resources` collection](#Resources)). Instead, `DataTemplates` are +placed in a `DataTemplates` collection on each control (and on `Application`): + + + + + + + + + + + + + +## UIElement, FrameworkElement and Control + +WPF's `UIElement` and `FrameworkElement` are non-templated control base classes, +which roughly equate to the Perspex `Control` class. WPF's `Control` class on +the other hand is a templated control - Perspex's equivalent of this is +`TemplatedControl`. + +So to recap: + +- `UIElement`: `Control` +- `FrameworkElement`: `Control` +- `Control`: `TemplatedControl` + +## DependencyProperty + +The Perspex equivalent of `DependencyProperty` is `StyledProperty`, however +Perspex [has a richer property system than WPF](../spec/defining-properties.md), +and includes `DirectProperty` for turning standard CLR properties into Perspex +properties. The common base class of `StyledProperty` and `DirectProperty` +is `PerspexProperty`. + +# Resources + +There is no `Resources` collection on controls in Perspex, however `Style`s +do have a `Resources` collection for style-related resources. These can be +referred to using the `{StyleResource}` markup extension both inside and outside +styles. + +For non-style-related resources, we suggest defining them in code and referring +to them in markup using the `{Static}` markup extension. There are [various +reasons](http://www.codemag.com/article/1501091) for this, but briefly: + +- Resources have to be parsed +- The tree has to be traversed to find them +- XAML doesn't handle immutable objects +- XAML syntax can be long-winded compared to C# + +## Grid + +Perspex has a `Grid` panel just like WPF, however a common use of `Grid` in WPF +is to stack two controls on top of each other. For this purpose in Perspex you +can just use a `Panel` which is more lightweight than `Grid`. + +We don't yet support `SharedSizeScope` in `Grid`. + +## Tunnelling Events + +Perspex has tunnelling events (unlike UWP!) but they're not exposed via +separate `Preview` CLR event handlers. To subscribe to a tunnelling event you +must call `AddHandler` with `RoutingStrategies.Tunnel`: + +``` +target.AddHandler(InputElement.KeyDownEvent, OnPreviewKeyDown, RoutingStrategies.Tunnel); + +void OnPreviewKeyDown(object sender, KeyEventArgs e) +{ + // Handler code +} +``` + +## Class Handlers + +In WPF, class handlers for events can be added by calling +[EventManager.RegisterClassHandler](https://msdn.microsoft.com/en-us/library/ms597875.aspx). +An example of registering a class handler in WPF might be: + + static MyControl() + { + EventManager.RegisterClassHandler(typeof(MyControl), MyEvent, HandleMyEvent)); + } + + private static void HandleMyEvent(object sender, RoutedEventArgs e) + { + } + +The equivalent of this in Perspex would be: + + static MyControl() + { + MyEvent.AddClassHandler(x => x.HandleMyEvent); + } + + private void HandleMyEvent(object sender, RoutedEventArgs e) + { + } + +Notice that in WPF you have to add the class handler as a static method, whereas +in Perspex the class handler is not static: the notification is automatically +directed to the correct instance. + +## PropertyChangedCallback + +Listening to changes on DependencyProperties in WPF can be complex. When you +register a `DependencyProperty` you can supply a static `PropertyChangedCallback` +but if you want to listen to changes from elsewhere [things can get complicated +and error-prone](http://stackoverflow.com/questions/23682232). + +In Perspex, there is no `PropertyChangedCallback` at the time of registration, +instead a class listener is [added to the control's static constructor in much +the same way that event class listeners are added](../spec/working-with-properties.md#Subscribing%20%to%20%a%20%Property%20%on%20%Any%20%Object). From 1231db168c678a61eb33b268241b0009b755f2bd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 15 Mar 2016 13:28:39 +0100 Subject: [PATCH 018/104] Fixed some links. --- docs/tutorial/from-wpf.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/from-wpf.md b/docs/tutorial/from-wpf.md index 11ab526294..34f1025951 100644 --- a/docs/tutorial/from-wpf.md +++ b/docs/tutorial/from-wpf.md @@ -23,7 +23,7 @@ collection: ## DataTemplates As styles aren't stored in `Resources`, neither are `DataTemplates` ([in fact -there is no `Resources` collection](#Resources)). Instead, `DataTemplates` are +there is no `Resources` collection](#resources)). Instead, `DataTemplates` are placed in a `DataTemplates` collection on each control (and on `Application`): @@ -139,4 +139,4 @@ and error-prone](http://stackoverflow.com/questions/23682232). In Perspex, there is no `PropertyChangedCallback` at the time of registration, instead a class listener is [added to the control's static constructor in much -the same way that event class listeners are added](../spec/working-with-properties.md#Subscribing%20%to%20%a%20%Property%20%on%20%Any%20%Object). +the same way that event class listeners are added](../spec/working-with-properties.md#subscribing-to-a-property-on-any-object). From 9ec94dc29d89e847dcf56dee90add53ad7fb2a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Tue, 15 Mar 2016 20:44:06 +0100 Subject: [PATCH 019/104] Added CommandParameter property for KeyBinding --- src/Perspex.Input/KeyBinding.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Perspex.Input/KeyBinding.cs b/src/Perspex.Input/KeyBinding.cs index 108690e6ef..0757922932 100644 --- a/src/Perspex.Input/KeyBinding.cs +++ b/src/Perspex.Input/KeyBinding.cs @@ -18,6 +18,15 @@ namespace Perspex.Input set { SetValue(CommandProperty, value); } } + public static readonly StyledProperty CommandParameterProperty = + PerspexProperty.Register("CommandParameter"); + + public object CommandParameter + { + get { return GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + public static readonly StyledProperty GestureProperty = PerspexProperty.Register("Gesture"); @@ -32,8 +41,8 @@ namespace Perspex.Input if (Gesture?.Matches(args) == true) { args.Handled = true; - if (Command?.CanExecute(null) == true) - Command.Execute(null); + if (Command?.CanExecute(CommandParameter) == true) + Command.Execute(CommandParameter); } } } From 8e9cf40ead026aefcdce865b96d9d08723439696 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Wed, 16 Mar 2016 22:28:56 +0000 Subject: [PATCH 020/104] implemented ScrollSize from IScrollable. --- .../XamlTestApplicationPcl/TestScrollable.cs | 16 +++++++++++++ .../Presenters/ScrollContentPresenter.cs | 23 +++++++++++++++---- .../Primitives/IScrollable.cs | 10 ++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/samples/XamlTestApplicationPcl/TestScrollable.cs b/samples/XamlTestApplicationPcl/TestScrollable.cs index cc5552375b..3d48345864 100644 --- a/samples/XamlTestApplicationPcl/TestScrollable.cs +++ b/samples/XamlTestApplicationPcl/TestScrollable.cs @@ -37,6 +37,22 @@ namespace XamlTestApplication get { return _viewport; } } + public Size ScrollSize + { + get + { + return new Size(double.PositiveInfinity, 1); + } + } + + public Size PageScrollSize + { + get + { + return new Size(double.PositiveInfinity, Bounds.Height); + } + } + protected override Size MeasureOverride(Size availableSize) { using (var line = new FormattedText( diff --git a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs index d9af0b3b6e..1e2550e717 100644 --- a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs @@ -220,11 +220,24 @@ namespace Perspex.Controls.Presenters { if (Extent.Height > Viewport.Height) { - var y = Offset.Y + (-e.Delta.Y * 50); - y = Math.Max(y, 0); - y = Math.Min(y, Extent.Height - Viewport.Height); - Offset = new Vector(Offset.X, y); - e.Handled = true; + var scrollable = Child as IScrollable; + + if (scrollable != null) + { + var y = Offset.Y + (-e.Delta.Y * scrollable.ScrollSize.Height); + y = Math.Max(y, 0); + y = Math.Min(y, Extent.Height - Viewport.Height); + Offset = new Vector(Offset.X, y); + e.Handled = true; + } + else + { + var y = Offset.Y + (-e.Delta.Y * 50); + y = Math.Max(y, 0); + y = Math.Min(y, Extent.Height - Viewport.Height); + Offset = new Vector(Offset.X, y); + e.Handled = true; + } } } diff --git a/src/Perspex.Controls/Primitives/IScrollable.cs b/src/Perspex.Controls/Primitives/IScrollable.cs index 752c02685d..8cab26c35e 100644 --- a/src/Perspex.Controls/Primitives/IScrollable.cs +++ b/src/Perspex.Controls/Primitives/IScrollable.cs @@ -40,5 +40,15 @@ namespace Perspex.Controls.Primitives /// Gets the size of the viewport, in logical units. /// Size Viewport { get; } + + /// + /// Gets the size to scroll by, in logical units. + /// + Size ScrollSize { get; } + + /// + /// Gets the size to page by, in logical units. + /// + Size PageScrollSize { get; } } } From 4107a59c09696d9cb6900378df0836c473fbc839 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Wed, 16 Mar 2016 22:45:38 +0000 Subject: [PATCH 021/104] implemented new interface in TestScrollable --- .../ScrollContentPresenterTests_IScrollable.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests_IScrollable.cs b/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests_IScrollable.cs index 22f024f2a3..d4e60ce94d 100644 --- a/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests_IScrollable.cs +++ b/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests_IScrollable.cs @@ -208,6 +208,22 @@ namespace Perspex.Controls.UnitTests } } + public Size ScrollSize + { + get + { + return new Size(double.PositiveInfinity, 1); + } + } + + public Size PageScrollSize + { + get + { + return new Size(double.PositiveInfinity, Viewport.Height); + } + } + protected override Size MeasureOverride(Size availableSize) { AvailableSize = availableSize; From c7ae87af3ccd104119bf3121e792c2fc7eb771d3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 17 Mar 2016 10:27:50 +0100 Subject: [PATCH 022/104] Added LayoutTransformControl to ControlCatalog. --- samples/ControlCatalog/ControlCatalog.csproj | 8 ++++++ samples/ControlCatalog/MainWindow.xaml | 1 + .../Pages/LayoutTransformControlPage.xaml | 26 +++++++++++++++++++ .../Pages/LayoutTransformControlPage.xaml.cs | 18 +++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml create mode 100644 samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml.cs diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index be2b312204..875e8b0505 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -60,6 +60,9 @@ MainWindow.xaml + + LayoutTransformControlPage.xaml + CheckBoxPage.xaml @@ -200,6 +203,11 @@ + + + Designer + + + \ No newline at end of file diff --git a/src/Perspex.Logging.Serilog/Properties/AssemblyInfo.cs b/src/Perspex.Logging.Serilog/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..91c95c4982 --- /dev/null +++ b/src/Perspex.Logging.Serilog/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Serilog")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Serilog")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Perspex.Logging.Serilog/SerilogLogger.cs b/src/Perspex.Logging.Serilog/SerilogLogger.cs new file mode 100644 index 0000000000..beb5989d55 --- /dev/null +++ b/src/Perspex.Logging.Serilog/SerilogLogger.cs @@ -0,0 +1,44 @@ +// 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.Collections.Generic; +using Serilog; +using PerspexLogEventLevel = Perspex.Logging.LogEventLevel; +using SerilogLogEventLevel = Serilog.Events.LogEventLevel; + +namespace Perspex.Logging.Serilog +{ + public class SerilogLogger : ILogSink + { + private readonly ILogger _output; + private readonly Dictionary _areas = new Dictionary(); + + public SerilogLogger(ILogger output) + { + _output = output; + } + + public static void Initialize(ILogger output) + { + Logger.Sink = new SerilogLogger(output); + } + + public void Log( + PerspexLogEventLevel level, + string area, + object source, + string messageTemplate, + params object[] propertyValues) + { + ILogger areaLogger; + + if (!_areas.TryGetValue(area, out areaLogger)) + { + areaLogger = _output.ForContext("Area", area); + _areas.Add(area, areaLogger); + } + + areaLogger.Write((SerilogLogEventLevel)level, messageTemplate, propertyValues); + } + } +} diff --git a/src/Perspex.Logging.Serilog/packages.config b/src/Perspex.Logging.Serilog/packages.config new file mode 100644 index 0000000000..dbc72b5331 --- /dev/null +++ b/src/Perspex.Logging.Serilog/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj index be199ab423..819ee3376e 100644 --- a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -140,10 +140,6 @@ - - ..\..\packages\Serilog.1.5.14\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+MonoTouch10\Serilog.dll - True - ..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll diff --git a/src/Perspex.SceneGraph/Visual.cs b/src/Perspex.SceneGraph/Visual.cs index d5570711dc..66f92aa920 100644 --- a/src/Perspex.SceneGraph/Visual.cs +++ b/src/Perspex.SceneGraph/Visual.cs @@ -2,19 +2,17 @@ // 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.Collections.Specialized; using System.Linq; using System.Reactive.Linq; using Perspex.Animation; using Perspex.Collections; using Perspex.Data; +using Perspex.Logging; using Perspex.Media; using Perspex.Platform; using Perspex.Rendering; using Perspex.VisualTree; -using Serilog; -using Serilog.Core.Enrichers; namespace Perspex { @@ -79,7 +77,6 @@ namespace Perspex private Rect _bounds; private IVisual _visualParent; - private readonly ILogger _visualLogger; /// /// Initializes static members of the class. @@ -95,13 +92,6 @@ namespace Perspex /// public Visual() { - _visualLogger = Log.ForContext(new[] - { - new PropertyEnricher("Area", "Visual"), - new PropertyEnricher("SourceContext", GetType()), - new PropertyEnricher("Id", GetHashCode()), - }); - var visualChildren = new PerspexList(); visualChildren.ResetBehavior = ResetBehavior.Remove; visualChildren.Validate = ValidateLogicalChild; @@ -474,7 +464,7 @@ namespace Perspex /// The event args. private void NotifyAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { - _visualLogger.Verbose("Attached to visual tree"); + Logger.Verbose(LogArea.Visual, this, "Attached to visual tree"); VisualRoot = e.Root; OnAttachedToVisualTree(e); @@ -495,7 +485,7 @@ namespace Perspex /// The event args. private void NotifyDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { - _visualLogger.Verbose("Detached from visual tree"); + Logger.Verbose(LogArea.Visual, this, "Detached from visual tree"); VisualRoot = null; OnDetachedFromVisualTree(e); diff --git a/src/Perspex.SceneGraph/VisualExtensions.cs b/src/Perspex.SceneGraph/VisualExtensions.cs index cb6362b7af..3228fa4b81 100644 --- a/src/Perspex.SceneGraph/VisualExtensions.cs +++ b/src/Perspex.SceneGraph/VisualExtensions.cs @@ -13,8 +13,6 @@ using Perspex.Media; using Perspex.Platform; using Perspex.Rendering; using Perspex.VisualTree; -using Serilog; -using Serilog.Core.Enrichers; namespace Perspex { diff --git a/src/Perspex.SceneGraph/packages.config b/src/Perspex.SceneGraph/packages.config index c1c997f8a7..d5af0dcd81 100644 --- a/src/Perspex.SceneGraph/packages.config +++ b/src/Perspex.SceneGraph/packages.config @@ -5,5 +5,4 @@ - \ No newline at end of file diff --git a/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj b/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj index da3d2aa9cc..879022611c 100644 --- a/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj +++ b/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj @@ -44,14 +44,6 @@ True - - ..\..\packages\Serilog.1.5.14\lib\net45\Serilog.dll - True - - - ..\..\packages\Serilog.1.5.14\lib\net45\Serilog.FullNetFx.dll - True - ..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll diff --git a/tests/Perspex.Base.UnitTests/PriorityValueTests.cs b/tests/Perspex.Base.UnitTests/PriorityValueTests.cs index e4cfa408f7..5894ea12c3 100644 --- a/tests/Perspex.Base.UnitTests/PriorityValueTests.cs +++ b/tests/Perspex.Base.UnitTests/PriorityValueTests.cs @@ -14,7 +14,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Initial_Value_Should_Be_UnsetValue() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); Assert.Same(PerspexProperty.UnsetValue, target.Value); } @@ -22,7 +22,7 @@ namespace Perspex.Base.UnitTests [Fact] public void First_Binding_Sets_Value() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); target.Add(Single("foo"), 0); @@ -32,7 +32,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Changing_Binding_Should_Set_Value() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var subject = new BehaviorSubject("foo"); target.Add(subject, 0); @@ -44,7 +44,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Setting_Direct_Value_Should_Override_Binding() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); target.Add(Single("foo"), 0); target.SetValue("bar", 0); @@ -55,7 +55,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Binding_Firing_Should_Override_Direct_Value() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var source = new BehaviorSubject("initial"); target.Add(source, 0); @@ -69,7 +69,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Earlier_Binding_Firing_Should_Override_Later_Priority_0() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var nonActive = new BehaviorSubject("na"); var source = new BehaviorSubject("initial"); @@ -85,7 +85,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Earlier_Binding_Firing_Should_Not_Override_Later_Priority_1() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var nonActive = new BehaviorSubject("na"); var source = new BehaviorSubject("initial"); @@ -101,7 +101,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Binding_Completing_Should_Revert_To_Direct_Value() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var source = new BehaviorSubject("initial"); target.Add(source, 0); @@ -117,7 +117,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Binding_With_Lower_Priority_Has_Precedence() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); target.Add(Single("foo"), 1); target.Add(Single("bar"), 0); @@ -129,7 +129,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Later_Binding_With_Same_Priority_Should_Take_Precedence() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); target.Add(Single("foo"), 1); target.Add(Single("bar"), 0); @@ -142,7 +142,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Changing_Binding_With_Lower_Priority_Should_Set_Not_Value() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var subject = new BehaviorSubject("bar"); target.Add(Single("foo"), 0); @@ -155,7 +155,7 @@ namespace Perspex.Base.UnitTests [Fact] public void UnsetValue_Should_Fall_Back_To_Next_Binding() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var subject = new BehaviorSubject("bar"); target.Add(subject, 0); @@ -171,7 +171,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Adding_Value_Should_Call_OnNext() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); bool called = false; target.Changed.Subscribe(value => called = value.Item1 == PerspexProperty.UnsetValue && (string)value.Item2 == "foo"); @@ -183,7 +183,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Changing_Value_Should_Call_OnNext() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var subject = new BehaviorSubject("foo"); bool called = false; @@ -197,7 +197,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Disposing_A_Binding_Should_Revert_To_Next_Value() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); target.Add(Single("foo"), 0); var disposable = target.Add(Single("bar"), 0); @@ -210,7 +210,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Disposing_A_Binding_Should_Remove_BindingEntry() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); target.Add(Single("foo"), 0); var disposable = target.Add(Single("bar"), 0); @@ -223,7 +223,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Completing_A_Binding_Should_Revert_To_Previous_Binding() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var source = new BehaviorSubject("bar"); target.Add(Single("foo"), 0); @@ -237,7 +237,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Completing_A_Binding_Should_Revert_To_Lower_Priority() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var source = new BehaviorSubject("bar"); target.Add(Single("foo"), 1); @@ -251,7 +251,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Completing_A_Binding_Should_Remove_BindingEntry() { - var target = new PriorityValue("Test", typeof(string)); + var target = new PriorityValue(null, "Test", typeof(string)); var subject = new BehaviorSubject("bar"); target.Add(Single("foo"), 0); @@ -265,7 +265,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Direct_Value_Should_Be_Coerced() { - var target = new PriorityValue("Test", typeof(int), x => Math.Min((int)x, 10)); + var target = new PriorityValue(null, "Test", typeof(int), x => Math.Min((int)x, 10)); target.SetValue(5, 0); Assert.Equal(5, target.Value); @@ -276,7 +276,7 @@ namespace Perspex.Base.UnitTests [Fact] public void Bound_Value_Should_Be_Coerced() { - var target = new PriorityValue("Test", typeof(int), x => Math.Min((int)x, 10)); + var target = new PriorityValue(null, "Test", typeof(int), x => Math.Min((int)x, 10)); var source = new Subject(); target.Add(source, 0); @@ -290,7 +290,7 @@ namespace Perspex.Base.UnitTests public void Revalidate_Should_ReCoerce_Value() { var max = 10; - var target = new PriorityValue("Test", typeof(int), x => Math.Min((int)x, max)); + var target = new PriorityValue(null, "Test", typeof(int), x => Math.Min((int)x, max)); var source = new Subject(); target.Add(source, 0); diff --git a/tests/Perspex.Base.UnitTests/packages.config b/tests/Perspex.Base.UnitTests/packages.config index 164dada787..be674ecc80 100644 --- a/tests/Perspex.Base.UnitTests/packages.config +++ b/tests/Perspex.Base.UnitTests/packages.config @@ -6,7 +6,6 @@ - diff --git a/tests/Perspex.RenderTests/app.config b/tests/Perspex.RenderTests/app.config index 6d348f8066..7940b2ce88 100644 --- a/tests/Perspex.RenderTests/app.config +++ b/tests/Perspex.RenderTests/app.config @@ -15,16 +15,16 @@ - - + + - - + + - - + + From 3bf4182007010e3e8867bf7d847a4a30cf1dfe95 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Mar 2016 17:20:57 +0100 Subject: [PATCH 052/104] Log binding errors. --- samples/BindingTest/App.xaml.cs | 19 ++++++++++++------- samples/BindingTest/BindingTest.csproj | 4 ++++ .../Plugins/InpcPropertyAccessorPlugin.cs | 18 ++++++++++++++++++ .../Plugins/PerspexPropertyAccessorPlugin.cs | 7 +++++++ .../Perspex.Markup/DefaultValueConverter.cs | 13 +++++++++++-- src/Perspex.Base/Logging/LogArea.cs | 7 ++++++- .../Utilities/WeakSubscriptionManager.cs | 2 +- .../Media/DrawingContext.cs | 2 +- 8 files changed, 60 insertions(+), 12 deletions(-) diff --git a/samples/BindingTest/App.xaml.cs b/samples/BindingTest/App.xaml.cs index 80fcaf8433..ff0fe8e65a 100644 --- a/samples/BindingTest/App.xaml.cs +++ b/samples/BindingTest/App.xaml.cs @@ -2,6 +2,7 @@ using Perspex; using Perspex.Controls; using Perspex.Diagnostics; +using Perspex.Logging.Serilog; using Perspex.Markup.Xaml; using Serilog; using Serilog.Filters; @@ -15,13 +16,7 @@ namespace BindingTest RegisterServices(); InitializeSubsystems((int)Environment.OSVersion.Platform); InitializeComponent(); - - Log.Logger = new LoggerConfiguration() - .Filter.ByIncludingOnly(Matching.WithProperty("Area", "Property")) - .Filter.ByIncludingOnly(Matching.WithProperty("Property", "Text")) - .MinimumLevel.Verbose() - .WriteTo.Trace(outputTemplate: "[{Id:X8}] [{SourceContext}] {Message}") - .CreateLogger(); + InitializeLogging(); } public static void AttachDevTools(Window window) @@ -41,5 +36,15 @@ namespace BindingTest { PerspexXamlLoader.Load(this); } + + private void InitializeLogging() + { +#if DEBUG + SerilogLogger.Initialize(new LoggerConfiguration() + .MinimumLevel.Warning() + .WriteTo.Trace(outputTemplate: "{Area}: {Message}") + .CreateLogger()); +#endif + } } } diff --git a/samples/BindingTest/BindingTest.csproj b/samples/BindingTest/BindingTest.csproj index 406c5b1cfb..91d9f590da 100644 --- a/samples/BindingTest/BindingTest.csproj +++ b/samples/BindingTest/BindingTest.csproj @@ -139,6 +139,10 @@ {42472427-4774-4c81-8aff-9f27b8e31721} Perspex.Layout + + {b61b66a3-b82d-4875-8001-89d3394fe0c9} + Perspex.Logging.Serilog + {6417b24e-49c2-4985-8db2-3ab9d898ec91} Perspex.ReactiveUI diff --git a/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs b/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs index 3a71859b4c..ac38ee74f9 100644 --- a/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Reactive.Linq; using System.Reflection; using Perspex.Data; +using Perspex.Logging; using Perspex.Utilities; namespace Perspex.Markup.Data.Plugins @@ -57,6 +58,13 @@ namespace Perspex.Markup.Data.Plugins } else { + Logger.Error( + LogArea.Binding, + this, + "Could not find CLR property {Property} on {Source}", + propertyName, + instance); + return null; } } @@ -88,6 +96,16 @@ namespace Perspex.Markup.Data.Plugins nameof(inpc.PropertyChanged), this); } + else + { + Logger.Warning( + LogArea.Binding, + this, + "Bound to property {Property} on {Source} which does not implement INotifyPropertyChanged", + property.Name, + reference.Target, + reference.Target.GetType()); + } } public Type PropertyType => _property.PropertyType; diff --git a/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs b/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs index 411fcc1555..4d2f6c5cb0 100644 --- a/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs +++ b/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs @@ -4,6 +4,7 @@ using System; using System.Reactive.Linq; using Perspex.Data; +using Perspex.Logging; namespace Perspex.Markup.Data.Plugins { @@ -53,6 +54,12 @@ namespace Perspex.Markup.Data.Plugins } else { + Logger.Error( + LogArea.Binding, + this, + "Could not find PerspexProperty {Property} on {Source}", + propertyName, + instance); return null; } } diff --git a/src/Markup/Perspex.Markup/DefaultValueConverter.cs b/src/Markup/Perspex.Markup/DefaultValueConverter.cs index 23bb0f774c..9f535081a7 100644 --- a/src/Markup/Perspex.Markup/DefaultValueConverter.cs +++ b/src/Markup/Perspex.Markup/DefaultValueConverter.cs @@ -5,6 +5,7 @@ using System; using System.Globalization; using System.Linq; using System.Reflection; +using Perspex.Logging; using Perspex.Utilities; namespace Perspex.Markup @@ -38,10 +39,18 @@ namespace Perspex.Markup { return result; } - else + + if (value != null) { - return PerspexProperty.UnsetValue; + Logger.Error( + LogArea.Binding, + this, + "Could not convert {Value} to {Type}", + value, + targetType); } + + return PerspexProperty.UnsetValue; } /// diff --git a/src/Perspex.Base/Logging/LogArea.cs b/src/Perspex.Base/Logging/LogArea.cs index cf9ac4b331..c77966be4b 100644 --- a/src/Perspex.Base/Logging/LogArea.cs +++ b/src/Perspex.Base/Logging/LogArea.cs @@ -9,10 +9,15 @@ namespace Perspex.Logging public static class LogArea { /// - /// The log event comes from the property and binding system. + /// The log event comes from the property system. /// public const string Property = "Property"; + /// + /// The log event comes from the binding system. + /// + public const string Binding = "Binding"; + /// /// The log event comes from the visual system. /// diff --git a/src/Perspex.Base/Utilities/WeakSubscriptionManager.cs b/src/Perspex.Base/Utilities/WeakSubscriptionManager.cs index d79600a038..cb4cc061f1 100644 --- a/src/Perspex.Base/Utilities/WeakSubscriptionManager.cs +++ b/src/Perspex.Base/Utilities/WeakSubscriptionManager.cs @@ -114,7 +114,7 @@ namespace Perspex.Utilities void Destroy() { - _info.RemoveEventHandler(_target, _delegate); + _info.RemoveMethod.Invoke(_target, new[] { _delegate }); _sdic.Remove(_eventName); } diff --git a/src/Perspex.SceneGraph/Media/DrawingContext.cs b/src/Perspex.SceneGraph/Media/DrawingContext.cs index 41a599858e..72f4bcbace 100644 --- a/src/Perspex.SceneGraph/Media/DrawingContext.cs +++ b/src/Perspex.SceneGraph/Media/DrawingContext.cs @@ -178,7 +178,7 @@ namespace Perspex.Media /// The opacity. /// A disposable used to undo the opacity. public PushedState PushOpacity(double opacity) - //TODO: Elimintate platform-specific push opacity call + //TODO: Eliminate platform-specific push opacity call { _impl.PushOpacity(opacity); return new PushedState(this, PushedState.PushedStateType.Opacity); From 46df444cda8e1fcf3c686246763f6cd9ca5839d6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Mar 2016 17:55:18 +0100 Subject: [PATCH 053/104] Logging documentation. --- docs/spec/logging.md | 54 ++++++++++++++++++++++++++ docs/spec/toc.yml | 4 +- samples/BindingTest/App.xaml.cs | 4 +- src/Perspex.Base/Logging/ILogSink.cs | 11 ++++++ src/Perspex.Base/Logging/Logger.cs | 57 +++++++++++++++++++++++++++- 5 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 docs/spec/logging.md diff --git a/docs/spec/logging.md b/docs/spec/logging.md new file mode 100644 index 0000000000..91d35023e4 --- /dev/null +++ b/docs/spec/logging.md @@ -0,0 +1,54 @@ +# Perspex Logging + +Perspex uses [Serilog](https://github.com/serilog/serilog) for logging via +the Perspex.Logging.Serilog assembly. + +The following method should be present in your App.xaml.cs file: + +```C# +private void InitializeLogging() +{ +#if DEBUG + SerilogLogger.Initialize(new LoggerConfiguration() + .MinimumLevel.Warning() + .WriteTo.Trace(outputTemplate: "{Area}: {Message}") + .CreateLogger()); +#endif +} +``` + +By default, this logging setup will write log messages with a severity of +`Warning` or higher to `System.Diagnostics.Trace`. See the [Serilog +documentation](https://github.com/serilog/serilog/wiki/Configuration-Basics) +for more information on the options here. + +## Areas + +Each Perspex log message has an "Area" that can be used to filter the log to +include only the type of events that you are interested in. These are currently: + +- Property +- Binding +- Visual +- Layout +- Control + +To limit the log output to a specific area you can add a filter; for example +to enable verbose logging but only about layout: + +```C# +SerilogLogger.Initialize(new LoggerConfiguration() + .Filter.ByIncludingOnly(Matching.WithProperty("Area", LogArea.Layout)) + .MinimumLevel.Verbose() + .WriteTo.Trace(outputTemplate: "{Area}: {Message}") + .CreateLogger()); +``` + +## Removing Serilog + +If you don't want a dependency on Serilog in your application, simply remove +the reference to Perspex.Logging.Serilog and the code that initializes it. If +you do however still want some kinda of logging, there are two steps: + +- Implement `Perspex.Logging.ILogSink` +- Assign your implementation to `Logger.Sink` diff --git a/docs/spec/toc.yml b/docs/spec/toc.yml index e5c40d7544..3075a03619 100644 --- a/docs/spec/toc.yml +++ b/docs/spec/toc.yml @@ -5,4 +5,6 @@ - name: Defining Properties href: defining-properties.md - name: Working with Properties - href: working-with-properties.md \ No newline at end of file + href: working-with-properties.md +- name: Logging + href: logging.md diff --git a/samples/BindingTest/App.xaml.cs b/samples/BindingTest/App.xaml.cs index ff0fe8e65a..837e17839d 100644 --- a/samples/BindingTest/App.xaml.cs +++ b/samples/BindingTest/App.xaml.cs @@ -2,6 +2,7 @@ using Perspex; using Perspex.Controls; using Perspex.Diagnostics; +using Perspex.Logging; using Perspex.Logging.Serilog; using Perspex.Markup.Xaml; using Serilog; @@ -41,7 +42,8 @@ namespace BindingTest { #if DEBUG SerilogLogger.Initialize(new LoggerConfiguration() - .MinimumLevel.Warning() + .Filter.ByIncludingOnly(Matching.WithProperty("Area", LogArea.Layout)) + .MinimumLevel.Verbose() .WriteTo.Trace(outputTemplate: "{Area}: {Message}") .CreateLogger()); #endif diff --git a/src/Perspex.Base/Logging/ILogSink.cs b/src/Perspex.Base/Logging/ILogSink.cs index b8d1540981..d2b6b62b4f 100644 --- a/src/Perspex.Base/Logging/ILogSink.cs +++ b/src/Perspex.Base/Logging/ILogSink.cs @@ -3,8 +3,19 @@ namespace Perspex.Logging { + /// + /// Defines a sink for Perspex logging messages. + /// public interface ILogSink { + /// + /// Logs a new event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// The message property values. void Log( LogEventLevel level, string area, diff --git a/src/Perspex.Base/Logging/Logger.cs b/src/Perspex.Base/Logging/Logger.cs index 311dc55fc0..f21915a332 100644 --- a/src/Perspex.Base/Logging/Logger.cs +++ b/src/Perspex.Base/Logging/Logger.cs @@ -1,15 +1,28 @@ // 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.Runtime.CompilerServices; namespace Perspex.Logging { + /// + /// Logs perspex messages. + /// public static class Logger { + /// + /// Gets or sets the application-defined sink that recieves the messages. + /// public static ILogSink Sink { get; set; } + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// The message property values. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Log( LogEventLevel level, @@ -21,6 +34,13 @@ namespace Perspex.Logging Sink?.Log(level, area, source, messageTemplate, propertyValues); } + /// + /// Logs an event with the level. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// The message property values. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Verbose( string area, @@ -31,6 +51,13 @@ namespace Perspex.Logging Log(LogEventLevel.Verbose, area, source, messageTemplate, propertyValues); } + /// + /// Logs an event with the level. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// The message property values. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Debug( string area, @@ -41,6 +68,13 @@ namespace Perspex.Logging Log(LogEventLevel.Debug, area, source, messageTemplate, propertyValues); } + /// + /// Logs an event with the level. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// The message property values. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Information( string area, @@ -51,6 +85,13 @@ namespace Perspex.Logging Log(LogEventLevel.Information, area, source, messageTemplate, propertyValues); } + /// + /// Logs an event with the level. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// The message property values. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Warning( string area, @@ -61,6 +102,13 @@ namespace Perspex.Logging Log(LogEventLevel.Warning, area, source, messageTemplate, propertyValues); } + /// + /// Logs an event with the level. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// The message property values. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Error( string area, @@ -71,6 +119,13 @@ namespace Perspex.Logging Log(LogEventLevel.Error, area, source, messageTemplate, propertyValues); } + /// + /// Logs an event with the level. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// The message property values. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Fatal( string area, From 7c658a2f3b6fa7283d860919410ef821916bb3b5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Mar 2016 17:57:51 +0100 Subject: [PATCH 054/104] Added logging assembly to NuGet. --- nuget/build-version.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nuget/build-version.ps1 b/nuget/build-version.ps1 index 59031527d5..29cfb32f4c 100644 --- a/nuget/build-version.ps1 +++ b/nuget/build-version.ps1 @@ -41,6 +41,8 @@ Copy-Item ..\src\Perspex.Interactivity\bin\Release\Perspex.Interactivity.dll $li Copy-Item ..\src\Perspex.Interactivity\bin\Release\Perspex.Interactivity.xml $lib Copy-Item ..\src\Perspex.Layout\bin\Release\Perspex.Layout.dll $lib Copy-Item ..\src\Perspex.Layout\bin\Release\Perspex.Layout.xml $lib +Copy-Item ..\src\Perspex.Logging.Serilog\bin\Release\Perspex.Logging.Serilog.dll $lib +Copy-Item ..\src\Perspex.Logging.Serilog\bin\Release\Perspex.Logging.Serilog.xml $lib Copy-Item ..\src\Perspex.SceneGraph\bin\Release\Perspex.SceneGraph.dll $lib Copy-Item ..\src\Perspex.SceneGraph\bin\Release\Perspex.SceneGraph.xml $lib Copy-Item ..\src\Perspex.Styling\bin\Release\Perspex.Styling.dll $lib From b15a17f033a3cb99e147b369d1610bc7b8d5d40b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Mar 2016 18:04:18 +0100 Subject: [PATCH 055/104] Fix #if scope. --- src/Perspex.Controls/Control.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perspex.Controls/Control.cs b/src/Perspex.Controls/Control.cs index 35d2a6edb6..8d612abcf0 100644 --- a/src/Perspex.Controls/Control.cs +++ b/src/Perspex.Controls/Control.cs @@ -552,8 +552,8 @@ namespace Perspex.Controls this, "{Type} detached from logical tree but still has class listeners", this.GetType()); -#endif } +#endif } } From 3e4dc686261471305aae5fca3f8532102bd5810b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Mar 2016 18:12:59 +0100 Subject: [PATCH 056/104] Create XML docs for Perspex.Logging.Serilog. --- src/Perspex.Logging.Serilog/Perspex.Logging.Serilog.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Perspex.Logging.Serilog/Perspex.Logging.Serilog.csproj b/src/Perspex.Logging.Serilog/Perspex.Logging.Serilog.csproj index 1baabb9665..9a5906d44c 100644 --- a/src/Perspex.Logging.Serilog/Perspex.Logging.Serilog.csproj +++ b/src/Perspex.Logging.Serilog/Perspex.Logging.Serilog.csproj @@ -24,6 +24,7 @@ DEBUG;TRACE prompt 4 + bin\Debug\Perspex.Logging.Serilog.XML pdbonly @@ -32,6 +33,7 @@ TRACE prompt 4 + bin\Release\Perspex.Logging.Serilog.XML From 3f5f21e6ad04dc845eb4c90e56faeb0e08b00323 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Mar 2016 18:34:17 +0100 Subject: [PATCH 057/104] Removed logging to normal in BindingTest. --- samples/BindingTest/App.xaml.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/samples/BindingTest/App.xaml.cs b/samples/BindingTest/App.xaml.cs index 837e17839d..e7ddb5ac66 100644 --- a/samples/BindingTest/App.xaml.cs +++ b/samples/BindingTest/App.xaml.cs @@ -42,8 +42,7 @@ namespace BindingTest { #if DEBUG SerilogLogger.Initialize(new LoggerConfiguration() - .Filter.ByIncludingOnly(Matching.WithProperty("Area", LogArea.Layout)) - .MinimumLevel.Verbose() + .MinimumLevel.Warning() .WriteTo.Trace(outputTemplate: "{Area}: {Message}") .CreateLogger()); #endif From b483ad929af3f2c1935bdb81beb9713de02ca943 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Mar 2016 18:41:03 +0100 Subject: [PATCH 058/104] Prevent spurious warning. --- src/Perspex.Controls/TopLevel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index 2bcf0d7b4a..fe87d3ce9f 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/src/Perspex.Controls/TopLevel.cs @@ -95,7 +95,8 @@ namespace Perspex.Controls _keyboardNavigationHandler = TryGetService(dependencyResolver); _renderQueueManager = TryGetService(dependencyResolver); _applicationLifecycle = TryGetService(dependencyResolver); - (TryGetService(dependencyResolver) ?? new DefaultTopLevelRenderer()).Attach(this); + + (dependencyResolver.GetService() ?? new DefaultTopLevelRenderer()).Attach(this); PlatformImpl.SetInputRoot(this); PlatformImpl.Activated = HandleActivated; From 9f8d9ed1cb523010d0b0c1f085a528275521440e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Mar 2016 18:45:22 +0100 Subject: [PATCH 059/104] Use pointer position in Popup if can't find target. If a rooted placement target could not be found, use the current pointer position as the position of the popup. --- src/Perspex.Controls/Primitives/Popup.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Perspex.Controls/Primitives/Popup.cs b/src/Perspex.Controls/Primitives/Popup.cs index e253d81912..3b16b349c2 100644 --- a/src/Perspex.Controls/Primitives/Popup.cs +++ b/src/Perspex.Controls/Primitives/Popup.cs @@ -276,12 +276,18 @@ namespace Perspex.Controls.Primitives protected virtual Point GetPosition() { var zero = default(Point); + var mode = PlacementMode; var target = PlacementTarget ?? this.GetVisualParent(); - switch (PlacementMode) + if (target?.GetVisualRoot() == null) + { + mode = PlacementMode.Pointer; + } + + switch (mode) { case PlacementMode.Pointer: - return MouseDevice.Instance.Position; + return MouseDevice.Instance?.Position ?? default(Point); case PlacementMode.Bottom: return target?.PointToScreen(new Point(0, target.Bounds.Height)) ?? zero; From 92452afc6fbd9db090def1ec57b73094413ecdec Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Mar 2016 19:10:54 +0100 Subject: [PATCH 060/104] Added ToolTip to ControlCatalog. --- samples/ControlCatalog/ControlCatalog.csproj | 8 +++++++ samples/ControlCatalog/MainWindow.xaml | 1 + samples/ControlCatalog/Pages/ToolTipPage.xaml | 22 +++++++++++++++++++ .../ControlCatalog/Pages/ToolTipPage.xaml.cs | 18 +++++++++++++++ src/Perspex.Controls/ToolTip.cs | 2 +- 5 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 samples/ControlCatalog/Pages/ToolTipPage.xaml create mode 100644 samples/ControlCatalog/Pages/ToolTipPage.xaml.cs diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index ed4b3936ed..eff5a6f148 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -60,6 +60,9 @@ MainWindow.xaml + + ToolTipPage.xaml + TextBoxPage.xaml @@ -220,6 +223,11 @@ Designer + + + Designer + + + \ No newline at end of file From ddc52222f63ecf560a6b4c3c4c833b112ea8d2ef Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 27 Mar 2016 22:14:53 +0200 Subject: [PATCH 090/104] Fixed DropDownItem alignment. This makes the DropDown with control items in ControlCatalog display correctly. --- src/Perspex.Themes.Default/DropDownItem.xaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Perspex.Themes.Default/DropDownItem.xaml b/src/Perspex.Themes.Default/DropDownItem.xaml index 26bbe9959b..4900c961e4 100644 --- a/src/Perspex.Themes.Default/DropDownItem.xaml +++ b/src/Perspex.Themes.Default/DropDownItem.xaml @@ -1,6 +1,8 @@  + \ No newline at end of file From 85f1c3a5c7ac3ced04bf7b907428c146482b1ff6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 31 Mar 2016 19:43:50 +0200 Subject: [PATCH 100/104] Added Expander to ControlCatalog. And fixed bug with CrossFade. --- samples/ControlCatalog/ControlCatalog.csproj | 8 +++++ samples/ControlCatalog/MainWindow.xaml | 1 + .../ControlCatalog/Pages/ExpanderPage.xaml | 32 +++++++++++++++++++ .../ControlCatalog/Pages/ExpanderPage.xaml.cs | 18 +++++++++++ src/Perspex.Controls/Expander.cs | 2 +- src/Perspex.SceneGraph/Animation/CrossFade.cs | 5 ++- 6 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 samples/ControlCatalog/Pages/ExpanderPage.xaml create mode 100644 samples/ControlCatalog/Pages/ExpanderPage.xaml.cs diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 9f7533fa30..06fab19d96 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -61,6 +61,9 @@ MainWindow.xaml + + ExpanderPage.xaml + RadioButtonPage.xaml @@ -250,6 +253,11 @@ Designer + + + Designer + +