From a6cfe88c61bf971ce20fc6c9dabde5b3553a049e Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 8 Jun 2021 23:40:40 +0200 Subject: [PATCH 01/25] Change ImmutableSolidColorBrush to a class. --- .../Media/Immutable/ImmutableSolidColorBrush.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs index 010184ad3b..8e93ac580e 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs @@ -5,7 +5,7 @@ namespace Avalonia.Media.Immutable /// /// Fills an area with a solid color. /// - public readonly struct ImmutableSolidColorBrush : ISolidColorBrush, IEquatable + public class ImmutableSolidColorBrush : ISolidColorBrush, IEquatable { /// /// Initializes a new instance of the class. @@ -48,8 +48,9 @@ namespace Avalonia.Media.Immutable public bool Equals(ImmutableSolidColorBrush other) { - // ReSharper disable once CompareOfFloatsByEqualityOperator - return Color == other.Color && Opacity == other.Opacity; + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Color.Equals(other.Color) && Opacity.Equals(other.Opacity); } public override bool Equals(object obj) @@ -67,12 +68,12 @@ namespace Avalonia.Media.Immutable public static bool operator ==(ImmutableSolidColorBrush left, ImmutableSolidColorBrush right) { - return left.Equals(right); + return Equals(left, right); } public static bool operator !=(ImmutableSolidColorBrush left, ImmutableSolidColorBrush right) { - return !left.Equals(right); + return !Equals(left, right); } /// From f9103e2c9561b614b3c94a71839c80f0ae8d4416 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 8 Jun 2021 23:41:13 +0200 Subject: [PATCH 02/25] Parse brushes as immutable by default. --- src/Avalonia.Visuals/Media/Brush.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/Brush.cs b/src/Avalonia.Visuals/Media/Brush.cs index fb03d19a4e..cf7f5f531c 100644 --- a/src/Avalonia.Visuals/Media/Brush.cs +++ b/src/Avalonia.Visuals/Media/Brush.cs @@ -2,6 +2,7 @@ using System; using System.ComponentModel; using Avalonia.Animation; using Avalonia.Animation.Animators; +using Avalonia.Media.Immutable; namespace Avalonia.Media { @@ -47,7 +48,7 @@ namespace Avalonia.Media if (s[0] == '#') { - return new SolidColorBrush(Color.Parse(s)); + return new ImmutableSolidColorBrush(Color.Parse(s)); } var brush = KnownColors.GetKnownBrush(s); From e742f81a7d2b1722d301132ce452a127a70e7af5 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 8 Jun 2021 23:41:34 +0200 Subject: [PATCH 03/25] Parse brushes during compile time. --- .../AvaloniaXamlIlLanguageParseIntrinsics.cs | 14 ++++++++++++++ .../Transformers/AvaloniaXamlIlWellKnownTypes.cs | 9 ++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index 059b5650cb..4592b9c8b4 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -207,6 +207,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions return true; } + if (types.IBrush.IsAssignableFrom(type)) + { + if (Color.TryParse(text, out Color color)) + { + var brushTypeRef = new XamlAstClrTypeReference(node, types.ImmutableSolidColorBrush, false); + + result = new XamlAstNewClrObjectNode(node, brushTypeRef, + types.ImmutableSolidColorBrushConstructorColor, + new List { new XamlConstantNode(node, types.UInt, color.ToUint32()) }); + + return true; + } + } + result = null; return false; } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index c4995b2de3..6dd3521183 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -80,7 +80,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType ColumnDefinitions { get; } public IXamlType Classes { get; } public IXamlMethod ClassesBindMethod { get; } - public IXamlProperty StyledElementClassesProperty { get; set; } + public IXamlProperty StyledElementClassesProperty { get; } + public IXamlType IBrush { get; } + public IXamlType ImmutableSolidColorBrush { get; } + public IXamlConstructor ImmutableSolidColorBrushConstructorColor { get; } public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) { @@ -178,6 +181,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers .FindMethod( "BindClass", IDisposable, false, IStyledElement, cfg.WellKnownTypes.String, IBinding, cfg.WellKnownTypes.Object); + + IBrush = cfg.TypeSystem.GetType("Avalonia.Media.IBrush"); + ImmutableSolidColorBrush = cfg.TypeSystem.GetType("Avalonia.Media.Immutable.ImmutableSolidColorBrush"); + ImmutableSolidColorBrushConstructorColor = ImmutableSolidColorBrush.GetConstructor(new List { UInt }); } } From 82d2ee175f39f044b6cc7864899167eafb4712c9 Mon Sep 17 00:00:00 2001 From: Splitwirez Date: Sat, 19 Jun 2021 15:36:58 -0400 Subject: [PATCH 04/25] Add `Arc`...again... --- src/Avalonia.Controls/Shapes/Arc.cs | 99 +++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/Avalonia.Controls/Shapes/Arc.cs diff --git a/src/Avalonia.Controls/Shapes/Arc.cs b/src/Avalonia.Controls/Shapes/Arc.cs new file mode 100644 index 0000000000..f6738e10f2 --- /dev/null +++ b/src/Avalonia.Controls/Shapes/Arc.cs @@ -0,0 +1,99 @@ +using System; +using Avalonia.Media; + +namespace Avalonia.Controls.Shapes +{ + public class Arc : Shape + { + /// + /// Defines the property. + /// + public static readonly StyledProperty StartAngleProperty = + AvaloniaProperty.Register(nameof(StartAngle), 0.0); + + /// + /// Defines the property. + /// + public static readonly StyledProperty SweepAngleProperty = + AvaloniaProperty.Register(nameof(SweepAngle), 0.0); + + static Arc() + { + StrokeThicknessProperty.OverrideDefaultValue(1); + AffectsGeometry(BoundsProperty, StrokeThicknessProperty, StartAngleProperty, SweepAngleProperty); + } + + /// + /// Gets or sets the angle at which the arc starts, in degrees. + /// + public double StartAngle + { + get { return GetValue(StartAngleProperty); } + set { SetValue(StartAngleProperty, value); } + } + + /// + /// Gets or sets the angle at which the arc ends relative to the start angle, in degrees. + /// + public double SweepAngle + { + get { return GetValue(SweepAngleProperty); } + set { SetValue(SweepAngleProperty, value); } + } + + protected override Geometry CreateDefiningGeometry() + { + double angle1 = DegreesToRad(StartAngle); + double angle2 = angle1 + DegreesToRad(SweepAngle); + + double startAngle = Math.Min(angle1, angle2); + double sweepAngle = Math.Max(angle1, angle2); + + double normStart = RadToNormRad(startAngle); + double normEnd = RadToNormRad(sweepAngle); + + var rect = new Rect(Bounds.Size); + + if ((normStart == normEnd) && (startAngle != sweepAngle)) //complete ring + { + return new EllipseGeometry(rect.Deflate(StrokeThickness / 2)); + } + else if (SweepAngle == 0) + { + return new StreamGeometry(); + } + else //partial arc + { + var deflatedRect = rect.Deflate(StrokeThickness / 2); + + double centerX = rect.Center.X; + double centerY = rect.Center.Y; + + double radiusX = deflatedRect.Width / 2; + double radiusY = deflatedRect.Height / 2; + + double angleGap = RadToNormRad(sweepAngle - startAngle); + + Point startPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, startAngle); + Point endPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, sweepAngle); + + StreamGeometry arcGeometry = new StreamGeometry(); + using (var ctx = arcGeometry.Open()) + { + ctx.BeginFigure(startPoint, false); + ctx.ArcTo(endPoint, new Size(radiusX, radiusY), angleGap, angleGap >= Math.PI, SweepDirection.Clockwise); + ctx.EndFigure(false); + } + return arcGeometry; + } + } + + static double DegreesToRad(double inAngle) => + inAngle * Math.PI / 180; + + static double RadToNormRad(double inAngle) => (0 + (inAngle % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2); + + static Point GetRingPoint(double radiusX, double radiusY, double centerX, double centerY, double angle) => + new Point((radiusX * Math.Cos(angle)) + centerX, (radiusY * Math.Sin(angle)) + centerY); + } +} From 6e99f1240634c36bc164a87399333af03b513ba9 Mon Sep 17 00:00:00 2001 From: Splitwirez Date: Sat, 3 Jul 2021 11:17:19 -0400 Subject: [PATCH 05/25] Fix comments --- src/Avalonia.Controls/Shapes/Arc.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Shapes/Arc.cs b/src/Avalonia.Controls/Shapes/Arc.cs index f6738e10f2..4ce72319aa 100644 --- a/src/Avalonia.Controls/Shapes/Arc.cs +++ b/src/Avalonia.Controls/Shapes/Arc.cs @@ -33,7 +33,7 @@ namespace Avalonia.Controls.Shapes } /// - /// Gets or sets the angle at which the arc ends relative to the start angle, in degrees. + /// Gets or sets the angle, in degrees, added to the defining where the arc ends. A positive value is clockwise, negative is counter-clockwise. /// public double SweepAngle { @@ -54,7 +54,7 @@ namespace Avalonia.Controls.Shapes var rect = new Rect(Bounds.Size); - if ((normStart == normEnd) && (startAngle != sweepAngle)) //complete ring + if ((normStart == normEnd) && (startAngle != sweepAngle)) // complete ring { return new EllipseGeometry(rect.Deflate(StrokeThickness / 2)); } @@ -62,7 +62,7 @@ namespace Avalonia.Controls.Shapes { return new StreamGeometry(); } - else //partial arc + else // partial arc { var deflatedRect = rect.Deflate(StrokeThickness / 2); From ea3f85e1263cfb75ce2452e205477080d1c9b531 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Jul 2021 09:34:18 +0200 Subject: [PATCH 06/25] Don't allow using a closed window as a parent/owner. --- src/Avalonia.Controls/Window.cs | 10 +++++++ .../WindowTests.cs | 30 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 700c3d9bad..2d369dae8c 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -635,6 +635,11 @@ namespace Avalonia.Controls throw new InvalidOperationException("Cannot re-show a closed window."); } + if (parent != null && parent.PlatformImpl == null) + { + throw new InvalidOperationException("Cannot Show a Window with a closed parent."); + } + if (IsVisible) { return; @@ -709,6 +714,11 @@ namespace Avalonia.Controls throw new ArgumentNullException(nameof(owner)); } + if (owner.PlatformImpl == null) + { + throw new InvalidOperationException("Cannot Show a Window with a closed owner."); + } + if (IsVisible) { throw new InvalidOperationException("The window is already being shown."); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index e8311b79ac..2d4559ddba 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -387,6 +387,36 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Calling_Show_With_Closed_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var parent = new Window(); + var target = new Window(); + + parent.Close(); + + var ex = Assert.Throws(() => target.Show(parent)); + Assert.Equal("Cannot Show a Window with a closed parent.", ex.Message); + } + } + + [Fact] + public async Task Calling_ShowDialog_With_Closed_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var parent = new Window(); + var target = new Window(); + + parent.Close(); + + var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent)); + Assert.Equal("Cannot Show a Window with a closed owner.", ex.Message); + } + } + [Fact] public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() { From 69852a56f588fe0e744aa11e7577eeb351f29670 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Jul 2021 12:16:44 +0200 Subject: [PATCH 07/25] Don't allow self as parent/owner window. --- src/Avalonia.Controls/Window.cs | 17 +++++++++++-- .../WindowTests.cs | 24 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 2d369dae8c..9f6c605d46 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -635,9 +635,17 @@ namespace Avalonia.Controls throw new InvalidOperationException("Cannot re-show a closed window."); } - if (parent != null && parent.PlatformImpl == null) + if (parent != null) { - throw new InvalidOperationException("Cannot Show a Window with a closed parent."); + if (parent.PlatformImpl == null) + { + throw new InvalidOperationException("Cannot Show a Window with a closed parent."); + } + + if (parent == this) + { + throw new InvalidOperationException("A Window cannot be its own parent."); + } } if (IsVisible) @@ -719,6 +727,11 @@ namespace Avalonia.Controls throw new InvalidOperationException("Cannot Show a Window with a closed owner."); } + if (owner == this) + { + throw new InvalidOperationException("A Window cannot be its own owner."); + } + if (IsVisible) { throw new InvalidOperationException("The window is already being shown."); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 2d4559ddba..5ba529292f 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -417,6 +417,30 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Calling_Show_With_Self_As_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new Window(); + + var ex = Assert.Throws(() => target.Show(target)); + Assert.Equal("A Window cannot be its own parent.", ex.Message); + } + } + + [Fact] + public async Task Calling_ShowDialog_With_Self_As_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new Window(); + + var ex = await Assert.ThrowsAsync(() => target.ShowDialog(target)); + Assert.Equal("A Window cannot be its own owner.", ex.Message); + } + } + [Fact] public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() { From 15cdee1bee18cc7466f68951f7bfa863fee748be Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Jul 2021 13:27:01 +0200 Subject: [PATCH 08/25] Ensure parent/owner windows are visible when showing child. --- src/Avalonia.Controls/Window.cs | 24 +++++---- .../WindowTests.cs | 50 +++++++++++++++---- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 9f6c605d46..ddda083aa8 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -639,13 +639,16 @@ namespace Avalonia.Controls { if (parent.PlatformImpl == null) { - throw new InvalidOperationException("Cannot Show a Window with a closed parent."); + throw new InvalidOperationException("Cannot show a window with a closed parent."); } - - if (parent == this) + else if (parent == this) { throw new InvalidOperationException("A Window cannot be its own parent."); } + else if (!parent.IsVisible) + { + throw new InvalidOperationException("Cannot show window with non-visible parent."); + } } if (IsVisible) @@ -721,21 +724,22 @@ namespace Avalonia.Controls { throw new ArgumentNullException(nameof(owner)); } - - if (owner.PlatformImpl == null) + else if (owner.PlatformImpl == null) { - throw new InvalidOperationException("Cannot Show a Window with a closed owner."); + throw new InvalidOperationException("Cannot show a window with a closed owner."); } - - if (owner == this) + else if (owner == this) { throw new InvalidOperationException("A Window cannot be its own owner."); } - - if (IsVisible) + else if (IsVisible) { throw new InvalidOperationException("The window is already being shown."); } + else if (!owner.IsVisible) + { + throw new InvalidOperationException("Cannot show window with non-visible parent."); + } RaiseEvent(new RoutedEventArgs(WindowOpenedEvent)); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 5ba529292f..88c6c86c46 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -279,10 +279,11 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var parent = Mock.Of(); + var parent = new Window(); var renderer = new Mock(); var target = new Window(CreateImpl(renderer)); + parent.Show(); target.ShowDialog(parent); renderer.Verify(x => x.Start(), Times.Once); @@ -294,10 +295,11 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var parent = Mock.Of(); + var parent = new Window(); var target = new Window(); var raised = false; + parent.Show(); target.Opened += (s, e) => raised = true; target.ShowDialog(parent); @@ -326,14 +328,15 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var parent = new Mock(); + var parent = new Window(); var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1); + parent.Show(); var target = new Window(windowImpl.Object); - var task = target.ShowDialog(parent.Object); + var task = target.ShowDialog(parent); windowImpl.Object.Closed(); @@ -366,14 +369,16 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var parent = new Mock(); + var parent = new Window(); var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1); + parent.Show(); + var target = new Window(windowImpl.Object); - var task = target.ShowDialog(parent.Object); + var task = target.ShowDialog(parent); windowImpl.Object.Closed(); await task; @@ -381,7 +386,7 @@ namespace Avalonia.Controls.UnitTests var openedRaised = false; target.Opened += (s, e) => openedRaised = true; - var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent.Object)); + var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent)); Assert.Equal("Cannot re-show a closed window.", ex.Message); Assert.False(openedRaised); } @@ -398,7 +403,7 @@ namespace Avalonia.Controls.UnitTests parent.Close(); var ex = Assert.Throws(() => target.Show(parent)); - Assert.Equal("Cannot Show a Window with a closed parent.", ex.Message); + Assert.Equal("Cannot show a window with a closed parent.", ex.Message); } } @@ -413,7 +418,33 @@ namespace Avalonia.Controls.UnitTests parent.Close(); var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent)); - Assert.Equal("Cannot Show a Window with a closed owner.", ex.Message); + Assert.Equal("Cannot show a window with a closed owner.", ex.Message); + } + } + + [Fact] + public void Calling_Show_With_Invisible_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var parent = new Window(); + var target = new Window(); + + var ex = Assert.Throws(() => target.Show(parent)); + Assert.Equal("Cannot show window with non-visible parent.", ex.Message); + } + } + + [Fact] + public async Task Calling_ShowDialog_With_Invisible_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var parent = new Window(); + var target = new Window(); + + var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent)); + Assert.Equal("Cannot show window with non-visible parent.", ex.Message); } } @@ -740,6 +771,7 @@ namespace Avalonia.Controls.UnitTests protected override void Show(Window window) { var owner = new Window(); + owner.Show(); window.ShowDialog(owner); } } From a42334d128eb919c6f29de6c7d51cd333b4221a0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Jul 2021 14:00:09 +0200 Subject: [PATCH 09/25] Hide child windows when hiding parent/owner. --- src/Avalonia.Controls/Window.cs | 8 +++++ .../WindowTests.cs | 36 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index ddda083aa8..ae314a33ce 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -592,6 +592,14 @@ namespace Avalonia.Controls owner.RemoveChild(this); } + if (_children.Count > 0) + { + foreach (var child in _children.ToArray()) + { + child.child.Hide(); + } + } + Owner = null; PlatformImpl?.Hide(); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 88c6c86c46..6b9921d83d 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -472,6 +472,42 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Hiding_Parent_Window_Should_Close_Children() + { + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) + { + var parent = new Window(); + var child = new Window(); + + parent.Show(); + child.Show(parent); + + parent.Hide(); + + Assert.False(parent.IsVisible); + Assert.False(child.IsVisible); + } + } + + [Fact] + public void Hiding_Parent_Window_Should_Close_Dialog_Children() + { + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) + { + var parent = new Window(); + var child = new Window(); + + parent.Show(); + child.ShowDialog(parent); + + parent.Hide(); + + Assert.False(parent.IsVisible); + Assert.False(child.IsVisible); + } + } + [Fact] public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() { From f9824007e1a3f47a2583bd86f819bd6cccc9f3b7 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 6 Jul 2021 20:26:23 +0100 Subject: [PATCH 10/25] add a method to retain com object during com calls. --- native/Avalonia.Native/inc/comimpl.h | 184 ++++++++++++++------------- 1 file changed, 95 insertions(+), 89 deletions(-) diff --git a/native/Avalonia.Native/inc/comimpl.h b/native/Avalonia.Native/inc/comimpl.h index 0ff64b7215..c9f70cfa3b 100644 --- a/native/Avalonia.Native/inc/comimpl.h +++ b/native/Avalonia.Native/inc/comimpl.h @@ -10,6 +10,95 @@ __IID_DEF(IUnknown, 0, 0, 0, C0, 00, 00, 00, 00, 00, 00, 46); +template +class ComPtr +{ +private: + TInterface* _obj; +public: + ComPtr() + { + _obj = 0; + } + + ComPtr(TInterface* pObj) + { + _obj = 0; + + if (pObj) + { + _obj = pObj; + _obj->AddRef(); + } + } + + ComPtr(const ComPtr& ptr) + { + _obj = 0; + + if (ptr._obj) + { + _obj = ptr._obj; + _obj->AddRef(); + } + + } + + ComPtr& operator=(ComPtr other) + { + if(_obj != NULL) + _obj->Release(); + _obj = other._obj; + if(_obj != NULL) + _obj->AddRef(); + return *this; + } + + ~ComPtr() + { + if (_obj) + { + _obj->Release(); + _obj = 0; + } + } + + TInterface* getRaw() + { + return _obj; + } + + TInterface* getRetainedReference() + { + if(_obj == NULL) + return NULL; + _obj->AddRef(); + return _obj; + } + + TInterface** getPPV() + { + return &_obj; + } + + operator TInterface*() const + { + return _obj; + } + TInterface& operator*() const + { + return *_obj; + } + TInterface** operator&() + { + return &_obj; + } + TInterface* operator->() const + { + return _obj; + } +}; + class ComObject : public virtual IUnknown { private: @@ -58,6 +147,12 @@ public: _refCount++; return S_OK; } + +protected: + ComPtr UnknownSelf() + { + return this; + } }; @@ -104,94 +199,5 @@ public: virtual ~ComSingleObject(){} }; -template -class ComPtr -{ -private: - TInterface* _obj; -public: - ComPtr() - { - _obj = 0; - } - - ComPtr(TInterface* pObj) - { - _obj = 0; - - if (pObj) - { - _obj = pObj; - _obj->AddRef(); - } - } - - ComPtr(const ComPtr& ptr) - { - _obj = 0; - - if (ptr._obj) - { - _obj = ptr._obj; - _obj->AddRef(); - } - - } - - ComPtr& operator=(ComPtr other) - { - if(_obj != NULL) - _obj->Release(); - _obj = other._obj; - if(_obj != NULL) - _obj->AddRef(); - return *this; - } - - ~ComPtr() - { - if (_obj) - { - _obj->Release(); - _obj = 0; - } - } - - TInterface* getRaw() - { - return _obj; - } - - TInterface* getRetainedReference() - { - if(_obj == NULL) - return NULL; - _obj->AddRef(); - return _obj; - } - - TInterface** getPPV() - { - return &_obj; - } - - operator TInterface*() const - { - return _obj; - } - TInterface& operator*() const - { - return *_obj; - } - TInterface** operator&() - { - return &_obj; - } - TInterface* operator->() const - { - return _obj; - } -}; - #endif // COMIMPL_H_INCLUDED #pragma clang diagnostic pop From 8058a46d260b1c6d173eb5cc775506f2c0f83565 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 6 Jul 2021 20:27:04 +0100 Subject: [PATCH 11/25] prevent osx crash when closing inside activate. --- native/Avalonia.Native/src/OSX/window.mm | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index c0936356d2..7bbff96270 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -113,9 +113,11 @@ public: UpdateStyle(); [Window setContentView: StandardContainer]; + [Window setTitle:_lastTitle]; if(ShouldTakeFocusOnShow() && activate) { + [Window orderFront: Window]; [Window makeKeyAndOrderFront:Window]; [NSApp activateIgnoringOtherApps:YES]; } @@ -123,7 +125,6 @@ public: { [Window orderFront: Window]; } - [Window setTitle:_lastTitle]; _shown = true; @@ -180,7 +181,10 @@ public: { if (Window != nullptr) { - [Window close]; + auto window = Window; + Window = nullptr; + + [window close]; } return S_OK; @@ -549,6 +553,11 @@ private: void HideOrShowTrafficLights () { + if (Window == nil) + { + return; + } + for (id subview in Window.contentView.superview.subviews) { if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) { NSView *titlebarView = [subview subviews][0]; @@ -963,6 +972,11 @@ private: virtual HRESULT SetWindowState (AvnWindowState state) override { + if(Window == nullptr) + { + return S_OK; + } + @autoreleasepool { if(_actualWindowState == state) From 57440e9d36c9bbb2825cc56d688b02ecb21abc91 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 6 Jul 2021 20:27:18 +0100 Subject: [PATCH 12/25] retain self (this) --- native/Avalonia.Native/src/OSX/window.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 7bbff96270..439f6710b9 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -585,7 +585,9 @@ private: virtual HRESULT Show (bool activate) override { @autoreleasepool - { + { + auto r = this->UnknownSelf(); + WindowBaseImpl::Show(activate); HideOrShowTrafficLights(); @@ -1925,7 +1927,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent { if(![self windowShouldClose:self]) return; } - + [self close]; } From f5ded2632133803632116efad9e5465f4811fee8 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Thu, 8 Jul 2021 13:07:59 +0800 Subject: [PATCH 13/25] Update Arc.cs --- src/Avalonia.Controls/Shapes/Arc.cs | 47 +++++++++++++++-------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Controls/Shapes/Arc.cs b/src/Avalonia.Controls/Shapes/Arc.cs index 4ce72319aa..dad47875c3 100644 --- a/src/Avalonia.Controls/Shapes/Arc.cs +++ b/src/Avalonia.Controls/Shapes/Arc.cs @@ -28,33 +28,34 @@ namespace Avalonia.Controls.Shapes /// public double StartAngle { - get { return GetValue(StartAngleProperty); } - set { SetValue(StartAngleProperty, value); } + get => GetValue(StartAngleProperty); + set => SetValue(StartAngleProperty, value); } /// - /// Gets or sets the angle, in degrees, added to the defining where the arc ends. A positive value is clockwise, negative is counter-clockwise. + /// Gets or sets the angle, in degrees, added to the defining where the arc ends. + /// A positive value is clockwise, negative is counter-clockwise. /// public double SweepAngle { - get { return GetValue(SweepAngleProperty); } - set { SetValue(SweepAngleProperty, value); } + get => GetValue(SweepAngleProperty); + set => SetValue(SweepAngleProperty, value); } protected override Geometry CreateDefiningGeometry() { - double angle1 = DegreesToRad(StartAngle); - double angle2 = angle1 + DegreesToRad(SweepAngle); + var angle1 = DegreesToRad(StartAngle); + var angle2 = angle1 + DegreesToRad(SweepAngle); - double startAngle = Math.Min(angle1, angle2); - double sweepAngle = Math.Max(angle1, angle2); + var startAngle = Math.Min(angle1, angle2); + var sweepAngle = Math.Max(angle1, angle2); - double normStart = RadToNormRad(startAngle); - double normEnd = RadToNormRad(sweepAngle); + var normStart = RadToNormRad(startAngle); + var normEnd = RadToNormRad(sweepAngle); var rect = new Rect(Bounds.Size); - if ((normStart == normEnd) && (startAngle != sweepAngle)) // complete ring + if ((normStart == normEnd) && (startAngle != sweepAngle)) // Complete ring. { return new EllipseGeometry(rect.Deflate(StrokeThickness / 2)); } @@ -62,28 +63,30 @@ namespace Avalonia.Controls.Shapes { return new StreamGeometry(); } - else // partial arc + else // Partial arc. { var deflatedRect = rect.Deflate(StrokeThickness / 2); - double centerX = rect.Center.X; - double centerY = rect.Center.Y; + var centerX = rect.Center.X; + var centerY = rect.Center.Y; - double radiusX = deflatedRect.Width / 2; - double radiusY = deflatedRect.Height / 2; + var radiusX = deflatedRect.Width / 2; + var radiusY = deflatedRect.Height / 2; - double angleGap = RadToNormRad(sweepAngle - startAngle); + var angleGap = RadToNormRad(sweepAngle - startAngle); - Point startPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, startAngle); - Point endPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, sweepAngle); + var startPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, startAngle); + var endPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, sweepAngle); - StreamGeometry arcGeometry = new StreamGeometry(); + var arcGeometry = new StreamGeometry(); + using (var ctx = arcGeometry.Open()) { ctx.BeginFigure(startPoint, false); ctx.ArcTo(endPoint, new Size(radiusX, radiusY), angleGap, angleGap >= Math.PI, SweepDirection.Clockwise); ctx.EndFigure(false); } + return arcGeometry; } } @@ -91,7 +94,7 @@ namespace Avalonia.Controls.Shapes static double DegreesToRad(double inAngle) => inAngle * Math.PI / 180; - static double RadToNormRad(double inAngle) => (0 + (inAngle % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2); + static double RadToNormRad(double inAngle) => (inAngle % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2); static Point GetRingPoint(double radiusX, double radiusY, double centerX, double centerY, double angle) => new Point((radiusX * Math.Cos(angle)) + centerX, (radiusY * Math.Sin(angle)) + centerY); From 3740c9d9733693e118454aca541c671bce110a85 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Thu, 8 Jul 2021 14:33:18 +0800 Subject: [PATCH 14/25] Update Arc.cs --- src/Avalonia.Controls/Shapes/Arc.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Shapes/Arc.cs b/src/Avalonia.Controls/Shapes/Arc.cs index dad47875c3..5ebb321f9b 100644 --- a/src/Avalonia.Controls/Shapes/Arc.cs +++ b/src/Avalonia.Controls/Shapes/Arc.cs @@ -41,12 +41,12 @@ namespace Avalonia.Controls.Shapes get => GetValue(SweepAngleProperty); set => SetValue(SweepAngleProperty, value); } - + protected override Geometry CreateDefiningGeometry() { var angle1 = DegreesToRad(StartAngle); var angle2 = angle1 + DegreesToRad(SweepAngle); - + var startAngle = Math.Min(angle1, angle2); var sweepAngle = Math.Max(angle1, angle2); @@ -72,29 +72,30 @@ namespace Avalonia.Controls.Shapes var radiusX = deflatedRect.Width / 2; var radiusY = deflatedRect.Height / 2; - + var angleGap = RadToNormRad(sweepAngle - startAngle); var startPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, startAngle); var endPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, sweepAngle); var arcGeometry = new StreamGeometry(); - + using (var ctx = arcGeometry.Open()) { ctx.BeginFigure(startPoint, false); - ctx.ArcTo(endPoint, new Size(radiusX, radiusY), angleGap, angleGap >= Math.PI, SweepDirection.Clockwise); + ctx.ArcTo(endPoint, new Size(radiusX, radiusY), angleGap, angleGap >= Math.PI, + SweepDirection.Clockwise); ctx.EndFigure(false); } - + return arcGeometry; } } static double DegreesToRad(double inAngle) => inAngle * Math.PI / 180; - - static double RadToNormRad(double inAngle) => (inAngle % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2); + + static double RadToNormRad(double inAngle) => ((inAngle % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2); static Point GetRingPoint(double radiusX, double radiusY, double centerX, double centerY, double angle) => new Point((radiusX * Math.Cos(angle)) + centerX, (radiusY * Math.Sin(angle)) + centerY); From ec85bf14e602c3e0eaed87a7761b1c09fd43ea17 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 8 Jul 2021 11:41:19 +0200 Subject: [PATCH 15/25] Call makeFirstResponder when showing window. This is needed in order for the view to receive keyboard events immediately after showing the window. Fixes #6202 --- native/Avalonia.Native/src/OSX/window.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index e40ec12461..17d9a150a8 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -117,6 +117,7 @@ public: if(ShouldTakeFocusOnShow() && activate) { [Window makeKeyAndOrderFront:Window]; + [Window makeFirstResponder:View]; [NSApp activateIgnoringOtherApps:YES]; } else From f053bfef3a6f948053ebe495e6eac6a0fdc1e9b3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Jul 2021 14:18:47 +0100 Subject: [PATCH 16/25] macro and add self pointer retaining to all COM calls. --- native/Avalonia.Native/inc/comimpl.h | 1 + native/Avalonia.Native/src/OSX/AvnString.mm | 26 ++- native/Avalonia.Native/src/OSX/Screens.mm | 4 + native/Avalonia.Native/src/OSX/cgl.mm | 8 + native/Avalonia.Native/src/OSX/clipboard.mm | 82 ++++--- native/Avalonia.Native/src/OSX/controlhost.mm | 63 ++++-- native/Avalonia.Native/src/OSX/cursor.mm | 52 +++-- native/Avalonia.Native/src/OSX/main.mm | 174 +++++++++++---- native/Avalonia.Native/src/OSX/menu.mm | 22 ++ .../src/OSX/platformthreading.mm | 2 + .../Avalonia.Native/src/OSX/rendertarget.mm | 6 + native/Avalonia.Native/src/OSX/window.mm | 207 +++++++++++++----- 12 files changed, 461 insertions(+), 186 deletions(-) diff --git a/native/Avalonia.Native/inc/comimpl.h b/native/Avalonia.Native/inc/comimpl.h index c9f70cfa3b..45a8e8690d 100644 --- a/native/Avalonia.Native/inc/comimpl.h +++ b/native/Avalonia.Native/inc/comimpl.h @@ -7,6 +7,7 @@ #define COMIMPL_H_INCLUDED #include +#define START_COM_CALL auto r = this->UnknownSelf() __IID_DEF(IUnknown, 0, 0, 0, C0, 00, 00, 00, 00, 00, 00, 46); diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm index 001cf151d8..6d057fb705 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.mm +++ b/native/Avalonia.Native/src/OSX/AvnString.mm @@ -43,6 +43,8 @@ public: virtual HRESULT Pointer(void**retOut) override { + START_COM_CALL; + @autoreleasepool { if(retOut == nullptr) @@ -58,14 +60,13 @@ public: virtual HRESULT Length(int*retOut) override { - if(retOut == nullptr) + START_COM_CALL; + + @autoreleasepool { - return E_POINTER; + i@autoreleasepool + {eturn S_OK; } - - *retOut = _length; - - return S_OK; } }; @@ -109,10 +110,15 @@ public: virtual HRESULT Get(unsigned int index, IAvnString**ppv) override { - if(_list.size() <= index) - return E_INVALIDARG; - *ppv = _list[index].getRetainedReference(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + if(_list.size() <= index) + return E_INVALIDARG; + *ppv = _list[index].getRetainedReference(); + return S_OK; + } } }; diff --git a/native/Avalonia.Native/src/OSX/Screens.mm b/native/Avalonia.Native/src/OSX/Screens.mm index 10f698ff45..b9c75ed742 100644 --- a/native/Avalonia.Native/src/OSX/Screens.mm +++ b/native/Avalonia.Native/src/OSX/Screens.mm @@ -8,6 +8,8 @@ class Screens : public ComSingleObject public: virtual HRESULT GetScreenCount (int* ret) override { + START_COM_CALL; + @autoreleasepool { *ret = (int)[NSScreen screens].count; @@ -18,6 +20,8 @@ public: virtual HRESULT GetScreen (int index, AvnScreen* ret) override { + START_COM_CALL; + @autoreleasepool { if(index < 0 || index >= [NSScreen screens].count) diff --git a/native/Avalonia.Native/src/OSX/cgl.mm b/native/Avalonia.Native/src/OSX/cgl.mm index a9d94cdf04..085037978e 100644 --- a/native/Avalonia.Native/src/OSX/cgl.mm +++ b/native/Avalonia.Native/src/OSX/cgl.mm @@ -69,6 +69,8 @@ public: virtual HRESULT LegacyMakeCurrent() override { + START_COM_CALL; + if(CGLSetCurrentContext(Context) != 0) return E_FAIL; return S_OK; @@ -76,6 +78,8 @@ public: virtual HRESULT MakeCurrent(IUnknown** ppv) override { + START_COM_CALL; + CGLContextObj saved = CGLGetCurrentContext(); CGLLockContext(Context); if(CGLSetCurrentContext(Context) != 0) @@ -128,6 +132,8 @@ public: virtual HRESULT CreateContext(IAvnGlContext* share, IAvnGlContext**ppv) override { + START_COM_CALL; + CGLContextObj shareContext = nil; if(share != nil) { @@ -144,6 +150,8 @@ public: virtual HRESULT WrapContext(void* native, IAvnGlContext**ppv) override { + START_COM_CALL; + if(native == nil) return E_INVALIDARG; *ppv = new AvnGlContext((CGLContextObj) native); diff --git a/native/Avalonia.Native/src/OSX/clipboard.mm b/native/Avalonia.Native/src/OSX/clipboard.mm index f148374759..9966971b73 100644 --- a/native/Avalonia.Native/src/OSX/clipboard.mm +++ b/native/Avalonia.Native/src/OSX/clipboard.mm @@ -25,6 +25,8 @@ public: virtual HRESULT GetText (char* type, IAvnString**ppv) override { + START_COM_CALL; + @autoreleasepool { if(ppv == nullptr) @@ -42,6 +44,8 @@ public: virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) override { + START_COM_CALL; + @autoreleasepool { *ppv= nil; @@ -69,56 +73,71 @@ public: virtual HRESULT SetText (char* type, char* utf8String) override { - Clear(); + START_COM_CALL; + @autoreleasepool { + Clear(); + auto string = [NSString stringWithUTF8String:(const char*)utf8String]; auto typeString = [NSString stringWithUTF8String:(const char*)type]; if(_item == nil) [_pb setString: string forType: typeString]; else [_item setString: string forType:typeString]; - } - return S_OK; + return S_OK; + } } virtual HRESULT SetBytes(char* type, void* bytes, int len) override { - auto typeString = [NSString stringWithUTF8String:(const char*)type]; - auto data = [NSData dataWithBytes:bytes length:len]; - if(_item == nil) - [_pb setData:data forType:typeString]; - else - [_item setData:data forType:typeString]; - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + auto typeString = [NSString stringWithUTF8String:(const char*)type]; + auto data = [NSData dataWithBytes:bytes length:len]; + if(_item == nil) + [_pb setData:data forType:typeString]; + else + [_item setData:data forType:typeString]; + return S_OK; + } } virtual HRESULT GetBytes(char* type, IAvnString**ppv) override { - *ppv = nil; - auto typeString = [NSString stringWithUTF8String:(const char*)type]; - NSData*data; - @try + START_COM_CALL; + + @autoreleasepool { - if(_item) - data = [_item dataForType:typeString]; - else - data = [_pb dataForType:typeString]; - if(data == nil) + *ppv = nil; + auto typeString = [NSString stringWithUTF8String:(const char*)type]; + NSData*data; + @try + { + if(_item) + data = [_item dataForType:typeString]; + else + data = [_pb dataForType:typeString]; + if(data == nil) + return E_FAIL; + } + @catch(NSException* e) + { return E_FAIL; + } + *ppv = CreateByteArray((void*)data.bytes, (int)data.length); + return S_OK; } - @catch(NSException* e) - { - return E_FAIL; - } - *ppv = CreateByteArray((void*)data.bytes, (int)data.length); - return S_OK; } virtual HRESULT Clear() override { + START_COM_CALL; + @autoreleasepool { if(_item != nil) @@ -128,15 +147,20 @@ public: [_pb clearContents]; [_pb setString:@"" forType:NSPasteboardTypeString]; } - } - return S_OK; + return S_OK; + } } virtual HRESULT ObtainFormats(IAvnStringArray** ppv) override { - *ppv = CreateAvnStringArray(_item == nil ? [_pb types] : [_item types]); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = CreateAvnStringArray(_item == nil ? [_pb types] : [_item types]); + return S_OK; + } } }; diff --git a/native/Avalonia.Native/src/OSX/controlhost.mm b/native/Avalonia.Native/src/OSX/controlhost.mm index 5ee2344ac7..f8e9a3b6d1 100644 --- a/native/Avalonia.Native/src/OSX/controlhost.mm +++ b/native/Avalonia.Native/src/OSX/controlhost.mm @@ -16,11 +16,16 @@ public: virtual HRESULT CreateDefaultChild(void* parent, void** retOut) override { - NSView* view = [NSView new]; - [view setWantsLayer: true]; + START_COM_CALL; - *retOut = (__bridge_retained void*)view; - return S_OK; + @autoreleasepool + { + NSView* view = [NSView new]; + [view setWantsLayer: true]; + + *retOut = (__bridge_retained void*)view; + return S_OK; + } }; virtual IAvnNativeControlHostTopLevelAttachment* CreateAttachment() override @@ -69,32 +74,42 @@ public: virtual HRESULT InitializeWithChildHandle(void* child) override { - if(_child != nil) - return E_FAIL; - _child = (__bridge NSView*)child; - if(_child == nil) - return E_FAIL; - [_holder addSubview:_child]; - [_child setHidden: false]; - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + if(_child != nil) + return E_FAIL; + _child = (__bridge NSView*)child; + if(_child == nil) + return E_FAIL; + [_holder addSubview:_child]; + [_child setHidden: false]; + return S_OK; + } }; virtual HRESULT AttachTo(IAvnNativeControlHost* host) override { - if(host == nil) - { - [_holder removeFromSuperview]; - [_holder setHidden: true]; - } - else + START_COM_CALL; + + @autoreleasepool { - AvnNativeControlHost* chost = dynamic_cast(host); - if(chost == nil || chost->View == nil) - return E_FAIL; - [_holder setHidden:true]; - [chost->View addSubview:_holder]; + if(host == nil) + { + [_holder removeFromSuperview]; + [_holder setHidden: true]; + } + else + { + AvnNativeControlHost* chost = dynamic_cast(host); + if(chost == nil || chost->View == nil) + return E_FAIL; + [_holder setHidden:true]; + [chost->View addSubview:_holder]; + } + return S_OK; } - return S_OK; }; virtual void ShowInBounds(float x, float y, float width, float height) override diff --git a/native/Avalonia.Native/src/OSX/cursor.mm b/native/Avalonia.Native/src/OSX/cursor.mm index 1732d6e71f..dc38294a18 100644 --- a/native/Avalonia.Native/src/OSX/cursor.mm +++ b/native/Avalonia.Native/src/OSX/cursor.mm @@ -53,36 +53,46 @@ public: virtual HRESULT GetCursor (AvnStandardCursorType cursorType, IAvnCursor** retOut) override { - *retOut = s_cursorMap[cursorType]; + START_COM_CALL; - if(*retOut != nullptr) + @autoreleasepool { - (*retOut)->AddRef(); - } + *retOut = s_cursorMap[cursorType]; - return S_OK; + if(*retOut != nullptr) + { + (*retOut)->AddRef(); + } + + return S_OK; + } } virtual HRESULT CreateCustomCursor (void* bitmapData, size_t length, AvnPixelSize hotPixel, IAvnCursor** retOut) override { - if(bitmapData == nullptr || retOut == nullptr) + START_COM_CALL; + + @autoreleasepool { - return E_POINTER; + if(bitmapData == nullptr || retOut == nullptr) + { + return E_POINTER; + } + + NSData *imageData = [NSData dataWithBytes:bitmapData length:length]; + NSImage *image = [[NSImage alloc] initWithData:imageData]; + + + NSPoint hotSpot; + hotSpot.x = hotPixel.Width; + hotSpot.y = hotPixel.Height; + + *retOut = new Cursor([[NSCursor new] initWithImage: image hotSpot: hotSpot]); + + (*retOut)->AddRef(); + + return S_OK; } - - NSData *imageData = [NSData dataWithBytes:bitmapData length:length]; - NSImage *image = [[NSImage alloc] initWithData:imageData]; - - - NSPoint hotSpot; - hotSpot.x = hotPixel.Width; - hotSpot.y = hotPixel.Height; - - *retOut = new Cursor([[NSCursor new] initWithImage: image hotSpot: hotSpot]); - - (*retOut)->AddRef(); - - return S_OK; } }; diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index aaaf381b26..3e152a6125 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -107,27 +107,42 @@ public: virtual HRESULT SetApplicationTitle(char* utf8String) override { - auto appTitle = [NSString stringWithUTF8String: utf8String]; + START_COM_CALL; - [[NSProcessInfo processInfo] setProcessName:appTitle]; - - - SetProcessName(appTitle); - - return S_OK; + @autoreleasepool + { + auto appTitle = [NSString stringWithUTF8String: utf8String]; + + [[NSProcessInfo processInfo] setProcessName:appTitle]; + + + SetProcessName(appTitle); + + return S_OK; + } } virtual HRESULT SetShowInDock(int show) override { - AvnDesiredActivationPolicy = show - ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory; - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + AvnDesiredActivationPolicy = show + ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory; + return S_OK; + } } virtual HRESULT SetDisableDefaultApplicationMenuItems (bool enabled) override { - SetAutoGenerateDefaultAppMenuItems(!enabled); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + SetAutoGenerateDefaultAppMenuItems(!enabled); + return S_OK; + } } }; @@ -165,6 +180,8 @@ public: FORWARD_IUNKNOWN() virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator, IAvnApplicationEvents* events) override { + START_COM_CALL; + _deallocator = deallocator; @autoreleasepool{ [[ThreadingInitializer new] do]; @@ -180,89 +197,154 @@ public: virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv) override { - if(cb == nullptr || ppv == nullptr) - return E_POINTER; - *ppv = CreateAvnWindow(cb, gl); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + if(cb == nullptr || ppv == nullptr) + return E_POINTER; + *ppv = CreateAvnWindow(cb, gl); + return S_OK; + } }; virtual HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv) override { - if(cb == nullptr || ppv == nullptr) - return E_POINTER; + START_COM_CALL; - *ppv = CreateAvnPopup(cb, gl); - return S_OK; + @autoreleasepool + { + if(cb == nullptr || ppv == nullptr) + return E_POINTER; + + *ppv = CreateAvnPopup(cb, gl); + return S_OK; + } } virtual HRESULT CreatePlatformThreadingInterface(IAvnPlatformThreadingInterface** ppv) override { - *ppv = CreatePlatformThreading(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = CreatePlatformThreading(); + return S_OK; + } } virtual HRESULT CreateSystemDialogs(IAvnSystemDialogs** ppv) override { - *ppv = ::CreateSystemDialogs(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateSystemDialogs(); + return S_OK; + } } virtual HRESULT CreateScreens (IAvnScreens** ppv) override { - *ppv = ::CreateScreens (); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateScreens (); + return S_OK; + } } virtual HRESULT CreateClipboard(IAvnClipboard** ppv) override { - *ppv = ::CreateClipboard (nil, nil); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateClipboard (nil, nil); + return S_OK; + } } virtual HRESULT CreateDndClipboard(IAvnClipboard** ppv) override { - *ppv = ::CreateClipboard (nil, [NSPasteboardItem new]); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateClipboard (nil, [NSPasteboardItem new]); + return S_OK; + } } virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) override { - *ppv = ::CreateCursorFactory(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateCursorFactory(); + return S_OK; + } } virtual HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv) override { - auto rv = ::GetGlDisplay(); - if(rv == NULL) - return E_FAIL; - rv->AddRef(); - *ppv = rv; - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + auto rv = ::GetGlDisplay(); + if(rv == NULL) + return E_FAIL; + rv->AddRef(); + *ppv = rv; + return S_OK; + } } virtual HRESULT CreateMenu (IAvnMenuEvents* cb, IAvnMenu** ppv) override { - *ppv = ::CreateAppMenu(cb); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateAppMenu(cb); + return S_OK; + } } virtual HRESULT CreateMenuItem (IAvnMenuItem** ppv) override { - *ppv = ::CreateAppMenuItem(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateAppMenuItem(); + return S_OK; + } } virtual HRESULT CreateMenuItemSeparator (IAvnMenuItem** ppv) override { - *ppv = ::CreateAppMenuItemSeparator(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateAppMenuItemSeparator(); + return S_OK; + } } virtual HRESULT SetAppMenu (IAvnMenu* appMenu) override { - ::SetAppMenu(s_appTitle, appMenu); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + ::SetAppMenu(s_appTitle, appMenu); + return S_OK; + } } }; diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index b9a95e7b3c..38f8c2a7cb 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -95,6 +95,8 @@ NSMenuItem* AvnAppMenuItem::GetNative() HRESULT AvnAppMenuItem::SetSubMenu (IAvnMenu* menu) { + START_COM_CALL; + @autoreleasepool { if(menu != nullptr) @@ -114,6 +116,8 @@ HRESULT AvnAppMenuItem::SetSubMenu (IAvnMenu* menu) HRESULT AvnAppMenuItem::SetTitle (char* utf8String) { + START_COM_CALL; + @autoreleasepool { if (utf8String != nullptr) @@ -128,6 +132,8 @@ HRESULT AvnAppMenuItem::SetTitle (char* utf8String) HRESULT AvnAppMenuItem::SetGesture (AvnKey key, AvnInputModifiers modifiers) { + START_COM_CALL; + @autoreleasepool { if(key != AvnKeyNone) @@ -183,6 +189,8 @@ HRESULT AvnAppMenuItem::SetGesture (AvnKey key, AvnInputModifiers modifiers) HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) { + START_COM_CALL; + @autoreleasepool { _predicate = predicate; @@ -193,6 +201,8 @@ HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionC HRESULT AvnAppMenuItem::SetIsChecked (bool isChecked) { + START_COM_CALL; + @autoreleasepool { [_native setState:(isChecked && _isCheckable ? NSOnState : NSOffState)]; @@ -202,6 +212,8 @@ HRESULT AvnAppMenuItem::SetIsChecked (bool isChecked) HRESULT AvnAppMenuItem::SetToggleType(AvnMenuItemToggleType toggleType) { + START_COM_CALL; + @autoreleasepool { switch(toggleType) @@ -231,6 +243,8 @@ HRESULT AvnAppMenuItem::SetToggleType(AvnMenuItemToggleType toggleType) HRESULT AvnAppMenuItem::SetIcon(void *data, size_t length) { + START_COM_CALL; + @autoreleasepool { if(data != nullptr) @@ -317,6 +331,8 @@ void AvnAppMenu::RaiseClosed() HRESULT AvnAppMenu::InsertItem(int index, IAvnMenuItem *item) { + START_COM_CALL; + @autoreleasepool { if([_native hasGlobalMenuItem]) @@ -337,6 +353,8 @@ HRESULT AvnAppMenu::InsertItem(int index, IAvnMenuItem *item) HRESULT AvnAppMenu::RemoveItem (IAvnMenuItem* item) { + START_COM_CALL; + @autoreleasepool { auto avnMenuItem = dynamic_cast(item); @@ -352,6 +370,8 @@ HRESULT AvnAppMenu::RemoveItem (IAvnMenuItem* item) HRESULT AvnAppMenu::SetTitle (char* utf8String) { + START_COM_CALL; + @autoreleasepool { if (utf8String != nullptr) @@ -365,6 +385,8 @@ HRESULT AvnAppMenu::SetTitle (char* utf8String) HRESULT AvnAppMenu::Clear() { + START_COM_CALL; + @autoreleasepool { [_native removeAllItems]; diff --git a/native/Avalonia.Native/src/OSX/platformthreading.mm b/native/Avalonia.Native/src/OSX/platformthreading.mm index e83bf53331..6d5bd4aa02 100644 --- a/native/Avalonia.Native/src/OSX/platformthreading.mm +++ b/native/Avalonia.Native/src/OSX/platformthreading.mm @@ -114,6 +114,8 @@ public: virtual HRESULT RunLoop(IAvnLoopCancellation* cancel) override { + START_COM_CALL; + auto can = dynamic_cast(cancel); if(can->Cancelled) return S_OK; diff --git a/native/Avalonia.Native/src/OSX/rendertarget.mm b/native/Avalonia.Native/src/OSX/rendertarget.mm index b2d4341bb9..dc5c24e41e 100644 --- a/native/Avalonia.Native/src/OSX/rendertarget.mm +++ b/native/Avalonia.Native/src/OSX/rendertarget.mm @@ -247,6 +247,8 @@ public: virtual HRESULT GetPixelSize(AvnPixelSize* ret) override { + START_COM_CALL; + if(!_surface) return E_FAIL; *ret = _surface->size; @@ -255,6 +257,8 @@ public: virtual HRESULT GetScaling(double* ret) override { + START_COM_CALL; + if(!_surface) return E_FAIL; *ret = _surface->scale; @@ -281,6 +285,8 @@ public: virtual HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret) override { + START_COM_CALL; + ComPtr releaseContext; @synchronized (_target->lock) { if(_target->surface == nil) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 439f6710b9..a163106d2d 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -54,6 +54,8 @@ public: virtual HRESULT ObtainNSWindowHandle(void** ret) override { + START_COM_CALL; + if (ret == nullptr) { return E_POINTER; @@ -66,6 +68,8 @@ public: virtual HRESULT ObtainNSWindowHandleRetained(void** ret) override { + START_COM_CALL; + if (ret == nullptr) { return E_POINTER; @@ -78,6 +82,8 @@ public: virtual HRESULT ObtainNSViewHandle(void** ret) override { + START_COM_CALL; + if (ret == nullptr) { return E_POINTER; @@ -90,6 +96,8 @@ public: virtual HRESULT ObtainNSViewHandleRetained(void** ret) override { + START_COM_CALL; + if (ret == nullptr) { return E_POINTER; @@ -107,6 +115,8 @@ public: virtual HRESULT Show(bool activate) override { + START_COM_CALL; + @autoreleasepool { SetPosition(lastPositionSet); @@ -139,6 +149,8 @@ public: virtual HRESULT Hide () override { + START_COM_CALL; + @autoreleasepool { if(Window != nullptr) @@ -153,6 +165,8 @@ public: virtual HRESULT Activate () override { + START_COM_CALL; + @autoreleasepool { if(Window != nullptr) @@ -167,6 +181,8 @@ public: virtual HRESULT SetTopMost (bool value) override { + START_COM_CALL; + @autoreleasepool { [Window setLevel: value ? NSFloatingWindowLevel : NSNormalWindowLevel]; @@ -177,6 +193,8 @@ public: virtual HRESULT Close() override { + START_COM_CALL; + @autoreleasepool { if (Window != nullptr) @@ -193,6 +211,8 @@ public: virtual HRESULT GetClientSize(AvnSize* ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -208,6 +228,8 @@ public: virtual HRESULT GetScaling (double* ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -226,6 +248,8 @@ public: virtual HRESULT SetMinMaxSize (AvnSize minSize, AvnSize maxSize) override { + START_COM_CALL; + @autoreleasepool { [Window setMinSize: ToNSSize(minSize)]; @@ -237,6 +261,8 @@ public: virtual HRESULT Resize(double x, double y) override { + START_COM_CALL; + @autoreleasepool { auto maxSize = [Window maxSize]; @@ -276,6 +302,8 @@ public: virtual HRESULT Invalidate (AvnRect rect) override { + START_COM_CALL; + @autoreleasepool { [View setNeedsDisplayInRect:[View frame]]; @@ -286,6 +314,8 @@ public: virtual HRESULT SetMainMenu(IAvnMenu* menu) override { + START_COM_CALL; + _mainMenu = menu; auto nativeMenu = dynamic_cast(menu); @@ -304,6 +334,8 @@ public: virtual HRESULT BeginMoveDrag () override { + START_COM_CALL; + @autoreleasepool { auto lastEvent = [View lastMouseDownEvent]; @@ -321,11 +353,15 @@ public: virtual HRESULT BeginResizeDrag (AvnWindowEdge edge) override { + START_COM_CALL; + return S_OK; } virtual HRESULT GetPosition (AvnPoint* ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -346,6 +382,8 @@ public: virtual HRESULT SetPosition (AvnPoint point) override { + START_COM_CALL; + @autoreleasepool { lastPositionSet = point; @@ -357,6 +395,8 @@ public: virtual HRESULT PointToClient (AvnPoint point, AvnPoint* ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -375,6 +415,8 @@ public: virtual HRESULT PointToScreen (AvnPoint point, AvnPoint* ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -392,12 +434,16 @@ public: virtual HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer* fb, IUnknown* dispose) override { + START_COM_CALL; + [View setSwRenderedFrame: fb dispose: dispose]; return S_OK; } virtual HRESULT SetCursor(IAvnCursor* cursor) override { + START_COM_CALL; + @autoreleasepool { Cursor* avnCursor = dynamic_cast(cursor); @@ -427,6 +473,8 @@ public: virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ppv) override { + START_COM_CALL; + if(View == NULL) return E_FAIL; *ppv = [renderTarget createSurfaceRenderTarget]; @@ -435,6 +483,8 @@ public: virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost** retOut) override { + START_COM_CALL; + if(View == NULL) return E_FAIL; *retOut = ::CreateNativeControlHost(View); @@ -443,6 +493,8 @@ public: virtual HRESULT SetBlurEnabled (bool enable) override { + START_COM_CALL; + [StandardContainer ShowBlur:enable]; return S_OK; @@ -452,6 +504,8 @@ public: IAvnClipboard* clipboard, IAvnDndResultCallback* cb, void* sourceHandle) override { + START_COM_CALL; + auto item = TryGetPasteboardItem(clipboard); [item setString:@"" forType:GetAvnCustomDataType()]; if(item == nil) @@ -584,10 +638,10 @@ private: virtual HRESULT Show (bool activate) override { + START_COM_CALL; + @autoreleasepool { - auto r = this->UnknownSelf(); - WindowBaseImpl::Show(activate); HideOrShowTrafficLights(); @@ -598,6 +652,8 @@ private: virtual HRESULT SetEnabled (bool enable) override { + START_COM_CALL; + @autoreleasepool { [Window setEnabled:enable]; @@ -607,6 +663,8 @@ private: virtual HRESULT SetParent (IAvnWindow* parent) override { + START_COM_CALL; + @autoreleasepool { if(parent == nullptr) @@ -718,6 +776,8 @@ private: virtual HRESULT SetCanResize(bool value) override { + START_COM_CALL; + @autoreleasepool { _canResize = value; @@ -728,6 +788,8 @@ private: virtual HRESULT SetDecorations(SystemDecorations value) override { + START_COM_CALL; + @autoreleasepool { auto currentWindowState = _lastWindowState; @@ -793,6 +855,8 @@ private: virtual HRESULT SetTitle (char* utf8title) override { + START_COM_CALL; + @autoreleasepool { _lastTitle = [NSString stringWithUTF8String:(const char*)utf8title]; @@ -804,6 +868,8 @@ private: virtual HRESULT SetTitleBarColor(AvnColor color) override { + START_COM_CALL; + @autoreleasepool { float a = (float)color.Alpha / 255.0f; @@ -833,6 +899,8 @@ private: virtual HRESULT GetWindowState (AvnWindowState*ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -866,86 +934,111 @@ private: virtual HRESULT TakeFocusFromChildren () override { - if(Window == nil) - return S_OK; - if([Window isKeyWindow]) - [Window makeFirstResponder: View]; + START_COM_CALL; - return S_OK; + @autoreleasepool + { + if(Window == nil) + return S_OK; + if([Window isKeyWindow]) + [Window makeFirstResponder: View]; + + return S_OK; + } } virtual HRESULT SetExtendClientArea (bool enable) override { - _isClientAreaExtended = enable; + START_COM_CALL; - if(enable) + @autoreleasepool { - Window.titleVisibility = NSWindowTitleHidden; - - [Window setTitlebarAppearsTransparent:true]; - - auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); - - if (wantsTitleBar) - { - [StandardContainer ShowTitleBar:true]; - } - else - { - [StandardContainer ShowTitleBar:false]; - } + _isClientAreaExtended = enable; - if(_extendClientHints & AvnOSXThickTitleBar) + if(enable) { - Window.toolbar = [NSToolbar new]; - Window.toolbar.showsBaselineSeparator = false; + Window.titleVisibility = NSWindowTitleHidden; + + [Window setTitlebarAppearsTransparent:true]; + + auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); + + if (wantsTitleBar) + { + [StandardContainer ShowTitleBar:true]; + } + else + { + [StandardContainer ShowTitleBar:false]; + } + + if(_extendClientHints & AvnOSXThickTitleBar) + { + Window.toolbar = [NSToolbar new]; + Window.toolbar.showsBaselineSeparator = false; + } + else + { + Window.toolbar = nullptr; + } } else { + Window.titleVisibility = NSWindowTitleVisible; Window.toolbar = nullptr; + [Window setTitlebarAppearsTransparent:false]; + View.layer.zPosition = 0; } + + [Window setIsExtended:enable]; + + HideOrShowTrafficLights(); + + UpdateStyle(); + + return S_OK; } - else - { - Window.titleVisibility = NSWindowTitleVisible; - Window.toolbar = nullptr; - [Window setTitlebarAppearsTransparent:false]; - View.layer.zPosition = 0; - } - - [Window setIsExtended:enable]; - - HideOrShowTrafficLights(); - - UpdateStyle(); - - return S_OK; } virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) override { - _extendClientHints = hints; + START_COM_CALL; - SetExtendClientArea(_isClientAreaExtended); - return S_OK; + @autoreleasepool + { + _extendClientHints = hints; + + SetExtendClientArea(_isClientAreaExtended); + return S_OK; + } } virtual HRESULT GetExtendTitleBarHeight (double*ret) override { - if(ret == nullptr) + START_COM_CALL; + + @autoreleasepool { - return E_POINTER; + if(ret == nullptr) + { + return E_POINTER; + } + + *ret = [Window getExtendedTitleBarHeight]; + + return S_OK; } - - *ret = [Window getExtendedTitleBarHeight]; - - return S_OK; } virtual HRESULT SetExtendTitleBarHeight (double value) override { - [StandardContainer SetTitleBarHeightHint:value]; - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + [StandardContainer SetTitleBarHeightHint:value]; + return S_OK; + } } void EnterFullScreenMode () @@ -974,13 +1067,15 @@ private: virtual HRESULT SetWindowState (AvnWindowState state) override { - if(Window == nullptr) - { - return S_OK; - } + START_COM_CALL; @autoreleasepool { + if(Window == nullptr) + { + return S_OK; + } + if(_actualWindowState == state) { return S_OK; From 9da6a28b5161d11d346c6205fce9cbde1cc3629d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Jul 2021 14:21:18 +0100 Subject: [PATCH 17/25] fix --- native/Avalonia.Native/src/OSX/AvnString.mm | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm index 6d057fb705..cd0e2cdf94 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.mm +++ b/native/Avalonia.Native/src/OSX/AvnString.mm @@ -64,8 +64,14 @@ public: @autoreleasepool { - i@autoreleasepool - {eturn S_OK; + if(retOut == nullptr) + { + return E_POINTER; + } + + *retOut = _length; + + return S_OK; } } }; From 927fd90d860038f97c0581da5e1cc59132f0fa51 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Jul 2021 15:24:21 +0100 Subject: [PATCH 18/25] add documentation about START_COM_CALL. --- native/Avalonia.Native/inc/comimpl.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/native/Avalonia.Native/inc/comimpl.h b/native/Avalonia.Native/inc/comimpl.h index 45a8e8690d..d38216b49e 100644 --- a/native/Avalonia.Native/inc/comimpl.h +++ b/native/Avalonia.Native/inc/comimpl.h @@ -7,6 +7,17 @@ #define COMIMPL_H_INCLUDED #include + +/** + START_COM_CALL causes AddRef to be called at the beggining of a function. + When a function is exited, it causes ReleaseRef to be called. + This ensures that the object cannot be destructed whilst the function is running. + For example: Window Show is called, which triggers an event, and user calls Close inside the event + causing the refcount to reach 0, and the object to be destroyed. Function then continues and this pointer + will now be invalid. + + START_COM_CALL protects against this scenario. + */ #define START_COM_CALL auto r = this->UnknownSelf() __IID_DEF(IUnknown, 0, 0, 0, C0, 00, 00, 00, 00, 00, 00, 46); From bac825c9995b8a48636e0f1eda81c3b9383a2041 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Jul 2021 15:33:44 +0100 Subject: [PATCH 19/25] destroyed, not destructed. --- native/Avalonia.Native/inc/comimpl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/Avalonia.Native/inc/comimpl.h b/native/Avalonia.Native/inc/comimpl.h index d38216b49e..47b0a3c5f2 100644 --- a/native/Avalonia.Native/inc/comimpl.h +++ b/native/Avalonia.Native/inc/comimpl.h @@ -11,7 +11,7 @@ /** START_COM_CALL causes AddRef to be called at the beggining of a function. When a function is exited, it causes ReleaseRef to be called. - This ensures that the object cannot be destructed whilst the function is running. + This ensures that the object cannot be destroyed whilst the function is running. For example: Window Show is called, which triggers an event, and user calls Close inside the event causing the refcount to reach 0, and the object to be destroyed. Function then continues and this pointer will now be invalid. From 3b56b93ba1822149d495d560263679d7d14ab80e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 9 Jul 2021 13:42:46 +0200 Subject: [PATCH 20/25] Make the designer work in the sandbox project. --- samples/Sandbox/Program.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/samples/Sandbox/Program.cs b/samples/Sandbox/Program.cs index 1e74105196..52321b46a9 100644 --- a/samples/Sandbox/Program.cs +++ b/samples/Sandbox/Program.cs @@ -4,12 +4,12 @@ namespace Sandbox { public class Program { - static void Main(string[] args) - { + static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() - .LogToTrace() - .StartWithClassicDesktopLifetime(args); - } + .LogToTrace(); } } From 0864f3c562f70f7d9900914d388714360e18819d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miha=20Marki=C4=8D?= Date: Sat, 10 Jul 2021 14:24:32 +0200 Subject: [PATCH 21/25] Makes TextBox.UndoRedoState struct readonly --- src/Avalonia.Controls/TextBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index c1516613b3..ddf55808ce 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -145,7 +145,7 @@ namespace Avalonia.Controls (o, v) => o.UndoLimit = v, unsetValue: -1); - struct UndoRedoState : IEquatable + readonly struct UndoRedoState : IEquatable { public string Text { get; } public int CaretPosition { get; } From cd54a9255df1e6f66c13cdf7a839df83d7b104dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miha=20Marki=C4=8D?= Date: Sat, 10 Jul 2021 14:30:28 +0200 Subject: [PATCH 22/25] Adds CodeRush directory to GIT ignored files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 7d672c7755..abf7674560 100644 --- a/.gitignore +++ b/.gitignore @@ -106,6 +106,9 @@ _NCrunch_*/ *.ncrunchsolution.user nCrunchTemp_* +# CodeRush +.cr/ + # Others sql/ *.Cache From 17d9755c3f22f3a20b09996fc7d72e2ffd3f406e Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sat, 10 Jul 2021 21:43:19 +0200 Subject: [PATCH 23/25] Update API compat. --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index c917902dc3..39a4c3004c 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -5,6 +5,7 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Task MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean, System.Threading.CancellationToken)' is present in the implementation but not in the contract. MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. +TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract. CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract. CannotSealType : Type 'Avalonia.Media.TextFormatting.GenericTextParagraphProperties' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. @@ -73,4 +74,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWr InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. -Total Issues: 74 +Total Issues: 75 From e55c11ec37a1fb87fe7e376c86919f69936700ea Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sat, 10 Jul 2021 21:57:53 +0200 Subject: [PATCH 24/25] Fix unit tests. --- .../StaticResourceExtensionTests.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs index 0ee7384c7d..fb3fd6d7d4 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs @@ -29,7 +29,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); var border = userControl.FindControl("border"); - var brush = (SolidColorBrush)border.Background; + var brush = (ISolidColorBrush)border.Background; Assert.Equal(0xff506070, brush.Color.ToUint32()); } @@ -72,7 +72,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); var border = userControl.FindControl("border"); - var brush = (SolidColorBrush)border.Background; + var brush = (ISolidColorBrush)border.Background; Assert.Equal(0xff506070, brush.Color.ToUint32()); } @@ -119,7 +119,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); var border = userControl.FindControl("border"); - var brush = (SolidColorBrush)border.Background; + var brush = (ISolidColorBrush)border.Background; Assert.Equal(0xff506070, brush.Color.ToUint32()); } @@ -149,7 +149,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); var border = userControl.FindControl("border"); - var brush = (SolidColorBrush)border.Background; + var brush = (ISolidColorBrush)border.Background; Assert.Equal(0xff506070, brush.Color.ToUint32()); } @@ -200,7 +200,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var button = window.FindControl