Browse Source

Merge branch 'master' into progressBar-automationPeer

pull/10275/head
Max Katz 4 years ago
committed by GitHub
parent
commit
a1c1d804a2
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. 2
      src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
  17. 3
      src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs
  18. 18
      src/Avalonia.Base/Input/DragEventArgs.cs
  19. 2
      src/Avalonia.Base/Input/KeyGesture.cs
  20. 44
      src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
  21. 48
      src/Avalonia.Base/Input/Navigation/TabNavigation.cs
  22. 6
      src/Avalonia.Base/LogicalTree/LogicalExtensions.cs
  23. 9
      src/Avalonia.Base/Media/Color.cs
  24. 2
      src/Avalonia.Base/Media/DrawingContext.cs
  25. 26
      src/Avalonia.Base/Media/DrawingGroup.cs
  26. 6
      src/Avalonia.Base/Media/DrawingImage.cs
  27. 4
      src/Avalonia.Base/Media/FontFamily.cs
  28. 5
      src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs
  29. 6
      src/Avalonia.Base/Media/FormattedText.cs
  30. 6
      src/Avalonia.Base/Media/GeometryDrawing.cs
  31. 12
      src/Avalonia.Base/Media/GlyphRunDrawing.cs
  32. 2
      src/Avalonia.Base/Media/HslColor.cs
  33. 2
      src/Avalonia.Base/Media/HsvColor.cs
  34. 3
      src/Avalonia.Base/Media/IVisualBrush.cs
  35. 24
      src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs
  36. 7
      src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs
  37. 14
      src/Avalonia.Base/Media/TextDecoration.cs
  38. 17
      src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
  39. 7
      src/Avalonia.Base/Media/VisualBrush.cs
  40. 4
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  41. 28
      src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs
  42. 2
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
  43. 2
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs
  44. 2
      src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs
  45. 82
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  46. 3
      src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs
  47. 4
      src/Avalonia.Base/Rendering/Composition/CompositionObject.cs
  48. 2
      src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
  49. 14
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
  50. 2
      src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs
  51. 1
      src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs
  52. 4
      src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
  53. 2
      src/Avalonia.Base/Rendering/IRenderRoot.cs
  54. 3
      src/Avalonia.Base/Rendering/IRenderer.cs
  55. 6
      src/Avalonia.Base/Rendering/ImmediateRenderer.cs
  56. 7
      src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs
  57. 11
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  58. 68
      src/Avalonia.Base/Utilities/WeakEvent.cs
  59. 45
      src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
  60. 34
      src/Avalonia.Base/Visual.cs
  61. 19
      src/Avalonia.Base/VisualTree/VisualExtensions.cs
  62. 2
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  63. 2
      src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs
  64. 2
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
  65. 4
      src/Avalonia.Controls/Button.cs
  66. 4
      src/Avalonia.Controls/Control.cs
  67. 18
      src/Avalonia.Controls/Documents/InlineUIContainer.cs
  68. 31
      src/Avalonia.Controls/ItemsControl.cs
  69. 1
      src/Avalonia.Controls/ListBox.cs
  70. 2
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  71. 12
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  72. 2
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  73. 2
      src/Avalonia.Controls/Primitives/Popup.cs
  74. 4
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  75. 284
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  76. 2
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  77. 2
      src/Avalonia.Controls/ProgressBar.cs
  78. 4
      src/Avalonia.Controls/TextBlock.cs
  79. 40
      src/Avalonia.Controls/TopLevel.cs
  80. 8
      src/Avalonia.Controls/Window.cs
  81. 6
      src/Avalonia.Controls/WindowBase.cs
  82. 2
      src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
  83. 2
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs
  84. 1
      src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
  85. 4
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  86. 3
      src/Avalonia.Native/WindowImpl.cs
  87. 2
      src/Avalonia.Native/WindowImplBase.cs
  88. 2
      src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj
  89. 12
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  90. 7
      src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs
  91. 2
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  92. 2
      src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
  93. 71
      src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs
  94. 25
      tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs
  95. 4
      tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
  96. 2
      tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
  97. 20
      tests/Avalonia.Controls.UnitTests/ButtonTests.cs
  98. 330
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs
  99. 39
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  100. 12
      tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.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;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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;

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

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

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

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

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

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

