Browse Source

Merge branch 'master' into progressBar-automationPeer

pull/10275/head
Max Katz 3 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
# static fields should have s_ prefix
dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
# private static fields should have s_ prefix
dotnet_naming_rule.private_static_fields_should_have_prefix.severity = suggestion
dotnet_naming_rule.private_static_fields_should_have_prefix.symbols = private_static_fields
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.static_fields.required_modifiers = static
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
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.static_prefix_style.capitalization = camel_case
dotnet_naming_style.private_static_prefix_style.required_prefix = s_
dotnet_naming_style.private_static_prefix_style.capitalization = camel_case
# internal and private fields should be _camelCase
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_semicolon_in_for_statement = true
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_comma = false
csharp_space_before_dot = false
@ -211,5 +212,5 @@ indent_size = 2
# Shell scripts
[*.sh]
end_of_line = lf
[*.{cmd, bat}]
[*.{cmd,bat}]
end_of_line = crlf

6
build/HarfBuzzSharp.props

@ -1,7 +1,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="HarfBuzzSharp" Version="2.8.2.1-preview.108" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2.1-preview.108" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.WebAssembly" 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.3" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="2.8.2.3" />
</ItemGroup>
</Project>

2
build/ImageSharp.props

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

2
build/Moq.props

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

1
build/SharedVersion.props

@ -3,6 +3,7 @@
<PropertyGroup>
<Product>Avalonia</Product>
<Version>11.0.999</Version>
<Authors>Avalonia Team</Authors>
<Copyright>Copyright 2022 &#169; The AvaloniaUI Project</Copyright>
<PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
<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">
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="2.88.1" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.1" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.1" />
<PackageReference Include="SkiaSharp" Version="2.88.3" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.3" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.3" />
</ItemGroup>
</Project>

15
build/XUnit.props

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

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

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

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

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

54
samples/IntegrationTestApp/MainWindow.axaml

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

91
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -7,9 +7,13 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Markup.Xaml;
using Avalonia.VisualTree;
using Microsoft.CodeAnalysis;
using Avalonia.Controls.Primitives;
using Avalonia.Threading;
using Avalonia.Controls.Primitives.PopupPositioning;
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()
{
var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
@ -175,6 +262,10 @@ namespace IntegrationTestApp
this.Get<ListBox>("BasicListBox").SelectedIndex = -1;
if (source?.Name == "MenuClickedMenuItemReset")
this.Get<TextBlock>("ClickedMenuItem").Text = "None";
if (source?.Name == "ShowTransparentWindow")
ShowTransparentWindow();
if (source?.Name == "ShowTransparentPopup")
ShowTransparentPopup();
if (source?.Name == "ShowWindow")
ShowWindow();
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="..\MobileSandbox\MobileSandbox.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
<!-- For native controls test -->
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
</ItemGroup>

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

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

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

@ -79,15 +79,12 @@ namespace Avalonia.Animation
/// <param name="culture">culture of the string</param>
/// <exception cref="FormatException">Thrown if the string does not have 4 values</exception>
/// <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}\"."))
{
return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
}
using var tokenizer = new StringTokenizer(value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\".");
return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
}
/// <summary>

38
src/Avalonia.Base/AvaloniaObject.cs

@ -152,7 +152,7 @@ namespace Avalonia
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
_values?.ClearLocalValue(property);
_values.ClearLocalValue(property);
}
/// <summary>
@ -242,7 +242,14 @@ namespace Avalonia
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)
{
_ = property ?? throw new ArgumentNullException(nameof(property));
@ -261,7 +268,7 @@ namespace Avalonia
VerifyAccess();
return _values?.IsAnimating(property) ?? false;
return _values.IsAnimating(property);
}
/// <summary>
@ -279,7 +286,7 @@ namespace Avalonia
VerifyAccess();
return _values?.IsSet(property) ?? false;
return _values.IsSet(property);
}
/// <summary>
@ -515,14 +522,12 @@ namespace Avalonia
/// <param name="property">The property.</param>
public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property);
/// <inheritdoc/>
internal void AddInheritanceChild(AvaloniaObject child)
{
_inheritanceChildren ??= new List<AvaloniaObject>();
_inheritanceChildren.Add(child);
}
/// <inheritdoc/>
internal void RemoveInheritanceChild(AvaloniaObject child)
{
_inheritanceChildren?.Remove(child);
@ -541,24 +546,11 @@ namespace Avalonia
return new AvaloniaPropertyValue(
property,
GetValue(property),
BindingPriority.Unset,
"Local Value");
}
else if (_values != null)
{
var result = _values.GetDiagnostic(property);
if (result != null)
{
return result;
}
BindingPriority.LocalValue,
null);
}
return new AvaloniaPropertyValue(
property,
GetValue(property),
BindingPriority.Unset,
"Unset");
return _values.GetDiagnostic(property);
}
internal ValueStore GetValueStore() => _values;

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

@ -30,7 +30,7 @@ namespace Avalonia.Data.Converters
{
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)

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

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

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

