Browse Source

Merge branch 'master' into fixes/InlineUIContainerLogical

pull/10191/head
Max Katz 4 years ago
committed by GitHub
parent
commit
b10a845260
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      .editorconfig
  2. 6
      build/HarfBuzzSharp.props
  3. 2
      build/ImageSharp.props
  4. 2
      build/Moq.props
  5. 1
      build/SharedVersion.props
  6. 6
      build/SkiaSharp.props
  7. 15
      build/XUnit.props
  8. 4
      samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj
  9. 1
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  10. 54
      samples/IntegrationTestApp/MainWindow.axaml
  11. 91
      samples/IntegrationTestApp/MainWindow.axaml.cs
  12. 1
      samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj
  13. 29
      src/Avalonia.Base/Animation/Animatable.cs
  14. 11
      src/Avalonia.Base/Animation/KeySpline.cs
  15. 38
      src/Avalonia.Base/AvaloniaObject.cs
  16. 3
      src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs
  17. 18
      src/Avalonia.Base/Input/DragEventArgs.cs
  18. 2
      src/Avalonia.Base/Input/KeyGesture.cs
  19. 44
      src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
  20. 48
      src/Avalonia.Base/Input/Navigation/TabNavigation.cs
  21. 6
      src/Avalonia.Base/LogicalTree/LogicalExtensions.cs
  22. 9
      src/Avalonia.Base/Media/Color.cs
  23. 2
      src/Avalonia.Base/Media/DrawingContext.cs
  24. 26
      src/Avalonia.Base/Media/DrawingGroup.cs
  25. 6
      src/Avalonia.Base/Media/DrawingImage.cs
  26. 4
      src/Avalonia.Base/Media/FontFamily.cs
  27. 5
      src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs
  28. 6
      src/Avalonia.Base/Media/FormattedText.cs
  29. 6
      src/Avalonia.Base/Media/GeometryDrawing.cs
  30. 12
      src/Avalonia.Base/Media/GlyphRunDrawing.cs
  31. 2
      src/Avalonia.Base/Media/HslColor.cs
  32. 2
      src/Avalonia.Base/Media/HsvColor.cs
  33. 3
      src/Avalonia.Base/Media/IVisualBrush.cs
  34. 24
      src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs
  35. 7
      src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs
  36. 14
      src/Avalonia.Base/Media/TextDecoration.cs
  37. 17
      src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
  38. 7
      src/Avalonia.Base/Media/VisualBrush.cs
  39. 4
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  40. 28
      src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs
  41. 2
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
  42. 2
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs
  43. 2
      src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs
  44. 82
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  45. 3
      src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs
  46. 4
      src/Avalonia.Base/Rendering/Composition/CompositionObject.cs
  47. 2
      src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
  48. 14
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
  49. 2
      src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs
  50. 1
      src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs
  51. 4
      src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
  52. 2
      src/Avalonia.Base/Rendering/IRenderRoot.cs
  53. 3
      src/Avalonia.Base/Rendering/IRenderer.cs
  54. 6
      src/Avalonia.Base/Rendering/ImmediateRenderer.cs
  55. 7
      src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs
  56. 11
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  57. 68
      src/Avalonia.Base/Utilities/WeakEvent.cs
  58. 45
      src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
  59. 34
      src/Avalonia.Base/Visual.cs
  60. 19
      src/Avalonia.Base/VisualTree/VisualExtensions.cs
  61. 4
      src/Avalonia.Controls/Button.cs
  62. 12
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  63. 40
      src/Avalonia.Controls/TopLevel.cs
  64. 8
      src/Avalonia.Controls/Window.cs
  65. 6
      src/Avalonia.Controls/WindowBase.cs
  66. 2
      src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
  67. 4
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  68. 3
      src/Avalonia.Native/WindowImpl.cs
  69. 2
      src/Avalonia.Native/WindowImplBase.cs
  70. 2
      src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj
  71. 12
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  72. 7
      src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs
  73. 2
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  74. 2
      src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
  75. 71
      src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.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. 3
      tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj
  85. 45
      tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
  86. 2
      tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj
  87. 8
      tests/Avalonia.UnitTests/MockGlyphRun.cs
  88. 54
      tests/Avalonia.UnitTests/TestTemplatedRoot.cs

21
.editorconfig

