Browse Source

Merge branch 'master' into feat/SelectedValue_SelectedValueBinding

pull/10180/head
amwx 3 years ago
committed by GitHub
parent
commit
249ccf6d12
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build/ImageSharp.props
  2. 2
      build/Moq.props
  3. 15
      build/XUnit.props
  4. 11
      src/Avalonia.Base/Animation/KeySpline.cs
  5. 38
      src/Avalonia.Base/AvaloniaObject.cs
  6. 2
      src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
  7. 3
      src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs
  8. 18
      src/Avalonia.Base/Input/DragEventArgs.cs
  9. 2
      src/Avalonia.Base/Input/KeyGesture.cs
  10. 44
      src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
  11. 48
      src/Avalonia.Base/Input/Navigation/TabNavigation.cs
  12. 6
      src/Avalonia.Base/LogicalTree/LogicalExtensions.cs
  13. 9
      src/Avalonia.Base/Media/Color.cs
  14. 2
      src/Avalonia.Base/Media/DrawingContext.cs
  15. 26
      src/Avalonia.Base/Media/DrawingGroup.cs
  16. 6
      src/Avalonia.Base/Media/DrawingImage.cs
  17. 4
      src/Avalonia.Base/Media/FontFamily.cs
  18. 5
      src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs
  19. 6
      src/Avalonia.Base/Media/FormattedText.cs
  20. 6
      src/Avalonia.Base/Media/GeometryDrawing.cs
  21. 12
      src/Avalonia.Base/Media/GlyphRunDrawing.cs
  22. 2
      src/Avalonia.Base/Media/HslColor.cs
  23. 2
      src/Avalonia.Base/Media/HsvColor.cs
  24. 3
      src/Avalonia.Base/Media/IVisualBrush.cs
  25. 24
      src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs
  26. 7
      src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs
  27. 14
      src/Avalonia.Base/Media/TextDecoration.cs
  28. 7
      src/Avalonia.Base/Media/VisualBrush.cs
  29. 4
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  30. 28
      src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs
  31. 2
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
  32. 2
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs
  33. 2
      src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs
  34. 82
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  35. 3
      src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs
  36. 4
      src/Avalonia.Base/Rendering/Composition/CompositionObject.cs
  37. 2
      src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
  38. 14
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
  39. 2
      src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs
  40. 1
      src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs
  41. 4
      src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
  42. 2
      src/Avalonia.Base/Rendering/IRenderRoot.cs
  43. 3
      src/Avalonia.Base/Rendering/IRenderer.cs
  44. 6
      src/Avalonia.Base/Rendering/ImmediateRenderer.cs
  45. 7
      src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs
  46. 11
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  47. 68
      src/Avalonia.Base/Utilities/WeakEvent.cs
  48. 45
      src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
  49. 34
      src/Avalonia.Base/Visual.cs
  50. 19
      src/Avalonia.Base/VisualTree/VisualExtensions.cs
  51. 2
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  52. 2
      src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs
  53. 2
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
  54. 4
      src/Avalonia.Controls/Button.cs
  55. 4
      src/Avalonia.Controls/Control.cs
  56. 18
      src/Avalonia.Controls/Documents/InlineUIContainer.cs
  57. 1
      src/Avalonia.Controls/ListBox.cs
  58. 2
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  59. 2
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  60. 2
      src/Avalonia.Controls/Primitives/Popup.cs
  61. 4
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  62. 2
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  63. 2
      src/Avalonia.Controls/ProgressBar.cs
  64. 4
      src/Avalonia.Controls/TextBlock.cs
  65. 40
      src/Avalonia.Controls/TopLevel.cs
  66. 6
      src/Avalonia.Controls/Window.cs
  67. 6
      src/Avalonia.Controls/WindowBase.cs
  68. 2
      src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
  69. 2
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs
  70. 1
      src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
  71. 4
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  72. 3
      src/Avalonia.Native/WindowImpl.cs
  73. 12
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  74. 7
      src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs
  75. 25
      tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs
  76. 4
      tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
  77. 2
      tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
  78. 20
      tests/Avalonia.Controls.UnitTests/ButtonTests.cs
  79. 39
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  80. 12
      tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs
  81. 14
      tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
  82. 2
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  83. 35
      tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs
  84. 2
      tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj
  85. 2
      tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj
  86. 70
      tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
  87. 8
      tests/Avalonia.UnitTests/MockGlyphRun.cs
  88. 54
      tests/Avalonia.UnitTests/TestTemplatedRoot.cs

2
build/ImageSharp.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.1" /> <PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
build/Moq.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="Moq" Version="4.14.1" /> <PackageReference Include="Moq" Version="4.18.4" />
</ItemGroup> </ItemGroup>
</Project> </Project>

15
build/XUnit.props

@ -1,13 +1,12 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" /> <PackageReference Include="xunit.assert" Version="2.4.2" />
<PackageReference Include="xunit.assert" Version="2.4.1" /> <PackageReference Include="xunit.core" Version="2.4.2" />
<PackageReference Include="xunit.core" Version="2.4.1" /> <PackageReference Include="xunit.extensibility.core" Version="2.4.2" />
<PackageReference Include="xunit.extensibility.core" Version="2.4.1" /> <PackageReference Include="xunit.extensibility.execution" Version="2.4.2" />
<PackageReference Include="xunit.extensibility.execution" Version="2.4.1" /> <PackageReference Include="xunit.runner.console" Version="2.4.2" />
<PackageReference Include="xunit.runner.console" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="Xunit.SkippableFact" Version="1.4.13" /> <PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
</ItemGroup> </ItemGroup>

11
src/Avalonia.Base/Animation/KeySpline.cs

@ -79,15 +79,12 @@ namespace Avalonia.Animation
/// <param name="culture">culture of the string</param> /// <param name="culture">culture of the string</param>
/// <exception cref="FormatException">Thrown if the string does not have 4 values</exception> /// <exception cref="FormatException">Thrown if the string does not have 4 values</exception>
/// <returns>A <see cref="KeySpline"/> with the appropriate values set</returns> /// <returns>A <see cref="KeySpline"/> with the appropriate values set</returns>
public static KeySpline Parse(string value, CultureInfo culture) public static KeySpline Parse(string value, CultureInfo? culture)
{ {
if (culture is null) culture ??= CultureInfo.InvariantCulture;
culture = CultureInfo.InvariantCulture;
using (var tokenizer = new StringTokenizer((string)value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\".")) using var tokenizer = new StringTokenizer(value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\".");
{ return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
}
} }
/// <summary> /// <summary>

38
src/Avalonia.Base/AvaloniaObject.cs

@ -152,7 +152,7 @@ namespace Avalonia
property = property ?? throw new ArgumentNullException(nameof(property)); property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess(); VerifyAccess();
_values?.ClearLocalValue(property); _values.ClearLocalValue(property);
} }
/// <summary> /// <summary>
@ -242,7 +242,14 @@ namespace Avalonia
return registered.InvokeGetter(this); return registered.InvokeGetter(this);
} }
/// <inheritdoc/> /// <summary>
/// Gets an <see cref="AvaloniaProperty"/> base value.
/// </summary>
/// <param name="property">The property.</param>
/// <remarks>
/// Gets the value of the property excluding animated values, otherwise <see cref="Optional{T}.Empty"/>.
/// Note that this method does not return property values that come from inherited or default values.
/// </remarks>
public Optional<T> GetBaseValue<T>(StyledProperty<T> property) public Optional<T> GetBaseValue<T>(StyledProperty<T> property)
{ {
_ = property ?? throw new ArgumentNullException(nameof(property)); _ = property ?? throw new ArgumentNullException(nameof(property));
@ -261,7 +268,7 @@ namespace Avalonia
VerifyAccess(); VerifyAccess();
return _values?.IsAnimating(property) ?? false; return _values.IsAnimating(property);
} }
/// <summary> /// <summary>
@ -279,7 +286,7 @@ namespace Avalonia
VerifyAccess(); VerifyAccess();
return _values?.IsSet(property) ?? false; return _values.IsSet(property);
} }
/// <summary> /// <summary>
@ -515,14 +522,12 @@ namespace Avalonia
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property); public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property);
/// <inheritdoc/>
internal void AddInheritanceChild(AvaloniaObject child) internal void AddInheritanceChild(AvaloniaObject child)
{ {
_inheritanceChildren ??= new List<AvaloniaObject>(); _inheritanceChildren ??= new List<AvaloniaObject>();
_inheritanceChildren.Add(child); _inheritanceChildren.Add(child);
} }
/// <inheritdoc/>
internal void RemoveInheritanceChild(AvaloniaObject child) internal void RemoveInheritanceChild(AvaloniaObject child)
{ {
_inheritanceChildren?.Remove(child); _inheritanceChildren?.Remove(child);
@ -541,24 +546,11 @@ namespace Avalonia
return new AvaloniaPropertyValue( return new AvaloniaPropertyValue(
property, property,
GetValue(property), GetValue(property),
BindingPriority.Unset, BindingPriority.LocalValue,
"Local Value"); null);
}
else if (_values != null)
{
var result = _values.GetDiagnostic(property);
if (result != null)
{
return result;
}
} }
return new AvaloniaPropertyValue( return _values.GetDiagnostic(property);
property,
GetValue(property),
BindingPriority.Unset,
"Unset");
} }
internal ValueStore GetValueStore() => _values; internal ValueStore GetValueStore() => _values;

2
src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs

@ -30,7 +30,7 @@ namespace Avalonia.Data.Converters
{ {
if (value == null) if (value == null)
{ {
return targetType.IsValueType ? AvaloniaProperty.UnsetValue : null; return null;
} }
if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1) if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1)

3
src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs

@ -1,6 +1,3 @@
using System;
using Avalonia.Data;
namespace Avalonia.Diagnostics namespace Avalonia.Diagnostics
{ {
/// <summary> /// <summary>

18
src/Avalonia.Base/Input/DragEventArgs.cs

@ -1,36 +1,28 @@
using System; using System;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Input namespace Avalonia.Input
{ {
public class DragEventArgs : RoutedEventArgs public class DragEventArgs : RoutedEventArgs
{ {
private Interactive _target; private readonly Interactive _target;
private Point _targetLocation; private readonly Point _targetLocation;
public DragDropEffects DragEffects { get; set; } public DragDropEffects DragEffects { get; set; }
public IDataObject Data { get; private set; } public IDataObject Data { get; }
public KeyModifiers KeyModifiers { get; private set; } public KeyModifiers KeyModifiers { get; }
public Point GetPosition(Visual relativeTo) public Point GetPosition(Visual relativeTo)
{ {
var point = new Point(0, 0);
if (relativeTo == null) if (relativeTo == null)
{ {
throw new ArgumentNullException(nameof(relativeTo)); throw new ArgumentNullException(nameof(relativeTo));
} }
if (_target != null) return _target.TranslatePoint(_targetLocation, relativeTo) ?? new Point(0, 0);
{
point = _target.TranslatePoint(_targetLocation, relativeTo) ?? point;
}
return point;
} }
[Unstable] [Unstable]

2
src/Avalonia.Base/Input/KeyGesture.cs

@ -136,7 +136,7 @@ namespace Avalonia.Input
return StringBuilderCache.GetStringAndRelease(s); return StringBuilderCache.GetStringAndRelease(s);
} }
public bool Matches(KeyEventArgs keyEvent) => public bool Matches(KeyEventArgs? keyEvent) =>
keyEvent != null && keyEvent != null &&
keyEvent.KeyModifiers == KeyModifiers && keyEvent.KeyModifiers == KeyModifiers &&
ResolveNumPadOperationKey(keyEvent.Key) == ResolveNumPadOperationKey(Key); ResolveNumPadOperationKey(keyEvent.Key) == ResolveNumPadOperationKey(Key);

44
src/Avalonia.Base/Input/KeyboardNavigationHandler.cs

@ -1,6 +1,5 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Input.Navigation; using Avalonia.Input.Navigation;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -51,7 +50,7 @@ namespace Avalonia.Input
// If there's a custom keyboard navigation handler as an ancestor, use that. // If there's a custom keyboard navigation handler as an ancestor, use that.
var custom = (element as Visual)?.FindAncestorOfType<ICustomKeyboardNavigation>(true); var custom = (element as Visual)?.FindAncestorOfType<ICustomKeyboardNavigation>(true);
if (custom is object && HandlePreCustomNavigation(custom, element, direction, out var ce)) if (custom is not null && HandlePreCustomNavigation(custom, element, direction, out var ce))
return ce; return ce;
var result = direction switch var result = direction switch
@ -117,32 +116,27 @@ namespace Avalonia.Input
NavigationDirection direction, NavigationDirection direction,
[NotNullWhen(true)] out IInputElement? result) [NotNullWhen(true)] out IInputElement? result)
{ {
if (customHandler != null) var (handled, next) = customHandler.GetNext(element, direction);
if (handled)
{ {
var (handled, next) = customHandler.GetNext(element, direction); if (next is not null)
{
result = next;
return true;
}
if (handled) var r = direction switch
{ {
if (next != null) NavigationDirection.Next => TabNavigation.GetNextTabOutside(customHandler),
{ NavigationDirection.Previous => TabNavigation.GetPrevTabOutside(customHandler),
result = next; _ => null
return true; };
}
else if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous) if (r is not null)
{ {
var r = direction switch result = r;
{ return true;
NavigationDirection.Next => TabNavigation.GetNextTabOutside(customHandler),
NavigationDirection.Previous => TabNavigation.GetPrevTabOutside(customHandler),
_ => throw new NotSupportedException(),
};
if (r is object)
{
result = r;
return true;
}
}
} }
} }

48
src/Avalonia.Base/Input/Navigation/TabNavigation.cs

@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Input.Navigation namespace Avalonia.Input.Navigation
@ -54,8 +52,7 @@ namespace Avalonia.Input.Navigation
// Avoid the endless loop here for Cycle groups // Avoid the endless loop here for Cycle groups
if (loopStartElement == nextTabElement) if (loopStartElement == nextTabElement)
break; break;
if (loopStartElement == null) loopStartElement ??= nextTabElement;
loopStartElement = nextTabElement;
var firstTabElementInside = GetNextTab(null, nextTabElement, true); var firstTabElementInside = GetNextTab(null, nextTabElement, true);
if (firstTabElementInside != null) if (firstTabElementInside != null)
@ -80,12 +77,9 @@ namespace Avalonia.Input.Navigation
public static IInputElement? GetNextTabOutside(ICustomKeyboardNavigation e) public static IInputElement? GetNextTabOutside(ICustomKeyboardNavigation e)
{ {
if (e is IInputElement container) if (e is IInputElement container && GetLastInTree(container) is { } last)
{ {
var last = GetLastInTree(container); return GetNextTab(last, false);
if (last is object)
return GetNextTab(last, false);
} }
return null; return null;
@ -93,11 +87,8 @@ namespace Avalonia.Input.Navigation
public static IInputElement? GetPrevTab(IInputElement? e, IInputElement? container, bool goDownOnly) public static IInputElement? GetPrevTab(IInputElement? e, IInputElement? container, bool goDownOnly)
{ {
if (e is null && container is null) container ??=
throw new InvalidOperationException("Either 'e' or 'container' must be non-null."); GetGroupParent(e ?? throw new InvalidOperationException("Either 'e' or 'container' must be non-null."));
if (container is null)
container = GetGroupParent(e!);
KeyboardNavigationMode tabbingType = GetKeyNavigationMode(container); KeyboardNavigationMode tabbingType = GetKeyNavigationMode(container);
@ -163,8 +154,7 @@ namespace Avalonia.Input.Navigation
// Avoid the endless loop here // Avoid the endless loop here
if (loopStartElement == nextTabElement) if (loopStartElement == nextTabElement)
break; break;
if (loopStartElement == null) loopStartElement ??= nextTabElement;
loopStartElement = nextTabElement;
// At this point nextTabElement is TabGroup // At this point nextTabElement is TabGroup
var lastTabElementInside = GetPrevTab(null, nextTabElement, true); var lastTabElementInside = GetPrevTab(null, nextTabElement, true);
@ -189,22 +179,18 @@ namespace Avalonia.Input.Navigation
public static IInputElement? GetPrevTabOutside(ICustomKeyboardNavigation e) public static IInputElement? GetPrevTabOutside(ICustomKeyboardNavigation e)
{ {
if (e is IInputElement container) if (e is IInputElement container && GetFirstChild(container) is { } first)
{ {
var first = GetFirstChild(container); return GetPrevTab(first, null, false);
if (first is object)
return GetPrevTab(first, null, false);
} }
return null; return null;
} }
private static IInputElement? FocusedElement(IInputElement e) private static IInputElement? FocusedElement(IInputElement? e)
{ {
var iie = e;
// Focus delegation is enabled only if keyboard focus is outside the container // Focus delegation is enabled only if keyboard focus is outside the container
if (iie != null && !iie.IsKeyboardFocusWithin) if (e != null && !e.IsKeyboardFocusWithin)
{ {
var focusedElement = (FocusManager.Instance as FocusManager)?.GetFocusedElement(e); var focusedElement = (FocusManager.Instance as FocusManager)?.GetFocusedElement(e);
if (focusedElement != null) if (focusedElement != null)
@ -229,13 +215,11 @@ namespace Avalonia.Input.Navigation
private static IInputElement? GetFirstChild(IInputElement e) private static IInputElement? GetFirstChild(IInputElement e)
{ {
// If the element has a FocusedElement it should be its first child // If the element has a FocusedElement it should be its first child
if (FocusedElement(e) is IInputElement focusedElement) if (FocusedElement(e) is { } focusedElement)
return focusedElement; return focusedElement;
// Return the first visible element. // Return the first visible element.
var uiElement = e as InputElement; if (e is not InputElement uiElement || IsVisibleAndEnabled(uiElement))
if (uiElement is null || IsVisibleAndEnabled(uiElement))
{ {
if (e is Visual elementAsVisual) if (e is Visual elementAsVisual)
{ {
@ -265,7 +249,7 @@ namespace Avalonia.Input.Navigation
private static IInputElement? GetLastChild(IInputElement e) private static IInputElement? GetLastChild(IInputElement e)
{ {
// If the element has a FocusedElement it should be its last child // If the element has a FocusedElement it should be its last child
if (FocusedElement(e) is IInputElement focusedElement) if (FocusedElement(e) is { } focusedElement)
return focusedElement; return focusedElement;
// Return the last visible element. // Return the last visible element.
@ -273,9 +257,7 @@ namespace Avalonia.Input.Navigation
if (uiElement == null || IsVisibleAndEnabled(uiElement)) if (uiElement == null || IsVisibleAndEnabled(uiElement))
{ {
var elementAsVisual = e as Visual; if (e is Visual elementAsVisual)
if (elementAsVisual != null)
{ {
var children = elementAsVisual.VisualChildren; var children = elementAsVisual.VisualChildren;
var count = children.Count; var count = children.Count;
@ -322,7 +304,7 @@ namespace Avalonia.Input.Navigation
return firstTabElement; return firstTabElement;
} }
private static IInputElement? GetLastInTree(IInputElement container) private static IInputElement GetLastInTree(IInputElement container)
{ {
IInputElement? result; IInputElement? result;
IInputElement? c = container; IInputElement? c = container;

6
src/Avalonia.Base/LogicalTree/LogicalExtensions.cs

@ -48,7 +48,7 @@ namespace Avalonia.LogicalTree
/// <param name="logical">The logical.</param> /// <param name="logical">The logical.</param>
/// <param name="includeSelf">If given logical should be included in search.</param> /// <param name="includeSelf">If given logical should be included in search.</param>
/// <returns>First ancestor of given type.</returns> /// <returns>First ancestor of given type.</returns>
public static T? FindLogicalAncestorOfType<T>(this ILogical logical, bool includeSelf = false) where T : class public static T? FindLogicalAncestorOfType<T>(this ILogical? logical, bool includeSelf = false) where T : class
{ {
if (logical is null) if (logical is null)
{ {
@ -120,7 +120,7 @@ namespace Avalonia.LogicalTree
/// <param name="logical">The logical.</param> /// <param name="logical">The logical.</param>
/// <param name="includeSelf">If given logical should be included in search.</param> /// <param name="includeSelf">If given logical should be included in search.</param>
/// <returns>First descendant of given type.</returns> /// <returns>First descendant of given type.</returns>
public static T? FindLogicalDescendantOfType<T>(this ILogical logical, bool includeSelf = false) where T : class public static T? FindLogicalDescendantOfType<T>(this ILogical? logical, bool includeSelf = false) where T : class
{ {
if (logical is null) if (logical is null)
{ {
@ -185,7 +185,7 @@ namespace Avalonia.LogicalTree
/// True if <paramref name="logical"/> is an ancestor of <paramref name="target"/>; /// True if <paramref name="logical"/> is an ancestor of <paramref name="target"/>;
/// otherwise false. /// otherwise false.
/// </returns> /// </returns>
public static bool IsLogicalAncestorOf(this ILogical logical, ILogical target) public static bool IsLogicalAncestorOf(this ILogical? logical, ILogical? target)
{ {
var current = target?.LogicalParent; var current = target?.LogicalParent;

9
src/Avalonia.Base/Media/Color.cs

@ -147,16 +147,11 @@ namespace Avalonia.Media
/// <param name="s">The color string.</param> /// <param name="s">The color string.</param>
/// <param name="color">The parsed color</param> /// <param name="color">The parsed color</param>
/// <returns>The status of the operation.</returns> /// <returns>The status of the operation.</returns>
public static bool TryParse(string s, out Color color) public static bool TryParse(string? s, out Color color)
{ {
color = default; color = default;
if (s is null) if (string.IsNullOrEmpty(s))
{
return false;
}
if (s.Length == 0)
{ {
return false; return false;
} }

2
src/Avalonia.Base/Media/DrawingContext.cs

@ -240,7 +240,7 @@ namespace Avalonia.Media
/// </summary> /// </summary>
/// <param name="foreground">The foreground brush.</param> /// <param name="foreground">The foreground brush.</param>
/// <param name="glyphRun">The glyph run.</param> /// <param name="glyphRun">The glyph run.</param>
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) public void DrawGlyphRun(IBrush? foreground, GlyphRun glyphRun)
{ {
_ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun)); _ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun));

26
src/Avalonia.Base/Media/DrawingGroup.cs

@ -13,14 +13,14 @@ namespace Avalonia.Media
public static readonly StyledProperty<double> OpacityProperty = public static readonly StyledProperty<double> OpacityProperty =
AvaloniaProperty.Register<DrawingGroup, double>(nameof(Opacity), 1); AvaloniaProperty.Register<DrawingGroup, double>(nameof(Opacity), 1);
public static readonly StyledProperty<Transform> TransformProperty = public static readonly StyledProperty<Transform?> TransformProperty =
AvaloniaProperty.Register<DrawingGroup, Transform>(nameof(Transform)); AvaloniaProperty.Register<DrawingGroup, Transform?>(nameof(Transform));
public static readonly StyledProperty<Geometry> ClipGeometryProperty = public static readonly StyledProperty<Geometry?> ClipGeometryProperty =
AvaloniaProperty.Register<DrawingGroup, Geometry>(nameof(ClipGeometry)); AvaloniaProperty.Register<DrawingGroup, Geometry?>(nameof(ClipGeometry));
public static readonly StyledProperty<IBrush> OpacityMaskProperty = public static readonly StyledProperty<IBrush?> OpacityMaskProperty =
AvaloniaProperty.Register<DrawingGroup, IBrush>(nameof(OpacityMask)); AvaloniaProperty.Register<DrawingGroup, IBrush?>(nameof(OpacityMask));
public static readonly DirectProperty<DrawingGroup, DrawingCollection> ChildrenProperty = public static readonly DirectProperty<DrawingGroup, DrawingCollection> ChildrenProperty =
AvaloniaProperty.RegisterDirect<DrawingGroup, DrawingCollection>( AvaloniaProperty.RegisterDirect<DrawingGroup, DrawingCollection>(
@ -36,19 +36,19 @@ namespace Avalonia.Media
set => SetValue(OpacityProperty, value); set => SetValue(OpacityProperty, value);
} }
public Transform Transform public Transform? Transform
{ {
get => GetValue(TransformProperty); get => GetValue(TransformProperty);
set => SetValue(TransformProperty, value); set => SetValue(TransformProperty, value);
} }
public Geometry ClipGeometry public Geometry? ClipGeometry
{ {
get => GetValue(ClipGeometryProperty); get => GetValue(ClipGeometryProperty);
set => SetValue(ClipGeometryProperty, value); set => SetValue(ClipGeometryProperty, value);
} }
public IBrush OpacityMask public IBrush? OpacityMask
{ {
get => GetValue(OpacityMaskProperty); get => GetValue(OpacityMaskProperty);
set => SetValue(OpacityMaskProperty, value); set => SetValue(OpacityMaskProperty, value);
@ -159,7 +159,7 @@ namespace Avalonia.Media
public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry) public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
{ {
if (((brush == null) && (pen == null)) || (geometry == null)) if ((brush == null) && (pen == null))
{ {
return; return;
} }
@ -167,9 +167,9 @@ namespace Avalonia.Media
AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry)); AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry));
} }
public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun) public void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun)
{ {
if (foreground == null || glyphRun == null) if (foreground == null)
{ {
return; return;
} }
@ -184,7 +184,7 @@ namespace Avalonia.Media
AddDrawing(glyphRunDrawing); AddDrawing(glyphRunDrawing);
} }
public void DrawLine(IPen pen, Point p1, Point p2) public void DrawLine(IPen? pen, Point p1, Point p2)
{ {
if (pen == null) if (pen == null)
{ {

6
src/Avalonia.Base/Media/DrawingImage.cs

@ -20,8 +20,8 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Defines the <see cref="Drawing"/> property. /// Defines the <see cref="Drawing"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<Drawing> DrawingProperty = public static readonly StyledProperty<Drawing?> DrawingProperty =
AvaloniaProperty.Register<DrawingImage, Drawing>(nameof(Drawing)); AvaloniaProperty.Register<DrawingImage, Drawing?>(nameof(Drawing));
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler? Invalidated; public event EventHandler? Invalidated;
@ -30,7 +30,7 @@ namespace Avalonia.Media
/// Gets or sets the drawing content. /// Gets or sets the drawing content.
/// </summary> /// </summary>
[Content] [Content]
public Drawing Drawing public Drawing? Drawing
{ {
get => GetValue(DrawingProperty); get => GetValue(DrawingProperty);
set => SetValue(DrawingProperty, value); set => SetValue(DrawingProperty, value);

4
src/Avalonia.Base/Media/FontFamily.cs

@ -119,7 +119,7 @@ namespace Avalonia.Media
case 2: case 2:
{ {
var source = segments[0].StartsWith("/") var source = segments[0].StartsWith("/", StringComparison.Ordinal)
? new Uri(segments[0], UriKind.Relative) ? new Uri(segments[0], UriKind.Relative)
: new Uri(segments[0], UriKind.RelativeOrAbsolute); : new Uri(segments[0], UriKind.RelativeOrAbsolute);
@ -188,7 +188,7 @@ namespace Avalonia.Media
{ {
unchecked unchecked
{ {
return ((FamilyNames != null ? FamilyNames.GetHashCode() : 0) * 397) ^ (Key != null ? Key.GetHashCode() : 0); return (FamilyNames.GetHashCode() * 397) ^ (Key is not null ? Key.GetHashCode() : 0);
} }
} }

5
src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs

@ -41,10 +41,7 @@ namespace Avalonia.Media.Fonts
{ {
var hash = (int)2166136261; var hash = (int)2166136261;
if (Source != null) hash = (hash * 16777619) ^ Source.GetHashCode();
{
hash = (hash * 16777619) ^ Source.GetHashCode();
}
if (BaseUri != null) if (BaseUri != null)
{ {

6
src/Avalonia.Base/Media/FormattedText.cs

@ -1354,7 +1354,7 @@ namespace Avalonia.Media
{ {
var highlightBounds = currentLine.GetTextBounds(x0,x1 - x0); var highlightBounds = currentLine.GetTextBounds(x0,x1 - x0);
if (highlightBounds != null) if (highlightBounds.Count > 0)
{ {
foreach (var bound in highlightBounds) foreach (var bound in highlightBounds)
{ {
@ -1365,7 +1365,7 @@ namespace Avalonia.Media
// Convert logical units (which extend leftward from the right edge // Convert logical units (which extend leftward from the right edge
// of the paragraph) to physical units. // of the paragraph) to physical units.
// //
// Note that since rect is in logical units, rect.Right corresponds to // Note that since rect is in logical units, rect.Right corresponds to
// the visual *left* edge of the rectangle in the RTL case. Specifically, // the visual *left* edge of the rectangle in the RTL case. Specifically,
// is the distance leftward from the right edge of the formatting rectangle // is the distance leftward from the right edge of the formatting rectangle
// whose width is the paragraph width passed to FormatLine. // whose width is the paragraph width passed to FormatLine.
@ -1384,7 +1384,7 @@ namespace Avalonia.Media
else else
{ {
accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union); accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union);
} }
} }
} }
} }

6
src/Avalonia.Base/Media/GeometryDrawing.cs

@ -15,8 +15,8 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Defines the <see cref="Geometry"/> property. /// Defines the <see cref="Geometry"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<Geometry> GeometryProperty = public static readonly StyledProperty<Geometry?> GeometryProperty =
AvaloniaProperty.Register<GeometryDrawing, Geometry>(nameof(Geometry)); AvaloniaProperty.Register<GeometryDrawing, Geometry?>(nameof(Geometry));
/// <summary> /// <summary>
/// Defines the <see cref="Brush"/> property. /// Defines the <see cref="Brush"/> property.
@ -34,7 +34,7 @@ namespace Avalonia.Media
/// Gets or sets the <see cref="Avalonia.Media.Geometry"/> that describes the shape of this <see cref="GeometryDrawing"/>. /// Gets or sets the <see cref="Avalonia.Media.Geometry"/> that describes the shape of this <see cref="GeometryDrawing"/>.
/// </summary> /// </summary>
[Content] [Content]
public Geometry Geometry public Geometry? Geometry
{ {
get => GetValue(GeometryProperty); get => GetValue(GeometryProperty);
set => SetValue(GeometryProperty, value); set => SetValue(GeometryProperty, value);

12
src/Avalonia.Base/Media/GlyphRunDrawing.cs

@ -2,19 +2,19 @@
{ {
public class GlyphRunDrawing : Drawing public class GlyphRunDrawing : Drawing
{ {
public static readonly StyledProperty<IBrush> ForegroundProperty = public static readonly StyledProperty<IBrush?> ForegroundProperty =
AvaloniaProperty.Register<GlyphRunDrawing, IBrush>(nameof(Foreground)); AvaloniaProperty.Register<GlyphRunDrawing, IBrush?>(nameof(Foreground));
public static readonly StyledProperty<GlyphRun> GlyphRunProperty = public static readonly StyledProperty<GlyphRun?> GlyphRunProperty =
AvaloniaProperty.Register<GlyphRunDrawing, GlyphRun>(nameof(GlyphRun)); AvaloniaProperty.Register<GlyphRunDrawing, GlyphRun?>(nameof(GlyphRun));
public IBrush Foreground public IBrush? Foreground
{ {
get => GetValue(ForegroundProperty); get => GetValue(ForegroundProperty);
set => SetValue(ForegroundProperty, value); set => SetValue(ForegroundProperty, value);
} }
public GlyphRun GlyphRun public GlyphRun? GlyphRun
{ {
get => GetValue(GlyphRunProperty); get => GetValue(GlyphRunProperty);
set => SetValue(GlyphRunProperty, value); set => SetValue(GlyphRunProperty, value);

2
src/Avalonia.Base/Media/HslColor.cs

@ -254,7 +254,7 @@ namespace Avalonia.Media
/// <param name="s">The HSL color string to parse.</param> /// <param name="s">The HSL color string to parse.</param>
/// <param name="hslColor">The parsed <see cref="HslColor"/>.</param> /// <param name="hslColor">The parsed <see cref="HslColor"/>.</param>
/// <returns>True if parsing was successful; otherwise, false.</returns> /// <returns>True if parsing was successful; otherwise, false.</returns>
public static bool TryParse(string s, out HslColor hslColor) public static bool TryParse(string? s, out HslColor hslColor)
{ {
bool prefixMatched = false; bool prefixMatched = false;

2
src/Avalonia.Base/Media/HsvColor.cs

@ -254,7 +254,7 @@ namespace Avalonia.Media
/// <param name="s">The HSV color string to parse.</param> /// <param name="s">The HSV color string to parse.</param>
/// <param name="hsvColor">The parsed <see cref="HsvColor"/>.</param> /// <param name="hsvColor">The parsed <see cref="HsvColor"/>.</param>
/// <returns>True if parsing was successful; otherwise, false.</returns> /// <returns>True if parsing was successful; otherwise, false.</returns>
public static bool TryParse(string s, out HsvColor hsvColor) public static bool TryParse(string? s, out HsvColor hsvColor)
{ {
bool prefixMatched = false; bool prefixMatched = false;

3
src/Avalonia.Base/Media/IVisualBrush.cs

@ -1,5 +1,4 @@
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Media namespace Avalonia.Media
{ {
@ -12,6 +11,6 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Gets the visual to draw. /// Gets the visual to draw.
/// </summary> /// </summary>
Visual Visual { get; } Visual? Visual { get; }
} }
} }

24
src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs

@ -39,17 +39,8 @@ namespace Avalonia.Media.Immutable
{ {
return true; return true;
} }
else if (other is null)
{
return false;
}
if (Offset != other.Offset) return other is not null && Offset == other.Offset && SequenceEqual(_dashes, other.Dashes);
{
return false;
}
return SequenceEqual(Dashes, other.Dashes);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -58,30 +49,27 @@ namespace Avalonia.Media.Immutable
var hashCode = 717868523; var hashCode = 717868523;
hashCode = hashCode * -1521134295 + Offset.GetHashCode(); hashCode = hashCode * -1521134295 + Offset.GetHashCode();
if (_dashes != null) foreach (var i in _dashes)
{ {
foreach (var i in _dashes) hashCode = hashCode * -1521134295 + i.GetHashCode();
{
hashCode = hashCode * -1521134295 + i.GetHashCode();
}
} }
return hashCode; return hashCode;
} }
private static bool SequenceEqual(IReadOnlyList<double> left, IReadOnlyList<double>? right) private static bool SequenceEqual(double[] left, IReadOnlyList<double>? right)
{ {
if (ReferenceEquals(left, right)) if (ReferenceEquals(left, right))
{ {
return true; return true;
} }
if (left == null || right == null || left.Count != right.Count) if (right is null || left.Length != right.Count)
{ {
return false; return false;
} }
for (var c = 0; c < left.Count; c++) for (var c = 0; c < left.Length; c++)
{ {
if (left[c] != right[c]) if (left[c] != right[c])
{ {

7
src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs

@ -1,5 +1,4 @@
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.VisualTree;
namespace Avalonia.Media.Immutable namespace Avalonia.Media.Immutable
{ {
@ -31,11 +30,11 @@ namespace Avalonia.Media.Immutable
RelativeRect? destinationRect = null, RelativeRect? destinationRect = null,
double opacity = 1, double opacity = 1,
ImmutableTransform? transform = null, ImmutableTransform? transform = null,
RelativePoint transformOrigin = new RelativePoint(), RelativePoint transformOrigin = default,
RelativeRect? sourceRect = null, RelativeRect? sourceRect = null,
Stretch stretch = Stretch.Uniform, Stretch stretch = Stretch.Uniform,
TileMode tileMode = TileMode.None, TileMode tileMode = TileMode.None,
Imaging.BitmapInterpolationMode bitmapInterpolationMode = Imaging.BitmapInterpolationMode.Default) BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
: base( : base(
alignmentX, alignmentX,
alignmentY, alignmentY,
@ -62,6 +61,6 @@ namespace Avalonia.Media.Immutable
} }
/// <inheritdoc/> /// <inheritdoc/>
public Visual Visual { get; } public Visual? Visual { get; }
} }
} }

14
src/Avalonia.Base/Media/TextDecoration.cs

@ -22,8 +22,8 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Defines the <see cref="Stroke"/> property. /// Defines the <see cref="Stroke"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<IBrush> StrokeProperty = public static readonly StyledProperty<IBrush?> StrokeProperty =
AvaloniaProperty.Register<TextDecoration, IBrush>(nameof(Stroke)); AvaloniaProperty.Register<TextDecoration, IBrush?>(nameof(Stroke));
/// <summary> /// <summary>
/// Defines the <see cref="StrokeThicknessUnit"/> property. /// Defines the <see cref="StrokeThicknessUnit"/> property.
@ -34,8 +34,8 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Defines the <see cref="StrokeDashArray"/> property. /// Defines the <see cref="StrokeDashArray"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<AvaloniaList<double>> StrokeDashArrayProperty = public static readonly StyledProperty<AvaloniaList<double>?> StrokeDashArrayProperty =
AvaloniaProperty.Register<TextDecoration, AvaloniaList<double>>(nameof(StrokeDashArray)); AvaloniaProperty.Register<TextDecoration, AvaloniaList<double>?>(nameof(StrokeDashArray));
/// <summary> /// <summary>
/// Defines the <see cref="StrokeDashOffset"/> property. /// Defines the <see cref="StrokeDashOffset"/> property.
@ -82,7 +82,7 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Gets or sets the <see cref="IBrush"/> that specifies how the <see cref="TextDecoration"/> is painted. /// Gets or sets the <see cref="IBrush"/> that specifies how the <see cref="TextDecoration"/> is painted.
/// </summary> /// </summary>
public IBrush Stroke public IBrush? Stroke
{ {
get { return GetValue(StrokeProperty); } get { return GetValue(StrokeProperty); }
set { SetValue(StrokeProperty, value); } set { SetValue(StrokeProperty, value); }
@ -101,7 +101,7 @@ namespace Avalonia.Media
/// Gets or sets a collection of <see cref="double"/> values that indicate the pattern of dashes and gaps /// Gets or sets a collection of <see cref="double"/> values that indicate the pattern of dashes and gaps
/// that is used to draw the <see cref="TextDecoration"/>. /// that is used to draw the <see cref="TextDecoration"/>.
/// </summary> /// </summary>
public AvaloniaList<double> StrokeDashArray public AvaloniaList<double>? StrokeDashArray
{ {
get { return GetValue(StrokeDashArrayProperty); } get { return GetValue(StrokeDashArrayProperty); }
set { SetValue(StrokeDashArrayProperty, value); } set { SetValue(StrokeDashArrayProperty, value); }
@ -220,7 +220,7 @@ namespace Avalonia.Media
var intersections = glyphRun.PlatformImpl.Item.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY)); var intersections = glyphRun.PlatformImpl.Item.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY));
if (intersections != null && intersections.Count > 0) if (intersections.Count > 0)
{ {
var last = baselineOrigin.X; var last = baselineOrigin.X;
var finalPos = last + glyphRun.Size.Width; var finalPos = last + glyphRun.Size.Width;

7
src/Avalonia.Base/Media/VisualBrush.cs

@ -1,5 +1,4 @@
using Avalonia.Media.Immutable; using Avalonia.Media.Immutable;
using Avalonia.VisualTree;
namespace Avalonia.Media namespace Avalonia.Media
{ {
@ -11,8 +10,8 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Defines the <see cref="Visual"/> property. /// Defines the <see cref="Visual"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<Visual> VisualProperty = public static readonly StyledProperty<Visual?> VisualProperty =
AvaloniaProperty.Register<VisualBrush, Visual>(nameof(Visual)); AvaloniaProperty.Register<VisualBrush, Visual?>(nameof(Visual));
static VisualBrush() static VisualBrush()
{ {
@ -38,7 +37,7 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Gets or sets the visual to draw. /// Gets or sets the visual to draw.
/// </summary> /// </summary>
public Visual Visual public Visual? Visual
{ {
get { return GetValue(VisualProperty); } get { return GetValue(VisualProperty); }
set { SetValue(VisualProperty, value); } set { SetValue(VisualProperty, value); }

4
src/Avalonia.Base/Platform/IDrawingContextImpl.cs

@ -49,7 +49,7 @@ namespace Avalonia.Platform
/// <param name="pen">The stroke pen.</param> /// <param name="pen">The stroke pen.</param>
/// <param name="p1">The first point of the line.</param> /// <param name="p1">The first point of the line.</param>
/// <param name="p2">The second point of the line.</param> /// <param name="p2">The second point of the line.</param>
void DrawLine(IPen pen, Point p1, Point p2); void DrawLine(IPen? pen, Point p1, Point p2);
/// <summary> /// <summary>
/// Draws a geometry. /// Draws a geometry.
@ -91,7 +91,7 @@ namespace Avalonia.Platform
/// </summary> /// </summary>
/// <param name="foreground">The foreground.</param> /// <param name="foreground">The foreground.</param>
/// <param name="glyphRun">The glyph run.</param> /// <param name="glyphRun">The glyph run.</param>
void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun); void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun);
/// <summary> /// <summary>
/// Creates a new <see cref="IRenderTargetBitmapImpl"/> that can be used as a render layer /// Creates a new <see cref="IRenderTargetBitmapImpl"/> that can be used as a render layer

28
src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs

@ -19,25 +19,20 @@ internal class AssemblyDescriptor : IAssemblyDescriptor
public AssemblyDescriptor(Assembly assembly) public AssemblyDescriptor(Assembly assembly)
{ {
Assembly = assembly; Assembly = assembly;
Resources = assembly.GetManifestResourceNames()
.ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n));
Name = assembly.GetName().Name;
if (assembly != null) using var resources = assembly.GetManifestResourceStream(Constants.AvaloniaResourceName);
if (resources != null)
{ {
Resources = assembly.GetManifestResourceNames() Resources.Remove(Constants.AvaloniaResourceName);
.ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n));
Name = assembly.GetName().Name;
using (var resources = assembly.GetManifestResourceStream(Constants.AvaloniaResourceName))
{
if (resources != null)
{
Resources.Remove(Constants.AvaloniaResourceName);
var indexLength = new BinaryReader(resources).ReadInt32(); var indexLength = new BinaryReader(resources).ReadInt32();
var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength)); var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength));
var baseOffset = indexLength + 4; var baseOffset = indexLength + 4;
AvaloniaResources = index.ToDictionary(r => GetPathRooted(r), r => (IAssetDescriptor) AvaloniaResources = index.ToDictionary(GetPathRooted, r => (IAssetDescriptor)
new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size)); new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size));
}
}
} }
} }
@ -45,6 +40,7 @@ internal class AssemblyDescriptor : IAssemblyDescriptor
public Dictionary<string, IAssetDescriptor>? Resources { get; } public Dictionary<string, IAssetDescriptor>? Resources { get; }
public Dictionary<string, IAssetDescriptor>? AvaloniaResources { get; } public Dictionary<string, IAssetDescriptor>? AvaloniaResources { get; }
public string? Name { get; } public string? Name { get; }
private static string GetPathRooted(AvaloniaResourcesIndexEntry r) => private static string GetPathRooted(AvaloniaResourcesIndexEntry r) =>
r.Path![0] == '/' ? r.Path : '/' + r.Path; r.Path![0] == '/' ? r.Path : '/' + r.Path;
} }

2
src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs

@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition.Animations
public abstract class CompositionAnimation : CompositionObject, ICompositionAnimationBase public abstract class CompositionAnimation : CompositionObject, ICompositionAnimationBase
{ {
private readonly CompositionPropertySet _propertySet; private readonly CompositionPropertySet _propertySet;
internal CompositionAnimation(Compositor compositor) : base(compositor, null!) internal CompositionAnimation(Compositor compositor) : base(compositor, null)
{ {
_propertySet = new CompositionPropertySet(compositor); _propertySet = new CompositionPropertySet(compositor);
} }

2
src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs

@ -19,7 +19,7 @@ namespace Avalonia.Rendering.Composition.Animations
public void Remove(CompositionAnimation value) => Animations.Remove(value); public void Remove(CompositionAnimation value) => Animations.Remove(value);
public void RemoveAll() => Animations.Clear(); public void RemoveAll() => Animations.Clear();
public CompositionAnimationGroup(Compositor compositor) : base(compositor, null!) public CompositionAnimationGroup(Compositor compositor) : base(compositor, null)
{ {
} }
} }

2
src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs

@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition.Animations
{ {
private Dictionary<string, ICompositionAnimationBase> _inner = new Dictionary<string, ICompositionAnimationBase>(); private Dictionary<string, ICompositionAnimationBase> _inner = new Dictionary<string, ICompositionAnimationBase>();
private IDictionary<string, ICompositionAnimationBase> _innerface; private IDictionary<string, ICompositionAnimationBase> _innerface;
internal ImplicitAnimationCollection(Compositor compositor) : base(compositor, null!) internal ImplicitAnimationCollection(Compositor compositor) : base(compositor, null)
{ {
_innerface = _inner; _innerface = _inner;
} }

82
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@ -20,15 +20,17 @@ public class CompositingRenderer : IRendererWithCompositor
{ {
private readonly IRenderRoot _root; private readonly IRenderRoot _root;
private readonly Compositor _compositor; private readonly Compositor _compositor;
CompositionDrawingContext _recorder = new(); private readonly CompositionDrawingContext _recorder = new();
DrawingContext _recordingContext; private readonly DrawingContext _recordingContext;
private HashSet<Visual> _dirty = new(); private readonly HashSet<Visual> _dirty = new();
private HashSet<Visual> _recalculateChildren = new(); private readonly HashSet<Visual> _recalculateChildren = new();
private readonly Action _update;
private bool _queuedUpdate; private bool _queuedUpdate;
private Action _update;
private bool _updating; private bool _updating;
private bool _isDisposed;
internal CompositionTarget CompositionTarget; internal CompositionTarget CompositionTarget { get; }
/// <summary> /// <summary>
/// Asks the renderer to only draw frames on the render thread. Makes Paint to wait until frame is rendered. /// Asks the renderer to only draw frames on the render thread. Makes Paint to wait until frame is rendered.
@ -38,6 +40,17 @@ public class CompositingRenderer : IRendererWithCompositor
/// <inheritdoc/> /// <inheritdoc/>
public RendererDiagnostics Diagnostics { get; } public RendererDiagnostics Diagnostics { get; }
/// <inheritdoc />
public Compositor Compositor => _compositor;
/// <summary>
/// Initializes a new instance of <see cref="CompositingRenderer"/>
/// </summary>
/// <param name="root">The render root using this renderer.</param>
/// <param name="compositor">The associated compositors.</param>
/// <param name="surfaces">
/// A function returning the list of native platform's surfaces that can be consumed by rendering subsystems.
/// </param>
public CompositingRenderer(IRenderRoot root, Compositor compositor, Func<IEnumerable<object>> surfaces) public CompositingRenderer(IRenderRoot root, Compositor compositor, Func<IEnumerable<object>> surfaces)
{ {
_root = root; _root = root;
@ -66,7 +79,7 @@ public class CompositingRenderer : IRendererWithCompositor
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler<SceneInvalidatedEventArgs>? SceneInvalidated; public event EventHandler<SceneInvalidatedEventArgs>? SceneInvalidated;
void QueueUpdate() private void QueueUpdate()
{ {
if(_queuedUpdate) if(_queuedUpdate)
return; return;
@ -77,9 +90,11 @@ public class CompositingRenderer : IRendererWithCompositor
/// <inheritdoc/> /// <inheritdoc/>
public void AddDirty(Visual visual) public void AddDirty(Visual visual)
{ {
if (_isDisposed)
return;
if (_updating) if (_updating)
throw new InvalidOperationException("Visual was invalidated during the render pass"); throw new InvalidOperationException("Visual was invalidated during the render pass");
_dirty.Add((Visual)visual); _dirty.Add(visual);
QueueUpdate(); QueueUpdate();
} }
@ -126,9 +141,11 @@ public class CompositingRenderer : IRendererWithCompositor
/// <inheritdoc/> /// <inheritdoc/>
public void RecalculateChildren(Visual visual) public void RecalculateChildren(Visual visual)
{ {
if (_isDisposed)
return;
if (_updating) if (_updating)
throw new InvalidOperationException("Visual was invalidated during the render pass"); throw new InvalidOperationException("Visual was invalidated during the render pass");
_recalculateChildren.Add((Visual)visual); _recalculateChildren.Add(visual);
QueueUpdate(); QueueUpdate();
} }
@ -171,7 +188,7 @@ public class CompositingRenderer : IRendererWithCompositor
if (sortedChildren != null) if (sortedChildren != null)
for (var c = 0; c < visualChildren.Count; c++) for (var c = 0; c < visualChildren.Count; c++)
{ {
if (!ReferenceEquals(compositionChildren[c], ((Visual)sortedChildren[c].visual).CompositionVisual)) if (!ReferenceEquals(compositionChildren[c], sortedChildren[c].visual.CompositionVisual))
{ {
mismatch = true; mismatch = true;
break; break;
@ -179,7 +196,7 @@ public class CompositingRenderer : IRendererWithCompositor
} }
else else
for (var c = 0; c < visualChildren.Count; c++) for (var c = 0; c < visualChildren.Count; c++)
if (!ReferenceEquals(compositionChildren[c], ((Visual)visualChildren[c]).CompositionVisual)) if (!ReferenceEquals(compositionChildren[c], visualChildren[c].CompositionVisual))
{ {
mismatch = true; mismatch = true;
break; break;
@ -201,7 +218,7 @@ public class CompositingRenderer : IRendererWithCompositor
{ {
foreach (var ch in sortedChildren) foreach (var ch in sortedChildren)
{ {
var compositionChild = ((Visual)ch.visual).CompositionVisual; var compositionChild = ch.visual.CompositionVisual;
if (compositionChild != null) if (compositionChild != null)
compositionChildren.Add(compositionChild); compositionChildren.Add(compositionChild);
} }
@ -210,7 +227,7 @@ public class CompositingRenderer : IRendererWithCompositor
else else
foreach (var ch in v.GetVisualChildren()) foreach (var ch in v.GetVisualChildren())
{ {
var compositionChild = ((Visual)ch).CompositionVisual; var compositionChild = ch.CompositionVisual;
if (compositionChild != null) if (compositionChild != null)
compositionChildren.Add(compositionChild); compositionChildren.Add(compositionChild);
} }
@ -289,13 +306,18 @@ public class CompositingRenderer : IRendererWithCompositor
_updating = false; _updating = false;
} }
} }
/// <inheritdoc />
public void Resized(Size size) public void Resized(Size size)
{ {
} }
/// <inheritdoc />
public void Paint(Rect rect) public void Paint(Rect rect)
{ {
if (_isDisposed)
return;
QueueUpdate(); QueueUpdate();
CompositionTarget.RequestRedraw(); CompositionTarget.RequestRedraw();
if(RenderOnlyOnRenderThread && Compositor.Loop.RunsInBackground) if(RenderOnlyOnRenderThread && Compositor.Loop.RunsInBackground)
@ -304,17 +326,34 @@ public class CompositingRenderer : IRendererWithCompositor
CompositionTarget.ImmediateUIThreadRender(); CompositionTarget.ImmediateUIThreadRender();
} }
public void Start() => CompositionTarget.IsEnabled = true; /// <inheritdoc />
public void Start()
public void Stop()
{ {
CompositionTarget.IsEnabled = false; if (_isDisposed)
return;
CompositionTarget.IsEnabled = true;
} }
public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType) => Compositor.TryGetRenderInterfaceFeature(featureType); /// <inheritdoc />
public void Stop()
=> CompositionTarget.IsEnabled = false;
/// <inheritdoc />
public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType)
=> Compositor.TryGetRenderInterfaceFeature(featureType);
/// <inheritdoc />
public void Dispose() public void Dispose()
{ {
if (_isDisposed)
return;
_isDisposed = true;
_dirty.Clear();
_recalculateChildren.Clear();
SceneInvalidated = null;
Stop(); Stop();
CompositionTarget.Dispose(); CompositionTarget.Dispose();
@ -323,9 +362,4 @@ public class CompositingRenderer : IRendererWithCompositor
if (Compositor.Loop.RunsInBackground) if (Compositor.Loop.RunsInBackground)
_compositor.Commit().Wait(); _compositor.Commit().Wait();
} }
/// <summary>
/// The associated <see cref="Avalonia.Rendering.Composition.Compositor"/> object
/// </summary>
public Compositor Compositor => _compositor;
} }

3
src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs

@ -1,4 +1,3 @@
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Server;
using Avalonia.Threading; using Avalonia.Threading;
@ -7,7 +6,7 @@ namespace Avalonia.Rendering.Composition;
public class CompositionDrawingSurface : CompositionSurface public class CompositionDrawingSurface : CompositionSurface
{ {
internal new ServerCompositionDrawingSurface Server => (ServerCompositionDrawingSurface)base.Server; internal new ServerCompositionDrawingSurface Server => (ServerCompositionDrawingSurface)base.Server!;
internal CompositionDrawingSurface(Compositor compositor) : base(compositor, new ServerCompositionDrawingSurface(compositor.Server)) internal CompositionDrawingSurface(Compositor compositor) : base(compositor, new ServerCompositionDrawingSurface(compositor.Server))
{ {
} }

4
src/Avalonia.Base/Rendering/Composition/CompositionObject.cs

@ -22,7 +22,7 @@ namespace Avalonia.Rendering.Composition
public ImplicitAnimationCollection? ImplicitAnimations { get; set; } public ImplicitAnimationCollection? ImplicitAnimations { get; set; }
private protected InlineDictionary<CompositionProperty, IAnimationInstance> PendingAnimations; private protected InlineDictionary<CompositionProperty, IAnimationInstance> PendingAnimations;
internal CompositionObject(Compositor compositor, ServerObject server) internal CompositionObject(Compositor compositor, ServerObject? server)
{ {
Compositor = compositor; Compositor = compositor;
Server = server; Server = server;
@ -32,7 +32,7 @@ namespace Avalonia.Rendering.Composition
/// The associated Compositor /// The associated Compositor
/// </summary> /// </summary>
public Compositor Compositor { get; } public Compositor Compositor { get; }
internal ServerObject Server { get; } internal ServerObject? Server { get; }
public bool IsDisposed { get; private set; } public bool IsDisposed { get; private set; }
private bool _registeredForSerialization; private bool _registeredForSerialization;

2
src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs

@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition
private readonly Dictionary<string, ExpressionVariant> _variants = new Dictionary<string, ExpressionVariant>(); private readonly Dictionary<string, ExpressionVariant> _variants = new Dictionary<string, ExpressionVariant>();
private readonly Dictionary<string, CompositionObject> _objects = new Dictionary<string, CompositionObject>(); private readonly Dictionary<string, CompositionObject> _objects = new Dictionary<string, CompositionObject>();
internal CompositionPropertySet(Compositor compositor) : base(compositor, null!) internal CompositionPropertySet(Compositor compositor) : base(compositor, null)
{ {
} }

14
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs

@ -88,8 +88,13 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
} }
/// <inheritdoc/> /// <inheritdoc/>
public void DrawLine(IPen pen, Point p1, Point p2) public void DrawLine(IPen? pen, Point p1, Point p2)
{ {
if (pen is null)
{
return;
}
var next = NextDrawAs<LineNode>(); var next = NextDrawAs<LineNode>();
if (next == null || !next.Item.Equals(Transform, pen, p1, p2)) if (next == null || !next.Item.Equals(Transform, pen, p1, p2))
@ -159,8 +164,13 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
public object? GetFeature(Type t) => null; public object? GetFeature(Type t) => null;
/// <inheritdoc/> /// <inheritdoc/>
public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun) public void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun)
{ {
if (foreground is null)
{
return;
}
var next = NextDrawAs<GlyphRunNode>(); var next = NextDrawAs<GlyphRunNode>();
if (next == null || !next.Item.Equals(Transform, foreground, glyphRun)) if (next == null || !next.Item.Equals(Transform, foreground, glyphRun))

2
src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs

@ -165,8 +165,6 @@ namespace Avalonia.Rendering.Composition.Expressions
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context) public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context)
{ {
if (context.ForeignFunctionInterface == null)
return default;
var args = new List<ExpressionVariant>(); var args = new List<ExpressionVariant>();
foreach (var expr in Parameters) foreach (var expr in Parameters)
args.Add(expr.Evaluate(ref context)); args.Add(expr.Evaluate(ref context));

1
src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Rendering.Composition.Server;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see> // Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>

4
src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs

@ -66,7 +66,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont
_impl.DrawBitmap(source, opacityMask, opacityMaskRect, destRect); _impl.DrawBitmap(source, opacityMask, opacityMaskRect, destRect);
} }
public void DrawLine(IPen pen, Point p1, Point p2) public void DrawLine(IPen? pen, Point p1, Point p2)
{ {
_impl.DrawLine(pen, p1, p2); _impl.DrawLine(pen, p1, p2);
} }
@ -86,7 +86,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont
_impl.DrawEllipse(brush, pen, rect); _impl.DrawEllipse(brush, pen, rect);
} }
public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun) public void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun)
{ {
_impl.DrawGlyphRun(foreground, glyphRun); _impl.DrawGlyphRun(foreground, glyphRun);
} }

2
src/Avalonia.Base/Rendering/IRenderRoot.cs

@ -1,6 +1,4 @@
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering namespace Avalonia.Rendering
{ {

3
src/Avalonia.Base/Rendering/IRenderer.cs

@ -90,6 +90,9 @@ namespace Avalonia.Rendering
public interface IRendererWithCompositor : IRenderer public interface IRendererWithCompositor : IRenderer
{ {
/// <summary>
/// The associated <see cref="Avalonia.Rendering.Composition.Compositor"/> object
/// </summary>
Compositor Compositor { get; } Compositor Compositor { get; }
} }
} }

6
src/Avalonia.Base/Rendering/ImmediateRenderer.cs

@ -48,8 +48,10 @@ namespace Avalonia.Rendering
/// <inheritdoc/> /// <inheritdoc/>
void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush) void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush)
{ {
var visual = brush.Visual; if (brush.Visual is { } visual)
Render(new DrawingContext(context), visual, visual.Bounds); {
Render(new DrawingContext(context), visual, visual.Bounds);
}
} }
internal static void Render(Visual visual, DrawingContext context, bool updateTransformedBounds) internal static void Render(Visual visual, DrawingContext context, bool updateTransformedBounds)

7
src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs

@ -80,11 +80,8 @@ namespace Avalonia.Rendering.SceneGraph
{ {
p *= Transform.Invert(); p *= Transform.Invert();
if (Material != null) var rect = Rect.Rect;
{ return rect.ContainsExclusive(p);
var rect = Rect.Rect;
return rect.ContainsExclusive(p);
}
} }
return false; return false;

11
src/Avalonia.Base/Utilities/TypeUtilities.cs

@ -212,7 +212,7 @@ namespace Avalonia.Utilities
var toTypeConverter = TypeDescriptor.GetConverter(toUnderl); var toTypeConverter = TypeDescriptor.GetConverter(toUnderl);
if (toTypeConverter.CanConvertFrom(from) == true) if (toTypeConverter.CanConvertFrom(from))
{ {
result = toTypeConverter.ConvertFrom(null, culture, value); result = toTypeConverter.ConvertFrom(null, culture, value);
return true; return true;
@ -220,7 +220,7 @@ namespace Avalonia.Utilities
var fromTypeConverter = TypeDescriptor.GetConverter(from); var fromTypeConverter = TypeDescriptor.GetConverter(from);
if (fromTypeConverter.CanConvertTo(toUnderl) == true) if (fromTypeConverter.CanConvertTo(toUnderl))
{ {
result = fromTypeConverter.ConvertTo(null, culture, value, toUnderl); result = fromTypeConverter.ConvertTo(null, culture, value, toUnderl);
return true; return true;
@ -329,7 +329,7 @@ namespace Avalonia.Utilities
} }
[RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)] [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)]
public static T ConvertImplicit<T>(object value) public static T ConvertImplicit<T>(object? value)
{ {
if (TryConvertImplicit(typeof(T), value, out var result)) if (TryConvertImplicit(typeof(T), value, out var result))
{ {
@ -369,11 +369,6 @@ namespace Avalonia.Utilities
/// </remarks> /// </remarks>
public static bool IsNumeric(Type type) public static bool IsNumeric(Type type)
{ {
if (type == null)
{
return false;
}
var underlyingType = Nullable.GetUnderlyingType(type); var underlyingType = Nullable.GetUnderlyingType(type);
if (underlyingType != null) if (underlyingType != null)

68
src/Avalonia.Base/Utilities/WeakEvent.cs

@ -1,8 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Avalonia.Threading; using Avalonia.Threading;
@ -15,7 +11,7 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
{ {
private readonly Func<TSender, EventHandler<TEventArgs>, Action> _subscribe; private readonly Func<TSender, EventHandler<TEventArgs>, Action> _subscribe;
readonly ConditionalWeakTable<object, Subscription> _subscriptions = new(); private readonly ConditionalWeakTable<object, Subscription> _subscriptions = new();
internal WeakEvent( internal WeakEvent(
Action<TSender, EventHandler<TEventArgs>> subscribe, Action<TSender, EventHandler<TEventArgs>> subscribe,
@ -51,56 +47,6 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
private readonly WeakEvent<TSender, TEventArgs> _ev; private readonly WeakEvent<TSender, TEventArgs> _ev;
private readonly TSender _target; private readonly TSender _target;
private readonly Action _compact; private readonly Action _compact;
struct Entry
{
WeakReference<IWeakEventSubscriber<TEventArgs>>? _reference;
int _hashCode;
public Entry(IWeakEventSubscriber<TEventArgs> r)
{
if (r == null)
{
_reference = null;
_hashCode = 0;
return;
}
_hashCode = r.GetHashCode();
_reference = new WeakReference<IWeakEventSubscriber<TEventArgs>>(r);
}
public bool IsEmpty
{
get
{
if (_reference == null)
return true;
if (_reference.TryGetTarget(out _))
return false;
_reference = null;
return true;
}
}
public bool TryGetTarget([MaybeNullWhen(false)]out IWeakEventSubscriber<TEventArgs> target)
{
if (_reference == null)
{
target = null!;
return false;
}
return _reference.TryGetTarget(out target);
}
public bool Equals(IWeakEventSubscriber<TEventArgs> r)
{
if (_reference == null || r.GetHashCode() != _hashCode)
return false;
return _reference.TryGetTarget(out var target) && target == r;
}
}
private readonly Action _unsubscribe; private readonly Action _unsubscribe;
private readonly WeakHashList<IWeakEventSubscriber<TEventArgs>> _list = new(); private readonly WeakHashList<IWeakEventSubscriber<TEventArgs>> _list = new();
private bool _compactScheduled; private bool _compactScheduled;
@ -114,7 +60,7 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
_unsubscribe = ev._subscribe(target, OnEvent); _unsubscribe = ev._subscribe(target, OnEvent);
} }
void Destroy() private void Destroy()
{ {
if(_destroyed) if(_destroyed)
return; return;
@ -134,15 +80,15 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
ScheduleCompact(); ScheduleCompact();
} }
void ScheduleCompact() private void ScheduleCompact()
{ {
if(_compactScheduled || _destroyed) if(_compactScheduled || _destroyed)
return; return;
_compactScheduled = true; _compactScheduled = true;
Dispatcher.UIThread.Post(_compact, DispatcherPriority.Background); Dispatcher.UIThread.Post(_compact, DispatcherPriority.Background);
} }
void Compact() private void Compact()
{ {
if(!_compactScheduled) if(!_compactScheduled)
return; return;
@ -152,7 +98,7 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
Destroy(); Destroy();
} }
void OnEvent(object? sender, TEventArgs eventArgs) private void OnEvent(object? sender, TEventArgs eventArgs)
{ {
var alive = _list.GetAlive(); var alive = _list.GetAlive();
if(alive == null) if(alive == null)
@ -196,4 +142,4 @@ public class WeakEvent
return () => unsubscribe(s, handler); return () => unsubscribe(s, handler);
}); });
} }
} }

45
src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs

@ -60,8 +60,7 @@ namespace Avalonia.Utilities
private static class SubscriptionTypeStorage<TArgs, TSubscriber> private static class SubscriptionTypeStorage<TArgs, TSubscriber>
where TArgs : EventArgs where TSubscriber : class where TArgs : EventArgs where TSubscriber : class
{ {
public static readonly ConditionalWeakTable<object, SubscriptionDic<TArgs, TSubscriber>> Subscribers public static readonly ConditionalWeakTable<object, SubscriptionDic<TArgs, TSubscriber>> Subscribers = new();
= new ConditionalWeakTable<object, SubscriptionDic<TArgs, TSubscriber>>();
} }
private class SubscriptionDic<T, TSubscriber> : Dictionary<string, Subscription<T, TSubscriber>> private class SubscriptionDic<T, TSubscriber> : Dictionary<string, Subscription<T, TSubscriber>>
@ -69,8 +68,7 @@ namespace Avalonia.Utilities
{ {
} }
private static readonly Dictionary<Type, Dictionary<string, EventInfo>> Accessors private static readonly Dictionary<Type, Dictionary<string, EventInfo>> s_accessors = new();
= new Dictionary<Type, Dictionary<string, EventInfo>>();
private class Subscription<T, TSubscriber> where T : EventArgs where TSubscriber : class private class Subscription<T, TSubscriber> where T : EventArgs where TSubscriber : class
{ {
@ -81,18 +79,17 @@ namespace Avalonia.Utilities
private readonly Delegate _delegate; private readonly Delegate _delegate;
private Descriptor[] _data = new Descriptor[2]; private Descriptor[] _data = new Descriptor[2];
private int _count = 0; private int _count;
delegate void CallerDelegate(TSubscriber s, object sender, T args); private delegate void CallerDelegate(TSubscriber s, object? sender, T args);
struct Descriptor private struct Descriptor
{ {
public WeakReference<TSubscriber> Subscriber; public WeakReference<TSubscriber>? Subscriber;
public CallerDelegate Caller; public CallerDelegate? Caller;
} }
private static Dictionary<MethodInfo, CallerDelegate> s_Callers = private static readonly Dictionary<MethodInfo, CallerDelegate> s_callers = new();
new Dictionary<MethodInfo, CallerDelegate>();
public Subscription(SubscriptionDic<T, TSubscriber> sdic, public Subscription(SubscriptionDic<T, TSubscriber> sdic,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] Type targetType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] Type targetType,
@ -101,8 +98,8 @@ namespace Avalonia.Utilities
_sdic = sdic; _sdic = sdic;
_target = target; _target = target;
_eventName = eventName; _eventName = eventName;
if (!Accessors.TryGetValue(targetType, out var evDic)) if (!s_accessors.TryGetValue(targetType, out var evDic))
Accessors[targetType] = evDic = new Dictionary<string, EventInfo>(); s_accessors[targetType] = evDic = new Dictionary<string, EventInfo>();
if (evDic.TryGetValue(eventName, out var info)) if (evDic.TryGetValue(eventName, out var info))
{ {
@ -123,12 +120,12 @@ namespace Avalonia.Utilities
var del = new Action<object, T>(OnEvent); var del = new Action<object, T>(OnEvent);
_delegate = del.GetMethodInfo().CreateDelegate(_info.EventHandlerType!, del.Target); _delegate = del.GetMethodInfo().CreateDelegate(_info.EventHandlerType!, del.Target);
_info.AddMethod!.Invoke(target, new[] { _delegate }); _info.AddMethod!.Invoke(target, new object?[] { _delegate });
} }
void Destroy() private void Destroy()
{ {
_info.RemoveMethod!.Invoke(_target, new[] { _delegate }); _info.RemoveMethod!.Invoke(_target, new object?[] { _delegate });
_sdic.Remove(_eventName); _sdic.Remove(_eventName);
} }
@ -146,8 +143,8 @@ namespace Avalonia.Utilities
MethodInfo method = s.Method; MethodInfo method = s.Method;
var subscriber = (TSubscriber)s.Target!; var subscriber = (TSubscriber)s.Target!;
if (!s_Callers.TryGetValue(method, out var caller)) if (!s_callers.TryGetValue(method, out var caller))
s_Callers[method] = caller = s_callers[method] = caller =
(CallerDelegate)Delegate.CreateDelegate(typeof(CallerDelegate), null, method); (CallerDelegate)Delegate.CreateDelegate(typeof(CallerDelegate), null, method);
_data[_count] = new Descriptor _data[_count] = new Descriptor
{ {
@ -178,7 +175,7 @@ namespace Avalonia.Utilities
} }
} }
void Compact(bool preventDestroy = false) private void Compact(bool preventDestroy = false)
{ {
int empty = -1; int empty = -1;
for (int c = 0; c < _count; c++) for (int c = 0; c < _count; c++)
@ -206,15 +203,15 @@ namespace Avalonia.Utilities
Destroy(); Destroy();
} }
void OnEvent(object sender, T eventArgs) private void OnEvent(object? sender, T eventArgs)
{ {
var needCompact = false; var needCompact = false;
for(var c=0; c<_count; c++) for (var c = 0; c < _count; c++)
{ {
var r = _data[c].Subscriber; var r = _data[c].Subscriber!;
if (r.TryGetTarget(out var sub)) if (r.TryGetTarget(out var sub))
{ {
_data[c].Caller(sub, sender, eventArgs); _data[c].Caller!(sub, sender, eventArgs);
} }
else else
needCompact = true; needCompact = true;

34
src/Avalonia.Base/Visual.cs

@ -348,7 +348,7 @@ namespace Avalonia
/// </summary> /// </summary>
public void InvalidateVisual() public void InvalidateVisual()
{ {
VisualRoot?.Renderer?.AddDirty(this); VisualRoot?.Renderer.AddDirty(this);
} }
/// <summary> /// <summary>
@ -449,7 +449,7 @@ namespace Avalonia
protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{ {
base.LogicalChildrenCollectionChanged(sender, e); base.LogicalChildrenCollectionChanged(sender, e);
VisualRoot?.Renderer?.RecalculateChildren(this); VisualRoot?.Renderer.RecalculateChildren(this);
} }
/// <summary> /// <summary>
@ -477,23 +477,19 @@ namespace Avalonia
OnAttachedToVisualTree(e); OnAttachedToVisualTree(e);
AttachedToVisualTree?.Invoke(this, e); AttachedToVisualTree?.Invoke(this, e);
InvalidateVisual(); InvalidateVisual();
_visualRoot.Renderer?.RecalculateChildren(_visualParent!); _visualRoot.Renderer.RecalculateChildren(_visualParent!);
if (ZIndex != 0 && VisualParent is Visual parent) if (ZIndex != 0 && VisualParent is Visual parent)
parent.HasNonUniformZIndexChildren = true; parent.HasNonUniformZIndexChildren = true;
var visualChildren = VisualChildren; var visualChildren = VisualChildren;
var visualChildrenCount = visualChildren.Count;
if (visualChildren != null) for (var i = 0; i < visualChildrenCount; i++)
{ {
var visualChildrenCount = visualChildren.Count; if (visualChildren[i] is { } child)
for (var i = 0; i < visualChildrenCount; i++)
{ {
if (visualChildren[i] is Visual child) child.OnAttachedToVisualTreeCore(e);
{
child.OnAttachedToVisualTreeCore(e);
}
} }
} }
} }
@ -540,20 +536,16 @@ namespace Avalonia
} }
DetachedFromVisualTree?.Invoke(this, e); DetachedFromVisualTree?.Invoke(this, e);
e.Root?.Renderer?.AddDirty(this); e.Root.Renderer.AddDirty(this);
var visualChildren = VisualChildren; var visualChildren = VisualChildren;
var visualChildrenCount = visualChildren.Count;
if (visualChildren != null) for (var i = 0; i < visualChildrenCount; i++)
{ {
var visualChildrenCount = visualChildren.Count; if (visualChildren[i] is { } child)
for (var i = 0; i < visualChildrenCount; i++)
{ {
if (visualChildren[i] is Visual child) child.OnDetachedFromVisualTreeCore(e);
{
child.OnDetachedFromVisualTreeCore(e);
}
} }
} }
} }
@ -659,7 +651,7 @@ namespace Avalonia
parentVisual.HasNonUniformZIndexChildren = true; parentVisual.HasNonUniformZIndexChildren = true;
sender?.InvalidateVisual(); sender?.InvalidateVisual();
parent?.VisualRoot?.Renderer?.RecalculateChildren(parent); parent?.VisualRoot?.Renderer.RecalculateChildren(parent);
} }
/// <summary> /// <summary>

19
src/Avalonia.Base/VisualTree/VisualExtensions.cs

@ -46,7 +46,7 @@ namespace Avalonia.VisualTree
Visual? v = visual ?? throw new ArgumentNullException(nameof(visual)); Visual? v = visual ?? throw new ArgumentNullException(nameof(visual));
var result = 0; var result = 0;
v = v?.VisualParent; v = v.VisualParent;
while (v != null) while (v != null)
{ {
@ -64,17 +64,13 @@ namespace Avalonia.VisualTree
/// <param name="visual">The first visual.</param> /// <param name="visual">The first visual.</param>
/// <param name="target">The second visual.</param> /// <param name="target">The second visual.</param>
/// <returns>The common ancestor, or null if not found.</returns> /// <returns>The common ancestor, or null if not found.</returns>
public static Visual? FindCommonVisualAncestor(this Visual visual, Visual target) public static Visual? FindCommonVisualAncestor(this Visual? visual, Visual? target)
{ {
Visual? v = visual ?? throw new ArgumentNullException(nameof(visual)); if (visual is null || target is null)
if (target is null)
{ {
return null; return null;
} }
Visual? t = target;
void GoUpwards(ref Visual? node, int count) void GoUpwards(ref Visual? node, int count)
{ {
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
@ -83,6 +79,9 @@ namespace Avalonia.VisualTree
} }
} }
Visual? v = visual;
Visual? t = target;
// We want to find lowest node first, then make sure that both nodes are at the same height. // We want to find lowest node first, then make sure that both nodes are at the same height.
// By doing that we can sometimes find out that other node is our lowest common ancestor. // By doing that we can sometimes find out that other node is our lowest common ancestor.
var firstHeight = CalculateDistanceFromRoot(v); var firstHeight = CalculateDistanceFromRoot(v);
@ -144,7 +143,7 @@ namespace Avalonia.VisualTree
/// <param name="visual">The visual.</param> /// <param name="visual">The visual.</param>
/// <param name="includeSelf">If given visual should be included in search.</param> /// <param name="includeSelf">If given visual should be included in search.</param>
/// <returns>First ancestor of given type.</returns> /// <returns>First ancestor of given type.</returns>
public static T? FindAncestorOfType<T>(this Visual visual, bool includeSelf = false) where T : class public static T? FindAncestorOfType<T>(this Visual? visual, bool includeSelf = false) where T : class
{ {
if (visual is null) if (visual is null)
{ {
@ -173,7 +172,7 @@ namespace Avalonia.VisualTree
/// <param name="visual">The visual.</param> /// <param name="visual">The visual.</param>
/// <param name="includeSelf">If given visual should be included in search.</param> /// <param name="includeSelf">If given visual should be included in search.</param>
/// <returns>First descendant of given type.</returns> /// <returns>First descendant of given type.</returns>
public static T? FindDescendantOfType<T>(this Visual visual, bool includeSelf = false) where T : class public static T? FindDescendantOfType<T>(this Visual? visual, bool includeSelf = false) where T : class
{ {
if (visual is null) if (visual is null)
{ {
@ -392,7 +391,7 @@ namespace Avalonia.VisualTree
/// True if <paramref name="visual"/> is an ancestor of <paramref name="target"/>; /// True if <paramref name="visual"/> is an ancestor of <paramref name="target"/>;
/// otherwise false. /// otherwise false.
/// </returns> /// </returns>
public static bool IsVisualAncestorOf(this Visual visual, Visual target) public static bool IsVisualAncestorOf(this Visual? visual, Visual? target)
{ {
Visual? current = target?.VisualParent; Visual? current = target?.VisualParent;

2
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -3979,7 +3979,7 @@ namespace Avalonia.Controls
{ {
if (focusedObject is Control element) if (focusedObject is Control element)
{ {
parent = element.Parent; parent = element.VisualParent;
if (parent != null) if (parent != null)
{ {
dataGridWillReceiveRoutedEvent = false; dataGridWillReceiveRoutedEvent = false;

2
src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs

@ -36,7 +36,7 @@ namespace Avalonia.Controls.Utils
{ {
if (child is Control childElement) if (child is Control childElement)
{ {
parent = childElement.Parent; parent = childElement.VisualParent;
} }
} }
child = parent; child = parent;

2
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs

@ -792,7 +792,7 @@ namespace Avalonia.Controls
Control? element = focused as Control; Control? element = focused as Control;
if (element != null) if (element != null)
{ {
parent = element.Parent; parent = element.VisualParent;
} }
} }
focused = parent; focused = parent;

4
src/Avalonia.Controls/Button.cs

@ -394,10 +394,10 @@ namespace Avalonia.Controls
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{ {
IsPressed = true; IsPressed = true;
e.Handled = true;
if (ClickMode == ClickMode.Press) if (ClickMode == ClickMode.Press)
{ {
e.Handled = true;
OnClick(); OnClick();
} }
} }
@ -411,11 +411,11 @@ namespace Avalonia.Controls
if (IsPressed && e.InitialPressMouseButton == MouseButton.Left) if (IsPressed && e.InitialPressMouseButton == MouseButton.Left)
{ {
IsPressed = false; IsPressed = false;
e.Handled = true;
if (ClickMode == ClickMode.Release && if (ClickMode == ClickMode.Release &&
this.GetVisualsAt(e.GetPosition(this)).Any(c => this == c || this.IsVisualAncestorOf(c))) this.GetVisualsAt(e.GetPosition(this)).Any(c => this == c || this.IsVisualAncestorOf(c)))
{ {
e.Handled = true;
OnClick(); OnClick();
} }
} }

4
src/Avalonia.Controls/Control.cs

@ -2,14 +2,12 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Threading; using Avalonia.Threading;
@ -211,8 +209,6 @@ namespace Avalonia.Controls
remove => RemoveHandler(SizeChangedEvent, value); remove => RemoveHandler(SizeChangedEvent, value);
} }
public new Control? Parent => (Control?)base.Parent;
/// <inheritdoc/> /// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;

18
src/Avalonia.Controls/Documents/InlineUIContainer.cs

@ -64,5 +64,23 @@ namespace Avalonia.Controls.Documents
internal override void AppendText(StringBuilder stringBuilder) internal override void AppendText(StringBuilder stringBuilder)
{ {
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ChildProperty)
{
if(change.OldValue is Control oldChild)
{
LogicalChildren.Remove(oldChild);
}
if(change.NewValue is Control newChild)
{
LogicalChildren.Add(newChild);
}
}
}
} }
} }

1
src/Avalonia.Controls/ListBox.cs

@ -104,6 +104,7 @@ namespace Avalonia.Controls
public void UnselectAll() => Selection.Clear(); public void UnselectAll() => Selection.Clear();
protected internal override Control CreateContainerForItemOverride() => new ListBoxItem(); protected internal override Control CreateContainerForItemOverride() => new ListBoxItem();
protected internal override bool IsItemItsOwnContainerOverride(Control item) => item is ListBoxItem;
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnGotFocus(GotFocusEventArgs e) protected override void OnGotFocus(GotFocusEventArgs e)

2
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -553,7 +553,7 @@ namespace Avalonia.Controls.Platform
} }
} }
protected static IMenuItem? GetMenuItem(Control? item) protected static IMenuItem? GetMenuItem(StyledElement? item)
{ {
while (true) while (true)
{ {

2
src/Avalonia.Controls/Primitives/OverlayPopupHost.cs

@ -48,7 +48,7 @@ namespace Avalonia.Controls.Primitives
set { /* Not currently supported in overlay popups */ } set { /* Not currently supported in overlay popups */ }
} }
protected internal override Interactive? InteractiveParent => Parent; protected internal override Interactive? InteractiveParent => (Interactive?)VisualParent;
public void Dispose() => Hide(); public void Dispose() => Hide();

2
src/Avalonia.Controls/Primitives/Popup.cs

@ -723,7 +723,7 @@ namespace Avalonia.Controls.Primitives
while (e is object && (!e.Focusable || !e.IsEffectivelyEnabled || !e.IsVisible)) while (e is object && (!e.Focusable || !e.IsEffectivelyEnabled || !e.IsVisible))
{ {
e = e.Parent; e = e.VisualParent as Control;
} }
if (e is object) if (e is object)

4
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -72,12 +72,12 @@ namespace Avalonia.Controls.Primitives
/// <remarks> /// <remarks>
/// Popup events are passed to their parent window. This facilitates this. /// Popup events are passed to their parent window. This facilitates this.
/// </remarks> /// </remarks>
protected internal override Interactive? InteractiveParent => Parent; protected internal override Interactive? InteractiveParent => (Interactive?)Parent;
/// <summary> /// <summary>
/// Gets the control that is hosting the popup root. /// Gets the control that is hosting the popup root.
/// </summary> /// </summary>
Visual? IHostedVisualTreeRoot.Host => Parent; Visual? IHostedVisualTreeRoot.Host => VisualParent;
/// <summary> /// <summary>
/// Gets the styling parent of the popup root. /// Gets the styling parent of the popup root.

2
src/Avalonia.Controls/Primitives/ToggleButton.cs

@ -20,7 +20,7 @@ namespace Avalonia.Controls.Primitives
nameof(IsChecked), nameof(IsChecked),
o => o.IsChecked, o => o.IsChecked,
(o, v) => o.IsChecked = v, (o, v) => o.IsChecked = v,
unsetValue: null, unsetValue: false,
defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
/// <summary> /// <summary>

2
src/Avalonia.Controls/ProgressBar.cs

@ -231,7 +231,7 @@ namespace Avalonia.Controls
private void UpdateIndicator() private void UpdateIndicator()
{ {
// Gets the size of the parent indicator container // Gets the size of the parent indicator container
var barSize = _indicator?.Parent?.Bounds.Size ?? Bounds.Size; var barSize = _indicator?.VisualParent?.Bounds.Size ?? Bounds.Size;
if (_indicator != null) if (_indicator != null)
{ {

4
src/Avalonia.Controls/TextBlock.cs

@ -673,8 +673,6 @@ namespace Avalonia.Controls
controlRun.Control is Control control) controlRun.Control is Control control)
{ {
VisualChildren.Remove(control); VisualChildren.Remove(control);
LogicalChildren.Remove(control);
} }
} }
} }
@ -693,8 +691,6 @@ namespace Avalonia.Controls
{ {
VisualChildren.Add(control); VisualChildren.Add(control);
LogicalChildren.Add(control);
control.Measure(Size.Infinity); control.Measure(Size.Infinity);
} }
} }

40
src/Avalonia.Controls/TopLevel.cs

@ -94,7 +94,6 @@ namespace Avalonia.Controls
private readonly IInputManager? _inputManager; private readonly IInputManager? _inputManager;
private readonly IAccessKeyHandler? _accessKeyHandler; private readonly IAccessKeyHandler? _accessKeyHandler;
private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler; private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler;
private readonly IPlatformRenderInterface? _renderInterface;
private readonly IGlobalStyles? _globalStyles; private readonly IGlobalStyles? _globalStyles;
private readonly IGlobalThemeVariantProvider? _applicationThemeHost; private readonly IGlobalThemeVariantProvider? _applicationThemeHost;
private readonly PointerOverPreProcessor? _pointerOverPreProcessor; private readonly PointerOverPreProcessor? _pointerOverPreProcessor;
@ -136,36 +135,21 @@ namespace Avalonia.Controls
/// </param> /// </param>
public TopLevel(ITopLevelImpl impl, IAvaloniaDependencyResolver? dependencyResolver) public TopLevel(ITopLevelImpl impl, IAvaloniaDependencyResolver? dependencyResolver)
{ {
if (impl == null) PlatformImpl = impl ?? throw new InvalidOperationException(
{ "Could not create window implementation: maybe no windowing subsystem was initialized?");
throw new InvalidOperationException(
"Could not create window implementation: maybe no windowing subsystem was initialized?");
}
PlatformImpl = impl;
_actualTransparencyLevel = PlatformImpl.TransparencyLevel; _actualTransparencyLevel = PlatformImpl.TransparencyLevel;
dependencyResolver = dependencyResolver ?? AvaloniaLocator.Current; dependencyResolver ??= AvaloniaLocator.Current;
_accessKeyHandler = TryGetService<IAccessKeyHandler>(dependencyResolver); _accessKeyHandler = TryGetService<IAccessKeyHandler>(dependencyResolver);
_inputManager = TryGetService<IInputManager>(dependencyResolver); _inputManager = TryGetService<IInputManager>(dependencyResolver);
_keyboardNavigationHandler = TryGetService<IKeyboardNavigationHandler>(dependencyResolver); _keyboardNavigationHandler = TryGetService<IKeyboardNavigationHandler>(dependencyResolver);
_renderInterface = TryGetService<IPlatformRenderInterface>(dependencyResolver);
_globalStyles = TryGetService<IGlobalStyles>(dependencyResolver); _globalStyles = TryGetService<IGlobalStyles>(dependencyResolver);
_applicationThemeHost = TryGetService<IGlobalThemeVariantProvider>(dependencyResolver); _applicationThemeHost = TryGetService<IGlobalThemeVariantProvider>(dependencyResolver);
Renderer = impl.CreateRenderer(this); Renderer = impl.CreateRenderer(this);
Renderer.SceneInvalidated += SceneInvalidated;
if (Renderer != null)
{
Renderer.SceneInvalidated += SceneInvalidated;
}
else
{
// Prevent nullable error.
Renderer = null!;
}
impl.SetInputRoot(this); impl.SetInputRoot(this);
@ -216,7 +200,7 @@ namespace Avalonia.Controls
if(impl.TryGetFeature<ISystemNavigationManagerImpl>() is {} systemNavigationManager) if(impl.TryGetFeature<ISystemNavigationManagerImpl>() is {} systemNavigationManager)
{ {
systemNavigationManager.BackRequested += (s, e) => systemNavigationManager.BackRequested += (_, e) =>
{ {
e.RoutedEvent = BackRequestedEvent; e.RoutedEvent = BackRequestedEvent;
RaiseEvent(e); RaiseEvent(e);
@ -337,7 +321,7 @@ namespace Avalonia.Controls
{ {
_layoutManager = CreateLayoutManager(); _layoutManager = CreateLayoutManager();
if (_layoutManager is LayoutManager typedLayoutManager && Renderer is not null) if (_layoutManager is LayoutManager typedLayoutManager)
{ {
_layoutDiagnosticBridge = new LayoutDiagnosticBridge(Renderer.Diagnostics, typedLayoutManager); _layoutDiagnosticBridge = new LayoutDiagnosticBridge(Renderer.Diagnostics, typedLayoutManager);
_layoutDiagnosticBridge.SetupBridge(); _layoutDiagnosticBridge.SetupBridge();
@ -356,7 +340,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets the renderer for the window. /// Gets the renderer for the window.
/// </summary> /// </summary>
public IRenderer Renderer { get; private set; } public IRenderer Renderer { get; }
internal PixelPoint? LastPointerPosition => _pointerOverPreProcessor?.LastPosition; internal PixelPoint? LastPointerPosition => _pointerOverPreProcessor?.LastPosition;
@ -450,7 +434,7 @@ namespace Avalonia.Controls
/// <param name="rect">The dirty area.</param> /// <param name="rect">The dirty area.</param>
protected virtual void HandlePaint(Rect rect) protected virtual void HandlePaint(Rect rect)
{ {
Renderer?.Paint(rect); Renderer.Paint(rect);
} }
/// <summary> /// <summary>
@ -468,8 +452,8 @@ namespace Avalonia.Controls
_applicationThemeHost.ActualThemeVariantChanged -= GlobalActualThemeVariantChanged; _applicationThemeHost.ActualThemeVariantChanged -= GlobalActualThemeVariantChanged;
} }
Renderer?.Dispose(); Renderer.SceneInvalidated -= SceneInvalidated;
Renderer = null!; Renderer.Dispose();
_layoutDiagnosticBridge?.Dispose(); _layoutDiagnosticBridge?.Dispose();
_layoutDiagnosticBridge = null; _layoutDiagnosticBridge = null;
@ -488,7 +472,7 @@ namespace Avalonia.Controls
OnClosed(EventArgs.Empty); OnClosed(EventArgs.Empty);
LayoutManager?.Dispose(); LayoutManager.Dispose();
} }
/// <summary> /// <summary>
@ -503,7 +487,7 @@ namespace Avalonia.Controls
Width = clientSize.Width; Width = clientSize.Width;
Height = clientSize.Height; Height = clientSize.Height;
LayoutManager.ExecuteLayoutPass(); LayoutManager.ExecuteLayoutPass();
Renderer?.Resized(clientSize); Renderer.Resized(clientSize);
} }
/// <summary> /// <summary>

6
src/Avalonia.Controls/Window.cs

@ -573,7 +573,7 @@ namespace Avalonia.Controls
return; return;
} }
Renderer?.Stop(); Renderer.Stop();
if (Owner is Window owner) if (Owner is Window owner)
{ {
@ -721,7 +721,7 @@ namespace Avalonia.Controls
SetWindowStartupLocation(owner?.PlatformImpl); SetWindowStartupLocation(owner?.PlatformImpl);
PlatformImpl?.Show(ShowActivated, false); PlatformImpl?.Show(ShowActivated, false);
Renderer?.Start(); Renderer.Start();
OnOpened(EventArgs.Empty); OnOpened(EventArgs.Empty);
} }
} }
@ -798,7 +798,7 @@ namespace Avalonia.Controls
PlatformImpl?.Show(ShowActivated, true); PlatformImpl?.Show(ShowActivated, true);
Renderer?.Start(); Renderer.Start();
Observable.FromEventPattern( Observable.FromEventPattern(
x => Closed += x, x => Closed += x,

6
src/Avalonia.Controls/WindowBase.cs

@ -129,7 +129,7 @@ namespace Avalonia.Controls
{ {
using (FreezeVisibilityChangeHandling()) using (FreezeVisibilityChangeHandling())
{ {
Renderer?.Stop(); Renderer.Stop();
PlatformImpl?.Hide(); PlatformImpl?.Hide();
IsVisible = false; IsVisible = false;
} }
@ -153,7 +153,7 @@ namespace Avalonia.Controls
} }
PlatformImpl?.Show(true, false); PlatformImpl?.Show(true, false);
Renderer?.Start(); Renderer.Start();
OnOpened(EventArgs.Empty); OnOpened(EventArgs.Empty);
} }
} }
@ -219,7 +219,7 @@ namespace Avalonia.Controls
{ {
ClientSize = clientSize; ClientSize = clientSize;
LayoutManager.ExecuteLayoutPass(); LayoutManager.ExecuteLayoutPass();
Renderer?.Resized(clientSize); Renderer.Resized(clientSize);
} }
} }

2
src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs

@ -33,7 +33,7 @@ namespace Avalonia.Diagnostics.Controls
RendererRoot = application.ApplicationLifetime switch RendererRoot = application.ApplicationLifetime switch
{ {
Lifetimes.IClassicDesktopStyleApplicationLifetime classic => classic.MainWindow?.Renderer, Lifetimes.IClassicDesktopStyleApplicationLifetime classic => classic.MainWindow?.Renderer,
Lifetimes.ISingleViewApplicationLifetime single => (single.MainView as Visual)?.VisualRoot?.Renderer, Lifetimes.ISingleViewApplicationLifetime single => single.MainView?.VisualRoot?.Renderer,
_ => null _ => null
}; };

2
src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs

@ -115,7 +115,7 @@ namespace Avalonia.Diagnostics.ViewModels
var link = _currentEvent.EventChain[linkIndex]; var link = _currentEvent.EventChain[linkIndex];
link.Handled = true; link.Handled = true;
_currentEvent.HandledBy = link; _currentEvent.HandledBy ??= link;
} }
} }

1
src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml

@ -29,6 +29,7 @@
<Style Selector="ListBoxItem.handled" > <Style Selector="ListBoxItem.handled" >
<Setter Property="Background" Value="#d9ffdc" /> <Setter Property="Background" Value="#d9ffdc" />
<Setter Property="Foreground" Value="Black" />
</Style> </Style>
</UserControl.Styles> </UserControl.Styles>

4
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -141,9 +141,7 @@ namespace Avalonia.Headless
} }
public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound) public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound)
{ => Array.Empty<float>();
return null;
}
} }
class HeadlessGeometryStub : IGeometryImpl class HeadlessGeometryStub : IGeometryImpl

3
src/Avalonia.Native/WindowImpl.cs

@ -119,7 +119,8 @@ namespace Avalonia.Native
{ {
if(e.Type == RawPointerEventType.LeftButtonDown) if(e.Type == RawPointerEventType.LeftButtonDown)
{ {
var visual = (_inputRoot as Window).Renderer.HitTestFirst(e.Position, _inputRoot as Window, x => var window = _inputRoot as Window;
var visual = window?.Renderer.HitTestFirst(e.Position, window, x =>
{ {
if (x is IInputElement ie && (!ie.IsHitTestVisible || !ie.IsEffectivelyVisible)) if (x is IInputElement ie && (!ie.IsHitTestVisible || !ie.IsEffectivelyVisible))
{ {

12
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -208,6 +208,12 @@ namespace Avalonia.Skia
public void DrawLine(IPen pen, Point p1, Point p2) public void DrawLine(IPen pen, Point p1, Point p2)
{ {
CheckLease(); CheckLease();
if (pen is null)
{
return;
}
using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y)))) using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y))))
{ {
if (paint.Paint is object) if (paint.Paint is object)
@ -495,6 +501,12 @@ namespace Avalonia.Skia
public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun) public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun)
{ {
CheckLease(); CheckLease();
if (foreground is null)
{
return;
}
using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Item.Size)) using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Item.Size))
{ {
var glyphRunImpl = (GlyphRunImpl)glyphRun.Item; var glyphRunImpl = (GlyphRunImpl)glyphRun.Item;

7
src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using Avalonia.Platform; using Avalonia.Platform;
using SharpDX.DirectWrite; using SharpDX.DirectWrite;
@ -25,8 +26,6 @@ namespace Avalonia.Direct2D1.Media
} }
public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound) public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound)
{ => Array.Empty<float>();
return null;
}
} }
} }

25
tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs

@ -78,18 +78,6 @@ namespace Avalonia.Base.UnitTests.Data.Core
GC.KeepAlive(data); GC.KeepAlive(data);
} }
[Fact]
public async Task Should_Coerce_Get_Null_Double_String_To_UnsetValue()
{
var data = new Class1 { StringValue = null };
var target = new BindingExpression(ExpressionObserver.Create(data, o => o.StringValue), typeof(double));
var result = await target.Take(1);
Assert.Equal(AvaloniaProperty.UnsetValue, result);
GC.KeepAlive(data);
}
[Fact] [Fact]
public void Should_Convert_Set_String_To_Double() public void Should_Convert_Set_String_To_Double()
{ {
@ -249,19 +237,6 @@ namespace Avalonia.Base.UnitTests.Data.Core
GC.KeepAlive(data); GC.KeepAlive(data);
} }
[Fact]
public void Should_Coerce_Setting_Null_Double_To_Default_Value()
{
var data = new Class1 { DoubleValue = 5.6 };
var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string));
target.OnNext(null);
Assert.Equal(0, data.DoubleValue);
GC.KeepAlive(data);
}
[Fact] [Fact]
public void Should_Coerce_Setting_UnsetValue_Double_To_Default_Value() public void Should_Coerce_Setting_UnsetValue_Double_To_Default_Value()
{ {

4
tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs

@ -29,7 +29,7 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
double height, double height,
double scaleX, double scaleX,
double scaleY, double scaleY,
double? penThickness, double penThickness,
double expectedX, double expectedX,
double expectedY, double expectedY,
double expectedWidth, double expectedWidth,
@ -38,7 +38,7 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
var target = new TestRectangleDrawOperation( var target = new TestRectangleDrawOperation(
new Rect(x, y, width, height), new Rect(x, y, width, height),
Matrix.CreateScale(scaleX, scaleY), Matrix.CreateScale(scaleX, scaleY),
penThickness.HasValue ? new Pen(Brushes.Black, penThickness.Value) : null); new Pen(Brushes.Black, penThickness));
Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), target.Bounds); Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), target.Bounds);
} }

2
tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj

@ -14,7 +14,7 @@
<ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" /> <ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" /> <PackageReference Include="BenchmarkDotNet" Version="0.13.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Properties\" /> <Folder Include="Properties\" />

20
tests/Avalonia.Controls.UnitTests/ButtonTests.cs

@ -140,10 +140,9 @@ namespace Avalonia.Controls.UnitTests
.Returns<Point, Visual, Func<Visual, bool>>((p, r, f) => .Returns<Point, Visual, Func<Visual, bool>>((p, r, f) =>
r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]); r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]);
var target = new TestButton() var target = new TestButton(renderer.Object)
{ {
Bounds = new Rect(0, 0, 100, 100), Bounds = new Rect(0, 0, 100, 100)
Renderer = renderer.Object
}; };
bool clicked = false; bool clicked = false;
@ -172,10 +171,9 @@ namespace Avalonia.Controls.UnitTests
.Returns<Point, Visual, Func<Visual, bool>>((p, r, f) => .Returns<Point, Visual, Func<Visual, bool>>((p, r, f) =>
r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]); r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]);
var target = new TestButton() var target = new TestButton(renderer.Object)
{ {
Bounds = new Rect(0, 0, 100, 100), Bounds = new Rect(0, 0, 100, 100)
Renderer = renderer.Object
}; };
bool clicked = false; bool clicked = false;
@ -206,11 +204,10 @@ namespace Avalonia.Controls.UnitTests
r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ? r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ?
new Visual[] { r } : new Visual[0]); new Visual[] { r } : new Visual[0]);
var target = new TestButton() var target = new TestButton(renderer.Object)
{ {
Bounds = new Rect(0, 0, 100, 100), Bounds = new Rect(0, 0, 100, 100),
RenderTransform = new TranslateTransform { X = 100, Y = 0 }, RenderTransform = new TranslateTransform { X = 100, Y = 0 }
Renderer = renderer.Object
}; };
//actual bounds of button should be 100,0,100,100 x -> translated 100 pixels //actual bounds of button should be 100,0,100,100 x -> translated 100 pixels
@ -386,9 +383,10 @@ namespace Avalonia.Controls.UnitTests
private class TestButton : Button, IRenderRoot private class TestButton : Button, IRenderRoot
{ {
public TestButton() public TestButton(IRenderer renderer)
{ {
IsVisible = true; IsVisible = true;
Renderer = renderer;
} }
public new Rect Bounds public new Rect Bounds
@ -399,7 +397,7 @@ namespace Avalonia.Controls.UnitTests
public Size ClientSize => throw new NotImplementedException(); public Size ClientSize => throw new NotImplementedException();
public IRenderer Renderer { get; set; } public IRenderer Renderer { get; }
public double RenderScaling => throw new NotImplementedException(); public double RenderScaling => throw new NotImplementedException();

39
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@ -6,6 +6,7 @@ using Avalonia.Input.Raw;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Moq; using Moq;
@ -20,7 +21,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var impl = new Mock<ITopLevelImpl>(); var impl = CreateMockTopLevelImpl();
var target = new TestTopLevel(impl.Object); var target = new TestTopLevel(impl.Object);
Assert.True(((ILogical)target).IsAttachedToLogicalTree); Assert.True(((ILogical)target).IsAttachedToLogicalTree);
@ -32,7 +33,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var impl = new Mock<ITopLevelImpl>(); var impl = CreateMockTopLevelImpl();
impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); impl.Setup(x => x.ClientSize).Returns(new Size(123, 456));
var target = new TestTopLevel(impl.Object); var target = new TestTopLevel(impl.Object);
@ -46,7 +47,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var impl = new Mock<ITopLevelImpl>(); var impl = CreateMockTopLevelImpl();
impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); impl.Setup(x => x.ClientSize).Returns(new Size(123, 456));
var target = new TestTopLevel(impl.Object); var target = new TestTopLevel(impl.Object);
@ -60,7 +61,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var impl = new Mock<ITopLevelImpl>(); var impl = CreateMockTopLevelImpl();
impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); impl.Setup(x => x.ClientSize).Returns(new Size(123, 456));
var target = new TestTopLevel(impl.Object); var target = new TestTopLevel(impl.Object);
@ -76,7 +77,7 @@ namespace Avalonia.Controls.UnitTests
using (UnitTestApplication.Start(services)) using (UnitTestApplication.Start(services))
{ {
var impl = new Mock<ITopLevelImpl>(); var impl = CreateMockTopLevelImpl();
var target = new TestTopLevel(impl.Object, Mock.Of<ILayoutManager>()); var target = new TestTopLevel(impl.Object, Mock.Of<ILayoutManager>());
@ -91,7 +92,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var impl = new Mock<ITopLevelImpl>(); var impl = CreateMockTopLevelImpl();
impl.SetupProperty(x => x.Resized); impl.SetupProperty(x => x.Resized);
impl.SetupGet(x => x.RenderScaling).Returns(1); impl.SetupGet(x => x.RenderScaling).Returns(1);
@ -117,7 +118,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var impl = new Mock<ITopLevelImpl>(); var impl = CreateMockTopLevelImpl();
impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); impl.Setup(x => x.ClientSize).Returns(new Size(123, 456));
var target = new TestTopLevel(impl.Object); var target = new TestTopLevel(impl.Object);
@ -133,7 +134,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var impl = new Mock<ITopLevelImpl>(); var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties(); impl.SetupAllProperties();
impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); impl.Setup(x => x.ClientSize).Returns(new Size(123, 456));
@ -151,7 +152,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var impl = new Mock<ITopLevelImpl>(); var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties(); impl.SetupAllProperties();
bool raised = false; bool raised = false;
@ -169,7 +170,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var impl = new Mock<ITopLevelImpl>(); var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties(); impl.SetupAllProperties();
var target = new TestTopLevel(impl.Object); var target = new TestTopLevel(impl.Object);
@ -200,7 +201,7 @@ namespace Avalonia.Controls.UnitTests
using (UnitTestApplication.Start(services)) using (UnitTestApplication.Start(services))
{ {
var impl = new Mock<ITopLevelImpl>(); var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties(); impl.SetupAllProperties();
var target = new TestTopLevel(impl.Object); var target = new TestTopLevel(impl.Object);
@ -222,7 +223,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var impl = new Mock<ITopLevelImpl>(); var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties(); impl.SetupAllProperties();
var target = new TestTopLevel(impl.Object); var target = new TestTopLevel(impl.Object);
var child = new TestTopLevel(impl.Object); var child = new TestTopLevel(impl.Object);
@ -240,7 +241,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var impl = new Mock<ITopLevelImpl>(); var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties(); impl.SetupAllProperties();
var target = new TestTopLevel(impl.Object); var target = new TestTopLevel(impl.Object);
var raised = false; var raised = false;
@ -257,7 +258,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var impl = new Mock<ITopLevelImpl>(); var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties(); impl.SetupAllProperties();
var layoutManager = new Mock<ILayoutManager>(); var layoutManager = new Mock<ILayoutManager>();
@ -274,7 +275,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var impl = new Mock<ITopLevelImpl>(); var impl = CreateMockTopLevelImpl();
impl.SetupGet(x => x.RenderScaling).Returns(1); impl.SetupGet(x => x.RenderScaling).Returns(1);
var child = new Border { Classes = { "foo" } }; var child = new Border { Classes = { "foo" } };
@ -317,6 +318,14 @@ namespace Avalonia.Controls.UnitTests
}.RegisterInNameScope(scope)); }.RegisterInNameScope(scope));
} }
private static Mock<ITopLevelImpl> CreateMockTopLevelImpl()
{
var renderer = new Mock<ITopLevelImpl>();
renderer.Setup(r => r.CreateRenderer(It.IsAny<IRenderRoot>()))
.Returns(RendererMocks.CreateRenderer().Object);
return renderer;
}
private class TestTopLevel : TopLevel private class TestTopLevel : TopLevel
{ {
private readonly ILayoutManager _layoutManager; private readonly ILayoutManager _layoutManager;

12
tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs

@ -4,9 +4,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Moq;
using Xunit; using Xunit;
using Factory = System.Func<int, System.Action<object>, Avalonia.Controls.Window, Avalonia.AvaloniaObject>; using Factory = System.Func<int, System.Action<object>, Avalonia.Controls.Window, Avalonia.AvaloniaObject>;
@ -20,7 +18,7 @@ namespace Avalonia.Controls.UnitTests.Utils
using (AvaloniaLocator.EnterScope()) using (AvaloniaLocator.EnterScope())
{ {
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock()); .Bind<IWindowingPlatform>().ToConstant(new MockWindowingPlatform());
var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control); var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control);
var gesture2 = new KeyGesture(Key.B, KeyModifiers.Control); var gesture2 = new KeyGesture(Key.B, KeyModifiers.Control);
@ -64,7 +62,7 @@ namespace Avalonia.Controls.UnitTests.Utils
var commandResult = 0; var commandResult = 0;
var expectedParameter = 1; var expectedParameter = 1;
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock()); .Bind<IWindowingPlatform>().ToConstant(new MockWindowingPlatform());
var gesture = new KeyGesture(Key.A, KeyModifiers.Control); var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
@ -106,7 +104,7 @@ namespace Avalonia.Controls.UnitTests.Utils
var target = new KeyboardDevice(); var target = new KeyboardDevice();
var isExecuted = false; var isExecuted = false;
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock()); .Bind<IWindowingPlatform>().ToConstant(new MockWindowingPlatform());
var gesture = new KeyGesture(Key.A, KeyModifiers.Control); var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
@ -146,7 +144,7 @@ namespace Avalonia.Controls.UnitTests.Utils
var target = new KeyboardDevice(); var target = new KeyboardDevice();
var clickExecutedCount = 0; var clickExecutedCount = 0;
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock()); .Bind<IWindowingPlatform>().ToConstant(new MockWindowingPlatform());
var gesture = new KeyGesture(Key.A, KeyModifiers.Control); var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
@ -199,7 +197,7 @@ namespace Avalonia.Controls.UnitTests.Utils
var clickExecutedCount = 0; var clickExecutedCount = 0;
var commandExecutedCount = 0; var commandExecutedCount = 0;
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock()); .Bind<IWindowingPlatform>().ToConstant(new MockWindowingPlatform());
var gesture = new KeyGesture(Key.A, KeyModifiers.Control); var gesture = new KeyGesture(Key.A, KeyModifiers.Control);

14
tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs

@ -22,7 +22,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var impl = new Mock<IWindowBaseImpl>(); var impl = CreateMockWindowBaseImpl();
var target = new TestWindowBase(impl.Object); var target = new TestWindowBase(impl.Object);
target.Activate(); target.Activate();
@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var impl = new Mock<IWindowBaseImpl>(); var impl = CreateMockWindowBaseImpl();
impl.SetupAllProperties(); impl.SetupAllProperties();
bool raised = false; bool raised = false;
@ -55,7 +55,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var impl = new Mock<IWindowBaseImpl>(); var impl = CreateMockWindowBaseImpl();
impl.SetupAllProperties(); impl.SetupAllProperties();
bool raised = false; bool raised = false;
@ -241,6 +241,14 @@ namespace Avalonia.Controls.UnitTests
}.RegisterInNameScope(scope)); }.RegisterInNameScope(scope));
} }
private static Mock<IWindowBaseImpl> CreateMockWindowBaseImpl()
{
var renderer = new Mock<IWindowBaseImpl>();
renderer.Setup(r => r.CreateRenderer(It.IsAny<IRenderRoot>()))
.Returns(RendererMocks.CreateRenderer().Object);
return renderer;
}
private class TestWindowBase : WindowBase private class TestWindowBase : WindowBase
{ {
public bool IsClosed { get; private set; } public bool IsClosed { get; private set; }

2
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -15,6 +15,8 @@ namespace Avalonia.Controls.UnitTests
public void Setting_Title_Should_Set_Impl_Title() public void Setting_Title_Should_Set_Impl_Title()
{ {
var windowImpl = new Mock<IWindowImpl>(); var windowImpl = new Mock<IWindowImpl>();
windowImpl.Setup(r => r.CreateRenderer(It.IsAny<IRenderRoot>()))
.Returns(RendererMocks.CreateRenderer().Object);
var windowingPlatform = new MockWindowingPlatform(() => windowImpl.Object); var windowingPlatform = new MockWindowingPlatform(() => windowImpl.Object);
using (UnitTestApplication.Start(new TestServices(windowingPlatform: windowingPlatform))) using (UnitTestApplication.Start(new TestServices(windowingPlatform: windowingPlatform)))

35
tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs

@ -1,35 +0,0 @@
using System;
using Moq;
using Avalonia.Platform;
namespace Avalonia.Controls.UnitTests
{
public class WindowingPlatformMock : IWindowingPlatform
{
private readonly Func<IWindowImpl> _windowImpl;
private readonly Func<IPopupImpl> _popupImpl;
public WindowingPlatformMock(Func<IWindowImpl> windowImpl = null, Func<IPopupImpl> popupImpl = null )
{
_windowImpl = windowImpl;
_popupImpl = popupImpl;
}
public IWindowImpl CreateWindow()
{
return _windowImpl?.Invoke() ?? Mock.Of<IWindowImpl>(x => x.RenderScaling == 1);
}
public IWindowImpl CreateEmbeddableWindow()
{
throw new NotImplementedException();
}
public ITrayIconImpl CreateTrayIcon()
{
return null;
}
public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of<IPopupImpl>(x => x.RenderScaling == 1);
}
}

2
tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj

@ -10,7 +10,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Appium.WebDriver" Version="4.3.1" /> <PackageReference Include="Appium.WebDriver" Version="4.4.0" />
<PackageReference Include="Xunit.Extensions.Ordering" Version="1.4.5" /> <PackageReference Include="Xunit.Extensions.Ordering" Version="1.4.5" />
</ItemGroup> </ItemGroup>

2
tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net461</TargetFrameworks> <TargetFrameworks>net462</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<Import Project="..\..\build\JetBrains.dotMemoryUnit.props" /> <Import Project="..\..\build\JetBrains.dotMemoryUnit.props" />
<Import Project="..\..\build\Moq.props" /> <Import Project="..\..\build\Moq.props" />

70
tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs

@ -648,16 +648,69 @@ namespace Avalonia.Markup.UnitTests.Data
}; };
} }
[Fact]
public void Binding_Producing_Default_Value_Should_Result_In_Correct_Priority()
{
var defaultValue = StyledPropertyClass.NullableDoubleProperty.GetDefaultValue(typeof(StyledPropertyClass));
var vm = new NullableValuesViewModel() { NullableDouble = defaultValue };
var target = new StyledPropertyClass();
target.Bind(StyledPropertyClass.NullableDoubleProperty, new Binding(nameof(NullableValuesViewModel.NullableDouble)) { Source = vm });
Assert.Equal(BindingPriority.LocalValue, target.GetDiagnosticInternal(StyledPropertyClass.NullableDoubleProperty).Priority);
Assert.Equal(defaultValue, target.GetValue(StyledPropertyClass.NullableDoubleProperty));
}
[Fact]
public void Binding_Non_Nullable_ValueType_To_Null_Reverts_To_Default_Value()
{
var source = new NullableValuesViewModel { NullableDouble = 42 };
var target = new StyledPropertyClass();
var binding = new Binding(nameof(source.NullableDouble)) { Source = source };
target.Bind(StyledPropertyClass.DoubleValueProperty, binding);
Assert.Equal(42, target.DoubleValue);
source.NullableDouble = null;
Assert.Equal(12.3, target.DoubleValue);
}
[Fact]
public void Binding_Nullable_ValueType_To_Null_Sets_Value_To_Null()
{
var source = new NullableValuesViewModel { NullableDouble = 42 };
var target = new StyledPropertyClass();
var binding = new Binding(nameof(source.NullableDouble)) { Source = source };
target.Bind(StyledPropertyClass.NullableDoubleProperty, binding);
Assert.Equal(42, target.NullableDouble);
source.NullableDouble = null;
Assert.Null(target.NullableDouble);
}
private class StyledPropertyClass : AvaloniaObject private class StyledPropertyClass : AvaloniaObject
{ {
public static readonly StyledProperty<double> DoubleValueProperty = public static readonly StyledProperty<double> DoubleValueProperty =
AvaloniaProperty.Register<StyledPropertyClass, double>(nameof(DoubleValue)); AvaloniaProperty.Register<StyledPropertyClass, double>(nameof(DoubleValue), 12.3);
public double DoubleValue public double DoubleValue
{ {
get { return GetValue(DoubleValueProperty); } get { return GetValue(DoubleValueProperty); }
set { SetValue(DoubleValueProperty, value); } set { SetValue(DoubleValueProperty, value); }
} }
public static StyledProperty<double?> NullableDoubleProperty =
AvaloniaProperty.Register<StyledPropertyClass, double?>(nameof(NullableDoubleProperty), -1);
public double? NullableDouble
{
get => GetValue(NullableDoubleProperty);
set => SetValue(NullableDoubleProperty, value);
}
} }
private class DirectPropertyClass : AvaloniaObject private class DirectPropertyClass : AvaloniaObject
@ -676,6 +729,21 @@ namespace Avalonia.Markup.UnitTests.Data
} }
} }
private class NullableValuesViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private double? _nullableDouble;
public double? NullableDouble
{
get => _nullableDouble; set
{
_nullableDouble = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NullableDouble)));
}
}
}
private class TestStackOverflowViewModel : INotifyPropertyChanged private class TestStackOverflowViewModel : INotifyPropertyChanged
{ {
public int SetterInvokedCount { get; private set; } public int SetterInvokedCount { get; private set; }

8
tests/Avalonia.UnitTests/MockGlyphRun.cs

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting;
using Avalonia.Platform; using Avalonia.Platform;
@ -24,12 +25,9 @@ namespace Avalonia.UnitTests
public void Dispose() public void Dispose()
{ {
} }
public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound) public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound)
{ => Array.Empty<float>();
return null;
}
} }
} }

54
tests/Avalonia.UnitTests/TestTemplatedRoot.cs

@ -1,54 +0,0 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Styling;
namespace Avalonia.UnitTests
{
public class TestTemplatedRoot : ContentControl, ILayoutRoot, IRenderRoot, ILogicalRoot
{
private readonly NameScope _nameScope = new NameScope();
public TestTemplatedRoot()
{
LayoutManager = new LayoutManager(this);
Template = new FuncControlTemplate<TestTemplatedRoot>((x, scope) => new ContentPresenter
{
Name = "PART_ContentPresenter",
}.RegisterInNameScope(scope));
}
public Size ClientSize => new Size(100, 100);
public Size MaxClientSize => Size.Infinity;
public double LayoutScaling => 1;
public ILayoutManager LayoutManager { get; set; }
public double RenderScaling => 1;
public IRenderTarget RenderTarget => null;
public IRenderer Renderer => null;
public IRenderTarget CreateRenderTarget()
{
throw new NotImplementedException();
}
public void Invalidate(Rect rect)
{
throw new NotImplementedException();
}
public Point PointToClient(PixelPoint p) => p.ToPoint(1);
public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1);
}
}
Loading…
Cancel
Save