4
src/Avalonia.Controls/Button.cs

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

4
src/Avalonia.Controls/Control.cs

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

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

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

31
src/Avalonia.Controls/ItemsControl.cs

@ -2,7 +2,6 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
@ -17,7 +16,6 @@ using Avalonia.Layout;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -91,10 +89,11 @@ namespace Avalonia.Controls
/// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item. /// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item.
/// </summary> /// </summary>
[AssignBinding] [AssignBinding]
[InheritDataTypeFromItems(nameof(Items))]
public IBinding? DisplayMemberBinding public IBinding? DisplayMemberBinding
{ {
get { return GetValue(DisplayMemberBindingProperty); } get => GetValue(DisplayMemberBindingProperty);
set { SetValue(DisplayMemberBindingProperty, value); } set => SetValue(DisplayMemberBindingProperty, value);
} }
private IEnumerable? _items = new AvaloniaList<object>(); private IEnumerable? _items = new AvaloniaList<object>();
@ -134,8 +133,8 @@ namespace Avalonia.Controls
[Content] [Content]
public IEnumerable? Items public IEnumerable? Items
{ {
get { return _items; } get => _items;
set { SetAndRaise(ItemsProperty, ref _items, value); } set => SetAndRaise(ItemsProperty, ref _items, value);
} }
/// <summary> /// <summary>
@ -143,8 +142,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public ControlTheme? ItemContainerTheme public ControlTheme? ItemContainerTheme
{ {
get { return GetValue(ItemContainerThemeProperty); } get => GetValue(ItemContainerThemeProperty);
set { SetValue(ItemContainerThemeProperty, value); } set => SetValue(ItemContainerThemeProperty, value);
} }
/// <summary> /// <summary>
@ -161,8 +160,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public ITemplate<Panel> ItemsPanel public ITemplate<Panel> ItemsPanel
{ {
get { return GetValue(ItemsPanelProperty); } get => GetValue(ItemsPanelProperty);
set { SetValue(ItemsPanelProperty, value); } set => SetValue(ItemsPanelProperty, value);
} }
/// <summary> /// <summary>
@ -171,8 +170,8 @@ namespace Avalonia.Controls
[InheritDataTypeFromItems(nameof(Items))] [InheritDataTypeFromItems(nameof(Items))]
public IDataTemplate? ItemTemplate public IDataTemplate? ItemTemplate
{ {
get { return GetValue(ItemTemplateProperty); } get => GetValue(ItemTemplateProperty);
set { SetValue(ItemTemplateProperty, value); } set => SetValue(ItemTemplateProperty, value);
} }
/// <summary> /// <summary>
@ -264,8 +263,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public bool AreHorizontalSnapPointsRegular public bool AreHorizontalSnapPointsRegular
{ {
get { return GetValue(AreHorizontalSnapPointsRegularProperty); } get => GetValue(AreHorizontalSnapPointsRegularProperty);
set { SetValue(AreHorizontalSnapPointsRegularProperty, value); } set => SetValue(AreHorizontalSnapPointsRegularProperty, value);
} }
/// <summary> /// <summary>
@ -273,8 +272,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public bool AreVerticalSnapPointsRegular public bool AreVerticalSnapPointsRegular
{ {
get { return GetValue(AreVerticalSnapPointsRegularProperty); } get => GetValue(AreVerticalSnapPointsRegularProperty);
set { SetValue(AreVerticalSnapPointsRegularProperty, value); } set => SetValue(AreVerticalSnapPointsRegularProperty, value);
} }
/// <summary> /// <summary>

1
src/Avalonia.Controls/ListBox.cs

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

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

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

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

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

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

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

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

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

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