@ -55,16 +55,17 @@ dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_naming_style.pascal_case_style.capitalization = pascal_case dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# static fields should have s_ prefix # private static fields should have s_ prefix
dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion dotnet_naming_rule.private_static_fields_should_have_prefix.severity = suggestion
dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields dotnet_naming_rule.private_static_fields_should_have_prefix.symbols = private_static_fields
dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style dotnet_naming_rule.private_static_fields_should_have_prefix.style = private_static_prefix_style
dotnet_naming_symbols.static_fields.applicable_kinds = field dotnet_naming_symbols.private_static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static dotnet_naming_symbols.private_static_fields.required_modifiers = static
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private
dotnet_naming_style.static_prefix_style.required_prefix = s_ dotnet_naming_style.private_static_prefix_style.required_prefix = s_
dotnet_naming_style.static_prefix_style.capitalization = camel_case dotnet_naming_style.private_static_prefix_style.capitalization = camel_case
# internal and private fields should be _camelCase # internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
@ -117,7 +118,7 @@ csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false csharp_space_before_comma = false
csharp_space_before_dot = false csharp_space_before_dot = false
@ -211,5 +212,5 @@ indent_size = 2
# Shell scripts # Shell scripts
[*.sh] [*.sh]
end_of_line = lf end_of_line = lf
[*.{cmd, bat}] [*.{cmd,bat}]
end_of_line = crlf end_of_line = crlf

6
build/HarfBuzzSharp.props

@ -1,7 +1,7 @@
<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="HarfBuzzSharp" Version="2.8.2.1-preview.108" /> <PackageReference Include="HarfBuzzSharp" Version="2.8.2.3" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2.1-preview.108" /> <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2.3" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="2.8.2.1-preview.108" /> <PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="2.8.2.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

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>

1
build/SharedVersion.props

@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Avalonia</Product> <Product>Avalonia</Product>
<Version>11.0.999</Version> <Version>11.0.999</Version>
<Authors>Avalonia Team</Authors>
<Copyright>Copyright 2022 &#169; The AvaloniaUI Project</Copyright> <Copyright>Copyright 2022 &#169; The AvaloniaUI Project</Copyright>
<PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl> <PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl> <RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>

6
build/SkiaSharp.props

@ -1,7 +1,7 @@
<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="SkiaSharp" Version="2.88.1" /> <PackageReference Include="SkiaSharp" Version="2.88.3" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.1" /> <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.3" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.1" /> <PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.3" />
</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>

4
samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj

@ -9,8 +9,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.0-rc.1.22427.2" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.0-rc.1.22427.2" PrivateAssets="all" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.2" PrivateAssets="all" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

1
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -31,7 +31,6 @@
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" /> <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" /> <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" /> <ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
<!-- For native controls test --> <!-- For native controls test -->
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" /> <PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
</ItemGroup> </ItemGroup>

54
samples/IntegrationTestApp/MainWindow.axaml