@ -1,36 +1,28 @@
using System;
using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Input
{
public class DragEventArgs : RoutedEventArgs
{
private Interactive _target;
private Point _targetLocation;
private readonly Interactive _target;
private readonly Point _targetLocation;
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)
{
var point = new Point(0, 0);
if (relativeTo == null)
{
throw new ArgumentNullException(nameof(relativeTo));
}
if (_target != null)
{
point = _target.TranslatePoint(_targetLocation, relativeTo) ?? point;
}
return point;
return _target.TranslatePoint(_targetLocation, relativeTo) ?? new Point(0, 0);
}
[Unstable]

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

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

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

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

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

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.VisualTree;
namespace Avalonia.Input.Navigation
@ -54,8 +52,7 @@ namespace Avalonia.Input.Navigation
// Avoid the endless loop here for Cycle groups
if (loopStartElement == nextTabElement)
break;
if (loopStartElement == null)
loopStartElement = nextTabElement;
loopStartElement ??= nextTabElement;
var firstTabElementInside = GetNextTab(null, nextTabElement, true);
if (firstTabElementInside != null)
@ -80,12 +77,9 @@ namespace Avalonia.Input.Navigation
public static IInputElement? GetNextTabOutside(ICustomKeyboardNavigation e)
{
if (e is IInputElement container)
if (e is IInputElement container && GetLastInTree(container) is { } last)
{
var last = GetLastInTree(container);
if (last is object)
return GetNextTab(last, false);
return GetNextTab(last, false);
}
return null;
@ -93,11 +87,8 @@ namespace Avalonia.Input.Navigation
public static IInputElement? GetPrevTab(IInputElement? e, IInputElement? container, bool goDownOnly)
{
if (e is null && container is null)
throw new InvalidOperationException("Either 'e' or 'container' must be non-null.");
if (container is null)
container = GetGroupParent(e!);
container ??=
GetGroupParent(e ?? throw new InvalidOperationException("Either 'e' or 'container' must be non-null."));
KeyboardNavigationMode tabbingType = GetKeyNavigationMode(container);
@ -163,8 +154,7 @@ namespace Avalonia.Input.Navigation
// Avoid the endless loop here
if (loopStartElement == nextTabElement)
break;
if (loopStartElement == null)
loopStartElement = nextTabElement;
loopStartElement ??= nextTabElement;
// At this point nextTabElement is TabGroup
var lastTabElementInside = GetPrevTab(null, nextTabElement, true);
@ -189,22 +179,18 @@ namespace Avalonia.Input.Navigation
public static IInputElement? GetPrevTabOutside(ICustomKeyboardNavigation e)
{
if (e is IInputElement container)
if (e is IInputElement container && GetFirstChild(container) is { } first)
{
var first = GetFirstChild(container);
if (first is object)
return GetPrevTab(first, null, false);
return GetPrevTab(first, null, false);
}
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
if (iie != null && !iie.IsKeyboardFocusWithin)
if (e != null && !e.IsKeyboardFocusWithin)
{
var focusedElement = (FocusManager.Instance as FocusManager)?.GetFocusedElement(e);
if (focusedElement != null)
@ -229,13 +215,11 @@ namespace Avalonia.Input.Navigation
private static IInputElement? GetFirstChild(IInputElement e)
{
// 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 the first visible element.
var uiElement = e as InputElement;
if (uiElement is null || IsVisibleAndEnabled(uiElement))
if (e is not InputElement uiElement || IsVisibleAndEnabled(uiElement))
{
if (e is Visual elementAsVisual)
{
@ -265,7 +249,7 @@ namespace Avalonia.Input.Navigation
private static IInputElement? GetLastChild(IInputElement e)
{
// 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 the last visible element.
@ -273,9 +257,7 @@ namespace Avalonia.Input.Navigation
if (uiElement == null || IsVisibleAndEnabled(uiElement))
{
var elementAsVisual = e as Visual;
if (elementAsVisual != null)
if (e is Visual elementAsVisual)
{
var children = elementAsVisual.VisualChildren;
var count = children.Count;
@ -322,7 +304,7 @@ namespace Avalonia.Input.Navigation
return firstTabElement;
}
private static IInputElement? GetLastInTree(IInputElement container)
private static IInputElement GetLastInTree(IInputElement container)
{
IInputElement? result;
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="includeSelf">If given logical should be included in search.</param>
/// <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)
{
@ -120,7 +120,7 @@ namespace Avalonia.LogicalTree
/// <param name="logical">The logical.</param>
/// <param name="includeSelf">If given logical should be included in search.</param>
/// <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)
{
@ -185,7 +185,7 @@ namespace Avalonia.LogicalTree
/// True if <paramref name="logical"/> is an ancestor of <paramref name="target"/>;
/// otherwise false.
/// </returns>
public static bool IsLogicalAncestorOf(this ILogical logical, ILogical target)
public static bool IsLogicalAncestorOf(this ILogical? logical, ILogical? target)
{
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="color">The parsed color</param>
/// <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;
if (s is null)
{
return false;
}
if (s.Length == 0)
if (string.IsNullOrEmpty(s))
{
return false;
}

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

@ -240,7 +240,7 @@ namespace Avalonia.Media
/// </summary>
/// <param name="foreground">The foreground brush.</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));

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

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

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

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

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

@ -119,7 +119,7 @@ namespace Avalonia.Media
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.RelativeOrAbsolute);
@ -188,7 +188,7 @@ namespace Avalonia.Media
{
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;
if (Source != null)
{
hash = (hash * 16777619) ^ Source.GetHashCode();
}
hash = (hash * 16777619) ^ Source.GetHashCode();
if (BaseUri != null)
{

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

@ -1354,7 +1354,7 @@ namespace Avalonia.Media
{
var highlightBounds = currentLine.GetTextBounds(x0,x1 - x0);
if (highlightBounds != null)
if (highlightBounds.Count > 0)
{
foreach (var bound in highlightBounds)
{
@ -1365,7 +1365,7 @@ namespace Avalonia.Media
// Convert logical units (which extend leftward from the right edge
// 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,
// is the distance leftward from the right edge of the formatting rectangle
// whose width is the paragraph width passed to FormatLine.
@ -1384,7 +1384,7 @@ namespace Avalonia.Media
else
{
accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union);
}
}
}
}
}

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

@ -15,8 +15,8 @@ namespace Avalonia.Media
/// <summary>
/// Defines the <see cref="Geometry"/> property.
/// </summary>
public static readonly StyledProperty<Geometry> GeometryProperty =
AvaloniaProperty.Register<GeometryDrawing, Geometry>(nameof(Geometry));
public static readonly StyledProperty<Geometry?> GeometryProperty =
AvaloniaProperty.Register<GeometryDrawing, Geometry?>(nameof(Geometry));
/// <summary>
/// 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"/>.
/// </summary>
[Content]
public Geometry Geometry
public Geometry? Geometry
{
get => GetValue(GeometryProperty);
set => SetValue(GeometryProperty, value);

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

@ -2,19 +2,19 @@
{
public class GlyphRunDrawing : Drawing
{
public static readonly StyledProperty<IBrush> ForegroundProperty =
AvaloniaProperty.Register<GlyphRunDrawing, IBrush>(nameof(Foreground));
public static readonly StyledProperty<IBrush?> ForegroundProperty =
AvaloniaProperty.Register<GlyphRunDrawing, IBrush?>(nameof(Foreground));
public static readonly StyledProperty<GlyphRun> GlyphRunProperty =
AvaloniaProperty.Register<GlyphRunDrawing, GlyphRun>(nameof(GlyphRun));
public static readonly StyledProperty<GlyphRun?> GlyphRunProperty =
AvaloniaProperty.Register<GlyphRunDrawing, GlyphRun?>(nameof(GlyphRun));
public IBrush Foreground
public IBrush? Foreground
{
get => GetValue(ForegroundProperty);
set => SetValue(ForegroundProperty, value);
}
public GlyphRun GlyphRun
public GlyphRun? GlyphRun
{
get => GetValue(GlyphRunProperty);
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="hslColor">The parsed <see cref="HslColor"/>.</param>
/// <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;

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="hsvColor">The parsed <see cref="HsvColor"/>.</param>
/// <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;

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

@ -1,5 +1,4 @@
using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Media
{
@ -12,6 +11,6 @@ namespace Avalonia.Media
/// <summary>
/// Gets the visual to draw.
/// </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;
}
else if (other is null)
{
return false;
}
if (Offset != other.Offset)
{
return false;
}
return SequenceEqual(Dashes, other.Dashes);
return other is not null && Offset == other.Offset && SequenceEqual(_dashes, other.Dashes);
}
/// <inheritdoc/>
@ -58,30 +49,27 @@ namespace Avalonia.Media.Immutable
var hashCode = 717868523;
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;
}
private static bool SequenceEqual(IReadOnlyList<double> left, IReadOnlyList<double>? right)
private static bool SequenceEqual(double[] left, IReadOnlyList<double>? right)
{
if (ReferenceEquals(left, right))
{
return true;
}
if (left == null || right == null || left.Count != right.Count)
if (right is null || left.Length != right.Count)
{
return false;
}
for (var c = 0; c < left.Count; c++)
for (var c = 0; c < left.Length; c++)
{
if (left[c] != right[c])
{

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

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

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

@ -22,8 +22,8 @@ namespace Avalonia.Media
/// <summary>
/// Defines the <see cref="Stroke"/> property.
/// </summary>
public static readonly StyledProperty<IBrush> StrokeProperty =
AvaloniaProperty.Register<TextDecoration, IBrush>(nameof(Stroke));
public static readonly StyledProperty<IBrush?> StrokeProperty =
AvaloniaProperty.Register<TextDecoration, IBrush?>(nameof(Stroke));
/// <summary>
/// Defines the <see cref="StrokeThicknessUnit"/> property.
@ -34,8 +34,8 @@ namespace Avalonia.Media
/// <summary>
/// Defines the <see cref="StrokeDashArray"/> property.
/// </summary>
public static readonly StyledProperty<AvaloniaList<double>> StrokeDashArrayProperty =
AvaloniaProperty.Register<TextDecoration, AvaloniaList<double>>(nameof(StrokeDashArray));
public static readonly StyledProperty<AvaloniaList<double>?> StrokeDashArrayProperty =
AvaloniaProperty.Register<TextDecoration, AvaloniaList<double>?>(nameof(StrokeDashArray));
/// <summary>
/// Defines the <see cref="StrokeDashOffset"/> property.
@ -82,7 +82,7 @@ namespace Avalonia.Media
/// <summary>
/// Gets or sets the <see cref="IBrush"/> that specifies how the <see cref="TextDecoration"/> is painted.
/// </summary>
public IBrush Stroke
public IBrush? Stroke
{
get { return GetValue(StrokeProperty); }
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
/// that is used to draw the <see cref="TextDecoration"/>.
/// </summary>
public AvaloniaList<double> StrokeDashArray
public AvaloniaList<double>? StrokeDashArray
{
get { return GetValue(StrokeDashArrayProperty); }
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));
if (intersections != null && intersections.Count > 0)
if (intersections.Count > 0)
{
var last = baselineOrigin.X;
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,
defaultTypeface.Stretch, defaultTypeface.FontFamily, defaultProperties.CultureInfo,
out var fallbackTypeface);
var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface);
if (matchFound && TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count))
if (matchFound)
{
//Fallback found
return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface),
biDiLevel);
// Fallback found
var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface);
if (TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count))
{
return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface),
biDiLevel);
}
}
// no fallback found

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

@ -1,5 +1,4 @@
using Avalonia.Media.Immutable;
using Avalonia.VisualTree;
namespace Avalonia.Media
{
@ -11,8 +10,8 @@ namespace Avalonia.Media
/// <summary>
/// Defines the <see cref="Visual"/> property.
/// </summary>
public static readonly StyledProperty<Visual> VisualProperty =
AvaloniaProperty.Register<VisualBrush, Visual>(nameof(Visual));
public static readonly StyledProperty<Visual?> VisualProperty =
AvaloniaProperty.Register<VisualBrush, Visual?>(nameof(Visual));
static VisualBrush()
{
@ -38,7 +37,7 @@ namespace Avalonia.Media
/// <summary>
/// Gets or sets the visual to draw.
/// </summary>
public Visual Visual
public Visual? Visual
{
get { return GetValue(VisualProperty); }
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="p1">The first 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>
/// Draws a geometry.
@ -91,7 +91,7 @@ namespace Avalonia.Platform
/// </summary>
/// <param name="foreground">The foreground.</param>
/// <param name="glyphRun">The glyph run.</param>
void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun);
void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun);
/// <summary>
/// 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)
{
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()
.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);
Resources.Remove(Constants.AvaloniaResourceName);
var indexLength = new BinaryReader(resources).ReadInt32();
var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength));
var baseOffset = indexLength + 4;
AvaloniaResources = index.ToDictionary(r => GetPathRooted(r), r => (IAssetDescriptor)
new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size));
}
}
var indexLength = new BinaryReader(resources).ReadInt32();
var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength));
var baseOffset = indexLength + 4;
AvaloniaResources = index.ToDictionary(GetPathRooted, r => (IAssetDescriptor)
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>? AvaloniaResources { get; }
public string? Name { get; }
private static string GetPathRooted(AvaloniaResourcesIndexEntry r) =>
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
{
private readonly CompositionPropertySet _propertySet;
internal CompositionAnimation(Compositor compositor) : base(compositor, null!)
internal CompositionAnimation(Compositor compositor) : base(compositor, null)
{
_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 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 IDictionary<string, ICompositionAnimationBase> _innerface;
internal ImplicitAnimationCollection(Compositor compositor) : base(compositor, null!)
internal ImplicitAnimationCollection(Compositor compositor) : base(compositor, null)
{
_innerface = _inner;
}

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

@ -20,15 +20,17 @@ public class CompositingRenderer : IRendererWithCompositor
{
private readonly IRenderRoot _root;
private readonly Compositor _compositor;
CompositionDrawingContext _recorder = new();
DrawingContext _recordingContext;
private HashSet<Visual> _dirty = new();
private HashSet<Visual> _recalculateChildren = new();
private readonly CompositionDrawingContext _recorder = new();
private readonly DrawingContext _recordingContext;
private readonly HashSet<Visual> _dirty = new();
private readonly HashSet<Visual> _recalculateChildren = new();
private readonly Action _update;
private bool _queuedUpdate;
private Action _update;
private bool _updating;
private bool _isDisposed;
internal CompositionTarget CompositionTarget;
internal CompositionTarget CompositionTarget { get; }
/// <summary>
/// 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/>
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)
{
_root = root;
@ -66,7 +79,7 @@ public class CompositingRenderer : IRendererWithCompositor
/// <inheritdoc/>
public event EventHandler<SceneInvalidatedEventArgs>? SceneInvalidated;
void QueueUpdate()
private void QueueUpdate()
{
if(_queuedUpdate)
return;
@ -77,9 +90,11 @@ public class CompositingRenderer : IRendererWithCompositor
/// <inheritdoc/>
public void AddDirty(Visual visual)
{
if (_isDisposed)
return;
if (_updating)
throw new InvalidOperationException("Visual was invalidated during the render pass");
_dirty.Add((Visual)visual);
_dirty.Add(visual);
QueueUpdate();
}
@ -126,9 +141,11 @@ public class CompositingRenderer : IRendererWithCompositor
/// <inheritdoc/>
public void RecalculateChildren(Visual visual)
{
if (_isDisposed)
return;
if (_updating)
throw new InvalidOperationException("Visual was invalidated during the render pass");
_recalculateChildren.Add((Visual)visual);
_recalculateChildren.Add(visual);
QueueUpdate();
}
@ -171,7 +188,7 @@ public class CompositingRenderer : IRendererWithCompositor
if (sortedChildren != null)
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;
break;
@ -179,7 +196,7 @@ public class CompositingRenderer : IRendererWithCompositor
}
else
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;
break;
@ -201,7 +218,7 @@ public class CompositingRenderer : IRendererWithCompositor
{
foreach (var ch in sortedChildren)
{
var compositionChild = ((Visual)ch.visual).CompositionVisual;
var compositionChild = ch.visual.CompositionVisual;
if (compositionChild != null)
compositionChildren.Add(compositionChild);
}
@ -210,7 +227,7 @@ public class CompositingRenderer : IRendererWithCompositor
else
foreach (var ch in v.GetVisualChildren())
{
var compositionChild = ((Visual)ch).CompositionVisual;
var compositionChild = ch.CompositionVisual;
if (compositionChild != null)
compositionChildren.Add(compositionChild);
}
@ -289,13 +306,18 @@ public class CompositingRenderer : IRendererWithCompositor
_updating = false;
}
}
/// <inheritdoc />
public void Resized(Size size)
{
}
/// <inheritdoc />
public void Paint(Rect rect)
{
if (_isDisposed)
return;
QueueUpdate();
CompositionTarget.RequestRedraw();
if(RenderOnlyOnRenderThread && Compositor.Loop.RunsInBackground)
@ -304,17 +326,34 @@ public class CompositingRenderer : IRendererWithCompositor
CompositionTarget.ImmediateUIThreadRender();
}
public void Start() => CompositionTarget.IsEnabled = true;
public void Stop()
/// <inheritdoc />
public void Start()
{
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()
{
if (_isDisposed)
return;
_isDisposed = true;
_dirty.Clear();
_recalculateChildren.Clear();
SceneInvalidated = null;
Stop();
CompositionTarget.Dispose();
@ -323,9 +362,4 @@ public class CompositingRenderer : IRendererWithCompositor
if (Compositor.Loop.RunsInBackground)
_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 Avalonia.Rendering.Composition.Server;
using Avalonia.Threading;
@ -7,7 +6,7 @@ namespace Avalonia.Rendering.Composition;
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))
{
}

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

@ -22,7 +22,7 @@ namespace Avalonia.Rendering.Composition
public ImplicitAnimationCollection? ImplicitAnimations { get; set; }
private protected InlineDictionary<CompositionProperty, IAnimationInstance> PendingAnimations;
internal CompositionObject(Compositor compositor, ServerObject server)
internal CompositionObject(Compositor compositor, ServerObject? server)
{
Compositor = compositor;
Server = server;
@ -32,7 +32,7 @@ namespace Avalonia.Rendering.Composition
/// The associated Compositor
/// </summary>
public Compositor Compositor { get; }
internal ServerObject Server { get; }
internal ServerObject? Server { get; }
public bool IsDisposed { get; private set; }
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, 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/>
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>();
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;
/// <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>();
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)
{
if (context.ForeignFunctionInterface == null)
return default;
var args = new List<ExpressionVariant>();
foreach (var expr in Parameters)
args.Add(expr.Evaluate(ref context));

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

@ -1,5 +1,4 @@
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>

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

@ -66,7 +66,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont
_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);
}
@ -86,7 +86,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont
_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);
}

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

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

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

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

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

@ -48,8 +48,10 @@ namespace Avalonia.Rendering
/// <inheritdoc/>
void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush)
{
var visual = brush.Visual;
Render(new DrawingContext(context), visual, visual.Bounds);
if (brush.Visual is { } visual)
{
Render(new DrawingContext(context), visual, visual.Bounds);
}
}
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();
if (Material != null)
{
var rect = Rect.Rect;
return rect.ContainsExclusive(p);
}
var rect = Rect.Rect;
return rect.ContainsExclusive(p);
}
return false;

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

@ -212,7 +212,7 @@ namespace Avalonia.Utilities
var toTypeConverter = TypeDescriptor.GetConverter(toUnderl);
if (toTypeConverter.CanConvertFrom(from) == true)
if (toTypeConverter.CanConvertFrom(from))
{
result = toTypeConverter.ConvertFrom(null, culture, value);
return true;
@ -220,7 +220,7 @@ namespace Avalonia.Utilities
var fromTypeConverter = TypeDescriptor.GetConverter(from);
if (fromTypeConverter.CanConvertTo(toUnderl) == true)
if (fromTypeConverter.CanConvertTo(toUnderl))
{
result = fromTypeConverter.ConvertTo(null, culture, value, toUnderl);
return true;
@ -329,7 +329,7 @@ namespace Avalonia.Utilities
}
[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))
{
@ -369,11 +369,6 @@ namespace Avalonia.Utilities
/// </remarks>
public static bool IsNumeric(Type type)
{
if (type == null)
{
return false;
}
var underlyingType = Nullable.GetUnderlyingType(type);
if (underlyingType != null)

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

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Avalonia.Threading;
@ -15,7 +11,7 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
{
private readonly Func<TSender, EventHandler<TEventArgs>, Action> _subscribe;
readonly ConditionalWeakTable<object, Subscription> _subscriptions = new();
private readonly ConditionalWeakTable<object, Subscription> _subscriptions = new();
internal WeakEvent(
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 TSender _target;
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 WeakHashList<IWeakEventSubscriber<TEventArgs>> _list = new();
private bool _compactScheduled;
@ -114,7 +60,7 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
_unsubscribe = ev._subscribe(target, OnEvent);
}
void Destroy()
private void Destroy()
{
if(_destroyed)
return;
@ -134,15 +80,15 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
ScheduleCompact();
}
void ScheduleCompact()
private void ScheduleCompact()
{
if(_compactScheduled || _destroyed)
return;
_compactScheduled = true;
Dispatcher.UIThread.Post(_compact, DispatcherPriority.Background);
}
void Compact()
private void Compact()
{
if(!_compactScheduled)
return;
@ -152,7 +98,7 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
Destroy();
}
void OnEvent(object? sender, TEventArgs eventArgs)
private void OnEvent(object? sender, TEventArgs eventArgs)
{
var alive = _list.GetAlive();
if(alive == null)
@ -196,4 +142,4 @@ public class WeakEvent
return () => unsubscribe(s, handler);
});
}
}
}

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

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

34
src/Avalonia.Base/Visual.cs

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

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

@ -46,7 +46,7 @@ namespace Avalonia.VisualTree
Visual? v = visual ?? throw new ArgumentNullException(nameof(visual));
var result = 0;
v = v?.VisualParent;
v = v.VisualParent;
while (v != null)
{
@ -64,17 +64,13 @@ namespace Avalonia.VisualTree
/// <param name="visual">The first visual.</param>
/// <param name="target">The second visual.</param>
/// <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 (target is null)
if (visual is null || target is null)
{
return null;
}
Visual? t = target;
void GoUpwards(ref Visual? node, int count)
{
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.
// By doing that we can sometimes find out that other node is our lowest common ancestor.
var firstHeight = CalculateDistanceFromRoot(v);
@ -144,7 +143,7 @@ namespace Avalonia.VisualTree
/// <param name="visual">The visual.</param>
/// <param name="includeSelf">If given visual should be included in search.</param>
/// <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)
{
@ -173,7 +172,7 @@ namespace Avalonia.VisualTree
/// <param name="visual">The visual.</param>
/// <param name="includeSelf">If given visual should be included in search.</param>
/// <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)
{
@ -392,7 +391,7 @@ namespace Avalonia.VisualTree
/// True if <paramref name="visual"/> is an ancestor of <paramref name="target"/>;
/// otherwise false.
/// </returns>
public static bool IsVisualAncestorOf(this Visual visual, Visual target)
public static bool IsVisualAncestorOf(this Visual? visual, Visual? target)
{
Visual? current = target?.VisualParent;

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

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

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

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

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

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

4
src/Avalonia.Controls/Button.cs

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

4
src/Avalonia.Controls/Control.cs

@ -2,14 +2,12 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.Threading;
@ -211,8 +209,6 @@ namespace Avalonia.Controls
remove => RemoveHandler(SizeChangedEvent, value);
}
public new Control? Parent => (Control?)base.Parent;
/// <inheritdoc/>
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)
{
}
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.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Automation.Peers;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
@ -17,7 +16,6 @@ using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
using Avalonia.Styling;
using Avalonia.VisualTree;
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.
/// </summary>
[AssignBinding]
[InheritDataTypeFromItems(nameof(Items))]
public IBinding? DisplayMemberBinding
{
get { return GetValue(DisplayMemberBindingProperty); }
set { SetValue(DisplayMemberBindingProperty, value); }
get => GetValue(DisplayMemberBindingProperty);
set => SetValue(DisplayMemberBindingProperty, value);
}
private IEnumerable? _items = new AvaloniaList<object>();
@ -134,8 +133,8 @@ namespace Avalonia.Controls
[Content]
public IEnumerable? Items
{
get { return _items; }
set { SetAndRaise(ItemsProperty, ref _items, value); }
get => _items;
set => SetAndRaise(ItemsProperty, ref _items, value);
}
/// <summary>
@ -143,8 +142,8 @@ namespace Avalonia.Controls
/// </summary>
public ControlTheme? ItemContainerTheme
{
get { return GetValue(ItemContainerThemeProperty); }
set { SetValue(ItemContainerThemeProperty, value); }
get => GetValue(ItemContainerThemeProperty);
set => SetValue(ItemContainerThemeProperty, value);
}
/// <summary>
@ -161,8 +160,8 @@ namespace Avalonia.Controls
/// </summary>
public ITemplate<Panel> ItemsPanel
{
get { return GetValue(ItemsPanelProperty); }
set { SetValue(ItemsPanelProperty, value); }
get => GetValue(ItemsPanelProperty);
set => SetValue(ItemsPanelProperty, value);
}
/// <summary>
@ -171,8 +170,8 @@ namespace Avalonia.Controls
[InheritDataTypeFromItems(nameof(Items))]
public IDataTemplate? ItemTemplate
{
get { return GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
get => GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
/// <summary>
@ -264,8 +263,8 @@ namespace Avalonia.Controls
/// </summary>
public bool AreHorizontalSnapPointsRegular
{
get { return GetValue(AreHorizontalSnapPointsRegularProperty); }
set { SetValue(AreHorizontalSnapPointsRegularProperty, value); }
get => GetValue(AreHorizontalSnapPointsRegularProperty);
set => SetValue(AreHorizontalSnapPointsRegularProperty, value);
}
/// <summary>
@ -273,8 +272,8 @@ namespace Avalonia.Controls
/// </summary>
public bool AreVerticalSnapPointsRegular
{
get { return GetValue(AreVerticalSnapPointsRegularProperty); }
set { SetValue(AreVerticalSnapPointsRegularProperty, value); }
get => GetValue(AreVerticalSnapPointsRegularProperty);
set => SetValue(AreVerticalSnapPointsRegularProperty, value);
}
/// <summary>

1
src/Avalonia.Controls/ListBox.cs

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

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

@ -28,19 +28,19 @@ namespace Avalonia.Controls.Presenters
/// Defines the <see cref="AreHorizontalSnapPointsRegular"/> property.
/// </summary>
public static readonly StyledProperty<bool> AreHorizontalSnapPointsRegularProperty =
AvaloniaProperty.Register<ItemsControl, bool>(nameof(AreHorizontalSnapPointsRegular));
AvaloniaProperty.Register<ItemsPresenter, bool>(nameof(AreHorizontalSnapPointsRegular));
/// <summary>
/// Defines the <see cref="AreVerticalSnapPointsRegular"/> property.
/// </summary>
public static readonly StyledProperty<bool> AreVerticalSnapPointsRegularProperty =
AvaloniaProperty.Register<ItemsControl, bool>(nameof(AreVerticalSnapPointsRegular));
AvaloniaProperty.Register<ItemsPresenter, bool>(nameof(AreVerticalSnapPointsRegular));
/// <summary>
/// Defines the <see cref="HorizontalSnapPointsChanged"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> HorizontalSnapPointsChangedEvent =
RoutedEvent.Register<StackPanel, RoutedEventArgs>(
RoutedEvent.Register<ItemsPresenter, RoutedEventArgs>(
nameof(HorizontalSnapPointsChanged),
RoutingStrategies.Bubble);
@ -48,7 +48,7 @@ namespace Avalonia.Controls.Presenters
/// Defines the <see cref="VerticalSnapPointsChanged"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> VerticalSnapPointsChangedEvent =
RoutedEvent.Register<StackPanel, RoutedEventArgs>(
RoutedEvent.Register<ItemsPresenter, RoutedEventArgs>(
nameof(VerticalSnapPointsChanged),
RoutingStrategies.Bubble);
@ -139,7 +139,7 @@ namespace Avalonia.Controls.Presenters
Size IScrollable.Viewport => _logicalScrollable?.Viewport ?? default;
/// <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>
public bool AreHorizontalSnapPointsRegular
{
@ -148,7 +148,7 @@ namespace Avalonia.Controls.Presenters
}
/// <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>
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 */ }
}
protected internal override Interactive? InteractiveParent => Parent;
protected internal override Interactive? InteractiveParent => (Interactive?)VisualParent;
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))
{
e = e.Parent;
e = e.VisualParent as Control;
}
if (e is object)

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

@ -72,12 +72,12 @@ namespace Avalonia.Controls.Primitives
/// <remarks>
/// Popup events are passed to their parent window. This facilitates this.
/// </remarks>
protected internal override Interactive? InteractiveParent => Parent;
protected internal override Interactive? InteractiveParent => (Interactive?)Parent;
/// <summary>
/// Gets the control that is hosting the popup root.
/// </summary>
Visual? IHostedVisualTreeRoot.Host => Parent;
Visual? IHostedVisualTreeRoot.Host => VisualParent;
/// <summary>
/// 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.ComponentModel;
using System.Linq;
using System.Xml.Linq;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Selection;
using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
@ -66,6 +65,19 @@ namespace Avalonia.Controls.Primitives
(o, v) => o.SelectedItem = v,
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>
/// Defines the <see cref="SelectedItems"/> property.
/// </summary>
@ -129,6 +141,8 @@ namespace Avalonia.Controls.Primitives
private bool _ignoreContainerSelectionChanged;
private UpdateState? _updateState;
private bool _hasScrolledToSelectedItem;
private BindingHelper? _bindingHelper;
private bool _isSelectionChangeActive;
/// <summary>
/// Initializes static members of the <see cref="SelectingItemsControl"/> class.
@ -143,8 +157,8 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public event EventHandler<SelectionChangedEventArgs>? SelectionChanged
{
add { AddHandler(SelectionChangedEvent, value); }
remove { RemoveHandler(SelectionChangedEvent, value); }
add => AddHandler(SelectionChangedEvent, value);
remove => RemoveHandler(SelectionChangedEvent, value);
}
/// <summary>
@ -152,8 +166,8 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public bool AutoScrollToSelectedItem
{
get { return GetValue(AutoScrollToSelectedItemProperty); }
set { SetValue(AutoScrollToSelectedItemProperty, value); }
get => GetValue(AutoScrollToSelectedItemProperty);
set => SetValue(AutoScrollToSelectedItemProperty, value);
}
/// <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>
/// Gets or sets the selected items.
/// </summary>
@ -322,8 +358,8 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public bool IsTextSearchEnabled
{
get { return GetValue(IsTextSearchEnabledProperty); }
set { SetValue(IsTextSearchEnabledProperty, value); }
get => GetValue(IsTextSearchEnabledProperty);
set => SetValue(IsTextSearchEnabledProperty, value);
}
/// <summary>
@ -332,8 +368,8 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public bool WrapSelection
{
get { return GetValue(WrapSelectionProperty); }
set { SetValue(WrapSelectionProperty, value); }
get => GetValue(WrapSelectionProperty);
set => SetValue(WrapSelectionProperty, value);
}
/// <summary>
@ -345,8 +381,8 @@ namespace Avalonia.Controls.Primitives
/// </remarks>
protected SelectionMode SelectionMode
{
get { return GetValue(SelectionModeProperty); }
set { SetValue(SelectionModeProperty, value); }
get => GetValue(SelectionModeProperty);
set => SetValue(SelectionModeProperty, value);
}
/// <summary>
@ -609,6 +645,60 @@ namespace Avalonia.Controls.Primitives
{
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>
@ -815,6 +905,10 @@ namespace Avalonia.Controls.Primitives
new BindingValue<IList?>(SelectedItems));
_oldSelectedItems = SelectedItems;
}
else if (e.PropertyName == nameof(ISelectionModel.Source))
{
ClearValue(SelectedValueProperty);
}
}
/// <summary>
@ -845,6 +939,11 @@ namespace Avalonia.Controls.Primitives
Mark(i, false);
}
if (!_isSelectionChangeActive)
{
UpdateSelectedValueFromItem();
}
var route = BuildEventRoute(SelectionChangedEvent);
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()
{
if (AutoScrollToSelectedItem &&
@ -1037,6 +1239,13 @@ namespace Avalonia.Controls.Primitives
Selection.Clear();
}
if (state.SelectedValue.HasValue)
{
var item = FindItemWithValue(state.SelectedValue.Value);
if (item != AvaloniaProperty.UnsetValue)
state.SelectedItem = item;
}
if (state.SelectedIndex.HasValue)
{
SelectedIndex = state.SelectedIndex.Value;
@ -1098,6 +1307,7 @@ namespace Avalonia.Controls.Primitives
{
private Optional<int> _selectedIndex;
private Optional<object?> _selectedItem;
private Optional<object?> _selectedValue;
public int UpdateCount { get; set; }
public Optional<ISelectionModel> Selection { get; set; }
@ -1122,6 +1332,54 @@ namespace Avalonia.Controls.Primitives
_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),
o => o.IsChecked,
(o, v) => o.IsChecked = v,
unsetValue: null,
unsetValue: false,
defaultBindingMode: BindingMode.TwoWay);
/// <summary>

2
src/Avalonia.Controls/ProgressBar.cs

@ -238,7 +238,7 @@ namespace Avalonia.Controls
private void UpdateIndicator()
{
// 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)
{

4
src/Avalonia.Controls/TextBlock.cs

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

40
src/Avalonia.Controls/TopLevel.cs

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

6
src/Avalonia.Controls/WindowBase.cs

@ -129,7 +129,7 @@ namespace Avalonia.Controls
{
using (FreezeVisibilityChangeHandling())
{
Renderer?.Stop();
Renderer.Stop();
PlatformImpl?.Hide();
IsVisible = false;
}
@ -153,7 +153,7 @@ namespace Avalonia.Controls
}
PlatformImpl?.Show(true, false);
Renderer?.Start();
Renderer.Start();
OnOpened(EventArgs.Empty);
}
}
@ -219,7 +219,7 @@ namespace Avalonia.Controls
{
ClientSize = clientSize;
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
{
Lifetimes.IClassicDesktopStyleApplicationLifetime classic => classic.MainWindow?.Renderer,
Lifetimes.ISingleViewApplicationLifetime single => (single.MainView as Visual)?.VisualRoot?.Renderer,
Lifetimes.ISingleViewApplicationLifetime single => single.MainView?.VisualRoot?.Renderer,
_ => null
};

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

@ -115,7 +115,7 @@ namespace Avalonia.Diagnostics.ViewModels
var link = _currentEvent.EventChain[linkIndex];
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" >
<Setter Property="Background" Value="#d9ffdc" />
<Setter Property="Foreground" Value="Black" />
</Style>
</UserControl.Styles>

4
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

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

3
src/Avalonia.Native/WindowImpl.cs

@ -119,7 +119,8 @@ namespace Avalonia.Native
{
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))
{

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

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

@ -15,7 +15,7 @@
<Import Project="..\..\..\build\TrimmingEnable.props" />
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="7.0.0-*" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="7.0.2" />
</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)
{
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))))
{
if (paint.Paint is object)
@ -495,6 +501,12 @@ namespace Avalonia.Skia
public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun)
{
CheckLease();
if (foreground is null)
{
return;
}
using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Item.Size))
{
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 SharpDX.DirectWrite;
@ -25,8 +26,6 @@ namespace Avalonia.Direct2D1.Media
}
public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound)
{
return null;
}
=> Array.Empty<float>();
}
}

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