284
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -4,15 +4,14 @@ using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Xml.Linq;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Selection; using Avalonia.Controls.Selection;
using Avalonia.Controls.Utils;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -66,6 +65,19 @@ namespace Avalonia.Controls.Primitives
(o, v) => o.SelectedItem = v, (o, v) => o.SelectedItem = v,
defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true); defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
/// <summary>
/// Defines the <see cref="SelectedValue"/> property
/// </summary>
public static readonly StyledProperty<object?> SelectedValueProperty =
AvaloniaProperty.Register<SelectingItemsControl, object?>(nameof(SelectedValue),
defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Defines the <see cref="SelectedValueBinding"/> property
/// </summary>
public static readonly StyledProperty<IBinding?> SelectedValueBindingProperty =
AvaloniaProperty.Register<SelectingItemsControl, IBinding?>(nameof(SelectedValueBinding));
/// <summary> /// <summary>
/// Defines the <see cref="SelectedItems"/> property. /// Defines the <see cref="SelectedItems"/> property.
/// </summary> /// </summary>
@ -129,6 +141,8 @@ namespace Avalonia.Controls.Primitives
private bool _ignoreContainerSelectionChanged; private bool _ignoreContainerSelectionChanged;
private UpdateState? _updateState; private UpdateState? _updateState;
private bool _hasScrolledToSelectedItem; private bool _hasScrolledToSelectedItem;
private BindingHelper? _bindingHelper;
private bool _isSelectionChangeActive;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="SelectingItemsControl"/> class. /// Initializes static members of the <see cref="SelectingItemsControl"/> class.
@ -143,8 +157,8 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public event EventHandler<SelectionChangedEventArgs>? SelectionChanged public event EventHandler<SelectionChangedEventArgs>? SelectionChanged
{ {
add { AddHandler(SelectionChangedEvent, value); } add => AddHandler(SelectionChangedEvent, value);
remove { RemoveHandler(SelectionChangedEvent, value); } remove => RemoveHandler(SelectionChangedEvent, value);
} }
/// <summary> /// <summary>
@ -152,8 +166,8 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public bool AutoScrollToSelectedItem public bool AutoScrollToSelectedItem
{ {
get { return GetValue(AutoScrollToSelectedItemProperty); } get => GetValue(AutoScrollToSelectedItemProperty);
set { SetValue(AutoScrollToSelectedItemProperty, value); } set => SetValue(AutoScrollToSelectedItemProperty, value);
} }
/// <summary> /// <summary>
@ -209,6 +223,28 @@ namespace Avalonia.Controls.Primitives
} }
} }
/// <summary>
/// Gets the <see cref="IBinding"/> instance used to obtain the
/// <see cref="SelectedValue"/> property
/// </summary>
[AssignBinding]
[InheritDataTypeFromItems(nameof(Items))]
public IBinding? SelectedValueBinding
{
get => GetValue(SelectedValueBindingProperty);
set => SetValue(SelectedValueBindingProperty, value);
}
/// <summary>
/// Gets or sets the value of the selected item, obtained using
/// <see cref="SelectedValueBinding"/>
/// </summary>
public object? SelectedValue
{
get => GetValue(SelectedValueProperty);
set => SetValue(SelectedValueProperty, value);
}
/// <summary> /// <summary>
/// Gets or sets the selected items. /// Gets or sets the selected items.
/// </summary> /// </summary>
@ -322,8 +358,8 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public bool IsTextSearchEnabled public bool IsTextSearchEnabled
{ {
get { return GetValue(IsTextSearchEnabledProperty); } get => GetValue(IsTextSearchEnabledProperty);
set { SetValue(IsTextSearchEnabledProperty, value); } set => SetValue(IsTextSearchEnabledProperty, value);
} }
/// <summary> /// <summary>
@ -332,8 +368,8 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public bool WrapSelection public bool WrapSelection
{ {
get { return GetValue(WrapSelectionProperty); } get => GetValue(WrapSelectionProperty);
set { SetValue(WrapSelectionProperty, value); } set => SetValue(WrapSelectionProperty, value);
} }
/// <summary> /// <summary>
@ -345,8 +381,8 @@ namespace Avalonia.Controls.Primitives
/// </remarks> /// </remarks>
protected SelectionMode SelectionMode protected SelectionMode SelectionMode
{ {
get { return GetValue(SelectionModeProperty); } get => GetValue(SelectionModeProperty);
set { SetValue(SelectionModeProperty, value); } set => SetValue(SelectionModeProperty, value);
} }
/// <summary> /// <summary>
@ -609,6 +645,60 @@ namespace Avalonia.Controls.Primitives
{ {
WrapFocus = WrapSelection; WrapFocus = WrapSelection;
} }
else if (change.Property == SelectedValueProperty)
{
if (_isSelectionChangeActive)
return;
if (_updateState is not null)
{
_updateState.SelectedValue = change.NewValue;
return;
}
SelectItemWithValue(change.NewValue);
}
else if (change.Property == SelectedValueBindingProperty)
{
var idx = SelectedIndex;
// If no selection is active, don't do anything as SelectedValue is already null
if (idx == -1)
{
return;
}
var value = change.GetNewValue<IBinding>();
if (value is null)
{
// Clearing SelectedValueBinding makes the SelectedValue the item itself
SelectedValue = SelectedItem;
return;
}
var selectedItem = SelectedItem;
try
{
_isSelectionChangeActive = true;
if (_bindingHelper is null)
{
_bindingHelper = new BindingHelper(value);
}
else
{
_bindingHelper.UpdateBinding(value);
}
// Re-evaluate SelectedValue with the new binding
SelectedValue = _bindingHelper.Evaluate(selectedItem);
}
finally
{
_isSelectionChangeActive = false;
}
}
} }
/// <summary> /// <summary>
@ -815,6 +905,10 @@ namespace Avalonia.Controls.Primitives
new BindingValue<IList?>(SelectedItems)); new BindingValue<IList?>(SelectedItems));
_oldSelectedItems = SelectedItems; _oldSelectedItems = SelectedItems;
} }
else if (e.PropertyName == nameof(ISelectionModel.Source))
{
ClearValue(SelectedValueProperty);
}
} }
/// <summary> /// <summary>
@ -845,6 +939,11 @@ namespace Avalonia.Controls.Primitives
Mark(i, false); Mark(i, false);
} }
if (!_isSelectionChangeActive)
{
UpdateSelectedValueFromItem();
}
var route = BuildEventRoute(SelectionChangedEvent); var route = BuildEventRoute(SelectionChangedEvent);
if (route.HasHandlers) if (route.HasHandlers)
@ -871,6 +970,109 @@ namespace Avalonia.Controls.Primitives
} }
} }
private void SelectItemWithValue(object? value)
{
if (ItemCount == 0 || _isSelectionChangeActive)
return;
try
{
_isSelectionChangeActive = true;
var si = FindItemWithValue(value);
if (si != AvaloniaProperty.UnsetValue)
{
SelectedItem = si;
}
else
{
SelectedItem = null;
}
}
finally
{
_isSelectionChangeActive = false;
}
}
private object FindItemWithValue(object? value)
{
if (ItemCount == 0 || value is null)
{
return AvaloniaProperty.UnsetValue;
}
var items = Items;
var binding = SelectedValueBinding;
if (binding is null)
{
// No SelectedValueBinding set, SelectedValue is the item itself
// Still verify the value passed in is in the Items list
var index = items!.IndexOf(value);
if (index >= 0)
{
return value;
}
else
{
return AvaloniaProperty.UnsetValue;
}
}
_bindingHelper ??= new BindingHelper(binding);
// Matching UWP behavior, if duplicates are present, return the first item matching
// the SelectedValue provided
foreach (var item in items!)
{
var itemValue = _bindingHelper.Evaluate(item);
if (itemValue.Equals(value))
{
return item;
}
}
return AvaloniaProperty.UnsetValue;
}
private void UpdateSelectedValueFromItem()
{
if (_isSelectionChangeActive)
return;
var binding = SelectedValueBinding;
var item = SelectedItem;
if (binding is null || item is null)
{
// No SelectedValueBinding, SelectedValue is Item itself
try
{
_isSelectionChangeActive = true;
SelectedValue = item;
}
finally
{
_isSelectionChangeActive = false;
}
return;
}
_bindingHelper ??= new BindingHelper(binding);
try
{
_isSelectionChangeActive = true;
SelectedValue = _bindingHelper.Evaluate(item);
}
finally
{
_isSelectionChangeActive = false;
}
}
private void AutoScrollToSelectedItemIfNecessary() private void AutoScrollToSelectedItemIfNecessary()
{ {
if (AutoScrollToSelectedItem && if (AutoScrollToSelectedItem &&
@ -1037,6 +1239,13 @@ namespace Avalonia.Controls.Primitives
Selection.Clear(); Selection.Clear();
} }
if (state.SelectedValue.HasValue)
{
var item = FindItemWithValue(state.SelectedValue.Value);
if (item != AvaloniaProperty.UnsetValue)
state.SelectedItem = item;
}
if (state.SelectedIndex.HasValue) if (state.SelectedIndex.HasValue)
{ {
SelectedIndex = state.SelectedIndex.Value; SelectedIndex = state.SelectedIndex.Value;
@ -1098,6 +1307,7 @@ namespace Avalonia.Controls.Primitives
{ {
private Optional<int> _selectedIndex; private Optional<int> _selectedIndex;
private Optional<object?> _selectedItem; private Optional<object?> _selectedItem;
private Optional<object?> _selectedValue;
public int UpdateCount { get; set; } public int UpdateCount { get; set; }
public Optional<ISelectionModel> Selection { get; set; } public Optional<ISelectionModel> Selection { get; set; }
@ -1122,6 +1332,54 @@ namespace Avalonia.Controls.Primitives
_selectedIndex = default; _selectedIndex = default;
} }
} }
public Optional<object?> SelectedValue
{
get => _selectedValue;
set
{
_selectedValue = value;
}
}
}
/// <summary>
/// Helper class for evaluating a binding from an Item and IBinding instance
/// </summary>
private class BindingHelper : StyledElement
{
public BindingHelper(IBinding binding)
{
UpdateBinding(binding);
}
public static readonly StyledProperty<object> ValueProperty =
AvaloniaProperty.Register<BindingHelper, object>("Value");
public object Evaluate(object? dataContext)
{
dataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext));
// Only update the DataContext if necessary
if (!dataContext.Equals(DataContext))
DataContext = dataContext;
return GetValue(ValueProperty);
}
public void UpdateBinding(IBinding binding)
{
_lastBinding = binding;
var ib = binding.Initiate(this, ValueProperty);
if (ib is null)
{
throw new InvalidOperationException("Unable to create binding");
}
BindingOperations.Apply(this, ValueProperty, ib, null);
}
private IBinding? _lastBinding;
} }
} }
} }

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

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