@ -120,30 +120,36 @@
</TabItem> </TabItem>
<TabItem Header="Window"> <TabItem Header="Window">
<StackPanel> <Grid ColumnDefinitions="*,8,*">
<TextBox Name="ShowWindowSize" Watermark="Window Size"/> <StackPanel Grid.Column="0">
<ComboBox Name="ShowWindowMode" SelectedIndex="0"> <TextBox Name="ShowWindowSize" Watermark="Window Size"/>
<ComboBoxItem>NonOwned</ComboBoxItem> <ComboBox Name="ShowWindowMode" SelectedIndex="0">
<ComboBoxItem>Owned</ComboBoxItem> <ComboBoxItem>NonOwned</ComboBoxItem>
<ComboBoxItem>Modal</ComboBoxItem> <ComboBoxItem>Owned</ComboBoxItem>
</ComboBox> <ComboBoxItem>Modal</ComboBoxItem>
<ComboBox Name="ShowWindowLocation" SelectedIndex="0"> </ComboBox>
<ComboBoxItem>Manual</ComboBoxItem> <ComboBox Name="ShowWindowLocation" SelectedIndex="0">
<ComboBoxItem>CenterScreen</ComboBoxItem> <ComboBoxItem>Manual</ComboBoxItem>
<ComboBoxItem>CenterOwner</ComboBoxItem> <ComboBoxItem>CenterScreen</ComboBoxItem>
</ComboBox> <ComboBoxItem>CenterOwner</ComboBoxItem>
<ComboBox Name="ShowWindowState" SelectedIndex="0"> </ComboBox>
<ComboBoxItem Name="ShowWindowStateNormal">Normal</ComboBoxItem> <ComboBox Name="ShowWindowState" SelectedIndex="0">
<ComboBoxItem Name="ShowWindowStateMinimized">Minimized</ComboBoxItem> <ComboBoxItem Name="ShowWindowStateNormal">Normal</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateMaximized">Maximized</ComboBoxItem> <ComboBoxItem Name="ShowWindowStateMinimized">Minimized</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateFullScreen">FullScreen</ComboBoxItem> <ComboBoxItem Name="ShowWindowStateMaximized">Maximized</ComboBoxItem>
</ComboBox> <ComboBoxItem Name="ShowWindowStateFullScreen">FullScreen</ComboBoxItem>
<Button Name="ShowWindow">Show Window</Button> </ComboBox>
<Button Name="SendToBack">Send to Back</Button> <Button Name="ShowWindow">Show Window</Button>
<Button Name="EnterFullscreen">Enter Fullscreen</Button> <Button Name="SendToBack">Send to Back</Button>
<Button Name="ExitFullscreen">Exit Fullscreen</Button> <Button Name="EnterFullscreen">Enter Fullscreen</Button>
<Button Name="RestoreAll">Restore All</Button> <Button Name="ExitFullscreen">Exit Fullscreen</Button>
</StackPanel> <Button Name="RestoreAll">Restore All</Button>
</StackPanel>
<StackPanel Grid.Column="2">
<Button Name="ShowTransparentWindow">Transparent Window</Button>
<Button Name="ShowTransparentPopup">Transparent Popup</Button>
</StackPanel>
</Grid>
</TabItem> </TabItem>
</TabControl> </TabControl>
</DockPanel> </DockPanel>

