diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 39333f37ba..3f4fbb0d50 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -32,7 +32,7 @@ jobs: - job: macOS pool: - vmImage: 'xcode9-macos10.13' + vmImage: 'macOS-10.14' steps: - task: DotNetCoreInstaller@0 inputs: @@ -49,7 +49,7 @@ jobs: inputs: actions: 'build' scheme: '' - sdk: 'macosx10.13' + sdk: 'macosx10.14' configuration: 'Release' xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace' xcodeVersion: 'default' # Options: 8, 9, default, specifyPath diff --git a/nukebuild/Numerge b/nukebuild/Numerge index 4464343aef..aef10ae67d 160000 --- a/nukebuild/Numerge +++ b/nukebuild/Numerge @@ -1 +1 @@ -Subproject commit 4464343aef5c8ab7a42fcb20a483a6058199f8b8 +Subproject commit aef10ae67dc55c95f49b52a505a0be33bfa297a5 diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs index 4b4203ba40..92734b128d 100644 --- a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs +++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs @@ -927,7 +927,7 @@ namespace Avalonia.Collections /// ///

/// Clear a sort criteria by assigning SortDescription.Empty to this property. - /// One or more sort criteria in form of + /// One or more sort criteria in form of /// can be used, each specifying a property and direction to sort by. ///

