From 0bdd0c3160d73a35982ed5bcb7fc421a3d7bd381 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 22 Jul 2022 15:24:20 +0100 Subject: [PATCH 01/30] add a failing integration test. --- .../WindowTests.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 2b10c302bc..9e39d0ae58 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -1,7 +1,9 @@ using System; +using System.Linq; using System.Runtime.InteropServices; using System.Threading; using Avalonia.Controls; +using OpenQA.Selenium; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Interactions; using Xunit; @@ -55,6 +57,43 @@ namespace Avalonia.IntegrationTests.Appium } } } + + [PlatformFact(TestPlatforms.Windows)] + public void OnWindows_Docked_Windows_Retain_Size_Position_When_Restored() + { + using (OpenWindow(new Size(400, 400), ShowWindowMode.NonOwned, WindowStartupLocation.Manual)) + { + var windowState = _session.FindElementByAccessibilityId("WindowState"); + + Assert.Equal("Normal", windowState.GetComboBoxValue()); + + + var window = _session.FindElements(By.XPath("//Window")).First(); + + new Actions(_session) + .KeyDown(Keys.Meta) + .SendKeys(Keys.Left) + .KeyUp(Keys.Meta) + .Perform(); + + var original = GetWindowInfo(); + + windowState.Click(); + _session.FindElementByName("Minimized").SendClick(); + + new Actions(_session) + .KeyDown(Keys.Alt) + .SendKeys(Keys.Tab) + .KeyUp(Keys.Alt) + .Perform(); + + var current = GetWindowInfo(); + + Assert.Equal(original.Position, current.Position); + Assert.Equal(original.FrameSize, current.FrameSize); + + } + } [Theory] From cdc3100097a48f5bc1a77d483d2ffdd6ad2718ae Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 22 Jul 2022 15:24:39 +0100 Subject: [PATCH 02/30] add fix to make code path consistent with wpf. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 2f1a116af7..ce8374a68f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -285,7 +285,7 @@ namespace Avalonia.Win32 set { - if (IsWindowVisible(_hwnd)) + if (IsWindowVisible(_hwnd) && _lastWindowState != value) { ShowWindow(value, value != WindowState.Minimized); // If the window is minimized, it shouldn't be activated } From 8c39dd54a08f9ea3bafac190b99cd29cac3351ca Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 22 Jul 2022 16:02:56 +0100 Subject: [PATCH 03/30] fix exiting fullscreen. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index ce8374a68f..e990efafdb 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -290,6 +290,7 @@ namespace Avalonia.Win32 ShowWindow(value, value != WindowState.Minimized); // If the window is minimized, it shouldn't be activated } + _lastWindowState = value; _showWindowState = value; } } From 5fcc5ecfea3e539e37f497ff8d11602fadfd69ca Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 22 Jul 2022 17:11:34 +0100 Subject: [PATCH 04/30] disable broken tests. --- tests/Avalonia.IntegrationTests.Appium/MenuTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs index d1d231466f..b0d395464b 100644 --- a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs @@ -57,7 +57,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("_Grandchild", clickedMenuItem.Text); } - [PlatformFact(TestPlatforms.Windows)] + /*[PlatformFact(TestPlatforms.Windows)] public void Select_Child_With_Alt_Arrow_Keys() { new Actions(_session) @@ -103,7 +103,7 @@ namespace Avalonia.IntegrationTests.Appium var clickedMenuItem = _session.FindElementByAccessibilityId("ClickedMenuItem"); Assert.Equal("_Grandchild", clickedMenuItem.Text); - } + }*/ [PlatformFact(TestPlatforms.Windows)] public void Select_Child_With_Click_Arrow_Keys() From a9cc499de3663600c9e8b9bce2646af3af659621 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 27 Jul 2022 10:04:32 +0100 Subject: [PATCH 05/30] Revert "disable broken tests." This reverts commit 5fcc5ecfea3e539e37f497ff8d11602fadfd69ca. --- tests/Avalonia.IntegrationTests.Appium/MenuTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs index b0d395464b..d1d231466f 100644 --- a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs @@ -57,7 +57,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("_Grandchild", clickedMenuItem.Text); } - /*[PlatformFact(TestPlatforms.Windows)] + [PlatformFact(TestPlatforms.Windows)] public void Select_Child_With_Alt_Arrow_Keys() { new Actions(_session) @@ -103,7 +103,7 @@ namespace Avalonia.IntegrationTests.Appium var clickedMenuItem = _session.FindElementByAccessibilityId("ClickedMenuItem"); Assert.Equal("_Grandchild", clickedMenuItem.Text); - }*/ + } [PlatformFact(TestPlatforms.Windows)] public void Select_Child_With_Click_Arrow_Keys() From eb8ddef4122fe59e54123e125282a13bcfe822fd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 28 Jul 2022 10:50:20 +0200 Subject: [PATCH 06/30] Add failing integration test on macOS. Hiding a child window, and then clicking on the parent window causes the child window to be erroneously re-shown. --- .../IntegrationTestApp/MainWindow.axaml.cs | 1 + .../IntegrationTestApp/ShowWindowTest.axaml | 3 +- .../WindowTests_MacOS.cs | 29 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 9e180b12c5..1dab74dc9e 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -99,6 +99,7 @@ namespace IntegrationTestApp foreach (var window in lifetime.Windows) { + window.Show(); if (window.WindowState == WindowState.Minimized) window.WindowState = WindowState.Normal; } diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml index 40c1642e91..4001bac7e2 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml @@ -3,7 +3,7 @@ x:Class="IntegrationTestApp.ShowWindowTest" Name="SecondaryWindow" Title="Show Window Test"> - + @@ -31,5 +31,6 @@ Maximized Fullscreen + diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index ecbdd5bade..4e5344dd25 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -211,6 +211,35 @@ namespace Avalonia.IntegrationTests.Appium } } + [PlatformFact(TestPlatforms.MacOS)] + public void Hidden_Child_Window_Is_Not_Reshown_When_Parent_Clicked() + { + var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); + + // We don't use dispose to close the window here, because it seems that hiding and re-showing a window + // causes Appium to think it's a different window. + OpenWindow(null, ShowWindowMode.Owned, WindowStartupLocation.Manual); + + var secondaryWindow = FindWindow(_session, "SecondaryWindow"); + var hideButton = secondaryWindow.FindElementByAccessibilityId("HideButton"); + + hideButton.Click(); + + var windows = _session.FindElementsByXPath("XCUIElementTypeWindow"); + Assert.Single(windows); + + mainWindow.Click(); + + windows = _session.FindElementsByXPath("XCUIElementTypeWindow"); + Assert.Single(windows); + + _session.FindElementByAccessibilityId("RestoreAll").Click(); + + // Close the window manually. + secondaryWindow = FindWindow(_session, "SecondaryWindow"); + secondaryWindow.GetChromeButtons().close.Click(); + } + private IDisposable OpenWindow(PixelSize? size, ShowWindowMode mode, WindowStartupLocation location) { var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); From 9d356894bca636e1e1789c456e2440f08c119a18 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 28 Jul 2022 11:04:33 +0200 Subject: [PATCH 07/30] macOS: Don't bring invisible window to front. Check that a window is visible before bringing it to front, as bringing to front also shows the window. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 95f61422cb..af8c53cb33 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -121,7 +121,7 @@ void WindowImpl::BringToFront() { if(Window != nullptr) { - if (![Window isMiniaturized]) + if ([Window isVisible] && ![Window isMiniaturized]) { if(IsDialog()) { From e7797eea673ecac0da6463314c43ce5645a96064 Mon Sep 17 00:00:00 2001 From: ErrorCraft <51973682+ErrorCraft@users.noreply.github.com> Date: Wed, 3 Aug 2022 20:51:10 +0200 Subject: [PATCH 08/30] Add Sector shape --- src/Avalonia.Base/Utilities/MathUtilities.cs | 35 +++++++++++ src/Avalonia.Controls/Shapes/Sector.cs | 65 ++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 src/Avalonia.Controls/Shapes/Sector.cs diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index d381979c1e..3c48c3469e 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -324,6 +324,41 @@ namespace Avalonia.Utilities return angle * 2 * Math.PI; } + /// + /// Calculates the point of an angle on an ellipse. + /// + /// The centre point of the ellipse. + /// The x radius of the ellipse. + /// The y radius of the ellipse. + /// The angle in radians. + /// A point on the ellipse. + public static Point GetEllipsePoint(Point centre, double radiusX, double radiusY, double angle) + { + return new Point(radiusX * Math.Cos(angle) + centre.X, radiusY * Math.Sin(angle) + centre.Y); + } + + /// + /// Gets the minimum and maximum from the specified numbers. + /// + /// The first number. + /// The second number. + /// A tuple containing the minimum and maximum of the two specified numbers. + public static (double min, double max) GetMinMax(double a, double b) + { + return a < b ? (a, b) : (b, a); + } + + /// + /// Gets the minimum and maximum from the specified number and the difference with that number. + /// + /// The initial value to use. + /// The difference for . + /// A tuple containing the minimum and maximum of the specified number and the difference with that number. + public static (double min, double max) GetMinMaxFromDelta(double initialValue, double delta) + { + return GetMinMax(initialValue, initialValue + delta); + } + private static void ThrowCannotBeGreaterThanException(T min, T max) { throw new ArgumentException($"{min} cannot be greater than {max}."); diff --git a/src/Avalonia.Controls/Shapes/Sector.cs b/src/Avalonia.Controls/Shapes/Sector.cs new file mode 100644 index 0000000000..5d2f6701a7 --- /dev/null +++ b/src/Avalonia.Controls/Shapes/Sector.cs @@ -0,0 +1,65 @@ +using System; +using Avalonia.Media; +using Avalonia.Utilities; + +namespace Avalonia.Controls.Shapes +{ + public class Sector : Shape + { + public static readonly StyledProperty StartAngleProperty = AvaloniaProperty.Register(nameof(StartAngle), 0.0d); + public static readonly StyledProperty AngleProperty = AvaloniaProperty.Register(nameof(Angle), 0.0d); + + public double StartAngle + { + get => GetValue(StartAngleProperty); + set => SetValue(StartAngleProperty, value); + } + + public double Angle + { + get => GetValue(AngleProperty); + set => SetValue(AngleProperty, value); + } + + static Sector() + { + StrokeThicknessProperty.OverrideDefaultValue(1.0d); + AffectsGeometry(BoundsProperty, StrokeThicknessProperty, StartAngleProperty, AngleProperty); + } + + protected override Geometry? CreateDefiningGeometry() + { + Rect rect = new Rect(Bounds.Size); + Rect deflatedRect = rect.Deflate(StrokeThickness * 0.5d); + + if (Angle >= 360.0d || Angle <= -360.0d) + { + return new EllipseGeometry(deflatedRect); + } + + if (Angle == 0.0d) + { + return new StreamGeometry(); + } + + (double startAngle, double endAngle) = MathUtilities.GetMinMaxFromDelta(MathUtilities.Deg2Rad(StartAngle), MathUtilities.Deg2Rad(Angle)); + + Point centre = new Point(rect.Width * 0.5d, rect.Height * 0.5d); + double radiusX = deflatedRect.Width * 0.5d; + double radiusY = deflatedRect.Height * 0.5d; + Point startCurvePoint = MathUtilities.GetEllipsePoint(centre, radiusX, radiusY, startAngle); + Point endCurvePoint = MathUtilities.GetEllipsePoint(centre, radiusX, radiusY, endAngle); + Size size = new Size(radiusX, radiusY); + + StreamGeometry streamGeometry = new StreamGeometry(); + using StreamGeometryContext streamGeometryContext = streamGeometry.Open(); + + streamGeometryContext.BeginFigure(startCurvePoint, false); + streamGeometryContext.ArcTo(endCurvePoint, size, 0.0d, Math.Abs(Angle) > 180.0d, SweepDirection.Clockwise); + streamGeometryContext.LineTo(centre); + streamGeometryContext.EndFigure(true); + + return streamGeometry; + } + } +} From 9acb2a11ee4793568b01769d2ffc9160d8ef004c Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Tue, 2 Aug 2022 18:42:02 +0200 Subject: [PATCH 09/30] Custom setters implement Equals/GetHashCode and have optimized code gen --- ...aloniaXamlIlDeferredResourceTransformer.cs | 50 +++++- .../AvaloniaXamlIlSetterTransformer.cs | 26 ++- .../XamlIlAvaloniaPropertyHelper.cs | 152 ++++++++++++++---- .../Avalonia.Markup.Xaml.Loader/xamlil.github | 2 +- 4 files changed, 182 insertions(+), 48 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs index 662263e513..c29dd94886 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using XamlX.Ast; using XamlX.Emit; @@ -47,7 +48,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers return !node.Type.GetClrType().IsValueType; } - class AdderSetter : IXamlPropertySetter, IXamlEmitablePropertySetter + class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable { private readonly IXamlMethod _getter; private readonly IXamlMethod _adder; @@ -58,16 +59,22 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers _adder = adder; TargetType = getter.DeclaringType; Parameters = adder.ParametersWithThis().Skip(1).ToList(); + + bool allowNull = Parameters.Last().AcceptsNull(); + BinderParameters = new PropertySetterBinderParameters + { + AllowMultiple = true, + AllowXNull = allowNull, + AllowRuntimeNull = allowNull + }; } public IXamlType TargetType { get; } - public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters - { - AllowMultiple = true - }; + public PropertySetterBinderParameters BinderParameters { get; } public IReadOnlyList Parameters { get; } + public void Emit(IXamlILEmitter emitter) { var locals = new Stack(); @@ -80,11 +87,40 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers } emitter.EmitCall(_getter); - while (locals.Count > 0) + while (locals.Count>0) using (var loc = locals.Pop()) emitter.Ldloc(loc.Local); emitter.EmitCall(_adder, true); } + + public void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + emitter.EmitCall(_getter); + + for (var i = 0; i < arguments.Count; ++i) + context.Emit(arguments[i], emitter, Parameters[i]); + + emitter.EmitCall(_adder, true); + } + + public bool Equals(AdderSetter other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return _getter.Equals(other._getter) && _adder.Equals(other._adder); + } + + public override bool Equals(object obj) + => Equals(obj as AdderSetter); + + public override int GetHashCode() + => (_getter.GetHashCode() * 397) ^ _adder.GetHashCode(); } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs index 6da95be1c1..ceaec972f6 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -75,17 +75,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { Getter = setterType.Methods.First(m => m.Name == "get_Value"); var method = setterType.Methods.First(m => m.Name == "set_Value"); - Setters.Add(new XamlIlDirectCallPropertySetter(method, types.IBinding)); - Setters.Add(new XamlIlDirectCallPropertySetter(method, types.UnsetValueType)); - Setters.Add(new XamlIlDirectCallPropertySetter(method, targetType)); + Setters.Add(new XamlIlDirectCallPropertySetter(method, types.IBinding, false)); + Setters.Add(new XamlIlDirectCallPropertySetter(method, types.UnsetValueType, false)); + Setters.Add(new XamlIlDirectCallPropertySetter(method, targetType, targetType.AcceptsNull())); } - class XamlIlDirectCallPropertySetter : IXamlPropertySetter, IXamlEmitablePropertySetter + sealed class XamlIlDirectCallPropertySetter : IXamlPropertySetter, IXamlEmitablePropertySetter { private readonly IXamlMethod _method; private readonly IXamlType _type; public IXamlType TargetType { get; } - public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters(); + public PropertySetterBinderParameters BinderParameters { get; } public IReadOnlyList Parameters { get; } public void Emit(IXamlILEmitter codegen) { @@ -94,13 +94,27 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers codegen.EmitCall(_method, true); } - public XamlIlDirectCallPropertySetter(IXamlMethod method, IXamlType type) + public XamlIlDirectCallPropertySetter(IXamlMethod method, IXamlType type, bool allowNull) { _method = method; _type = type; Parameters = new[] {type}; TargetType = method.ThisOrFirstParameter(); + BinderParameters = new PropertySetterBinderParameters + { + AllowXNull = allowNull, + AllowRuntimeNull = allowNull + }; } + + private bool Equals(XamlIlDirectCallPropertySetter other) + => Equals(_method, other._method) && Equals(_type, other._type); + + public override bool Equals(object obj) + => Equals(obj as XamlIlDirectCallPropertySetter); + + public override int GetHashCode() + => (_method.GetHashCode() * 397) ^ _type.GetHashCode(); } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs index 5c7a80e680..6c9d510ba0 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs @@ -206,38 +206,64 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions Setters.Insert(0, new UnsetValueSetter(types, original.DeclaringType, field)); } - abstract class AvaloniaPropertyCustomSetter : IXamlPropertySetter, IXamlEmitablePropertySetter + abstract class AvaloniaPropertyCustomSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable { - protected AvaloniaXamlIlWellKnownTypes Types; - protected IXamlField AvaloniaProperty; + protected readonly AvaloniaXamlIlWellKnownTypes Types; + protected readonly IXamlField AvaloniaProperty; - public AvaloniaPropertyCustomSetter(AvaloniaXamlIlWellKnownTypes types, + protected AvaloniaPropertyCustomSetter( + AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, - IXamlField avaloniaProperty) + IXamlField avaloniaProperty, + bool allowNull) { Types = types; AvaloniaProperty = avaloniaProperty; TargetType = declaringType; + BinderParameters = new PropertySetterBinderParameters + { + AllowXNull = allowNull, + AllowRuntimeNull = allowNull + }; } public IXamlType TargetType { get; } - public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters - { - AllowXNull = false - }; + public PropertySetterBinderParameters BinderParameters { get; } public IReadOnlyList Parameters { get; set; } - public abstract void Emit(IXamlILEmitter codegen); + + public abstract void Emit(IXamlILEmitter emitter); + + public abstract void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments); + + public bool Equals(AvaloniaPropertyCustomSetter other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return GetType() == other.GetType() && AvaloniaProperty.Equals(other.AvaloniaProperty); + } + + public override bool Equals(object obj) + => Equals(obj as AvaloniaPropertyCustomSetter); + + public override int GetHashCode() + => AvaloniaProperty.GetHashCode(); } class BindingSetter : AvaloniaPropertyCustomSetter { public BindingSetter(AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, - IXamlField avaloniaProperty) : base(types, declaringType, avaloniaProperty) + IXamlField avaloniaProperty) : base(types, declaringType, avaloniaProperty, false) { - Parameters = new[] {types.IBinding}; + Parameters = new[] { types.IBinding }; } public override void Emit(IXamlILEmitter emitter) @@ -246,10 +272,25 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions emitter .Stloc(bloc.Local) .Ldsfld(AvaloniaProperty) - .Ldloc(bloc.Local) - // TODO: provide anchor? - .Ldnull(); - emitter.EmitCall(Types.AvaloniaObjectBindMethod, true); + .Ldloc(bloc.Local); + EmitAnchorAndBind(emitter); + } + + public override void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + emitter.Ldsfld(AvaloniaProperty); + context.Emit(arguments[0], emitter, Parameters[0]); + EmitAnchorAndBind(emitter); + } + + private void EmitAnchorAndBind(IXamlILEmitter emitter) + { + emitter + .Ldnull() // TODO: provide anchor? + .EmitCall(Types.AvaloniaObjectBindMethod, true); } } @@ -257,7 +298,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { public BindingWithPrioritySetter(AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, - IXamlField avaloniaProperty) : base(types, declaringType, avaloniaProperty) + IXamlField avaloniaProperty) : base(types, declaringType, avaloniaProperty, false) { Parameters = new[] { types.BindingPriority, types.IBinding }; } @@ -265,15 +306,29 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions public override void Emit(IXamlILEmitter emitter) { using (var bloc = emitter.LocalsPool.GetLocal(Types.IBinding)) - using (var priorityLocal = emitter.LocalsPool.GetLocal(Types.Int)) emitter .Stloc(bloc.Local) - .Stloc(priorityLocal.Local) + .Pop() // ignore priority .Ldsfld(AvaloniaProperty) - .Ldloc(bloc.Local) - // TODO: provide anchor? - .Ldnull(); - emitter.EmitCall(Types.AvaloniaObjectBindMethod, true); + .Ldloc(bloc.Local); + EmitAnchorAndBind(emitter); + } + + public override void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + emitter.Ldsfld(AvaloniaProperty); + context.Emit(arguments[1], emitter, Parameters[1]); + EmitAnchorAndBind(emitter); + } + + private void EmitAnchorAndBind(IXamlILEmitter emitter) + { + emitter + .Ldnull() // TODO: provide anchor? + .EmitCall(Types.AvaloniaObjectBindMethod, true); } } @@ -281,7 +336,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { public SetValueWithPrioritySetter(AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, IXamlField avaloniaProperty, IXamlType propertyType) - : base(types, declaringType, avaloniaProperty) + : base(types, declaringType, avaloniaProperty, propertyType.AcceptsNull()) { Parameters = new[] { types.BindingPriority, propertyType }; } @@ -295,9 +350,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions - value */ - var method = Types.AvaloniaObjectSetStyledPropertyValue - .MakeGenericMethod(new[] { Parameters[1] }); - using (var valueLocal = emitter.LocalsPool.GetLocal(Parameters[1])) using (var priorityLocal = emitter.LocalsPool.GetLocal(Types.Int)) emitter @@ -305,25 +357,57 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions .Stloc(priorityLocal.Local) .Ldsfld(AvaloniaProperty) .Ldloc(valueLocal.Local) - .Ldloc(priorityLocal.Local) - .EmitCall(method, true); + .Ldloc(priorityLocal.Local); + + EmitSetStyledPropertyValue(emitter); + } + + public override void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + emitter.Ldsfld(AvaloniaProperty); + context.Emit(arguments[1], emitter, Parameters[1]); + context.Emit(arguments[0], emitter, Parameters[0]); + EmitSetStyledPropertyValue(emitter); + } + + private void EmitSetStyledPropertyValue(IXamlILEmitter emitter) + { + var method = Types.AvaloniaObjectSetStyledPropertyValue.MakeGenericMethod(new[] { Parameters[1] }); + emitter.EmitCall(method, true); } } class UnsetValueSetter : AvaloniaPropertyCustomSetter { public UnsetValueSetter(AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, IXamlField avaloniaProperty) - : base(types, declaringType, avaloniaProperty) + : base(types, declaringType, avaloniaProperty, false) { - Parameters = new[] {types.UnsetValueType}; + Parameters = new[] { types.UnsetValueType }; } public override void Emit(IXamlILEmitter codegen) { + codegen.Pop(); + EmitSetValue(codegen); + } + + public override void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + EmitSetValue(emitter); + } + + private void EmitSetValue(IXamlILEmitter emitter) + { + // Ignore the instance and load one from the static field to avoid extra local variable var unsetValue = Types.AvaloniaProperty.Fields.First(f => f.Name == "UnsetValue"); - codegen - // Ignore the instance and load one from the static field to avoid extra local variable - .Pop() + + emitter .Ldsfld(AvaloniaProperty) .Ldsfld(unsetValue) .Ldc_I4(0) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github index a4e6be2d14..c1c0594ec2 160000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github @@ -1 +1 @@ -Subproject commit a4e6be2d1407abec4f35fcb208848830ce513ead +Subproject commit c1c0594ec2c35b08988183b1a5b3e34dfa19179d From ed582a9fde3aef0a2cd9462cbe66c9dc1446076d Mon Sep 17 00:00:00 2001 From: aldelaro5 Date: Wed, 3 Aug 2022 22:39:40 -0400 Subject: [PATCH 10/30] Implement DataContext update notifications on the DataGrid This fixes #5661 by allowing the DataGrid to notify its cells that their DataContext is about to be changed. --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index d42468f47e..f4cd425c53 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -2167,6 +2167,38 @@ namespace Avalonia.Controls return desiredSize; } + + /// + protected override void OnDataContextBeginUpdate() + { + base.OnDataContextBeginUpdate(); + foreach (DataGridRow row in GetAllRows()) + { + foreach (DataGridCell cell in row.Cells) + { + if (cell.Content is StyledElement) + { + DataContextProperty.Notifying?.Invoke((IAvaloniaObject)cell.Content, true); + } + } + } + } + + /// + protected override void OnDataContextEndUpdate() + { + base.OnDataContextEndUpdate(); + foreach (DataGridRow row in GetAllRows()) + { + foreach (DataGridCell cell in row.Cells) + { + if (cell.Content is StyledElement) + { + DataContextProperty.Notifying?.Invoke((IAvaloniaObject)cell.Content, false); + } + } + } + } /// /// Raises the BeginningEdit event. From ee662cecdd0c9b57f986d44e93afa26b5d524b3e Mon Sep 17 00:00:00 2001 From: ili Date: Mon, 8 Aug 2022 20:13:47 +0500 Subject: [PATCH 11/30] Avoid lock in timers --- .../AndroidThreadingInterface.cs | 45 +++++++---------- .../InternalPlatformThreadingInterface.cs | 4 +- .../HeadlessPlatformThreadingInterface.cs | 48 +++++++++---------- 3 files changed, 42 insertions(+), 55 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs index 42f75a27e1..de9149e9a1 100644 --- a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs +++ b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs @@ -27,46 +27,33 @@ namespace Avalonia.Android { if (interval.TotalMilliseconds < 10) interval = TimeSpan.FromMilliseconds(10); - object l = new object(); + var stopped = false; Timer timer = null; - var scheduled = false; timer = new Timer(_ => { - lock (l) + if (stopped) + return; + + EnsureInvokeOnMainThread(() => { - if (stopped) + try { - timer.Dispose(); - return; + tick(); } - if (scheduled) - return; - scheduled = true; - EnsureInvokeOnMainThread(() => + finally { - try - { - tick(); - } - finally - { - lock (l) - { - scheduled = false; - } - } - }); - } - }, null, TimeSpan.Zero, interval); + if (!stopped) + timer.Change(interval, Timeout.InfiniteTimeSpan); + } + }); + }, + null, interval, Timeout.InfiniteTimeSpan); return Disposable.Create(() => { - lock (l) - { - stopped = true; - timer.Dispose(); - } + stopped = true; + timer.Dispose(); }); } diff --git a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs index 630d2d8efb..e1f6db9c60 100644 --- a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs +++ b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs @@ -43,7 +43,7 @@ namespace Avalonia.Controls.Platform _priority = priority; _interval = interval; _tick = tick; - _timer = new Timer(OnTimer, null, interval, TimeSpan.FromMilliseconds(-1)); + _timer = new Timer(OnTimer, null, interval, Timeout.InfiniteTimeSpan); _handle = GCHandle.Alloc(_timer); } @@ -57,7 +57,7 @@ namespace Avalonia.Controls.Platform if (_timer == null) return; _tick(); - _timer?.Change(_interval, TimeSpan.FromMilliseconds(-1)); + _timer?.Change(_interval, Timeout.InfiniteTimeSpan); }); } diff --git a/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs b/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs index e42a7b1a71..b233b46dd0 100644 --- a/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs @@ -36,35 +36,35 @@ namespace Avalonia.Headless public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) { - var cancelled = false; - var enqueued = false; - var l = new object(); - var timer = new Timer(_ => + if (interval.TotalMilliseconds < 10) + interval = TimeSpan.FromMilliseconds(10); + + var stopped = false; + Timer timer = null; + timer = new Timer(_ => { - lock (l) + if (stopped) + return; + + Dispatcher.UIThread.Post(() => { - if (cancelled || enqueued) - return; - enqueued = true; - Dispatcher.UIThread.Post(() => + try { - lock (l) - { - enqueued = false; - if (cancelled) - return; - tick(); - } - }, priority); - } - }, null, interval, interval); + tick(); + } + finally + { + if (!stopped) + timer.Change(interval, Timeout.InfiniteTimeSpan); + } + }); + }, + null, interval, Timeout.InfiniteTimeSpan); + return Disposable.Create(() => { - lock (l) - { - timer.Dispose(); - cancelled = true; - } + stopped = true; + timer.Dispose(); }); } From f314abd8abd719e014370f77d7f2cf1608231b73 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 9 Aug 2022 03:19:05 -0400 Subject: [PATCH 12/30] Fix android options initalization order --- src/Android/Avalonia.Android/AndroidPlatform.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 89444dea10..ed5b46d398 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -17,10 +17,8 @@ namespace Avalonia { public static T UseAndroid(this T builder) where T : AppBuilderBase, new() { - var options = AvaloniaLocator.Current.GetService() ?? new AndroidPlatformOptions(); - return builder - .UseWindowingSubsystem(() => AndroidPlatform.Initialize(options), "Android") + .UseWindowingSubsystem(() => AndroidPlatform.Initialize(), "Android") .UseSkia(); } } @@ -45,9 +43,9 @@ namespace Avalonia.Android internal static Compositor Compositor { get; private set; } - public static void Initialize(AndroidPlatformOptions options) + public static void Initialize() { - Options = options; + Options = AvaloniaLocator.Current.GetService() ?? new AndroidPlatformOptions(); AvaloniaLocator.CurrentMutable .Bind().ToTransient() @@ -61,12 +59,12 @@ namespace Avalonia.Android .Bind().ToConstant(new RenderLoop()) .Bind().ToSingleton(); - if (options.UseGpu) + if (Options.UseGpu) { EglPlatformOpenGlInterface.TryInitialize(); } - if (options.UseCompositor) + if (Options.UseCompositor) { Compositor = new Compositor( AvaloniaLocator.Current.GetRequiredService(), From a88451c650d4f3d130d9c743a4ac19725c72f122 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 9 Aug 2022 03:19:15 -0400 Subject: [PATCH 13/30] Reset render target when it's corrupted --- .../Composition/Server/ServerCompositionTarget.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index c20594aaca..5c1ac0312c 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -78,6 +78,13 @@ namespace Avalonia.Rendering.Composition.Server if (Root == null) return; + + if ((_renderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true) + { + _renderTarget!.Dispose(); + _renderTarget = null; + } + _renderTarget ??= _renderTargetFactory(); Compositor.UpdateServerTime(); From 2c39f80f9bab22fbdb66d41db33543a84f556ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Tue, 9 Aug 2022 17:43:12 +0100 Subject: [PATCH 14/30] Changed Skia OpenGL bitmap origin to bottom left. --- samples/ControlCatalog/Pages/OpenGlPage.xaml.cs | 2 +- src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs index f37e0f8701..063ad50b0b 100644 --- a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs @@ -345,7 +345,7 @@ namespace ControlCatalog.Pages 0.01f, 1000); - var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, -1, 0)); + var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, 1, 0)); var model = Matrix4x4.CreateFromYawPitchRoll(_yaw, _pitch, _roll); var modelLoc = GL.GetUniformLocationString(_shaderProgram, "uModel"); var viewLoc = GL.GetUniformLocationString(_shaderProgram, "uView"); diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs index a4617bb4d5..fb69a1ad3d 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs @@ -41,7 +41,7 @@ namespace Avalonia.Skia new GRGlTextureInfo( GlConsts.GL_TEXTURE_2D, (uint)_surface.GetTextureId(), (uint)_surface.InternalFormat))) - using (var surface = SKSurface.Create(context.GrContext, backendTexture, GRSurfaceOrigin.TopLeft, + using (var surface = SKSurface.Create(context.GrContext, backendTexture, GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888)) { // Again, silently ignore, if something went wrong it's not our fault From 6054fa98f117d3b64adf70ae2c09767a0662af3b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 9 Aug 2022 17:48:44 +0100 Subject: [PATCH 15/30] fix reliability issue in osx window state test. --- tests/Avalonia.IntegrationTests.Appium/WindowTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 2b10c302bc..5afcb7cf43 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -92,7 +92,8 @@ namespace Avalonia.IntegrationTests.Appium Assert.True(clientSize.Width >= current.ScreenRect.Width); Assert.True(clientSize.Height >= current.ScreenRect.Height); - windowState.Click(); + windowState.SendClick(); + _session.FindElementByName("Normal").SendClick(); current = GetWindowInfo(); From 3e3661a2d423ac40b020f697698403c8e384b795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Tue, 9 Aug 2022 18:00:10 +0100 Subject: [PATCH 16/30] Removed outdated comment in PR template. --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 46e8665945..2f63750cdc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -21,7 +21,7 @@ - [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Documentation with user documentation ## Breaking changes - + ## Obsoletions / Deprecations From f63ed9cf6bf19b8490c55d2f9bf6cc6b9ae05fb3 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 10 Aug 2022 10:33:00 +0200 Subject: [PATCH 17/30] fix: Null Annotation --- samples/ControlCatalog/MainView.xaml.cs | 6 +- samples/ControlCatalog/Models/Person.cs | 8 +-- .../Pages/AutoCompleteBoxPage.xaml.cs | 24 +++---- .../Pages/ButtonSpinnerPage.xaml.cs | 25 ++++--- .../ControlCatalog/Pages/ButtonsPage.xaml.cs | 2 +- .../ControlCatalog/Pages/CarouselPage.xaml.cs | 2 +- .../Pages/ClipboardPage.xaml.cs | 70 +++++++++++++------ .../ControlCatalog/Pages/ComboBoxPage.xaml.cs | 2 +- .../Pages/CompositionPage.axaml.cs | 20 +++--- .../Pages/ContextFlyoutPage.xaml.cs | 22 +++--- .../Pages/ContextMenuPage.xaml.cs | 23 +++--- .../ControlCatalog/Pages/DataGridPage.xaml.cs | 4 +- .../ControlCatalog/Pages/DialogsPage.xaml.cs | 13 +++- .../Pages/DragAndDropPage.xaml.cs | 20 +++--- .../ControlCatalog/Pages/FlyoutsPage.axaml.cs | 2 +- .../Pages/ItemsRepeaterPage.xaml.cs | 10 +-- .../Pages/NumericUpDownPage.xaml.cs | 6 +- .../ControlCatalog/Pages/OpenGlPage.xaml.cs | 4 +- .../ControlCatalog/Pages/PointersPage.xaml.cs | 31 ++++---- ...ransitioningContentControlPageViewModel.cs | 6 +- 20 files changed, 165 insertions(+), 135 deletions(-) diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 58433f13ce..7133ddaa6a 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -22,8 +22,8 @@ namespace ControlCatalog if (AvaloniaLocator.Current?.GetService()?.GetRuntimeInfo().IsDesktop == true) { - IList tabItems = ((IList)sideBar.Items); - tabItems.Add(new TabItem() + var tabItems = (sideBar.Items as IList); + tabItems?.Add(new TabItem() { Header = "Screens", Content = new ScreenPage() @@ -36,7 +36,7 @@ namespace ControlCatalog { if (themes.SelectedItem is CatalogTheme theme) { - var themeStyle = Application.Current.Styles[0]; + var themeStyle = Application.Current!.Styles[0]; if (theme == CatalogTheme.FluentLight) { if (App.Fluent.Mode != FluentThemeMode.Light) diff --git a/samples/ControlCatalog/Models/Person.cs b/samples/ControlCatalog/Models/Person.cs index 2dfa02c7ed..99bc50250b 100644 --- a/samples/ControlCatalog/Models/Person.cs +++ b/samples/ControlCatalog/Models/Person.cs @@ -85,7 +85,7 @@ namespace ControlCatalog.Models } else { - if (_errorLookup.TryGetValue(propertyName, out List errorList)) + if (_errorLookup.TryGetValue(propertyName, out var errorList)) { errorList.Clear(); errorList.Add(error!); @@ -114,12 +114,12 @@ namespace ControlCatalog.Models PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } - public IEnumerable? GetErrors(string propertyName) + public IEnumerable GetErrors(string? propertyName) { - if (_errorLookup.TryGetValue(propertyName, out List errorList)) + if (propertyName is { } && _errorLookup.TryGetValue(propertyName, out var errorList)) return errorList; else - return null; + return Array.Empty(); } } } diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs index 7a0957b02d..bc18327f12 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs @@ -1,8 +1,6 @@ using Avalonia.Controls; using Avalonia.LogicalTree; -using Avalonia.Markup; using Avalonia.Markup.Xaml; -using Avalonia.Markup.Data; using System; using System.Collections.Generic; using System.Linq; @@ -161,23 +159,23 @@ namespace ControlCatalog.Pages private bool LastWordContains(string? searchText, string? item) { var words = searchText?.Split(' ') ?? Array.Empty(); - var options = Sentences.Select(x => x.First).ToArray(); + var options = Sentences.Select(x => x.First) + .ToArray?>(); for (var i = 0; i < words.Length; ++i) { var word = words[i]; for (var j = 0; word is { } && j < options.Length; ++j) { - var option = options[j]; - if (option == null) - continue; - - if (i == words.Length - 1) - { - options[j] = option.Value.ToLower().Contains(word.ToLower()) ? option : null; - } - else + if (options[i] is { } option) { - options[j] = option.Value.Equals(word, StringComparison.InvariantCultureIgnoreCase) ? option.Next : null; + if (i == words.Length - 1) + { + options[j] = option.Value.ToLower().Contains(word.ToLower()) ? option : null; + } + else + { + options[j] = option.Value.Equals(word, StringComparison.InvariantCultureIgnoreCase) ? option.Next : null; + } } } } diff --git a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs index 5c584b8781..e7450075ad 100644 --- a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs @@ -21,20 +21,23 @@ namespace ControlCatalog.Pages public void OnSpin(object sender, SpinEventArgs e) { var spinner = (ButtonSpinner)sender; - var txtBox = (TextBlock)spinner.Content; - int value = Array.IndexOf(_mountains, txtBox?.Text); - if (e.Direction == SpinDirection.Increase) - value++; - else - value--; + if (spinner.Content is TextBlock txtBox) + { + int value = Array.IndexOf(_mountains, txtBox.Text); + if (e.Direction == SpinDirection.Increase) + value++; + else + value--; - if (value < 0) - value = _mountains.Length - 1; - else if (value >= _mountains.Length) - value = 0; + if (value < 0) + value = _mountains.Length - 1; + else if (value >= _mountains.Length) + value = 0; + + txtBox.Text = _mountains[value]; + } - txtBox.Text = _mountains[value]; } private readonly string[] _mountains = new[] diff --git a/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs b/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs index 3e748dd6f6..2d63f1fee9 100644 --- a/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs @@ -19,7 +19,7 @@ namespace ControlCatalog.Pages AvaloniaXamlLoader.Load(this); } - public void OnRepeatButtonClick(object sender, object args) + public void OnRepeatButtonClick(object? sender, object args) { repeatButtonClickCount++; var textBlock = this.Get("RepeatButtonTextBlock"); diff --git a/samples/ControlCatalog/Pages/CarouselPage.xaml.cs b/samples/ControlCatalog/Pages/CarouselPage.xaml.cs index 5b74e3e19e..c6aab5c4d5 100644 --- a/samples/ControlCatalog/Pages/CarouselPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CarouselPage.xaml.cs @@ -33,7 +33,7 @@ namespace ControlCatalog.Pages } - private void TransitionChanged(object sender, SelectionChangedEventArgs e) + private void TransitionChanged(object? sender, SelectionChangedEventArgs e) { switch (_transition.SelectedIndex) { diff --git a/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs b/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs index eed46265ff..ef3d2bbafa 100644 --- a/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs @@ -23,55 +23,79 @@ namespace ControlCatalog.Pages AvaloniaXamlLoader.Load(this); } - private async void CopyText(object sender, RoutedEventArgs args) + private async void CopyText(object? sender, RoutedEventArgs args) { - await Application.Current.Clipboard.SetTextAsync(ClipboardContent.Text); + if (Application.Current!.Clipboard is { } clipboard && ClipboardContent is { } clipboardContent) + await clipboard.SetTextAsync(clipboardContent.Text ?? String.Empty); } - private async void PasteText(object sender, RoutedEventArgs args) + private async void PasteText(object? sender, RoutedEventArgs args) { - ClipboardContent.Text = await Application.Current.Clipboard.GetTextAsync(); + if(Application.Current!.Clipboard is { } clipboard) + { + ClipboardContent.Text = await clipboard.GetTextAsync(); + } } - private async void CopyTextDataObject(object sender, RoutedEventArgs args) + private async void CopyTextDataObject(object? sender, RoutedEventArgs args) { - var dataObject = new DataObject(); - dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty); - await Application.Current.Clipboard.SetDataObjectAsync(dataObject); + if (Application.Current!.Clipboard is { } clipboard) + { + var dataObject = new DataObject(); + dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty); + await clipboard.SetDataObjectAsync(dataObject); + } } - private async void PasteTextDataObject(object sender, RoutedEventArgs args) + private async void PasteTextDataObject(object? sender, RoutedEventArgs args) { - ClipboardContent.Text = await Application.Current.Clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty; + if (Application.Current!.Clipboard is { } clipboard) + { + ClipboardContent.Text = await clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty; + } } - private async void CopyFilesDataObject(object sender, RoutedEventArgs args) + private async void CopyFilesDataObject(object? sender, RoutedEventArgs args) { - var files = ClipboardContent.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); - if (files.Length == 0) + if (Application.Current!.Clipboard is { } clipboard) { - return; + var files = (ClipboardContent.Text ?? String.Empty) + .Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + if (files.Length == 0) + { + return; + } + var dataObject = new DataObject(); + dataObject.Set(DataFormats.FileNames, files); + await clipboard.SetDataObjectAsync(dataObject); } - var dataObject = new DataObject(); - dataObject.Set(DataFormats.FileNames, files); - await Application.Current.Clipboard.SetDataObjectAsync(dataObject); } - private async void PasteFilesDataObject(object sender, RoutedEventArgs args) + private async void PasteFilesDataObject(object? sender, RoutedEventArgs args) { - var fiels = await Application.Current.Clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable; - ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty; + if (Application.Current!.Clipboard is { } clipboard) + { + var fiels = await clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable; + ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty; + } } private async void GetFormats(object sender, RoutedEventArgs args) { - var formats = await Application.Current.Clipboard.GetFormatsAsync(); - ClipboardContent.Text = string.Join(Environment.NewLine, formats); + if (Application.Current!.Clipboard is { } clipboard) + { + var formats = await clipboard.GetFormatsAsync(); + ClipboardContent.Text = string.Join(Environment.NewLine, formats); + } } private async void Clear(object sender, RoutedEventArgs args) { - await Application.Current.Clipboard.ClearAsync(); + if (Application.Current!.Clipboard is { } clipboard) + { + await clipboard.ClearAsync(); + } + } } } diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs b/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs index d304bf227d..6d624c9a07 100644 --- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs @@ -17,7 +17,7 @@ namespace ControlCatalog.Pages private void InitializeComponent() { AvaloniaXamlLoader.Load(this); - var fontComboBox = this.Find("fontComboBox"); + var fontComboBox = this.Get("fontComboBox"); fontComboBox.Items = FontManager.Current.GetInstalledFontFamilyNames().Select(x => new FontFamily(x)); fontComboBox.SelectedIndex = 0; } diff --git a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs index 18069ca857..61e0ed5acb 100644 --- a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs +++ b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs @@ -1,14 +1,8 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Primitives; -using Avalonia.Interactivity; using Avalonia.Markup.Xaml; -using Avalonia.Markup.Xaml.Templates; using Avalonia.Media; using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition.Animations; @@ -18,7 +12,7 @@ namespace ControlCatalog.Pages; public partial class CompositionPage : UserControl { - private ImplicitAnimationCollection _implicitAnimations; + private ImplicitAnimationCollection? _implicitAnimations; public CompositionPage() { @@ -28,7 +22,7 @@ public partial class CompositionPage : UserControl protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); - this.FindControl("Items").Items = CreateColorItems(); + this.Get("Items").Items = CreateColorItems(); } private List CreateColorItems() @@ -115,7 +109,6 @@ public partial class CompositionPage : UserControl public static void SetEnableAnimations(Border border, bool value) { - var page = border.FindAncestorOfType(); if (page == null) { @@ -127,8 +120,11 @@ public partial class CompositionPage : UserControl return; page.EnsureImplicitAnimations(); - ElementComposition.GetElementVisual((Visual)border.GetVisualParent()).ImplicitAnimations = - page._implicitAnimations; + if (border.GetVisualParent() is Visual visualParent + && ElementComposition.GetElementVisual(visualParent) is CompositionVisual compositionVisual) + { + compositionVisual.ImplicitAnimations = page._implicitAnimations; + } } } @@ -150,4 +146,4 @@ public class CompositionPageColorItem { Color = color; } -} \ No newline at end of file +} diff --git a/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs b/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs index 4d72fc5311..8bd1f4d85a 100644 --- a/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs @@ -52,13 +52,13 @@ namespace ControlCatalog.Pages base.OnDataContextChanged(e); } - private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e) + private void ContextFlyoutPage_Closing(object? sender, CancelEventArgs e) { var cancelCloseCheckBox = this.FindControl("CancelCloseCheckBox"); e.Cancel = cancelCloseCheckBox?.IsChecked ?? false; } - private void ContextFlyoutPage_Opening(object sender, EventArgs e) + private void ContextFlyoutPage_Opening(object? sender, EventArgs e) { if (e is CancelEventArgs cancelArgs) { @@ -67,20 +67,20 @@ namespace ControlCatalog.Pages } } - private void CloseFlyout(object sender, RoutedEventArgs e) + private void CloseFlyout(object? sender, RoutedEventArgs e) { _textBox.ContextFlyout?.Hide(); } - public void CustomContextRequested(object sender, ContextRequestedEventArgs e) + public void CustomContextRequested(object? sender, ContextRequestedEventArgs e) { - var border = (Border)sender; - var textBlock = (TextBlock)border.Child; - - textBlock.Text = e.TryGetPosition(border, out var point) - ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}" - : "Context was requested without pointer"; - e.Handled = true; + if (sender is Border border && border.Child is TextBlock textBlock) + { + textBlock.Text = e.TryGetPosition(border, out var point) + ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}" + : "Context was requested without pointer"; + e.Handled = true; + } } private void InitializeComponent() diff --git a/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs b/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs index 4581642024..96fcb54650 100644 --- a/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs @@ -35,30 +35,31 @@ namespace ControlCatalog.Pages base.OnDataContextChanged(e); } - private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e) + private void ContextFlyoutPage_Closing(object? sender, CancelEventArgs e) { var cancelCloseCheckBox = this.FindControl("CancelCloseCheckBox"); - e.Cancel = cancelCloseCheckBox.IsChecked ?? false; + e.Cancel = cancelCloseCheckBox?.IsChecked ?? false; } - private void ContextFlyoutPage_Opening(object sender, EventArgs e) + private void ContextFlyoutPage_Opening(object? sender, EventArgs e) { if (e is CancelEventArgs cancelArgs) { var cancelCloseCheckBox = this.FindControl("CancelOpenCheckBox"); - cancelArgs.Cancel = cancelCloseCheckBox.IsChecked ?? false; + cancelArgs.Cancel = cancelCloseCheckBox?.IsChecked ?? false; } } - public void CustomContextRequested(object sender, ContextRequestedEventArgs e) + public void CustomContextRequested(object? sender, ContextRequestedEventArgs e) { - var border = (Border)sender; - var textBlock = (TextBlock)border.Child; + if (sender is Border border && border.Child is TextBlock textBlock) + { + textBlock.Text = e.TryGetPosition(border, out var point) + ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}" + : "Context was requested without pointer"; + e.Handled = true; + } - textBlock.Text = e.TryGetPosition(border, out var point) - ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}" - : "Context was requested without pointer"; - e.Handled = true; } private void InitializeComponent() diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs index 219b7aeac4..3565d113bc 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs @@ -62,7 +62,7 @@ namespace ControlCatalog.Pages addButton.Click += (a, b) => collectionView3.AddNew(); } - private void Dg1_LoadingRow(object sender, DataGridRowEventArgs e) + private void Dg1_LoadingRow(object? sender, DataGridRowEventArgs e) { e.Row.Header = e.Row.GetIndex() + 1; } @@ -74,7 +74,7 @@ namespace ControlCatalog.Pages private class ReversedStringComparer : IComparer, IComparer { - public int Compare(object x, object y) + public int Compare(object? x, object? y) { if (x is string left && y is string right) { diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index 036dccde0e..67e9ef4e40 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -111,9 +111,16 @@ namespace ControlCatalog.Pages Title = "Select folder", Directory = lastSelectedDirectory?.TryGetUri(out var path) == true ? path.LocalPath : null }.ShowAsync(GetWindow()); - lastSelectedDirectory = new BclStorageFolder(new System.IO.DirectoryInfo(result)); - results.Items = new [] { result }; - resultsVisible.IsVisible = result != null; + if (string.IsNullOrEmpty(result)) + { + resultsVisible.IsVisible = false; + } + else + { + lastSelectedDirectory = new BclStorageFolder(new System.IO.DirectoryInfo(result)); + results.Items = new[] { result }; + resultsVisible.IsVisible = true; + } }; this.Get