91
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -7,9 +7,13 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Avalonia.Controls.Primitives;
using Avalonia.Threading;
using Avalonia.Controls.Primitives.PopupPositioning;
namespace IntegrationTestApp namespace IntegrationTestApp
{ {
@ -103,6 +107,89 @@ namespace IntegrationTestApp
} }
} }
private void ShowTransparentWindow()
{
// Show a background window to make sure the color behind the transparent window is
// a known color (green).
var backgroundWindow = new Window
{
Title = "Transparent Window Background",
Name = "TransparentWindowBackground",
Width = 300,
Height = 300,
Background = Brushes.Green,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
};
// This is the transparent window with a red circle.
var window = new Window
{
Title = "Transparent Window",
Name = "TransparentWindow",
SystemDecorations = SystemDecorations.None,
Background = Brushes.Transparent,
TransparencyLevelHint = WindowTransparencyLevel.Transparent,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Width = 200,
Height = 200,
Content = new Border
{
Background = Brushes.Red,
CornerRadius = new CornerRadius(100),
}
};
window.PointerPressed += (_, _) =>
{
window.Close();
backgroundWindow.Close();
};
backgroundWindow.Show(this);
window.Show(backgroundWindow);
}
private void ShowTransparentPopup()
{
var popup = new Popup
{
WindowManagerAddShadowHint = false,
PlacementMode = PlacementMode.AnchorAndGravity,
PlacementAnchor = PopupAnchor.Top,
PlacementGravity = PopupGravity.Bottom,
Width= 200,
Height= 200,
Child = new Border
{
Background = Brushes.Red,
CornerRadius = new CornerRadius(100),
}
};
// Show a background window to make sure the color behind the transparent window is
// a known color (green).
var backgroundWindow = new Window
{
Title = "Transparent Popup Background",
Name = "TransparentPopupBackground",
Width = 200,
Height = 200,
Background = Brushes.Green,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Content = new Border
{
Name = "PopupContainer",
Child = popup,
[AutomationProperties.AccessibilityViewProperty] = AccessibilityView.Content,
}
};
backgroundWindow.PointerPressed += (_, _) => backgroundWindow.Close();
backgroundWindow.Show(this);
popup.Open();
}
private void SendToBack() private void SendToBack()
{ {
var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!; var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
@ -175,6 +262,10 @@ namespace IntegrationTestApp
this.Get<ListBox>("BasicListBox").SelectedIndex = -1; this.Get<ListBox>("BasicListBox").SelectedIndex = -1;
if (source?.Name == "MenuClickedMenuItemReset") if (source?.Name == "MenuClickedMenuItemReset")
this.Get<TextBlock>("ClickedMenuItem").Text = "None"; this.Get<TextBlock>("ClickedMenuItem").Text = "None";
if (source?.Name == "ShowTransparentWindow")
ShowTransparentWindow();
if (source?.Name == "ShowTransparentPopup")
ShowTransparentPopup();
if (source?.Name == "ShowWindow") if (source?.Name == "ShowWindow")
ShowWindow(); ShowWindow();
if (source?.Name == "SendToBack") if (source?.Name == "SendToBack")

1
samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj

@ -24,7 +24,6 @@
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" /> <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MobileSandbox\MobileSandbox.csproj" /> <ProjectReference Include="..\MobileSandbox\MobileSandbox.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" /> <ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
<!-- For native controls test --> <!-- For native controls test -->
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" /> <PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
</ItemGroup> </ItemGroup>

29
src/Avalonia.Base/Animation/Animatable.cs

@ -29,6 +29,9 @@ namespace Avalonia.Animation
private bool _transitionsEnabled = true; private bool _transitionsEnabled = true;
private bool _isSubscribedToTransitionsCollection = false; private bool _isSubscribedToTransitionsCollection = false;
private Dictionary<ITransition, TransitionState>? _transitionState; private Dictionary<ITransition, TransitionState>? _transitionState;
private NotifyCollectionChangedEventHandler? _collectionChanged;
private NotifyCollectionChangedEventHandler TransitionsCollectionChangedHandler =>
_collectionChanged ??= TransitionsCollectionChanged;
/// <summary> /// <summary>
/// Gets or sets the clock which controls the animations on the control. /// Gets or sets the clock which controls the animations on the control.
@ -61,14 +64,14 @@ namespace Avalonia.Animation
{ {
_transitionsEnabled = true; _transitionsEnabled = true;
if (Transitions is object) if (Transitions is Transitions transitions)
{ {
if (!_isSubscribedToTransitionsCollection) if (!_isSubscribedToTransitionsCollection)
{ {
_isSubscribedToTransitionsCollection = true; _isSubscribedToTransitionsCollection = true;
Transitions.CollectionChanged += TransitionsCollectionChanged; transitions.CollectionChanged += TransitionsCollectionChangedHandler;
} }
AddTransitions(Transitions); AddTransitions(transitions);
} }
} }
} }
@ -86,14 +89,14 @@ namespace Avalonia.Animation
{ {
_transitionsEnabled = false; _transitionsEnabled = false;
if (Transitions is object) if (Transitions is Transitions transitions)
{ {
if (_isSubscribedToTransitionsCollection) if (_isSubscribedToTransitionsCollection)
{ {
_isSubscribedToTransitionsCollection = false; _isSubscribedToTransitionsCollection = false;
Transitions.CollectionChanged -= TransitionsCollectionChanged; transitions.CollectionChanged -= TransitionsCollectionChangedHandler;
} }
RemoveTransitions(Transitions); RemoveTransitions(transitions);
} }
} }
} }
@ -120,7 +123,7 @@ namespace Avalonia.Animation
toAdd = newTransitions.Except(oldTransitions).ToList(); toAdd = newTransitions.Except(oldTransitions).ToList();
} }
newTransitions.CollectionChanged += TransitionsCollectionChanged; newTransitions.CollectionChanged += TransitionsCollectionChangedHandler;
_isSubscribedToTransitionsCollection = true; _isSubscribedToTransitionsCollection = true;
AddTransitions(toAdd); AddTransitions(toAdd);
} }
@ -134,19 +137,19 @@ namespace Avalonia.Animation
toRemove = oldTransitions.Except(newTransitions).ToList(); toRemove = oldTransitions.Except(newTransitions).ToList();
} }
oldTransitions.CollectionChanged -= TransitionsCollectionChanged; oldTransitions.CollectionChanged -= TransitionsCollectionChangedHandler;
RemoveTransitions(toRemove); RemoveTransitions(toRemove);
} }
} }
else if (_transitionsEnabled && else if (_transitionsEnabled &&
Transitions is object && Transitions is Transitions transitions &&
_transitionState is object && _transitionState is object &&
!change.Property.IsDirect && !change.Property.IsDirect &&
change.Priority > BindingPriority.Animation) change.Priority > BindingPriority.Animation)
{ {
for (var i = Transitions.Count -1; i >= 0; --i) for (var i = transitions.Count - 1; i >= 0; --i)
{ {
var transition = Transitions[i]; var transition = transitions[i];
if (transition.Property == change.Property && if (transition.Property == change.Property &&
_transitionState.TryGetValue(transition, out var state)) _transitionState.TryGetValue(transition, out var state))
@ -166,11 +169,11 @@ namespace Avalonia.Animation
{ {
oldValue = animatedValue; oldValue = animatedValue;
} }
var clock = Clock ?? AvaloniaLocator.Current.GetRequiredService<IGlobalClock>();
state.Instance?.Dispose(); state.Instance?.Dispose();
state.Instance = transition.Apply( state.Instance = transition.Apply(
this, this,
Clock ?? AvaloniaLocator.Current.GetRequiredService<IGlobalClock>(), clock,
oldValue, oldValue,
newValue); newValue);
return; return;

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;

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;

17
src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs

@ -118,14 +118,17 @@ namespace Avalonia.Media.TextFormatting
fontManager.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, fontManager.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight,
defaultTypeface.Stretch, defaultTypeface.FontFamily, defaultProperties.CultureInfo, defaultTypeface.Stretch, defaultTypeface.FontFamily, defaultProperties.CultureInfo,
out var fallbackTypeface); out var fallbackTypeface);
var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface); if (matchFound)
if (matchFound && TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count))
{ {
//Fallback found // Fallback found
return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface), var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface);
biDiLevel);
if (TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count))
{
return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface),
biDiLevel);
}
} }
// no fallback found // no fallback found

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;

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();
} }
} }