@ -10,7 +10,7 @@
</ItemGroup>
<ItemGroup>
<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" />
<MicroComIdl Include="WinRT\winrt.idl" CSharpInteropPath="WinRT\WinRT.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" />
</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>
<Import Project="..\..\..\build\NetFX.props" />
</Project>

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

@ -1,16 +1,79 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Avalonia.Markup.Xaml;
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);
}
[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]
public void Should_Convert_Set_String_To_Double()
{
@ -249,19 +237,6 @@ namespace Avalonia.Base.UnitTests.Data.Core
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]
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 scaleX,
double scaleY,
double? penThickness,
double penThickness,
double expectedX,
double expectedY,
double expectedWidth,
@ -38,7 +38,7 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
var target = new TestRectangleDrawOperation(
new Rect(x, y, width, height),
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);
}

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

@ -14,7 +14,7 @@
<ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.4" />
</ItemGroup>
<ItemGroup>
<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) =>
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),
Renderer = renderer.Object
Bounds = new Rect(0, 0, 100, 100)
};
bool clicked = false;
@ -172,10 +171,9 @@ namespace Avalonia.Controls.UnitTests
.Returns<Point, Visual, Func<Visual, bool>>((p, r, f) =>
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),
Renderer = renderer.Object
Bounds = new Rect(0, 0, 100, 100)
};
bool clicked = false;
@ -206,11 +204,10 @@ namespace Avalonia.Controls.UnitTests
r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ?
new Visual[] { r } : new Visual[0]);
var target = new TestButton()
var target = new TestButton(renderer.Object)
{
Bounds = new Rect(0, 0, 100, 100),
RenderTransform = new TranslateTransform { X = 100, Y = 0 },
Renderer = renderer.Object
RenderTransform = new TranslateTransform { X = 100, Y = 0 }
};
//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
{
public TestButton()
public TestButton(IRenderer renderer)
{
IsVisible = true;
Renderer = renderer;
}
public new Rect Bounds
@ -399,7 +397,7 @@ namespace Avalonia.Controls.UnitTests
public Size ClientSize => throw new NotImplementedException();
public IRenderer Renderer { get; set; }
public IRenderer Renderer { get; }
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.LogicalTree;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Moq;
@ -20,7 +21,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
var impl = CreateMockTopLevelImpl();
var target = new TestTopLevel(impl.Object);
Assert.True(((ILogical)target).IsAttachedToLogicalTree);
@ -32,7 +33,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
var impl = CreateMockTopLevelImpl();
impl.Setup(x => x.ClientSize).Returns(new Size(123, 456));
var target = new TestTopLevel(impl.Object);
@ -46,7 +47,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
var impl = CreateMockTopLevelImpl();
impl.Setup(x => x.ClientSize).Returns(new Size(123, 456));
var target = new TestTopLevel(impl.Object);
@ -60,7 +61,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
var impl = CreateMockTopLevelImpl();
impl.Setup(x => x.ClientSize).Returns(new Size(123, 456));
var target = new TestTopLevel(impl.Object);
@ -76,7 +77,7 @@ namespace Avalonia.Controls.UnitTests
using (UnitTestApplication.Start(services))
{
var impl = new Mock<ITopLevelImpl>();
var impl = CreateMockTopLevelImpl();
var target = new TestTopLevel(impl.Object, Mock.Of<ILayoutManager>());
@ -91,7 +92,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
var impl = CreateMockTopLevelImpl();
impl.SetupProperty(x => x.Resized);
impl.SetupGet(x => x.RenderScaling).Returns(1);
@ -117,7 +118,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
var impl = CreateMockTopLevelImpl();
impl.Setup(x => x.ClientSize).Returns(new Size(123, 456));
var target = new TestTopLevel(impl.Object);
@ -133,7 +134,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties();
impl.Setup(x => x.ClientSize).Returns(new Size(123, 456));
@ -151,7 +152,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties();
bool raised = false;
@ -169,7 +170,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties();
var target = new TestTopLevel(impl.Object);
@ -200,7 +201,7 @@ namespace Avalonia.Controls.UnitTests
using (UnitTestApplication.Start(services))
{
var impl = new Mock<ITopLevelImpl>();
var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties();
var target = new TestTopLevel(impl.Object);
@ -222,7 +223,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties();
var target = new TestTopLevel(impl.Object);
var child = new TestTopLevel(impl.Object);
@ -240,7 +241,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties();
var target = new TestTopLevel(impl.Object);
var raised = false;
@ -257,7 +258,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties();
var layoutManager = new Mock<ILayoutManager>();
@ -274,7 +275,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
var impl = CreateMockTopLevelImpl();
impl.SetupGet(x => x.RenderScaling).Returns(1);
var child = new Border { Classes = { "foo" } };
@ -317,6 +318,14 @@ namespace Avalonia.Controls.UnitTests
}.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 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.Raw;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Moq;
using Xunit;
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())
{
AvaloniaLocator.CurrentMutable
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock());
.Bind<IWindowingPlatform>().ToConstant(new MockWindowingPlatform());
var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control);
var gesture2 = new KeyGesture(Key.B, KeyModifiers.Control);
@ -64,7 +62,7 @@ namespace Avalonia.Controls.UnitTests.Utils
var commandResult = 0;
var expectedParameter = 1;
AvaloniaLocator.CurrentMutable
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock());
.Bind<IWindowingPlatform>().ToConstant(new MockWindowingPlatform());
var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
@ -106,7 +104,7 @@ namespace Avalonia.Controls.UnitTests.Utils
var target = new KeyboardDevice();
var isExecuted = false;
AvaloniaLocator.CurrentMutable
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock());
.Bind<IWindowingPlatform>().ToConstant(new MockWindowingPlatform());
var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
@ -146,7 +144,7 @@ namespace Avalonia.Controls.UnitTests.Utils
var target = new KeyboardDevice();
var clickExecutedCount = 0;
AvaloniaLocator.CurrentMutable
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock());
.Bind<IWindowingPlatform>().ToConstant(new MockWindowingPlatform());
var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
@ -199,7 +197,7 @@ namespace Avalonia.Controls.UnitTests.Utils
var clickExecutedCount = 0;
var commandExecutedCount = 0;
AvaloniaLocator.CurrentMutable
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock());
.Bind<IWindowingPlatform>().ToConstant(new MockWindowingPlatform());
var gesture = new KeyGesture(Key.A, KeyModifiers.Control);

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

Loading…
Cancel
Save