2
src/Avalonia.Controls/ProgressBar.cs

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

4
src/Avalonia.Controls/TextBlock.cs

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

40
src/Avalonia.Controls/TopLevel.cs

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

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

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

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

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

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

4
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

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

3
src/Avalonia.Native/WindowImpl.cs

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

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

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

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

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

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

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

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

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

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

330
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs

@ -0,0 +1,330 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests.Primitives
{
public class SelectingItemsControlTests_SelectedValue
{
[Fact]
public void Setting_SelectedItem_Sets_SelectedValue()
{
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
SelectedValueBinding = new Binding("Name"),
Template = Template()
};
sic.SelectedItem = items[0];
Assert.Equal(items[0].Name, sic.SelectedValue);
}
[Fact]
public void Setting_SelectedIndex_Sets_SelectedValue()
{
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
SelectedValueBinding = new Binding("Name"),
Template = Template()
};
sic.SelectedIndex = 0;
Assert.Equal(items[0].Name, sic.SelectedValue);
}
[Fact]
public void Setting_SelectedItems_Sets_SelectedValue()
{
var items = TestClass.GetItems();
var sic = new ListBox
{
Items = items,
SelectedValueBinding = new Binding("Name"),
Template = Template()
};
sic.SelectedItems = new List<TestClass>
{
items[1],
items[3],
items[4]
};
// When interacting, SelectedItem is the first item in the SelectedItems collection
// But when set here, it's the last
Assert.Equal(items[4].Name, sic.SelectedValue);
}
[Fact]
public void Setting_SelectedValue_Sets_SelectedIndex()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
SelectedValueBinding = new Binding("Name"),
Template = Template()
};
Prepare(sic);
sic.SelectedValue = items[1].Name;
Assert.Equal(1, sic.SelectedIndex);
}
}
[Fact]
public void Setting_SelectedValue_Sets_SelectedItem()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
SelectedValueBinding = new Binding("Name"),
Template = Template()
};
Prepare(sic);
sic.SelectedValue = "Item2";
Assert.Equal(items[1], sic.SelectedItem);
}
}
[Fact]
public void Changing_SelectedValueBinding_Updates_SelectedValue()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
SelectedValueBinding = new Binding("Name"),
Template = Template()
};
sic.SelectedValue = "Item2";
sic.SelectedValueBinding = new Binding("AltProperty");
// Ensure SelectedItem didn't change
Assert.Equal(items[1], sic.SelectedItem);
Assert.Equal("Alt2", sic.SelectedValue);
}
}
[Fact]
public void SelectedValue_With_Null_SelectedValueBinding_Is_Item()
{
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
Template = Template()
};
sic.SelectedIndex = 0;
Assert.Equal(items[0], sic.SelectedValue);
}
[Fact]
public void Setting_SelectedValue_Before_Initialize_Should_Retain_Selection()
{
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
Template = Template(),
SelectedValueBinding = new Binding("Name"),
SelectedValue = "Item2"
};
sic.BeginInit();
sic.EndInit();
Assert.Equal(items[1].Name, sic.SelectedValue);
}
[Fact]
public void Setting_SelectedValue_During_Initialize_Should_Take_Priority_Over_Previous_Value()
{
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
Template = Template(),
SelectedValueBinding = new Binding("Name"),
SelectedValue = "Item2"
};
sic.BeginInit();
sic.SelectedValue = "Item1";
sic.EndInit();
Assert.Equal(items[0].Name, sic.SelectedValue);
}
[Fact]
public void Changing_Items_Should_Clear_SelectedValue()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
Template = Template(),
SelectedValueBinding = new Binding("Name"),
SelectedValue = "Item2"
};
Prepare(sic);
sic.Items = new List<TestClass>
{
new TestClass("NewItem", string.Empty)
};
Assert.Equal(null, sic.SelectedValue);
}
}
[Fact]
public void Setting_SelectedValue_Should_Raise_SelectionChanged_Event()
{
// Unlike SelectedIndex/SelectedItem tests, we need the ItemsControl to
// initialize so that SelectedValue can actually be looked up
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
Template = Template(),
SelectedValueBinding = new Binding("Name"),
};
Prepare(sic);
var called = false;
sic.SelectionChanged += (s, e) =>
{
Assert.Same(items[1], e.AddedItems.Cast<object>().Single());
Assert.Empty(e.RemovedItems);
called = true;
};
sic.SelectedValue = "Item2";
Assert.True(called);
}
}
[Fact]
public void Clearing_SelectedValue_Should_Raise_SelectionChanged_Event()
{
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
Template = Template(),
SelectedValueBinding = new Binding("Name"),
SelectedValue = "Item2"
};
var called = false;
sic.SelectionChanged += (s, e) =>
{
Assert.Same(items[1], e.RemovedItems.Cast<object>().Single());
Assert.Empty(e.AddedItems);
called = true;
};
sic.SelectedValue = null;
Assert.True(called);
}
private static FuncControlTemplate Template()
{
return new FuncControlTemplate<SelectingItemsControl>((control, scope) =>
new ItemsPresenter
{
Name = "itemsPresenter",
[~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty],
}.RegisterInNameScope(scope));
}
private static void Prepare(SelectingItemsControl target)
{
var root = new TestRoot
{
Child = target,
Width = 100,
Height = 100,
Styles =
{
new Style(x => x.Is<SelectingItemsControl>())
{
Setters =
{
new Setter(ListBox.TemplateProperty, Template()),
},
},
},
};
root.LayoutManager.ExecuteInitialLayoutPass();
}
}
internal class TestClass
{
public TestClass(string name, string alt)
{
Name = name;
AltProperty = alt;
}
public string Name { get; set; }
public string AltProperty { get; set; }
public static List<TestClass> GetItems()
{
return new List<TestClass>
{
new TestClass("Item1", "Alt1"),
new TestClass("Item2", "Alt2"),
new TestClass("Item3", "Alt3"),
new TestClass("Item4", "Alt4"),
new TestClass("Item5", "Alt5"),
};
}
}
}

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

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save