12
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@ -28,19 +28,19 @@ namespace Avalonia.Controls.Presenters
/// Defines the <see cref="AreHorizontalSnapPointsRegular"/> property. /// Defines the <see cref="AreHorizontalSnapPointsRegular"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> AreHorizontalSnapPointsRegularProperty = public static readonly StyledProperty<bool> AreHorizontalSnapPointsRegularProperty =
AvaloniaProperty.Register<ItemsControl, bool>(nameof(AreHorizontalSnapPointsRegular)); AvaloniaProperty.Register<ItemsPresenter, bool>(nameof(AreHorizontalSnapPointsRegular));
/// <summary> /// <summary>
/// Defines the <see cref="AreVerticalSnapPointsRegular"/> property. /// Defines the <see cref="AreVerticalSnapPointsRegular"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> AreVerticalSnapPointsRegularProperty = public static readonly StyledProperty<bool> AreVerticalSnapPointsRegularProperty =
AvaloniaProperty.Register<ItemsControl, bool>(nameof(AreVerticalSnapPointsRegular)); AvaloniaProperty.Register<ItemsPresenter, bool>(nameof(AreVerticalSnapPointsRegular));
/// <summary> /// <summary>
/// Defines the <see cref="HorizontalSnapPointsChanged"/> event. /// Defines the <see cref="HorizontalSnapPointsChanged"/> event.
/// </summary> /// </summary>
public static readonly RoutedEvent<RoutedEventArgs> HorizontalSnapPointsChangedEvent = public static readonly RoutedEvent<RoutedEventArgs> HorizontalSnapPointsChangedEvent =
RoutedEvent.Register<StackPanel, RoutedEventArgs>( RoutedEvent.Register<ItemsPresenter, RoutedEventArgs>(
nameof(HorizontalSnapPointsChanged), nameof(HorizontalSnapPointsChanged),
RoutingStrategies.Bubble); RoutingStrategies.Bubble);
@ -48,7 +48,7 @@ namespace Avalonia.Controls.Presenters
/// Defines the <see cref="VerticalSnapPointsChanged"/> event. /// Defines the <see cref="VerticalSnapPointsChanged"/> event.
/// </summary> /// </summary>
public static readonly RoutedEvent<RoutedEventArgs> VerticalSnapPointsChangedEvent = public static readonly RoutedEvent<RoutedEventArgs> VerticalSnapPointsChangedEvent =
RoutedEvent.Register<StackPanel, RoutedEventArgs>( RoutedEvent.Register<ItemsPresenter, RoutedEventArgs>(
nameof(VerticalSnapPointsChanged), nameof(VerticalSnapPointsChanged),
RoutingStrategies.Bubble); RoutingStrategies.Bubble);
@ -139,7 +139,7 @@ namespace Avalonia.Controls.Presenters
Size IScrollable.Viewport => _logicalScrollable?.Viewport ?? default; Size IScrollable.Viewport => _logicalScrollable?.Viewport ?? default;
/// <summary> /// <summary>
/// Gets or sets whether the horizontal snap points for the <see cref="ItemsControl"/> are equidistant from each other. /// Gets or sets whether the horizontal snap points for the <see cref="ItemsPresenter"/> are equidistant from each other.
/// </summary> /// </summary>
public bool AreHorizontalSnapPointsRegular public bool AreHorizontalSnapPointsRegular
{ {
@ -148,7 +148,7 @@ namespace Avalonia.Controls.Presenters
} }
/// <summary> /// <summary>
/// Gets or sets whether the vertical snap points for the <see cref="ItemsControl"/> are equidistant from each other. /// Gets or sets whether the vertical snap points for the <see cref="ItemsPresenter"/> are equidistant from each other.
/// </summary> /// </summary>
public bool AreVerticalSnapPointsRegular public bool AreVerticalSnapPointsRegular
{ {

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>

8
src/Avalonia.Controls/Window.cs

@ -450,7 +450,7 @@ namespace Avalonia.Controls
/// resulting task will produce the <see cref="_dialogResult"/> value when the window /// resulting task will produce the <see cref="_dialogResult"/> value when the window
/// is closed. /// is closed.
/// </remarks> /// </remarks>
public void Close(object dialogResult) public void Close(object? dialogResult)
{ {
_dialogResult = dialogResult; _dialogResult = dialogResult;
CloseCore(WindowCloseReason.WindowClosing, true); CloseCore(WindowCloseReason.WindowClosing, true);
@ -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
}; };

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))
{ {

2
src/Avalonia.Native/WindowImplBase.cs

@ -501,7 +501,7 @@ namespace Avalonia.Native
} }
} }
public WindowTransparencyLevel TransparencyLevel { get; private set; } = WindowTransparencyLevel.Transparent; public WindowTransparencyLevel TransparencyLevel { get; private set; } = WindowTransparencyLevel.None;
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{ {

2
src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj

@ -15,7 +15,7 @@
<Import Project="..\..\..\build\TrimmingEnable.props" /> <Import Project="..\..\..\build\TrimmingEnable.props" />
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="7.0.0-*" /> <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="7.0.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

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;
}
} }
} }