///
@@ -4312,4 +4312,4 @@ namespace Avalonia.Collections } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs b/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs index d5ad4c75f8..f15442addf 100644 --- a/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs @@ -8,7 +8,6 @@ using Avalonia.Metadata; [assembly: InternalsVisibleTo("Avalonia.Controls.UnitTests")] [assembly: InternalsVisibleTo("Avalonia.DesignerSupport")] -[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")] -[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Primitives")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Collections")] +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Primitives")] diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index b51583d8b3..90a27d0b31 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -177,6 +177,17 @@ namespace Avalonia.Controls return element.GetValue(RowSpanProperty); } + + /// + /// Gets the value of the IsSharedSizeScope attached property for a control. + /// + /// The control. + /// The control's IsSharedSizeScope value. + public static bool GetIsSharedSizeScope(AvaloniaObject element) + { + return element.GetValue(IsSharedSizeScopeProperty); + } + /// /// Sets the value of the Column attached property for a control. /// @@ -217,6 +228,16 @@ namespace Avalonia.Controls element.SetValue(RowSpanProperty, value); } + /// + /// Sets the value of IsSharedSizeScope property for a control. + /// + /// The control. + /// The IsSharedSizeScope value. + public static void SetIsSharedSizeScope(AvaloniaObject element, bool value) + { + element.SetValue(IsSharedSizeScopeProperty, value); + } + /// /// Gets the result of the last column measurement. /// Use this result to reduce the arrange calculation. diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs index f8ae9f4249..0918da1a90 100644 --- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs +++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs @@ -147,7 +147,10 @@ namespace Avalonia.Platform e.Handled = true; } else if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl || e.Key == Key.LeftAlt || e.Key == Key.RightAlt) - RaiseEventAndUpdateCursor(RawDragEventType.DragOver, _lastRoot, _lastPosition, e.Modifiers); + { + if (_lastRoot != null) + RaiseEventAndUpdateCursor(RawDragEventType.DragOver, _lastRoot, _lastPosition, e.Modifiers); + } } private void ProcessMouseEvents(RawMouseEventArgs e) diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index 57dbeba1cc..499dfb5320 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -21,13 +21,19 @@ namespace Avalonia.Controls.Shapes public static readonly StyledProperty> StrokeDashArrayProperty = AvaloniaProperty.Register>(nameof(StrokeDashArray)); - + public static readonly StyledProperty StrokeDashOffsetProperty = AvaloniaProperty.Register(nameof(StrokeDashOffset)); public static readonly StyledProperty StrokeThicknessProperty = AvaloniaProperty.Register(nameof(StrokeThickness)); + public static readonly StyledProperty StrokeLineCapProperty = + AvaloniaProperty.Register(nameof(StrokeLineCap), PenLineCap.Flat); + + public static readonly StyledProperty StrokeJoinProperty = + AvaloniaProperty.Register(nameof(StrokeJoin), PenLineJoin.Miter); + private Matrix _transform = Matrix.Identity; private Geometry _definingGeometry; private Geometry _renderedGeometry; @@ -36,7 +42,9 @@ namespace Avalonia.Controls.Shapes static Shape() { AffectsMeasure(StretchProperty, StrokeThicknessProperty); - AffectsRender(FillProperty, StrokeProperty, StrokeDashArrayProperty); + + AffectsRender(FillProperty, StrokeProperty, StrokeDashArrayProperty, StrokeDashOffsetProperty, + StrokeThicknessProperty, StrokeLineCapProperty, StrokeJoinProperty); } public Geometry DefiningGeometry @@ -106,7 +114,7 @@ namespace Avalonia.Controls.Shapes get { return GetValue(StrokeDashArrayProperty); } set { SetValue(StrokeDashArrayProperty, value); } } - + public double StrokeDashOffset { get { return GetValue(StrokeDashOffsetProperty); } @@ -119,13 +127,17 @@ namespace Avalonia.Controls.Shapes set { SetValue(StrokeThicknessProperty, value); } } - public PenLineCap StrokeDashCap { get; set; } = PenLineCap.Flat; - - public PenLineCap StrokeStartLineCap { get; set; } = PenLineCap.Flat; - - public PenLineCap StrokeEndLineCap { get; set; } = PenLineCap.Flat; + public PenLineCap StrokeLineCap + { + get { return GetValue(StrokeLineCapProperty); } + set { SetValue(StrokeLineCapProperty, value); } + } - public PenLineJoin StrokeJoin { get; set; } = PenLineJoin.Miter; + public PenLineJoin StrokeJoin + { + get { return GetValue(StrokeJoinProperty); } + set { SetValue(StrokeJoinProperty, value); } + } public override void Render(DrawingContext context) { @@ -133,8 +145,8 @@ namespace Avalonia.Controls.Shapes if (geometry != null) { - var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray, StrokeDashOffset), - StrokeDashCap, StrokeStartLineCap, StrokeEndLineCap, StrokeJoin); + var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray, StrokeDashOffset), + StrokeLineCap, StrokeJoin); context.DrawGeometry(Fill, pen, geometry); } } @@ -169,11 +181,11 @@ namespace Avalonia.Controls.Shapes protected void InvalidateGeometry() { - this._renderedGeometry = null; - this._definingGeometry = null; + _renderedGeometry = null; + _definingGeometry = null; InvalidateMeasure(); } - + protected override Size MeasureOverride(Size availableSize) { bool deferCalculateTransform; @@ -203,10 +215,10 @@ namespace Avalonia.Controls.Shapes return CalculateShapeSizeAndSetTransform(availableSize); } } - + protected override Size ArrangeOverride(Size finalSize) { - if(_calculateTransformOnArrange) + if (_calculateTransformOnArrange) { _calculateTransformOnArrange = false; CalculateShapeSizeAndSetTransform(finalSize); @@ -312,25 +324,25 @@ namespace Avalonia.Controls.Shapes private static void AffectsGeometryInvalidate(AvaloniaPropertyChangedEventArgs e) { - var control = e.Sender as Shape; + if (!(e.Sender is Shape control)) + { + return; + } - if (control != null) + // If the geometry is invalidated when Bounds changes, only invalidate when the Size + // portion changes. + if (e.Property == BoundsProperty) { - // If the geometry is invalidated when Bounds changes, only invalidate when the Size - // portion changes. - if (e.Property == BoundsProperty) - { - var oldBounds = (Rect)e.OldValue; - var newBounds = (Rect)e.NewValue; + var oldBounds = (Rect)e.OldValue; + var newBounds = (Rect)e.NewValue; - if (oldBounds.Size == newBounds.Size) - { - return; - } + if (oldBounds.Size == newBounds.Size) + { + return; } - - control.InvalidateGeometry(); } + + control.InvalidateGeometry(); } } } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index c8e09b8f9c..e40e114769 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -371,8 +371,16 @@ namespace Avalonia.Controls /// /// Shows the window. /// + /// + /// The window has already been closed. + /// public override void Show() { + if (PlatformImpl == null) + { + throw new InvalidOperationException("Cannot re-show a closed window."); + } + if (IsVisible) { return; @@ -397,6 +405,9 @@ namespace Avalonia.Controls /// Shows the window as a dialog. /// /// The dialog's owner window. + /// + /// The window has already been closed. + /// /// /// A task that can be used to track the lifetime of the dialog. /// diff --git a/src/Avalonia.Visuals/Media/BrushExtensions.cs b/src/Avalonia.Visuals/Media/BrushExtensions.cs index cd351071dd..522953eb04 100644 --- a/src/Avalonia.Visuals/Media/BrushExtensions.cs +++ b/src/Avalonia.Visuals/Media/BrushExtensions.cs @@ -34,16 +34,14 @@ namespace Avalonia.Media { Contract.Requires(pen != null); - var brush = pen?.Brush?.ToImmutable(); - return pen == null || ReferenceEquals(pen?.Brush, brush) ? + var brush = pen.Brush?.ToImmutable(); + return ReferenceEquals(pen.Brush, brush) ? pen : new Pen( brush, thickness: pen.Thickness, - dashStyle: pen.DashStyle, - dashCap: pen.DashCap, - startLineCap: pen.StartLineCap, - endLineCap: pen.EndLineCap, + dashStyle: pen.DashStyle, + lineCap: pen.LineCap, lineJoin: pen.LineJoin, miterLimit: pen.MiterLimit); } diff --git a/src/Avalonia.Visuals/Media/Pen.cs b/src/Avalonia.Visuals/Media/Pen.cs index ddd8091801..ee427c913b 100644 --- a/src/Avalonia.Visuals/Media/Pen.cs +++ b/src/Avalonia.Visuals/Media/Pen.cs @@ -11,63 +11,45 @@ namespace Avalonia.Media /// /// Initializes a new instance of the class. /// - /// The brush used to draw. + /// The stroke color. /// The stroke thickness. /// The dash style. - /// The dash cap. - /// The start line cap. - /// The end line cap. + /// Specifies the type of graphic shape to use on both ends of a line. /// The line join. /// The miter limit. public Pen( - IBrush brush, + uint color, double thickness = 1.0, - DashStyle dashStyle = null, - PenLineCap dashCap = PenLineCap.Flat, - PenLineCap startLineCap = PenLineCap.Flat, - PenLineCap endLineCap = PenLineCap.Flat, - PenLineJoin lineJoin = PenLineJoin.Miter, - double miterLimit = 10.0) + DashStyle dashStyle = null, + PenLineCap lineCap = PenLineCap.Flat, + PenLineJoin lineJoin = PenLineJoin.Miter, + double miterLimit = 10.0) : this(new SolidColorBrush(color), thickness, dashStyle, lineCap, lineJoin, miterLimit) { - Brush = brush; - Thickness = thickness; - DashCap = dashCap; - StartLineCap = startLineCap; - EndLineCap = endLineCap; - LineJoin = lineJoin; - MiterLimit = miterLimit; - DashStyle = dashStyle; } /// /// Initializes a new instance of the class. /// - /// The stroke color. + /// The brush used to draw. /// The stroke thickness. /// The dash style. - /// The dash cap. - /// The start line cap. - /// The end line cap. + /// The line cap. /// The line join. /// The miter limit. public Pen( - uint color, + IBrush brush, double thickness = 1.0, - DashStyle dashStyle = null, - PenLineCap dashCap = PenLineCap.Flat, - PenLineCap startLineCap = PenLineCap.Flat, - PenLineCap endLineCap = PenLineCap.Flat, - PenLineJoin lineJoin = PenLineJoin.Miter, + DashStyle dashStyle = null, + PenLineCap lineCap = PenLineCap.Flat, + PenLineJoin lineJoin = PenLineJoin.Miter, double miterLimit = 10.0) { - Brush = new SolidColorBrush(color); + Brush = brush; Thickness = thickness; - StartLineCap = startLineCap; - EndLineCap = endLineCap; + LineCap = lineCap; LineJoin = lineJoin; MiterLimit = miterLimit; DashStyle = dashStyle; - DashCap = dashCap; } /// @@ -78,18 +60,26 @@ namespace Avalonia.Media /// /// Gets the stroke thickness. /// - public double Thickness { get; } = 1.0; + public double Thickness { get; } + /// + /// Specifies the style of dashed lines drawn with a object. + /// public DashStyle DashStyle { get; } - public PenLineCap DashCap { get; } - - public PenLineCap StartLineCap { get; } = PenLineCap.Flat; - - public PenLineCap EndLineCap { get; } = PenLineCap.Flat; + /// + /// Specifies the type of graphic shape to use on both ends of a line. + /// + public PenLineCap LineCap { get; } - public PenLineJoin LineJoin { get; } = PenLineJoin.Miter; + /// + /// Specifies how to join consecutive line or curve segments in a (subpath) contained in a object. + /// + public PenLineJoin LineJoin { get; } - public double MiterLimit { get; } = 10.0; + /// + /// The limit on the ratio of the miter length to half this pen's Thickness. + /// + public double MiterLimit { get; } } } diff --git a/src/Avalonia.Visuals/Media/PenLineCap.cs b/src/Avalonia.Visuals/Media/PenLineCap.cs index 56c5c040eb..83d8f11613 100644 --- a/src/Avalonia.Visuals/Media/PenLineCap.cs +++ b/src/Avalonia.Visuals/Media/PenLineCap.cs @@ -4,7 +4,6 @@ namespace Avalonia.Media { Flat, Round, - Square, - Triangle + Square } } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 5272f4b22d..ee4564ff35 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -578,25 +578,17 @@ namespace Avalonia.Skia // Need to modify dashes due to Skia modifying their lengths // https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/paths/dots // TODO: Still something is off, dashes are now present, but don't look the same as D2D ones. - float dashLengthModifier; - float gapLengthModifier; - switch (pen.StartLineCap) + switch (pen.LineCap) { case PenLineCap.Round: paint.StrokeCap = SKStrokeCap.Round; - dashLengthModifier = -paint.StrokeWidth; - gapLengthModifier = paint.StrokeWidth; break; case PenLineCap.Square: paint.StrokeCap = SKStrokeCap.Square; - dashLengthModifier = -paint.StrokeWidth; - gapLengthModifier = paint.StrokeWidth; break; default: paint.StrokeCap = SKStrokeCap.Butt; - dashLengthModifier = 0.0f; - gapLengthModifier = 0.0f; break; } @@ -622,13 +614,12 @@ namespace Avalonia.Skia for (var i = 0; i < srcDashes.Count; ++i) { - var lengthModifier = i % 2 == 0 ? dashLengthModifier : gapLengthModifier; - - // Avalonia dash lengths are relative, but Skia takes absolute sizes - need to scale - dashesArray[i] = (float) srcDashes[i] * paint.StrokeWidth + lengthModifier; + dashesArray[i] = (float) srcDashes[i] * paint.StrokeWidth; } - var pe = SKPathEffect.CreateDash(dashesArray, (float) pen.DashStyle.Offset); + var offset = (float)(pen.DashStyle.Offset * pen.Thickness); + + var pe = SKPathEffect.CreateDash(dashesArray, offset); paint.PathEffect = pe; rv.AddDisposable(pe); diff --git a/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs b/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs index 5209014271..6b0d30f250 100644 --- a/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs +++ b/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs @@ -122,14 +122,16 @@ namespace Avalonia.Direct2D1 /// The Direct2D brush. public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.Pen pen, Factory factory) { + var d2dLineCap = pen.LineCap.ToDirect2D(); + var properties = new StrokeStyleProperties { DashStyle = DashStyle.Solid, MiterLimit = (float)pen.MiterLimit, LineJoin = pen.LineJoin.ToDirect2D(), - StartCap = pen.StartLineCap.ToDirect2D(), - EndCap = pen.EndLineCap.ToDirect2D(), - DashCap = pen.DashCap.ToDirect2D() + StartCap = d2dLineCap, + EndCap = d2dLineCap, + DashCap = d2dLineCap }; float[] dashes = null; if (pen.DashStyle?.Dashes != null && pen.DashStyle.Dashes.Count > 0) diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 8221dadc86..ee416c4cb0 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -292,6 +292,51 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Calling_Show_On_Closed_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var windowImpl = Mock.Of(x => x.Scaling == 1); + var target = new Window(windowImpl); + + target.Show(); + target.Close(); + + var openedRaised = false; + target.Opened += (s, e) => openedRaised = true; + + var ex = Assert.Throws(() => target.Show()); + Assert.Equal("Cannot re-show a closed window.", ex.Message); + Assert.False(openedRaised); + } + } + + [Fact] + public async Task Calling_ShowDialog_On_Closed_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var parent = new Mock(); + var windowImpl = new Mock(); + windowImpl.SetupProperty(x => x.Closed); + windowImpl.Setup(x => x.Scaling).Returns(1); + + var target = new Window(windowImpl.Object); + var task = target.ShowDialog(parent.Object); + + windowImpl.Object.Closed(); + await task; + + var openedRaised = false; + target.Opened += (s, e) => openedRaised = true; + + var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent.Object)); + Assert.Equal("Cannot re-show a closed window.", ex.Message); + Assert.False(openedRaised); + } + } + [Fact] public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() { diff --git a/tests/Avalonia.RenderTests/Shapes/PathTests.cs b/tests/Avalonia.RenderTests/Shapes/PathTests.cs index 4703daca25..2fa93b49e2 100644 --- a/tests/Avalonia.RenderTests/Shapes/PathTests.cs +++ b/tests/Avalonia.RenderTests/Shapes/PathTests.cs @@ -334,11 +334,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes CompareImages(); } -#if AVALONIA_SKIA_SKIP_FAIL - [Fact(Skip = "FIXME")] -#else [Fact] -#endif public async Task Path_With_PenLineCap() { Decorator target = new Decorator @@ -351,10 +347,8 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes StrokeThickness = 10, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, - StrokeDashCap = PenLineCap.Triangle, StrokeDashArray = new AvaloniaList(3, 1), - StrokeStartLineCap = PenLineCap.Round, - StrokeEndLineCap = PenLineCap.Square, + StrokeLineCap = PenLineCap.Round, Data = StreamGeometry.Parse("M 20,20 L 180,180"), } }; diff --git a/tests/Avalonia.RenderTests/Shapes/PolylineTests.cs b/tests/Avalonia.RenderTests/Shapes/PolylineTests.cs index 3b586d55ea..8afaeb8838 100644 --- a/tests/Avalonia.RenderTests/Shapes/PolylineTests.cs +++ b/tests/Avalonia.RenderTests/Shapes/PolylineTests.cs @@ -61,8 +61,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes Points = polylinePoints, Stretch = Stretch.Uniform, StrokeJoin = PenLineJoin.Round, - StrokeStartLineCap = PenLineCap.Round, - StrokeEndLineCap = PenLineCap.Round, + StrokeLineCap = PenLineCap.Round, StrokeThickness = 10 } }; diff --git a/tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png b/tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png index d33068d62c..a61ba9f080 100644 Binary files a/tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png and b/tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png differ diff --git a/tests/TestFiles/Skia/Shapes/Path/Path_With_PenLineCap.expected.png b/tests/TestFiles/Skia/Shapes/Path/Path_With_PenLineCap.expected.png index d33068d62c..a61ba9f080 100644 Binary files a/tests/TestFiles/Skia/Shapes/Path/Path_With_PenLineCap.expected.png and b/tests/TestFiles/Skia/Shapes/Path/Path_With_PenLineCap.expected.png differ