2
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@ -10,7 +10,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" /> <ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" /> <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2023020321" />
<PackageReference Include="MicroCom.CodeGenerator.MSBuild" Version="0.11.0" PrivateAssets="all" /> <PackageReference Include="MicroCom.CodeGenerator.MSBuild" Version="0.11.0" PrivateAssets="all" />
<MicroComIdl Include="WinRT\winrt.idl" CSharpInteropPath="WinRT\WinRT.Generated.cs" /> <MicroComIdl Include="WinRT\winrt.idl" CSharpInteropPath="WinRT\WinRT.Generated.cs" />
<MicroComIdl Include="Win32Com\win32.idl" CSharpInteropPath="Win32Com\Win32.Generated.cs" /> <MicroComIdl Include="Win32Com\win32.idl" CSharpInteropPath="Win32Com\Win32.Generated.cs" />

2
src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj

@ -23,7 +23,7 @@
<Compile Include="..\..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" /> <Compile Include="..\..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" /> <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2023020321" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\..\build\NetFX.props" /> <Import Project="..\..\..\build\NetFX.props" />
</Project> </Project>

71
src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs

@ -1,16 +1,79 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.XamlIl; using Avalonia.Markup.Xaml.XamlIl;
namespace Avalonia.Designer.HostApp namespace Avalonia.Designer.HostApp;
class DesignXamlLoader : AvaloniaXamlLoader.IRuntimeXamlLoader
{ {
class DesignXamlLoader : AvaloniaXamlLoader.IRuntimeXamlLoader public object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
{
PreloadDepsAssemblies(configuration.LocalAssembly ?? Assembly.GetEntryAssembly());
return AvaloniaXamlIlRuntimeCompiler.Load(document, configuration);
}
private void PreloadDepsAssemblies(Assembly targetAssembly)
{ {
public object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) // Assemblies loaded in memory (e.g. single file) return empty string from Location.
// In these cases, don't try probing next to the assembly.
var assemblyLocation = targetAssembly.Location;
if (string.IsNullOrEmpty(assemblyLocation))
{
return;
}
var depsJsonFile = Path.ChangeExtension(assemblyLocation, ".deps.json");
if (!File.Exists(depsJsonFile))
{
return;
}
using var stream = File.OpenRead(depsJsonFile);
/*
We can't use any references in the Avalonia.Designer.HostApp. Including even json.
Ideally we would prefer Microsoft.Extensions.DependencyModel package, but can't use it here.
So, instead we need to fallback to some JSON parsing using pretty easy regex.
Json part example:
"Avalonia.Xaml.Interactions/11.0.0-preview5": {
"dependencies": {
"Avalonia": "11.0.999",
"Avalonia.Xaml.Interactivity": "11.0.0-preview5"
},
"runtime": {
"lib/net6.0/Avalonia.Xaml.Interactions.dll": {
"assemblyVersion": "11.0.0.0",
"fileVersion": "11.0.0.0"
}
}
},
We want to extract "lib/net6.0/Avalonia.Xaml.Interactions.dll" from here.
No need to resolve real path of ref assemblies.
No need to handle special cases with .NET Framework and GAC.
*/
var text = new StreamReader(stream).ReadToEnd();
var matches = Regex.Matches( text, """runtime"\s*:\s*{\s*"([^"]+)""");
foreach (Match match in matches)
{ {
return AvaloniaXamlIlRuntimeCompiler.Load(document, configuration); if (match.Groups[1] is { Success: true } g)
{
var assemblyName = Path.GetFileNameWithoutExtension(g.Value);
try
{
_ = Assembly.Load(new AssemblyName(assemblyName));
}
catch
{
}
}
} }
} }
} }

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);
}
}

3
tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj

@ -10,10 +10,11 @@
</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>
<Import Project="..\..\build\XUnit.props" /> <Import Project="..\..\build\XUnit.props" />
<Import Project="..\..\build\Rx.props" /> <Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ImageSharp.props" />
</Project> </Project>

45
tests/Avalonia.IntegrationTests.Appium/WindowTests.cs

@ -1,11 +1,14 @@
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media.Imaging;
using OpenQA.Selenium; using OpenQA.Selenium;
using OpenQA.Selenium.Appium; using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Interactions; using OpenQA.Selenium.Interactions;
using SixLabors.ImageSharp.PixelFormats;
using Xunit; using Xunit;
using Xunit.Sdk; using Xunit.Sdk;
@ -141,7 +144,6 @@ namespace Avalonia.IntegrationTests.Appium
} }
} }
[Theory] [Theory]
[InlineData(ShowWindowMode.NonOwned)] [InlineData(ShowWindowMode.NonOwned)]
[InlineData(ShowWindowMode.Owned)] [InlineData(ShowWindowMode.Owned)]
@ -187,6 +189,47 @@ namespace Avalonia.IntegrationTests.Appium
} }
} }
[Fact]
public void TransparentWindow()
{
var showTransparentWindow = _session.FindElementByAccessibilityId("ShowTransparentWindow");
showTransparentWindow.Click();
Thread.Sleep(1000);
var window = _session.FindElementByAccessibilityId("TransparentWindow");
var screenshot = window.GetScreenshot();
window.Click();
var img = SixLabors.ImageSharp.Image.Load<Rgba32>(screenshot.AsByteArray);
var topLeftColor = img[10, 10];
var centerColor = img[img.Width / 2, img.Height / 2];
Assert.Equal(new Rgba32(0, 128, 0), topLeftColor);
Assert.Equal(new Rgba32(255, 0, 0), centerColor);
}
[Fact]
public void TransparentPopup()
{
var showTransparentWindow = _session.FindElementByAccessibilityId("ShowTransparentPopup");
showTransparentWindow.Click();
Thread.Sleep(1000);
var window = _session.FindElementByAccessibilityId("TransparentPopupBackground");
var container = window.FindElementByAccessibilityId("PopupContainer");
var screenshot = container.GetScreenshot();
window.Click();
var img = SixLabors.ImageSharp.Image.Load<Rgba32>(screenshot.AsByteArray);
var topLeftColor = img[10, 10];
var centerColor = img[img.Width / 2, img.Height / 2];
Assert.Equal(new Rgba32(0, 128, 0), topLeftColor);
Assert.Equal(new Rgba32(255, 0, 0), centerColor);
}
public static TheoryData<Size?, ShowWindowMode, WindowStartupLocation> StartupLocationData() public static TheoryData<Size?, ShowWindowMode, WindowStartupLocation> StartupLocationData()
{ {
var sizes = new Size?[] { null, new Size(400, 300) }; var sizes = new Size?[] { null, new Size(400, 300) };

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" />

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