Browse Source

Merge branch 'master' into poker/wrappanel-enhance

pull/20549/head
Poker 1 month ago
committed by GitHub
parent
commit
a6e83880e9
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      .ncrunch/Avalonia.HarfBuzz.v3.ncrunchproject
  2. 3
      Avalonia.v3.ncrunchsolution
  3. 6
      api/Avalonia.Android.nupkg.xml
  4. 40
      api/Avalonia.Headless.nupkg.xml
  5. 100
      api/Avalonia.Skia.nupkg.xml
  6. 2452
      api/Avalonia.nupkg.xml
  7. 3
      native/Avalonia.Native/src/OSX/AvnView.mm
  8. 44
      samples/BindingDemo/MainWindow.xaml
  9. 40
      samples/ControlCatalog.Desktop/Program.cs
  10. 3
      samples/ControlCatalog/MainView.xaml
  11. 8
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
  12. 20
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
  13. 45
      samples/ControlCatalog/Pages/BitmapCachePage.axaml
  14. 13
      samples/ControlCatalog/Pages/BitmapCachePage.axaml.cs
  15. 19
      samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml
  16. 4
      samples/ControlCatalog/Pages/ClipboardPage.xaml
  17. 21
      samples/ControlCatalog/Pages/DialogsPage.xaml
  18. 95
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  19. 26
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  20. 43
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  21. 6
      samples/ControlCatalog/Pages/ThemePage.axaml
  22. 14
      samples/Generators.Sandbox/Controls/SignUpView.xaml
  23. 2
      samples/IntegrationTestApp/MainWindow.axaml.cs
  24. 2
      samples/IntegrationTestApp/Pages/EmbeddingPage.axaml
  25. 69
      samples/IntegrationTestApp/Pages/EmbeddingPage.axaml.cs
  26. 14
      samples/IntegrationTestApp/Pages/ScreensPage.axaml
  27. 2
      samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml
  28. 2
      samples/IntegrationTestApp/Pages/WindowPage.axaml
  29. 4
      samples/SafeAreaDemo/Views/MainView.xaml
  30. 8
      samples/SingleProjectSandbox/MainView.axaml
  31. 44
      src/Android/Avalonia.Android/AvaloniaActivity.cs
  32. 2
      src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
  33. 19
      src/Android/Avalonia.Android/Platform/AndroidScreens.cs
  34. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  35. 6
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  36. 11
      src/Android/Avalonia.Android/Platform/Specific/IAndroidView.cs
  37. 2
      src/Android/Avalonia.Android/Stubs.cs
  38. 13
      src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs
  39. 17
      src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs
  40. 30
      src/Avalonia.Base/Animation/ICustomAnimator.cs
  41. 27
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  42. 8
      src/Avalonia.Base/Controls/IResourceHost.cs
  43. 4
      src/Avalonia.Base/Controls/ResourceDictionary.cs
  44. 22
      src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
  45. 6
      src/Avalonia.Base/Controls/ResourceProvider.cs
  46. 28
      src/Avalonia.Base/Controls/ResourcesChangedEventArgs.cs
  47. 45
      src/Avalonia.Base/Controls/ResourcesChangedHelper.cs
  48. 11
      src/Avalonia.Base/Controls/ResourcesChangedToken.cs
  49. 5
      src/Avalonia.Base/Data/BindingPriority.cs
  50. 93
      src/Avalonia.Base/Data/CompiledBinding.cs
  51. 6
      src/Avalonia.Base/Data/CompiledBindingPath.cs
  52. 53
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  53. 402
      src/Avalonia.Base/Data/Core/Parsers/BindingExpressionVisitor.cs
  54. 20
      src/Avalonia.Base/Data/Core/Parsers/BindingExpressionVisitorMembers.cs
  55. 13
      src/Avalonia.Base/Data/ReflectionBinding.cs
  56. 1
      src/Avalonia.Base/Data/TemplateBinding.cs
  57. 12
      src/Avalonia.Base/Diagnostics/ObsoletionMessages.cs
  58. 37
      src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs
  59. 3
      src/Avalonia.Base/Diagnostics/StyleValueFrameDiagnostic.cs
  60. 24
      src/Avalonia.Base/Diagnostics/StyledElementExtensions.cs
  61. 3
      src/Avalonia.Base/IOptionalFeatureProvider.cs
  62. 61
      src/Avalonia.Base/Input/DataFormats.cs
  63. 45
      src/Avalonia.Base/Input/DataObject.cs
  64. 54
      src/Avalonia.Base/Input/DataObjectExtensions.cs
  65. 9
      src/Avalonia.Base/Input/DataTransferExtensions.cs
  66. 10
      src/Avalonia.Base/Input/DragDrop.cs
  67. 17
      src/Avalonia.Base/Input/DragEventArgs.cs
  68. 32
      src/Avalonia.Base/Input/IDataObject.cs
  69. 2
      src/Avalonia.Base/Input/MouseDevice.cs
  70. 55
      src/Avalonia.Base/Input/Platform/Clipboard.cs
  71. 76
      src/Avalonia.Base/Input/Platform/DataObjectToDataTransferItemWrapper.cs
  72. 95
      src/Avalonia.Base/Input/Platform/DataObjectToDataTransferWrapper.cs
  73. 42
      src/Avalonia.Base/Input/Platform/DataTransferToDataObjectWrapper.cs
  74. 55
      src/Avalonia.Base/Input/Platform/IClipboard.cs
  75. 9
      src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs
  76. 24
      src/Avalonia.Base/Input/Pointer.cs
  77. 22
      src/Avalonia.Base/Input/Raw/RawDragEvent.cs
  78. 33
      src/Avalonia.Base/Input/Raw/RawKeyEventArgs.cs
  79. 12
      src/Avalonia.Base/Layout/Layoutable.cs
  80. 114
      src/Avalonia.Base/Media/BitmapCache.cs
  81. 21
      src/Avalonia.Base/Media/CacheMode.cs
  82. 7
      src/Avalonia.Base/Media/Color.cs
  83. 9
      src/Avalonia.Base/Media/DrawingContext.cs
  84. 2
      src/Avalonia.Base/Media/IRadialGradientBrush.cs
  85. 4
      src/Avalonia.Base/Media/Imaging/Bitmap.cs
  86. 2
      src/Avalonia.Base/Media/Immutable/ImmutableRadialGradientBrush.cs
  87. 40
      src/Avalonia.Base/Media/RadialGradientBrush.cs
  88. 4
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  89. 4
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  90. 17
      src/Avalonia.Base/Metadata/ConstructorArgumentAttribute.cs
  91. 8
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  92. 47
      src/Avalonia.Base/Platform/IRenderTarget.cs
  93. 6
      src/Avalonia.Base/Platform/ISurfaceOrientation.cs
  94. 70
      src/Avalonia.Base/Platform/LtrbRect.cs
  95. 9
      src/Avalonia.Base/Platform/SurfaceOrientation.cs
  96. 2
      src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleCompositionBrush.cs
  97. 5
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  98. 10
      src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs
  99. 5
      src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
  100. 6
      src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs

5
.ncrunch/Avalonia.HarfBuzz.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>False</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

3
Avalonia.v3.ncrunchsolution

@ -13,8 +13,9 @@
<Value>TargetFrameworks = net10.0</Value>
</CustomBuildProperties>
<EnableRDI>False</EnableRDI>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
<ProjectConfigStoragePathRelativeToSolutionDir>.ncrunch</ProjectConfigStoragePathRelativeToSolutionDir>
<RdiConfigured>True</RdiConfigured>
<SolutionConfigured>True</SolutionConfigured>
</Settings>
</SolutionConfiguration>
</SolutionConfiguration>

6
api/Avalonia.Android.nupkg.xml

@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Android.Platform.Specific.IAndroidView</Target>
<Left>baseline/Avalonia.Android/lib/net10.0-android36.0/Avalonia.Android.dll</Left>
<Right>current/Avalonia.Android/lib/net10.0-android36.0/Avalonia.Android.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Android.AvaloniaActivity</Target>

40
api/Avalonia.Headless.nupkg.xml

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Headless.HeadlessWindowExtensions.DragDrop(Avalonia.Controls.TopLevel,Avalonia.Point,Avalonia.Input.Raw.RawDragEventType,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects,Avalonia.Input.RawInputModifiers)</Target>
<Left>baseline/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll</Left>
<Right>current/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Headless.HeadlessWindowExtensions.KeyPress(Avalonia.Controls.TopLevel,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers)</Target>
<Left>baseline/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll</Left>
<Right>current/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Headless.HeadlessWindowExtensions.KeyRelease(Avalonia.Controls.TopLevel,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers)</Target>
<Left>baseline/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll</Left>
<Right>current/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Headless.HeadlessWindowExtensions.DragDrop(Avalonia.Controls.TopLevel,Avalonia.Point,Avalonia.Input.Raw.RawDragEventType,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects,Avalonia.Input.RawInputModifiers)</Target>
<Left>baseline/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll</Left>
<Right>current/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Headless.HeadlessWindowExtensions.KeyPress(Avalonia.Controls.TopLevel,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers)</Target>
<Left>baseline/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll</Left>
<Right>current/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Headless.HeadlessWindowExtensions.KeyRelease(Avalonia.Controls.TopLevel,Avalonia.Input.Key,Avalonia.Input.RawInputModifiers)</Target>
<Left>baseline/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll</Left>
<Right>current/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll</Right>
</Suppression>
</Suppressions>

100
api/Avalonia.Skia.nupkg.xml

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Skia.ISkiaGpuRenderTarget2</Target>
<Left>baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll</Left>
<Right>current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Skia.ISkiaGpuWithPlatformGraphicsContext</Target>
<Left>baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll</Left>
<Right>current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Skia.ISkiaGpuRenderTarget2</Target>
<Left>baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll</Left>
<Right>current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Skia.ISkiaGpuWithPlatformGraphicsContext</Target>
<Left>baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll</Left>
<Right>current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Skia.ISkiaGpuRenderTarget.BeginRenderingSession</Target>
<Left>baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll</Left>
<Right>current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Skia.ISkiaGpuRenderTarget.BeginRenderingSession</Target>
<Left>baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll</Left>
<Right>current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Skia.ISkiaGpu.TryGetGrContext</Target>
<Left>baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll</Left>
<Right>current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Skia.ISkiaGpuRenderTarget.BeginRenderingSession(System.Nullable{Avalonia.PixelSize})</Target>
<Left>baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll</Left>
<Right>current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>P:Avalonia.Skia.ISkiaGpu.PlatformGraphicsContext</Target>
<Left>baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll</Left>
<Right>current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Skia.ISkiaGpu.TryGetGrContext</Target>
<Left>baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll</Left>
<Right>current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Skia.ISkiaGpuRenderTarget.BeginRenderingSession(System.Nullable{Avalonia.PixelSize})</Target>
<Left>baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll</Left>
<Right>current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>P:Avalonia.Skia.ISkiaGpu.PlatformGraphicsContext</Target>
<Left>baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll</Left>
<Right>current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Skia.ISkiaGpu</Target>
<Left>baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll</Left>
<Right>current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Skia.ISkiaGpuWithPlatformGraphicsContext</Target>
<Left>baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll</Left>
<Right>current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Skia.ISkiaGpu</Target>
<Left>baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll</Left>
<Right>current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Skia.ISkiaGpuWithPlatformGraphicsContext</Target>
<Left>baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll</Left>
<Right>current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll</Right>
</Suppression>
</Suppressions>

2452
api/Avalonia.nupkg.xml

File diff suppressed because it is too large

3
native/Avalonia.Native/src/OSX/AvnView.mm

@ -42,7 +42,8 @@
- (void) updateRenderTarget
{
if(_currentRenderTarget) {
[_currentRenderTarget resize:_lastPixelSize withScale:static_cast<float>([[self window] backingScaleFactor])];
AvnPixelSize size { MAX(_lastPixelSize.Width, 1), MAX(_lastPixelSize.Height, 1) };
[_currentRenderTarget resize:size withScale:static_cast<float>([[self window] backingScaleFactor])];
[self setNeedsDisplayInRect:[self frame]];
}
}

44
samples/BindingDemo/MainWindow.xaml

@ -1,7 +1,7 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class="BindingDemo.MainWindow"
xmlns:vm="using:BindingDemo.ViewModels"
xmlns:vm="using:BindingDemo.ViewModels"
xmlns:local="using:BindingDemo"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
Title="AvaloniaUI Bindings Test"
@ -13,29 +13,29 @@
<Setter Property="FontSize" Value="18"/>
</Style>
</Window.Styles>
<TabControl>
<TabItem Header="Basic">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<StackPanel Margin="18" Spacing="4" Width="200">
<TextBlock FontSize="16" Text="Simple Bindings"/>
<TextBox Watermark="Two Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue}" Name="first"/>
<TextBox Watermark="Two Way (LostFocus)" UseFloatingWatermark="True" Text="{Binding Path=StringValue, UpdateSourceTrigger=LostFocus}"/>
<TextBox Watermark="One Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWay}"/>
<TextBox Watermark="One Time" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneTime}"/>
<TextBox PlaceholderText="Two Way" UseFloatingPlaceholder="True" Text="{Binding Path=StringValue}" Name="first"/>
<TextBox PlaceholderText="Two Way (LostFocus)" UseFloatingPlaceholder="True" Text="{Binding Path=StringValue, UpdateSourceTrigger=LostFocus}"/>
<TextBox PlaceholderText="One Way" UseFloatingPlaceholder="True" Text="{Binding Path=StringValue, Mode=OneWay}"/>
<TextBox PlaceholderText="One Time" UseFloatingPlaceholder="True" Text="{Binding Path=StringValue, Mode=OneTime}"/>
<!-- Removed due to #2983: reinstate when that's fixed.
<TextBox Watermark="One Way to Source" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWayToSource}"/>
<TextBox Placeholder="One Way to Source" UseFloatingPlaceholder="True" Text="{Binding Path=StringValue, Mode=OneWayToSource}"/>
-->
</StackPanel>
<StackPanel Margin="18" Spacing="4" Width="200">
<TextBlock FontSize="16" Text="Collection Bindings"/>
<TextBox Watermark="Items[1].Value" UseFloatingWatermark="True" Text="{Binding Path=Items[1].Value}"/>
<TextBox PlaceholderText="Items[1].Value" UseFloatingPlaceholder="True" Text="{Binding Path=Items[1].Value}"/>
<Button Command="{Binding ShuffleItems}">Shuffle</Button>
</StackPanel>
<StackPanel Margin="18" Spacing="4" Width="200">
<TextBlock FontSize="16" Text="Negated Bindings"/>
<TextBox Watermark="Boolean String" UseFloatingWatermark="True" Text="{Binding Path=BooleanString}"/>
<TextBox PlaceholderText="Boolean String" UseFloatingPlaceholder="True" Text="{Binding Path=BooleanString}"/>
<CheckBox IsChecked="{Binding !BooleanString}">!BooleanString</CheckBox>
<CheckBox IsChecked="{Binding !!BooleanString}">!!BooleanString</CheckBox>
</StackPanel>
@ -43,24 +43,24 @@
<StackPanel Orientation="Horizontal">
<StackPanel Margin="18" Spacing="4" Width="200" HorizontalAlignment="Left">
<TextBlock FontSize="16" Text="Numeric Bindings"/>
<TextBox Watermark="Double" UseFloatingWatermark="True" Text="{Binding Path=DoubleValue, Mode=TwoWay}"/>
<TextBox PlaceholderText="Double" UseFloatingPlaceholder="True" Text="{Binding Path=DoubleValue, Mode=TwoWay}"/>
<TextBlock Text="{Binding Path=DoubleValue}"/>
<ProgressBar Maximum="10" Value="{Binding DoubleValue}"/>
</StackPanel>
<StackPanel Margin="18" Spacing="4" Width="200" HorizontalAlignment="Left">
<TextBlock FontSize="16" Text="Binding Sources"/>
<TextBox Watermark="Value of first TextBox" UseFloatingWatermark="True"
<TextBox PlaceholderText="Value of first TextBox" UseFloatingPlaceholder="True"
Text="{Binding #first.Text, Mode=TwoWay}"/>
<TextBox Watermark="Value of SharedItem.StringValue" UseFloatingWatermark="True"
<TextBox PlaceholderText="Value of SharedItem.StringValue" UseFloatingPlaceholder="True"
Text="{Binding Value, Source={StaticResource SharedItem}, Mode=TwoWay, DataType={x:Type vm:MainWindowViewModel+TestItem, x:TypeArguments=x:String}}"/>
<TextBox Watermark="Value of SharedItem.StringValue (duplicate)" UseFloatingWatermark="True"
<TextBox PlaceholderText="Value of SharedItem.StringValue (duplicate)" UseFloatingPlaceholder="True"
Text="{Binding Value, Source={StaticResource SharedItem}, Mode=TwoWay, DataType={x:Type vm:MainWindowViewModel+TestItem, x:TypeArguments=x:String}}"/>
</StackPanel>
<StackPanel Margin="18" Spacing="4" Width="200" HorizontalAlignment="Left">
<TextBlock FontSize="16" Text="Scheduler"/>
<TextBox Watermark="Background Thread" Text="{Binding CurrentTime, Mode=OneWay}"/>
<TextBox PlaceholderText="Background Thread" Text="{Binding CurrentTime, Mode=OneWay}"/>
<TextBlock FontSize="16" Text="Stream Operator"/>
<TextBox Watermark="StreamOperator" Text="{CompiledBinding CurrentTimeObservable^, Mode=OneWay}"/>
<TextBox PlaceholderText="StreamOperator" Text="{CompiledBinding CurrentTimeObservable^, Mode=OneWay}"/>
</StackPanel>
</StackPanel>
</StackPanel>
@ -91,19 +91,19 @@
</TabItem>
<TabItem Header="Property Validation">
<StackPanel Orientation="Horizontal">
<StackPanel Margin="18" Spacing="4" MinWidth="200" DataContext="{Binding ExceptionDataValidation}">
<StackPanel Margin="18" Spacing="4" MinWidth="200" DataContext="{Binding ExceptionDataValidation}">
<TextBlock FontSize="16" Text="Exception Validation"/>
<TextBox Watermark="Less Than 10" UseFloatingWatermark="True" Text="{Binding Path=LessThan10}"/>
<TextBox PlaceholderText="Less Than 10" UseFloatingPlaceholder="True" Text="{Binding Path=LessThan10}"/>
</StackPanel>
<StackPanel Margin="18" Spacing="4" MinWidth="200" DataContext="{Binding IndeiDataValidation}">
<TextBlock FontSize="16" Text="INotifyDataErrorInfo Validation"/>
<TextBox Watermark="Maximum" UseFloatingWatermark="True" Text="{Binding Path=Maximum}"/>
<TextBox Watermark="Value" UseFloatingWatermark="True" Text="{Binding Path=Value}"/>
<TextBox PlaceholderText="Maximum" UseFloatingPlaceholder="True" Text="{Binding Path=Maximum}"/>
<TextBox PlaceholderText="Value" UseFloatingPlaceholder="True" Text="{Binding Path=Value}"/>
</StackPanel>
<StackPanel Margin="18" Spacing="4" MinWidth="200" DataContext="{Binding DataAnnotationsValidation}">
<StackPanel Margin="18" Spacing="4" MinWidth="200" DataContext="{Binding DataAnnotationsValidation}">
<TextBlock FontSize="16" Text="Data Annotations Validation"/>
<TextBox Watermark="Phone #" UseFloatingWatermark="True" Text="{Binding PhoneNumber}"/>
<TextBox Watermark="Less Than 10" UseFloatingWatermark="True" Text="{Binding Path=LessThan10}"/>
<TextBox PlaceholderText="Phone #" UseFloatingPlaceholder="True" Text="{Binding PhoneNumber}"/>
<TextBox PlaceholderText="Less Than 10" UseFloatingPlaceholder="True" Text="{Binding Path=LessThan10}"/>
</StackPanel>
</StackPanel>
</TabItem>

40
samples/ControlCatalog.Desktop/Program.cs

@ -8,8 +8,10 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Headless;
using Avalonia.LinuxFramebuffer;
using Avalonia.LinuxFramebuffer.Output;
using Avalonia.LogicalTree;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using Avalonia.Vulkan;
@ -19,16 +21,9 @@ namespace ControlCatalog.Desktop
{
static class Program
{
private static bool s_useFramebuffer;
[STAThread]
static int Main(string[] args)
{
if (args.Contains("--fbdev"))
{
s_useFramebuffer = true;
}
if (args.Contains("--wait-for-attach"))
{
Console.WriteLine("Attach debugger and use 'Set next statement'");
@ -45,12 +40,27 @@ namespace ControlCatalog.Desktop
double GetScaling()
{
var idx = Array.IndexOf(args, "--scaling");
if (idx != 0 && args.Length > idx + 1 &&
if (idx >= 0 && args.Length > idx + 1 &&
double.TryParse(args[idx + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out var scaling))
return scaling;
return 1;
}
if (s_useFramebuffer)
SurfaceOrientation GetOrientation()
{
var idx = Array.IndexOf(args, "--orientation");
if (idx >= 0 && args.Length > idx + 1 &&
Enum.TryParse<SurfaceOrientation>(args[idx + 1], true, out var orientation))
return orientation;
return SurfaceOrientation.Rotation0;
}
string? GetCard()
{
var idx = Array.IndexOf(args, "--card");
if (idx >= 0 && args.Length > idx + 1)
return args[idx + 1];
return null;
}
if (args.Contains("--fbdev"))
{
SilenceConsole();
return builder.StartLinuxFbDev(args, new FbDevOutputOptions()
@ -108,13 +118,17 @@ namespace ControlCatalog.Desktop
else if (args.Contains("--drm"))
{
SilenceConsole();
return builder.StartLinuxDrm(args, scaling: GetScaling());
return builder.StartLinuxDrm(args, card: GetCard(), options: new DrmOutputOptions()
{
Scaling = GetScaling(),
Orientation = GetOrientation(),
});
}
else if (args.Contains("--dxgi"))
{
builder.With(new Win32PlatformOptions()
{
CompositionMode = new [] { Win32CompositionMode.LowLatencyDxgiSwapChain }
CompositionMode = [Win32CompositionMode.LowLatencyDxgiSwapChain]
});
return builder.StartWithClassicDesktopLifetime(args);
}
@ -151,14 +165,14 @@ namespace ControlCatalog.Desktop
.WithDeveloperTools()
.AfterSetup(builder =>
{
EmbedSample.Implementation = OperatingSystem.IsWindows() ? (INativeDemoControl)new EmbedSampleWin()
EmbedSample.Implementation = OperatingSystem.IsWindows() ? new EmbedSampleWin()
: OperatingSystem.IsMacOS() ? new EmbedSampleMac()
: OperatingSystem.IsLinux() ? new EmbedSampleGtk()
: null;
})
.LogToTrace();
static void SilenceConsole()
private static void SilenceConsole()
{
new Thread(() =>
{

3
samples/ControlCatalog/MainView.xaml

@ -34,6 +34,9 @@
<TabItem Header="Border">
<pages:BorderPage />
</TabItem>
<TabItem Header="BitmapCache">
<pages:BitmapCachePage />
</TabItem>
<TabItem Header="Buttons">
<pages:ButtonsPage />
</TabItem>

8
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml

@ -39,8 +39,12 @@
<AutoCompleteBox MaxDropDownHeight="60" />
</StackPanel>
<StackPanel>
<TextBlock Text="Watermark" />
<AutoCompleteBox Watermark="Hello World" />
<TextBlock Text="PlaceholderText" />
<AutoCompleteBox PlaceholderText="Hello World" />
</StackPanel>
<StackPanel>
<TextBlock Text="PlaceholderText with custom color" />
<AutoCompleteBox PlaceholderText="Search here..." PlaceholderForeground="Green" />
</StackPanel>
<StackPanel>
<TextBlock Text="Disabled" />

20
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs

@ -7,30 +7,12 @@ using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.LogicalTree;
using ControlCatalog.Models;
namespace ControlCatalog.Pages
{
public partial class AutoCompleteBoxPage : UserControl
{
public class StateData
{
public string Name { get; private set; }
public string Abbreviation { get; private set; }
public string Capital { get; private set; }
public StateData(string name, string abbreviatoin, string capital)
{
Name = name;
Abbreviation = abbreviatoin;
Capital = capital;
}
public override string ToString()
{
return Name;
}
}
private static StateData[] BuildAllStates()
{
return new StateData[]

45
samples/ControlCatalog/Pages/BitmapCachePage.axaml

@ -0,0 +1,45 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ControlCatalog.Pages.BitmapCachePage">
<DockPanel>
<DockPanel.Resources>
<BitmapCache x:Key="Cache"/>
<ScaleTransform x:Key="Transform" ScaleX="1" ScaleY="{Binding $self.ScaleX, Mode=OneWay}"/>
<TranslateTransform x:Key="SubPixelTransform"/>
</DockPanel.Resources>
<StackPanel DockPanel.Dock="Right" ZIndex="1">
<TextBlock>Render at scale</TextBlock>
<Slider Minimum="0.1" Maximum="4" Value="{Binding Source={StaticResource Cache}, Path=RenderAtScale, Mode=TwoWay}" Width="200"/>
<TextBlock>Scale</TextBlock>
<Slider Minimum="0.1" Maximum="4" Value="{Binding Source={StaticResource Transform}, Path=ScaleX, Mode=TwoWay}" Width="200"/>
<CheckBox IsChecked="{Binding Source={StaticResource Cache}, Path=EnableClearType, Mode=TwoWay}">Enable clear type</CheckBox>
<CheckBox IsChecked="{Binding Source={StaticResource Cache}, Path=SnapsToDevicePixels, Mode=TwoWay}">Snap to device pixels</CheckBox>
<TextBlock>Subpixel offset X</TextBlock>
<Slider Minimum="0" Maximum="1" Value="{Binding Source={StaticResource SubPixelTransform}, Path=X, Mode=TwoWay}" Width="200"/>
</StackPanel>
<Decorator RenderTransform="{StaticResource SubPixelTransform}">
<Border Background="Beige" Margin="10"
RenderTransform="{StaticResource Transform}"
RenderTransformOrigin="0.5,0.5">
<Border Margin="10"
CacheMode="{StaticResource Cache}"
TextElement.Foreground="Black">
<Border
Margin="10"
BorderThickness="2"
BorderBrush="Black"
Background="White">
<StackPanel>
<Slider MinWidth="200"/>
<TextBlock TextWrapping="Wrap">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</TextBlock>
</StackPanel>
</Border>
</Border>
</Border>
</Decorator>
</DockPanel>
</UserControl>

13
samples/ControlCatalog/Pages/BitmapCachePage.axaml.cs

@ -0,0 +1,13 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages;
public partial class BitmapCachePage : UserControl
{
public BitmapCachePage()
{
InitializeComponent();
}
}

19
samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml

@ -5,7 +5,7 @@
x:Class="ControlCatalog.Pages.CalendarDatePickerPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h2">A control for selecting dates with a calendar drop-down</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
@ -32,19 +32,24 @@
Margin="0,0,0,8"/>
<CalendarDatePicker Margin="0,0,0,8"
Watermark="Watermark"/>
PlaceholderText="Placeholder"/>
<CalendarDatePicker Margin="0,0,0,8"
Name="DatePicker5"
Watermark="Floating Watermark"
UseFloatingWatermark="True"/>
PlaceholderText="Floating Placeholder"
UseFloatingPlaceholder="True"/>
<TextBlock Text="PlaceholderText with custom color"/>
<CalendarDatePicker Margin="0,0,0,8"
PlaceholderText="Select a date"
PlaceholderForeground="Purple"/>
<TextBlock Text="Disabled"/>
<CalendarDatePicker IsEnabled="False"/>
<TextBlock Text="Validation Example"/>
<CalendarDatePicker SelectedDate="{CompiledBinding ValidatedDateExample, Mode=TwoWay}"/>
</StackPanel>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

4
samples/ControlCatalog/Pages/ClipboardPage.xaml

@ -1,4 +1,4 @@
<UserControl x:Class="ControlCatalog.Pages.ClipboardPage"
<UserControl x:Class="ControlCatalog.Pages.ClipboardPage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Vertical" Spacing="4">
@ -21,7 +21,7 @@
<TextBox x:Name="ClipboardContent"
MinHeight="100"
AcceptsReturn="True"
Watermark="Text to copy of file names per line" />
PlaceholderText="Text to copy of file names per line" />
<Viewbox Width="420" Height="360">
<Image x:Name="ClipboardImage"
/>

21
samples/ControlCatalog/Pages/DialogsPage.xaml

@ -49,26 +49,17 @@
<Button Name="OpenFolderFromBookmark">Open Folder Bookmark</Button>
</StackPanel>
</Expander>
<Expander Header="Legacy OpenFileDialog">
<StackPanel Spacing="4">
<Button Name="OpenFile">_Open File</Button>
<Button Name="OpenMultipleFiles">Open _Multiple File</Button>
<Button Name="SaveFile">_Save File</Button>
<Button Name="SelectFolder">Select Fo_lder</Button>
<Button Name="OpenBoth">Select _Both</Button>
</StackPanel>
</Expander>
<Expander Header="Launcher dialogs">
<StackPanel Spacing="4">
<TextBox Name="UriToLaunch" Watermark="Uri to launch" Text="https://avaloniaui.net/" />
<TextBox Name="UriToLaunch" PlaceholderText="Uri to launch" Text="https://avaloniaui.net/" />
<Button Name="LaunchUri">Launch Uri</Button>
<Button Name="LaunchFile">Launch File</Button>
<TextBlock Name="LaunchStatus" />
</StackPanel>
</Expander>
<AutoCompleteBox x:Name="CurrentFolderBox" Watermark="Write full path/uri or well known folder name">
<AutoCompleteBox x:Name="CurrentFolderBox" PlaceholderText="Write full path/uri or well known folder name">
<AutoCompleteBox.ItemsSource>
<generic:List x:TypeArguments="storage:WellKnownFolder">
<storage:WellKnownFolder>Desktop</storage:WellKnownFolder>
@ -80,17 +71,17 @@
</generic:List>
</AutoCompleteBox.ItemsSource>
</AutoCompleteBox>
<TextBlock x:Name="PickerLastResultsVisible"
Classes="h2"
IsVisible="False"
Text="Last picker results:" />
<ItemsControl x:Name="PickerLastResults" />
<TextBox Name="BookmarkContainer" Watermark="Bookmark" />
<TextBox Name="BookmarkContainer" PlaceholderText="Bookmark" />
<TextBox Name="OpenedFileContent"
MaxLines="10"
Watermark="Picked file content" />
PlaceholderText="Picked file content" />
</StackPanel>
</UserControl>

95
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -1,23 +1,14 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Dialogs;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO;
#pragma warning disable CS0618 // Type or member is obsolete
#nullable enable
namespace ControlCatalog.Pages
{
@ -70,14 +61,6 @@ namespace ControlCatalog.Pages
};
List<FileDialogFilter> GetFilters()
{
return GetFileTypes()?.Select(f => new FileDialogFilter
{
Name = f.Name, Extensions = f.Patterns!.ToList()
}).ToList() ?? new List<FileDialogFilter>();
}
List<FilePickerFileType>? BuildFileTypes()
{
var selectedItem = (FilterSelector.SelectedItem as ComboBoxItem)?.Content
@ -168,88 +151,12 @@ namespace ControlCatalog.Pages
void UpdateSuggestedFilterSelectorState() =>
suggestedFilterSelector.IsEnabled = useSuggestedFilter.IsChecked == true;
useSuggestedFilter.Checked += (_, _) => UpdateSuggestedFilterSelectorState();
useSuggestedFilter.Unchecked += (_, _) => UpdateSuggestedFilterSelectorState();
useSuggestedFilter.IsCheckedChanged += (_, _) => UpdateSuggestedFilterSelectorState();
UpdateSuggestedFilterSelectorState();
FilterSelector.SelectionChanged += (_, _) => UpdateSuggestedFilterSelector(BuildFileTypes());
UpdateSuggestedFilterSelector(BuildFileTypes());
OpenFile.Click += async delegate
{
// Almost guaranteed to exist
var uri = Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName;
var initialFileName = uri == null ? null : System.IO.Path.GetFileName(uri);
var initialDirectory = uri == null ? null : System.IO.Path.GetDirectoryName(uri);
var result = await new OpenFileDialog()
{
Title = "Open file",
Filters = GetFilters(),
Directory = initialDirectory,
InitialFileName = initialFileName
}.ShowAsync(GetWindow());
results.ItemsSource = result;
resultsVisible.IsVisible = result?.Any() == true;
};
OpenMultipleFiles.Click += async delegate
{
var result = await new OpenFileDialog()
{
Title = "Open multiple files",
Filters = GetFilters(),
Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
AllowMultiple = true
}.ShowAsync(GetWindow());
results.ItemsSource = result;
resultsVisible.IsVisible = result?.Any() == true;
};
SaveFile.Click += async delegate
{
var filters = GetFilters();
var result = await new SaveFileDialog()
{
Title = "Save file",
Filters = filters,
Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
DefaultExtension = filters?.Any() == true ? "txt" : null,
InitialFileName = "test.txt"
}.ShowAsync(GetWindow());
results.ItemsSource = new[] { result };
resultsVisible.IsVisible = result != null;
};
SelectFolder.Click += async delegate
{
var result = await new OpenFolderDialog()
{
Title = "Select folder",
Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
}.ShowAsync(GetWindow());
if (string.IsNullOrEmpty(result))
{
resultsVisible.IsVisible = false;
}
else
{
SetFolder(await GetStorageProvider().TryGetFolderFromPathAsync(result!));
results.ItemsSource = new[] { result };
resultsVisible.IsVisible = true;
}
};
OpenBoth.Click += async delegate
{
var result = await new OpenFileDialog()
{
Title = "Select both",
Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
AllowMultiple = true
}.ShowManagedAsync(GetWindow(), new ManagedFileDialogOptions
{
AllowDirectorySelection = true
});
results.ItemsSource = result;
resultsVisible.IsVisible = result?.Any() == true;
};
DecoratedWindow.Click += delegate
{
new DecoratedWindow().Show();

26
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@ -1,4 +1,4 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="using:System"
xmlns:converter="using:ControlCatalog.Converter"
@ -48,8 +48,8 @@
<ComboBox x:Name="CultureSelector" Grid.Row="2" Grid.Column="1" ItemsSource="{Binding Cultures}"
VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">Watermark:</TextBlock>
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding #upDown.Watermark}" VerticalAlignment="Center" Margin="2" />
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">PlaceholderText:</TextBlock>
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding #upDown.PlaceholderText}" VerticalAlignment="Center" Margin="2" />
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Margin="2">Text:</TextBlock>
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding #upDown.Text}" VerticalAlignment="Center" Margin="2" />
@ -81,23 +81,23 @@
<NumericUpDown Name="upDown" Minimum="0" Maximum="10" Increment="0.5"
NumberFormat="{Binding #CultureSelector.SelectedItem, Converter={x:Static pages:NumericUpDownPage.CultureConverter}}"
VerticalAlignment="Center" Value="{Binding DecimalValue}"
Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}"/>
PlaceholderText="Enter text" FormatString="{Binding SelectedFormat.Value}"/>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="10">
<Label Target="DoubleUpDown" FontSize="14" FontWeight="Bold" VerticalAlignment="Center">Usage of double NumericUpDown:</Label>
<NumericUpDown Name="DoubleUpDown" Minimum="0" Maximum="10" Increment="0.5"
VerticalAlignment="Center" Value="{Binding DoubleValue}"
Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}"/>
PlaceholderText="Enter text" FormatString="{Binding SelectedFormat.Value}"/>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="10">
<Label Target="ValidationUpDown" FontSize="14" FontWeight="Bold" VerticalAlignment="Center">NumericUpDown with Validation Errors:</Label>
<NumericUpDown x:Name="ValidationUpDown" Minimum="0" Maximum="10" Increment="0.5"
VerticalAlignment="Center"
Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}">
PlaceholderText="Enter text" FormatString="{Binding SelectedFormat.Value}">
<DataValidationErrors.Error>
<sys:Exception />
<sys:Exception />
</DataValidationErrors.Error>
</NumericUpDown>
</StackPanel>
@ -110,10 +110,18 @@
<converter:HexConverter></converter:HexConverter>
</NumericUpDown.TextConverter>
</NumericUpDown>
</StackPanel>
</WrapPanel>
<StackPanel Orientation="Vertical" Margin="10">
<Label FontSize="14" FontWeight="Bold" Target="PlaceholderForegroundUpDown"
Content="Placeholder with custom foreground color:"/>
<NumericUpDown x:Name="PlaceholderForegroundUpDown"
PlaceholderText="Enter amount"
PlaceholderForeground="Orange"/>
</StackPanel>
<StackPanel Margin="10">
<Label FontSize="14" FontWeight="Bold" Target="InnerContent"
Content="Inner Contents"/>

43
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -17,34 +17,41 @@
</Flyout>
</TextBox.ContextFlyout>
</TextBox>
<TextBox Width="200" Watermark="ReadOnly" IsReadOnly="True" Text="This is read only"/>
<TextBox Width="200" Watermark="Numeric with watermark" TextInputOptions.ContentType="Number" />
<TextBox Width="200" PlaceholderText="ReadOnly" IsReadOnly="True" Text="This is read only"/>
<TextBox Width="200" PlaceholderText="Numeric with placeholder" TextInputOptions.ContentType="Number" />
<TextBox Width="200"
Watermark="Floating Watermark"
UseFloatingWatermark="True"
PlaceholderText="Floating Placeholder"
UseFloatingPlaceholder="True"
Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<TextBox Width="200"
PlaceholderText="Custom Placeholder Color"
PlaceholderForeground="Red"/>
<TextBox Width="200"
PlaceholderText="Floating Placeholder Color"
UseFloatingPlaceholder="True"
PlaceholderForeground="Purple"/>
<MaskedTextBox Width="200" ResetOnSpace="False" Mask="(LLL) 999-0000"/>
<TextBox Width="200" Text="Validation Error">
<DataValidationErrors.Error>
<sys:Exception />
<sys:Exception />
</DataValidationErrors.Error>
</TextBox>
<TextBox Width="200"
Watermark="Password Box"
PlaceholderText="Password Box"
Classes="revealPasswordButton"
TextInputOptions.ContentType="Password"
UseFloatingWatermark="True"
UseFloatingPlaceholder="True"
PasswordChar="*"
Text="Password" />
<TextBox Width="200" Watermark="Suggestions are hidden" TextInputOptions.ShowSuggestions="False" />
<TextBox Width="200" PlaceholderText="Suggestions are hidden" TextInputOptions.ShowSuggestions="False" />
<TextBox Width="200" Text="Left aligned text" TextAlignment="Left" AcceptsTab="True" />
<TextBox Width="200" Text="Center aligned text" TextAlignment="Center" />
<TextBox Width="200" Text="Right aligned text" TextAlignment="Right" />
<TextBox Width="200" Text="Custom selection brush"
SelectionStart="5" SelectionEnd="22"
SelectionBrush="Green" SelectionForegroundBrush="Yellow" ClearSelectionOnLostFocus="False"/>
SelectionStart="5" SelectionEnd="22"
SelectionBrush="Green" SelectionForegroundBrush="Yellow" ClearSelectionOnLostFocus="False"/>
<TextBox Width="200" Text="Custom caret brush" CaretBrush="DarkOrange"/>
</StackPanel>
@ -54,11 +61,11 @@
<TextBox AcceptsReturn="True" Width="200" Height="125"
Text="Multiline TextBox with no TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
<TextBox Classes="clearButton" Text="Clear Content" Width="200" FontWeight="Normal" FontStyle="Normal" Watermark="Watermark" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
<TextBox Classes="clearButton" Text="Clear Content" Width="200" FontWeight="Normal" FontStyle="Normal" PlaceholderText="Placeholder" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
<TextBox Text="IME small font" Width="200"
FontFamily="Comic Sans MS"
FontSize="10"
Foreground="Red"/>
FontFamily="Comic Sans MS"
FontSize="10"
Foreground="Red"/>
<TextBox Text="IME large font" Width="200"
FontFamily="Comic Sans MS"
FontSize="22"
@ -67,9 +74,9 @@
FontFamily="Comic Sans MS"
InputMethod.IsInputMethodEnabled="False"
Foreground="Red"/>
<TextBox AcceptsReturn="True"
TextWrapping="Wrap"
Width="200"
<TextBox AcceptsReturn="True"
TextWrapping="Wrap"
Width="200"
Height="125"
LineHeight="32"
Text="Multiline TextBox with TextWrapping and increased LineHeight.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />

6
samples/ControlCatalog/Pages/ThemePage.axaml

@ -1,4 +1,4 @@
<UserControl x:Class="ControlCatalog.Pages.ThemePage"
<UserControl x:Class="ControlCatalog.Pages.ThemePage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@ -70,8 +70,8 @@
</ComboBox>
<TextBlock Grid.Column="0" Grid.Row="2" Text="Username:" VerticalAlignment="Center" />
<TextBlock Grid.Column="0" Grid.Row="4" Text="Password:" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="2" Watermark="Input here" HorizontalAlignment="Stretch" />
<TextBox Grid.Column="1" Grid.Row="4" Watermark="Input here" HorizontalAlignment="Stretch" />
<TextBox Grid.Column="1" Grid.Row="2" PlaceholderText="Input here" HorizontalAlignment="Stretch" />
<TextBox Grid.Column="1" Grid.Row="4" PlaceholderText="Input here" HorizontalAlignment="Stretch" />
<Button Grid.Column="1" Grid.Row="6" Content="Login" HorizontalAlignment="Stretch" />
</Grid>
</Border>

14
samples/Generators.Sandbox/Controls/SignUpView.xaml

@ -1,4 +1,4 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Generators.Sandbox.Controls"
xmlns:vm="clr-namespace:Generators.Sandbox.ViewModels"
@ -17,8 +17,8 @@
<controls:CustomTextBox Margin="0 10 0 0"
x:Name="UserNameTextBox"
Text="{Binding UserName}"
Watermark="Please, enter user name..."
UseFloatingWatermark="True" />
PlaceholderText="Please, enter user name..."
UseFloatingPlaceholder="True" />
<TextBlock x:Name="UserNameValidation"
Text="{Binding UserNameValidation}"
Foreground="Green"
@ -26,8 +26,8 @@
<TextBox Margin="0 10 0 0"
x:Name="PasswordTextBox"
Text="{Binding Password}"
Watermark="Please, enter your password..."
UseFloatingWatermark="True"
PlaceholderText="Please, enter your password..."
UseFloatingPlaceholder="True"
PasswordChar="*" />
<TextBlock x:Name="PasswordValidation"
Text="{Binding PasswordValidation}"
@ -36,8 +36,8 @@
<TextBox Margin="0 10 0 0"
x:Name="ConfirmPasswordTextBox"
Text="{Binding ConfirmPassword}"
Watermark="Please, confirm the password..."
UseFloatingWatermark="True"
PlaceholderText="Please, confirm the password..."
UseFloatingPlaceholder="True"
PasswordChar="*" />
<TextBlock x:Name="ConfirmPasswordValidation"
Text="{Binding ConfirmPasswordValidation}"

2
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -37,7 +37,7 @@ namespace IntegrationTestApp
{
Header = (string?)page.Name,
ToolTip = $"Tip:{(string?)page.Name}",
ToggleType = NativeMenuItemToggleType.Radio,
ToggleType = MenuItemToggleType.Radio
};
menuItem.Click += (_, _) =>

2
samples/IntegrationTestApp/Pages/EmbeddingPage.axaml

@ -16,5 +16,7 @@
</Popup>
</StackPanel>
<Button Name="Reset" Click="Reset_Click">Reset</Button>
<Button Name="RunNativeModalSession" Click="RunNativeModalSession_OnClick">Open Native Modal</Button>
<TextBox Name="ModalResultTextBox" />
</StackPanel>
</UserControl>

69
samples/IntegrationTestApp/Pages/EmbeddingPage.axaml.cs

@ -1,10 +1,19 @@
using System;
using Avalonia.Automation;
using Avalonia.Controls;
using Avalonia.Controls.Embedding;
using Avalonia.Interactivity;
using IntegrationTestApp.Embedding;
using MonoMac.AppKit;
using MonoMac.CoreGraphics;
using MonoMac.ObjCRuntime;
namespace IntegrationTestApp;
public partial class EmbeddingPage : UserControl
{
private const long NSModalResponseContinue = -1002;
public EmbeddingPage()
{
InitializeComponent();
@ -19,5 +28,65 @@ public partial class EmbeddingPage : UserControl
private void Reset_Click(object? sender, RoutedEventArgs e)
{
ResetText();
ModalResultTextBox.Text = "";
}
private void RunNativeModalSession_OnClick(object? sender, RoutedEventArgs e)
{
MacHelper.EnsureInitialized();
var app = NSApplication.SharedApplication;
var modalWindow = CreateNativeWindow();
var session = app.BeginModalSession(modalWindow);
while (true)
{
if (app.RunModalSession(session) != NSModalResponseContinue)
break;
}
app.EndModalSession(session);
}
private NSWindow CreateNativeWindow()
{
var button = new Button
{
Name = "ButtonInModal",
Content = "Button"
};
AutomationProperties.SetAutomationId(button, "ButtonInModal");
var root = new EmbeddableControlRoot
{
Width = 200,
Height = 200,
Content = button
};
root.Prepare();
var window = new NSWindow(
new CGRect(0, 0, root.Width, root.Height),
NSWindowStyle.Titled | NSWindowStyle.Closable,
NSBackingStore.Buffered,
false);
window.Identifier = "ModalNativeWindow";
window.WillClose += (_, _) => NSApplication.SharedApplication.StopModal();
button.Click += (_, _) =>
{
ModalResultTextBox.Text = "Clicked";
window.Close();
};
if (root.TryGetPlatformHandle() is not { } handle)
throw new InvalidOperationException("Could not get platform handle");
window.ContentView = (NSView)Runtime.GetNSObject(handle.Handle)!;
root.StartRendering();
return window;
}
}

14
samples/IntegrationTestApp/Pages/ScreensPage.axaml

@ -8,12 +8,12 @@
<Button Name="ScreenRefresh"
Content="Refresh"
Click="ScreenRefresh_Click"/>
<TextBox Name="ScreenName" Watermark="DisplayName" UseFloatingWatermark="true" />
<TextBox Name="ScreenHandle" Watermark="Handle" UseFloatingWatermark="true" />
<TextBox Name="ScreenScaling" Watermark="Scaling" UseFloatingWatermark="true" />
<TextBox Name="ScreenBounds" Watermark="Bounds" UseFloatingWatermark="true" />
<TextBox Name="ScreenWorkArea" Watermark="WorkArea" UseFloatingWatermark="true" />
<TextBox Name="ScreenOrientation" Watermark="Orientation" UseFloatingWatermark="true" />
<TextBox Name="ScreenSameReference" Watermark="Is same reference" UseFloatingWatermark="true" />
<TextBox Name="ScreenName" PlaceholderText="DisplayName" UseFloatingPlaceholder="true" />
<TextBox Name="ScreenHandle" PlaceholderText="Handle" UseFloatingPlaceholder="true" />
<TextBox Name="ScreenScaling" PlaceholderText="Scaling" UseFloatingPlaceholder="true" />
<TextBox Name="ScreenBounds" PlaceholderText="Bounds" UseFloatingPlaceholder="true" />
<TextBox Name="ScreenWorkArea" PlaceholderText="WorkArea" UseFloatingPlaceholder="true" />
<TextBox Name="ScreenOrientation" PlaceholderText="Orientation" UseFloatingPlaceholder="true" />
<TextBox Name="ScreenSameReference" PlaceholderText="Is same reference" UseFloatingPlaceholder="true" />
</StackPanel>
</UserControl>

2
samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml

@ -10,7 +10,7 @@
<CheckBox Name="WindowPreferSystemChrome" Content="Prefer SystemChrome" />
<CheckBox Name="WindowMacThickSystemChrome" Content="Mac Thick SystemChrome" />
<CheckBox Name="WindowShowTitleAreaControl" Content="Show Title Area Control" />
<TextBox Name="WindowTitleBarHeightHint" Text="-1" Watermark="In dips" />
<TextBox Name="WindowTitleBarHeightHint" Text="-1" PlaceholderText="In dips" />
<Button Name="ApplyWindowDecorations"
Content="Apply decorations on this Window"
Click="ApplyWindowDecorations_Click"/>

2
samples/IntegrationTestApp/Pages/WindowPage.axaml

@ -6,7 +6,7 @@
x:Class="IntegrationTestApp.Pages.WindowPage">
<Grid ColumnDefinitions="*,8,*">
<StackPanel Grid.Column="0">
<TextBox Name="ShowWindowSize" Watermark="Window Size"/>
<TextBox Name="ShowWindowSize" PlaceholderText="Window Size"/>
<ComboBox Name="ShowWindowMode" SelectedIndex="0">
<ComboBoxItem>NonOwned</ComboBoxItem>
<ComboBoxItem>Owned</ComboBoxItem>

4
samples/SafeAreaDemo/Views/MainView.xaml

@ -24,7 +24,7 @@
<Label Margin="5"
Foreground="Red"
VerticalAlignment="Bottom"
HorizontalContentAlignment="Right">View Bounds</Label>
HorizontalContentAlignment="Right">View Bounds</Label>
</Grid>
</Border>
<Border BorderBrush="LimeGreen"
@ -47,7 +47,7 @@
<CheckBox IsChecked="{Binding UseSafeArea}" IsEnabled="{Binding !AutoSafeAreaPadding}">Use Safe Area</CheckBox>
<CheckBox IsChecked="{Binding AutoSafeAreaPadding}">Automatic Paddings</CheckBox>
<CheckBox IsChecked="{Binding HideSystemBars}">Hide System Bars</CheckBox>
<TextBox Width="200" Watermark="Tap to Show Keyboard"/>
<TextBox Width="200" PlaceholderText="Tap to Show Keyboard"/>
</StackPanel>
</Grid>
</DockPanel>

8
samples/SingleProjectSandbox/MainView.axaml

@ -5,10 +5,10 @@
x:DataType="local:MainView">
<StackPanel Margin="100 50" Spacing="50">
<TextBlock Text="Login" Foreground="White" />
<TextBox TextInputOptions.Multiline="True" AcceptsReturn="True" Watermark="Text" Height="200" TextWrapping="Wrap"/>
<TextBox Watermark="Username" TextInputOptions.ContentType="Email" TextInputOptions.ReturnKeyType="Done" />
<TextBox Watermark="Password" PasswordChar="*" TextInputOptions.ContentType="Password" />
<TextBox Watermark="Pin" PasswordChar="*" TextInputOptions.ContentType="Digits" TextInputOptions.ReturnKeyType="Next" />
<TextBox TextInputOptions.Multiline="True" AcceptsReturn="True" PlaceholderText="Text" Height="200" TextWrapping="Wrap"/>
<TextBox PlaceholderText="Username" TextInputOptions.ContentType="Email" TextInputOptions.ReturnKeyType="Done" />
<TextBox PlaceholderText="Password" PasswordChar="*" TextInputOptions.ContentType="Password" />
<TextBox PlaceholderText="Pin" PasswordChar="*" TextInputOptions.ContentType="Digits" TextInputOptions.ReturnKeyType="Next" />
<Button Content="Login" Command="{Binding ButtonCommand}" />
</StackPanel>
</UserControl>

44
src/Android/Avalonia.Android/AvaloniaActivity.cs

@ -119,22 +119,14 @@ public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity
activatableLifetime.CurrentIntendActivity = this;
}
if (Intent?.Data is { } androidUri
&& androidUri.IsAbsolute
&& Uri.TryCreate(androidUri.ToString(), UriKind.Absolute, out var uri))
{
if (uri.Scheme == Uri.UriSchemeFile)
{
if (AndroidStorageItem.CreateItem(this, androidUri) is { } item)
{
_onActivated?.Invoke(this, new FileActivatedEventArgs(new [] { item }));
}
}
else
{
_onActivated?.Invoke(this, new ProtocolActivatedEventArgs(uri));
}
}
HandleIntent(Intent);
}
protected override void OnNewIntent(Intent? intent)
{
base.OnNewIntent(intent);
HandleIntent(intent);
}
protected override void OnStop()
@ -218,6 +210,26 @@ public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity
_view = new AvaloniaView(this) { Content = initialContent };
}
private void HandleIntent(Intent? intent)
{
if (intent?.Data is { } androidUri
&& androidUri.IsAbsolute
&& Uri.TryCreate(androidUri.ToString(), UriKind.Absolute, out var uri))
{
if (uri.Scheme == Uri.UriSchemeFile || uri.Scheme == "content")
{
if (AndroidStorageItem.CreateItem(this, androidUri) is { } item)
{
_onActivated?.Invoke(this, new FileActivatedEventArgs(new[] { item }));
}
}
else
{
_onActivated?.Invoke(this, new ProtocolActivatedEventArgs(uri));
}
}
}
public void OnBackInvoked()
{
var eventArgs = new AndroidBackRequestedEventArgs();

2
src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs

@ -318,8 +318,6 @@ namespace Avalonia.Android.Platform
}
}
public bool DisplayEdgeToEdge { get => DisplaysEdgeToEdge; set => DisplayEdgeToEdgePreference = value; }
public bool DisplaysEdgeToEdge => _displaysEdgeToEdge;
internal void ApplyStatusBarState()

19
src/Android/Avalonia.Android/Platform/AndroidScreens.cs

@ -10,6 +10,7 @@ using AndroidX.Window.Layout;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Platform;
using AndroidOrientation = global::Android.Content.Res.Orientation;
using AndroidRotation = global::Android.Views.SurfaceOrientation;
namespace Avalonia.Android.Platform;
@ -53,7 +54,7 @@ internal class AndroidScreen(Display display) : PlatformScreen(new PlatformHandl
var orientation = displayContext.Resources?.Configuration?.Orientation;
if (orientation == AndroidOrientation.Square)
naturalOrientation = ScreenOrientation.None;
else if (rotation is SurfaceOrientation.Rotation0 or SurfaceOrientation.Rotation180)
else if (rotation is AndroidRotation.Rotation0 or AndroidRotation.Rotation180)
naturalOrientation = orientation == AndroidOrientation.Landscape ?
ScreenOrientation.Landscape :
ScreenOrientation.Portrait;
@ -73,14 +74,14 @@ internal class AndroidScreen(Display display) : PlatformScreen(new PlatformHandl
CurrentOrientation = (display.Rotation, naturalOrientation) switch
{
(_, ScreenOrientation.None) => ScreenOrientation.None,
(SurfaceOrientation.Rotation0, ScreenOrientation.Landscape) => ScreenOrientation.Landscape,
(SurfaceOrientation.Rotation90, ScreenOrientation.Landscape) => ScreenOrientation.Portrait,
(SurfaceOrientation.Rotation180, ScreenOrientation.Landscape) => ScreenOrientation.LandscapeFlipped,
(SurfaceOrientation.Rotation270, ScreenOrientation.Landscape) => ScreenOrientation.PortraitFlipped,
(SurfaceOrientation.Rotation0, _) => ScreenOrientation.Portrait,
(SurfaceOrientation.Rotation90, _) => ScreenOrientation.Landscape,
(SurfaceOrientation.Rotation180, _) => ScreenOrientation.PortraitFlipped,
(SurfaceOrientation.Rotation270, _) => ScreenOrientation.LandscapeFlipped,
(AndroidRotation.Rotation0, ScreenOrientation.Landscape) => ScreenOrientation.Landscape,
(AndroidRotation.Rotation90, ScreenOrientation.Landscape) => ScreenOrientation.Portrait,
(AndroidRotation.Rotation180, ScreenOrientation.Landscape) => ScreenOrientation.LandscapeFlipped,
(AndroidRotation.Rotation270, ScreenOrientation.Landscape) => ScreenOrientation.PortraitFlipped,
(AndroidRotation.Rotation0, _) => ScreenOrientation.Portrait,
(AndroidRotation.Rotation90, _) => ScreenOrientation.Landscape,
(AndroidRotation.Rotation180, _) => ScreenOrientation.PortraitFlipped,
(AndroidRotation.Rotation270, _) => ScreenOrientation.LandscapeFlipped,
_ => ScreenOrientation.Portrait
};
}

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -26,7 +26,7 @@ using ClipboardManager = Android.Content.ClipboardManager;
namespace Avalonia.Android.Platform.SkiaPlatform
{
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfoWithWaitPolicy
class TopLevelImpl : ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfoWithWaitPolicy
{
private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
private readonly AndroidMotionEventsHelper _pointerHelper;

6
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs

@ -8,7 +8,7 @@ using Avalonia.Input.Raw;
namespace Avalonia.Android.Platform.Specific.Helpers
{
internal class AndroidKeyboardEventsHelper<TView> : IDisposable where TView : TopLevelImpl, IAndroidView
internal class AndroidKeyboardEventsHelper<TView> : IDisposable where TView : TopLevelImpl
{
private readonly TView _view;
@ -65,8 +65,8 @@ namespace Avalonia.Android.Platform.Specific.Helpers
AndroidKeyboardDevice.ConvertKey(e.KeyCode),
GetModifierKeys(e),
physicalKey,
keyDeviceType,
keySymbol);
keySymbol,
keyDeviceType);
_view.Input?.Invoke(rawKeyEvent);

11
src/Android/Avalonia.Android/Platform/Specific/IAndroidView.cs

@ -1,11 +0,0 @@
using System;
using Android.Views;
namespace Avalonia.Android.Platform.Specific
{
public interface IAndroidView
{
[Obsolete("Use TopLevel.TryGetPlatformHandle instead, which can be casted to AndroidViewControlHandle.")]
View View { get; }
}
}

2
src/Android/Avalonia.Android/Stubs.cs

@ -12,6 +12,8 @@ namespace Avalonia.Android
public IWindowImpl CreateEmbeddableWindow() => throw new NotSupportedException();
public ITrayIconImpl? CreateTrayIcon() => null;
public void GetWindowsZOrder(ReadOnlySpan<IWindowImpl> windows, Span<long> zOrder) => throw new NotSupportedException();
}
internal class PlatformIconLoaderStub : IPlatformIconLoader

13
src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs

@ -7,17 +7,6 @@ namespace Avalonia.Animation;
partial class Animation
{
/// <summary>
/// Sets the value of the Animator attached property for a setter.
/// </summary>
/// <param name="setter">The animation setter.</param>
/// <param name="value">The property animator value.</param>
[Obsolete("CustomAnimatorBase will be removed before 11.0, use InterpolatingAnimator<T>", true)]
public static void SetAnimator(IAnimationSetter setter, CustomAnimatorBase value)
{
s_animators[setter] = (value.WrapperType, value.CreateWrapper);
}
/// <summary>
/// Sets the value of the Animator attached property for a setter.
/// </summary>
@ -92,4 +81,4 @@ partial class Animation
return null;
}
}
}

17
src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs

@ -1,17 +0,0 @@
using System;
namespace Avalonia.Animation.Easings;
[Obsolete("Use SplineEasing instead")]
public sealed class CubicBezierEasing : IEasing
{
private CubicBezierEasing()
{
}
public Point ControlPoint2 { get; set; }
public Point ControlPoint1 { get; set; }
double IEasing.Ease(double progress)
=> throw new NotSupportedException();
}

30
src/Avalonia.Base/Animation/ICustomAnimator.cs

@ -2,34 +2,6 @@ using System;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation;
[Obsolete("This class will be removed before 11.0, use InterpolatingAnimator<T>", true)]
public abstract class CustomAnimatorBase
{
internal abstract IAnimator CreateWrapper();
internal abstract Type WrapperType { get; }
}
[Obsolete("This class will be removed before 11.0, use InterpolatingAnimator<T>", true)]
public abstract class CustomAnimatorBase<T> : CustomAnimatorBase
{
public abstract T Interpolate(double progress, T oldValue, T newValue);
internal override Type WrapperType => typeof(AnimatorWrapper);
internal override IAnimator CreateWrapper() => new AnimatorWrapper(this);
internal class AnimatorWrapper : Animator<T>
{
private readonly CustomAnimatorBase<T> _parent;
public AnimatorWrapper(CustomAnimatorBase<T> parent)
{
_parent = parent;
}
public override T Interpolate(double progress, T oldValue, T newValue) => _parent.Interpolate(progress, oldValue, newValue);
}
}
public interface ICustomAnimator
{
internal IAnimator CreateWrapper();
@ -55,4 +27,4 @@ public abstract class InterpolatingAnimator<T> : ICustomAnimator
public override T Interpolate(double progress, T oldValue, T newValue) => _parent.Interpolate(progress, oldValue, newValue);
}
}
}

27
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -227,33 +227,6 @@ namespace Avalonia
};
}
/// <summary>
/// Binds a property on an <see cref="AvaloniaObject"/> to an <see cref="BindingBase"/>.
/// </summary>
/// <param name="target">The object.</param>
/// <param name="property">The property to bind.</param>
/// <param name="binding">The binding.</param>
/// <param name="anchor">
/// An optional anchor from which to locate required context. When binding to objects that
/// are not in the logical tree, certain types of binding need an anchor into the tree in
/// order to locate named controls or resources. The <paramref name="anchor"/> parameter
/// can be used to provide this context.
/// </param>
/// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns>
[Obsolete("Use AvaloniaObject.Bind(AvaloniaProperty, IBinding")]
public static IDisposable Bind(
this AvaloniaObject target,
AvaloniaProperty property,
BindingBase binding,
object? anchor = null)
{
target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property));
binding = binding ?? throw new ArgumentNullException(nameof(binding));
return target.Bind(property, binding);
}
/// <summary>
/// Gets a <see cref="AvaloniaProperty"/> value.
/// </summary>

8
src/Avalonia.Base/Controls/IResourceHost.cs

@ -29,12 +29,4 @@ namespace Avalonia.Controls
/// </remarks>
void NotifyHostedResourcesChanged(ResourcesChangedEventArgs e);
}
// TODO12: merge with IResourceHost
internal interface IResourceHost2 : IResourceHost
{
event EventHandler<ResourcesChangedToken> ResourcesChanged2;
void NotifyHostedResourcesChanged(ResourcesChangedToken token);
}
}

4
src/Avalonia.Base/Controls/ResourceDictionary.cs

@ -358,7 +358,7 @@ namespace Avalonia.Controls
if (hasResources)
{
owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create());
owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create());
}
}
@ -385,7 +385,7 @@ namespace Avalonia.Controls
if (hasResources)
{
owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create());
owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create());
}
}

22
src/Avalonia.Base/Controls/ResourceNodeExtensions.cs

@ -158,7 +158,7 @@ namespace Avalonia.Controls
protected override void Initialize()
{
_target.SubscribeToResourcesChanged(ResourcesChanged, ResourcesChanged2);
_target.ResourcesChanged += ResourcesChanged;
if (_target is IThemeVariantHost themeVariantHost)
{
@ -168,7 +168,7 @@ namespace Avalonia.Controls
protected override void Deinitialize()
{
_target.UnsubscribeFromResourcesChanged(ResourcesChanged, ResourcesChanged2);
_target.ResourcesChanged -= ResourcesChanged;
if (_target is IThemeVariantHost themeVariantHost)
{
@ -186,11 +186,6 @@ namespace Avalonia.Controls
PublishNext(GetValue());
}
private void ResourcesChanged2(object? sender, ResourcesChangedToken token)
{
PublishNext(GetValue());
}
private void ActualThemeVariantChanged(object? sender, EventArgs e)
{
PublishNext(GetValue());
@ -230,7 +225,7 @@ namespace Avalonia.Controls
_target.OwnerChanged += OwnerChanged;
_owner = _target.Owner;
_owner?.SubscribeToResourcesChanged(ResourcesChanged, ResourcesChanged2);
_owner?.ResourcesChanged += ResourcesChanged;
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost)
{
@ -242,7 +237,7 @@ namespace Avalonia.Controls
{
_target.OwnerChanged -= OwnerChanged;
_owner?.UnsubscribeFromResourcesChanged(ResourcesChanged, ResourcesChanged2);
_owner?.ResourcesChanged -= ResourcesChanged;
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost)
{
@ -270,7 +265,7 @@ namespace Avalonia.Controls
private void OwnerChanged(object? sender, EventArgs e)
{
_owner?.UnsubscribeFromResourcesChanged(ResourcesChanged, ResourcesChanged2);
_owner?.ResourcesChanged -= ResourcesChanged;
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost)
{
@ -279,7 +274,7 @@ namespace Avalonia.Controls
_owner = _target.Owner;
_owner?.SubscribeToResourcesChanged(ResourcesChanged, ResourcesChanged2);
_owner?.ResourcesChanged += ResourcesChanged;
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost2)
{
@ -299,11 +294,6 @@ namespace Avalonia.Controls
PublishNext();
}
private void ResourcesChanged2(object? sender, ResourcesChangedToken token)
{
PublishNext();
}
private object? GetValue()
{
var theme = _overrideThemeVariant ?? (_target.Owner as IThemeVariantHost)?.ActualThemeVariant;

6
src/Avalonia.Base/Controls/ResourceProvider.cs

@ -45,7 +45,7 @@ public abstract class ResourceProvider : AvaloniaObject, IResourceProvider
protected void RaiseResourcesChanged()
{
Owner?.NotifyHostedResourcesChanged(ResourcesChangedToken.Create());
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create());
}
/// <summary>
@ -57,7 +57,7 @@ public abstract class ResourceProvider : AvaloniaObject, IResourceProvider
{
if (HasResources)
{
owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create());
owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create());
}
}
@ -70,7 +70,7 @@ public abstract class ResourceProvider : AvaloniaObject, IResourceProvider
{
if (HasResources)
{
owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create());
owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create());
}
}

28
src/Avalonia.Base/Controls/ResourcesChangedEventArgs.cs

@ -1,10 +1,24 @@
using System;
using System.Threading;
namespace Avalonia.Controls
namespace Avalonia.Controls;
/// <summary>
/// Represents the event arguments of <see cref="IResourceHost.ResourcesChanged"/>.
/// The <see cref="SequenceNumber"/> identifies the changes.
/// </summary>
/// <param name="SequenceNumber">The sequence number used to identify the changes.</param>
/// <remarks>
/// For performance reasons, this type is a struct.
/// Avoid using a default instance of this type or its default constructor, call <see cref="Create"/> instead.
/// </remarks>
public readonly record struct ResourcesChangedEventArgs(int SequenceNumber)
{
// TODO12: change this to be a struct, remove ResourcesChangedToken
public class ResourcesChangedEventArgs : EventArgs
{
public static new readonly ResourcesChangedEventArgs Empty = new ResourcesChangedEventArgs();
}
private static int s_lastSequenceNumber;
/// <summary>
/// Creates a new instance of <see cref="ResourcesChangedEventArgs"/> with an auto-incremented sequence number.
/// </summary>
/// <returns></returns>
public static ResourcesChangedEventArgs Create()
=> new(Interlocked.Increment(ref s_lastSequenceNumber));
}

45
src/Avalonia.Base/Controls/ResourcesChangedHelper.cs

@ -1,45 +0,0 @@
using System;
using Avalonia.LogicalTree;
namespace Avalonia.Controls;
internal static class ResourcesChangedHelper
{
internal static void NotifyHostedResourcesChanged(this IResourceHost host, ResourcesChangedToken token)
{
if (host is IResourceHost2 host2)
host2.NotifyHostedResourcesChanged(token);
else
host.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
internal static void NotifyResourcesChanged(this ILogical logical, ResourcesChangedToken token)
{
if (logical is StyledElement styledElement)
styledElement.NotifyResourcesChanged(token);
else
logical.NotifyResourcesChanged(ResourcesChangedEventArgs.Empty);
}
internal static void SubscribeToResourcesChanged(
this IResourceHost host,
EventHandler<ResourcesChangedEventArgs> handler,
EventHandler<ResourcesChangedToken> handler2)
{
if (host is IResourceHost2 host2)
host2.ResourcesChanged2 += handler2;
else
host.ResourcesChanged += handler;
}
internal static void UnsubscribeFromResourcesChanged(
this IResourceHost host,
EventHandler<ResourcesChangedEventArgs> handler,
EventHandler<ResourcesChangedToken> handler2)
{
if (host is IResourceHost2 host2)
host2.ResourcesChanged2 -= handler2;
else
host.ResourcesChanged -= handler;
}
}

11
src/Avalonia.Base/Controls/ResourcesChangedToken.cs

@ -1,11 +0,0 @@
using System.Threading;
namespace Avalonia.Controls;
internal record struct ResourcesChangedToken(int SequenceNumber)
{
private static int s_lastSequenceNumber;
public static ResourcesChangedToken Create()
=> new(Interlocked.Increment(ref s_lastSequenceNumber));
}

5
src/Avalonia.Base/Data/BindingPriority.cs

@ -46,9 +46,6 @@ namespace Avalonia.Data
/// <summary>
/// The value is uninitialized.
/// </summary>
Unset = int.MaxValue,
[Obsolete("Use Template priority"), EditorBrowsable(EditorBrowsableState.Never)]
TemplatedParent = Template,
Unset = int.MaxValue
}
}

93
src/Avalonia.Base/Data/CompiledBinding.cs

@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq.Expressions;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Data.Core.Parsers;
using Avalonia.Metadata;
namespace Avalonia.Data;
@ -26,6 +29,95 @@ public class CompiledBinding : BindingBase
/// <param name="path">The binding path.</param>
public CompiledBinding(CompiledBindingPath path) => Path = path;
/// <summary>
/// Creates a <see cref="CompiledBinding"/> from a lambda expression.
/// </summary>
/// <typeparam name="TIn">The input type of the binding expression.</typeparam>
/// <typeparam name="TOut">The output type of the binding expression.</typeparam>
/// <param name="expression">
/// The lambda expression representing the binding path
/// (e.g., <c>vm => vm.PropertyName</c>).
/// </param>
/// <param name="source"
/// >The source object for the binding. If null, uses the target's DataContext.
/// </param>
/// <param name="converter">
/// Optional value converter to transform values between source and target.
/// </param>
/// <param name="mode">
/// The binding mode. Default is <see cref="BindingMode.Default"/> which resolves to the
/// property's default binding mode.
/// </param>
/// <param name="priority">The binding priority.</param>
/// <param name="converterCulture">The culture in which to evaluate the converter.</param>
/// <param name="converterParameter">A parameter to pass to the converter.</param>
/// <param name="fallbackValue">
/// The value to use when the binding is unable to produce a value.
/// </param>
/// <param name="stringFormat">The string format for the binding result.</param>
/// <param name="targetNullValue">The value to use when the binding result is null.</param>
/// <param name="updateSourceTrigger">
/// The timing of binding source updates for TwoWay/OneWayToSource bindings.
/// </param>
/// <param name="delay">
/// The amount of time, in milliseconds, to wait before updating the binding source.
/// </param>
/// <returns>
/// A configured <see cref="CompiledBinding"/> instance ready to be applied to a property.
/// </returns>
/// <exception cref="ExpressionParseException">
/// Thrown when the expression contains unsupported operations or invalid syntax for binding
/// expressions.
/// </exception>
/// <remarks>
/// This builds a <see cref="CompiledBinding"/> with a path described by a lambda expression.
/// The resulting binding avoids reflection for property access, providing better performance
/// than reflection-based bindings.
///
/// Supported expressions include:
/// <list type="bullet">
/// <item>Property access: <c>x => x.Property</c></item>
/// <item>Nested properties: <c>x => x.Property.Nested</c></item>
/// <item>Indexers: <c>x => x.Items[0]</c></item>
/// <item>Type casts: <c>x => ((DerivedType)x).Property</c></item>
/// <item>Logical NOT: <c>x => !x.BoolProperty</c></item>
/// <item>Stream bindings: <c>x => x.TaskProperty</c> (Task/Observable)</item>
/// <item>AvaloniaProperty access: <c>x => x[MyProperty]</c></item>
/// </list>
/// </remarks>
[RequiresDynamicCode(TrimmingMessages.ExpressionNodeRequiresDynamicCodeMessage)]
[RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)]
public static CompiledBinding Create<TIn, TOut>(
Expression<Func<TIn, TOut>> expression,
object? source = null,
IValueConverter? converter = null,
BindingMode mode = BindingMode.Default,
BindingPriority priority = BindingPriority.LocalValue,
CultureInfo? converterCulture = null,
object? converterParameter = null,
object? fallbackValue = null,
string? stringFormat = null,
object? targetNullValue = null,
UpdateSourceTrigger updateSourceTrigger = UpdateSourceTrigger.Default,
int delay = 0)
{
var path = BindingExpressionVisitor<TIn>.BuildPath(expression);
return new CompiledBinding(path)
{
Source = source ?? AvaloniaProperty.UnsetValue,
Converter = converter,
ConverterCulture = converterCulture,
ConverterParameter = converterParameter,
FallbackValue = fallbackValue ?? AvaloniaProperty.UnsetValue,
Mode = mode,
Priority = priority,
StringFormat = stringFormat,
TargetNullValue = targetNullValue ?? AvaloniaProperty.UnsetValue,
UpdateSourceTrigger = updateSourceTrigger,
Delay = delay
};
}
/// <summary>
/// Gets or sets the amount of time, in milliseconds, to wait before updating the binding
/// source after the value on the target changes.
@ -70,6 +162,7 @@ public class CompiledBinding : BindingBase
/// <summary>
/// Gets or sets the binding path.
/// </summary>
[ConstructorArgument("path")]
public CompiledBindingPath? Path { get; set; }
/// <summary>

6
src/Avalonia.Base/Data/CompiledBindingPath.cs

@ -203,12 +203,6 @@ namespace Avalonia.Data
return this;
}
[Obsolete("This method doesn't do anything anymore. Use Binding.Source instead.")]
public CompiledBindingPathBuilder SetRawSource(object? rawSource)
{
return this;
}
public CompiledBindingPath Build() => new CompiledBindingPath(_elements.ToArray());
}

53
src/Avalonia.Base/Data/Core/BindingExpression.cs

@ -173,59 +173,6 @@ internal class BindingExpression : UntypedBindingExpressionBase, IDescription, I
_nodes[0].SetSource(source, null);
}
/// <summary>
/// Creates an <see cref="BindingExpression"/> from an expression tree.
/// </summary>
/// <typeparam name="TIn">The input type of the binding expression.</typeparam>
/// <typeparam name="TOut">The output type of the binding expression.</typeparam>
/// <param name="source">The source from which the binding value will be read.</param>
/// <param name="expression">The expression representing the binding path.</param>
/// <param name="converter">The converter to use.</param>
/// <param name="converterCulture">The converter culture to use.</param>
/// <param name="converterParameter">The converter parameter.</param>
/// <param name="enableDataValidation">Whether data validation should be enabled for the binding.</param>
/// <param name="fallbackValue">The fallback value.</param>
/// <param name="mode">The binding mode.</param>
/// <param name="priority">The binding priority.</param>
/// <param name="targetNullValue">The null target value.</param>
/// <param name="allowReflection">Whether to allow reflection for target type conversion.</param>
[RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)]
#if NET8_0_OR_GREATER
[RequiresDynamicCode(TrimmingMessages.ExpressionNodeRequiresDynamicCodeMessage)]
#endif
internal static BindingExpression Create<TIn, TOut>(
TIn source,
Expression<Func<TIn, TOut>> expression,
IValueConverter? converter = null,
CultureInfo? converterCulture = null,
object? converterParameter = null,
bool enableDataValidation = false,
Optional<object?> fallbackValue = default,
BindingMode mode = BindingMode.OneWay,
BindingPriority priority = BindingPriority.LocalValue,
object? targetNullValue = null,
bool allowReflection = true)
where TIn : class?
{
var nodes = BindingExpressionVisitor<TIn>.BuildNodes(expression, enableDataValidation);
var fallback = fallbackValue.HasValue ? fallbackValue.Value : AvaloniaProperty.UnsetValue;
return new BindingExpression(
source,
nodes,
fallback,
converter: converter,
converterCulture: converterCulture,
converterParameter: converterParameter,
enableDataValidation: enableDataValidation,
mode: mode,
priority: priority,
targetNullValue: targetNullValue,
targetTypeConverter: allowReflection ?
TargetTypeConverter.GetReflectionConverter() :
TargetTypeConverter.GetDefaultConverter());
}
/// <summary>
/// Called by an <see cref="ExpressionNode"/> belonging to this binding when its
/// <see cref="ExpressionNode.Value"/> changes.

402
src/Avalonia.Base/Data/Core/Parsers/BindingExpressionVisitor.cs

@ -1,47 +1,59 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Data.Core.ExpressionNodes.Reflection;
using Avalonia.Data.Core.Plugins;
using Avalonia.Reactive;
using Avalonia.Utilities;
namespace Avalonia.Data.Core.Parsers;
[RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)]
#if NET8_0_OR_GREATER
/// <summary>
/// Visits and processes a LINQ expression to build a compiled binding path.
/// </summary>
/// <typeparam name="TIn">The input parameter type for the binding expression.</typeparam>
/// <remarks>
/// This visitor traverses lambda expressions used in compiled bindings and uses
/// <see cref="CompiledBindingPathBuilder"/> to construct a <see cref="CompiledBindingPath"/>, which
/// can then be converted into <see cref="ExpressionNode"/> instances. It supports property access,
/// indexers, AvaloniaProperty access, stream bindings, type casts, and logical operators.
/// </remarks>
[RequiresDynamicCode(TrimmingMessages.ExpressionNodeRequiresDynamicCodeMessage)]
#endif
internal class BindingExpressionVisitor<TIn> : ExpressionVisitor
[RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)]
internal class BindingExpressionVisitor<TIn>(LambdaExpression expression) : ExpressionVisitor
{
private static readonly PropertyInfo AvaloniaObjectIndexer;
private static readonly MethodInfo CreateDelegateMethod;
private const string IndexerGetterName = "get_Item";
private const string MultiDimensionalArrayGetterMethodName = "Get";
private readonly bool _enableDataValidation;
private readonly LambdaExpression _rootExpression;
private readonly List<ExpressionNode> _nodes = new();
private readonly LambdaExpression _rootExpression = expression;
private readonly CompiledBindingPathBuilder _builder = new();
private Expression? _head;
public BindingExpressionVisitor(LambdaExpression expression, bool enableDataValidation)
{
_rootExpression = expression;
_enableDataValidation = enableDataValidation;
}
static BindingExpressionVisitor()
{
AvaloniaObjectIndexer = typeof(AvaloniaObject).GetProperty("Item", new[] { typeof(AvaloniaProperty) })!;
CreateDelegateMethod = typeof(MethodInfo).GetMethod("CreateDelegate", new[] { typeof(Type), typeof(object) })!;
}
public static List<ExpressionNode> BuildNodes<TOut>(Expression<Func<TIn, TOut>> expression, bool enableDataValidation)
/// <summary>
/// Builds a compiled binding path from a lambda expression.
/// </summary>
/// <typeparam name="TOut">The output type of the binding expression.</typeparam>
/// <param name="expression">
/// The lambda expression to parse and convert into a binding path.
/// </param>
/// <returns>
/// A <see cref="CompiledBindingPath"/> representing the binding path.
/// </returns>
/// <exception cref="ExpressionParseException">
/// Thrown when the expression contains unsupported operations or invalid syntax for binding
/// expressions.
/// </exception>
public static CompiledBindingPath BuildPath<TOut>(Expression<Func<TIn, TOut>> expression)
{
var visitor = new BindingExpressionVisitor<TIn>(expression, enableDataValidation);
var visitor = new BindingExpressionVisitor<TIn>(expression);
visitor.Visit(expression);
return visitor._nodes;
return visitor._builder.Build();
}
protected override Expression VisitBinary(BinaryExpression node)
@ -49,33 +61,64 @@ internal class BindingExpressionVisitor<TIn> : ExpressionVisitor
// Indexers require more work since the compiler doesn't generate IndexExpressions:
// they weren't in System.Linq.Expressions v1 and so must be generated manually.
if (node.NodeType == ExpressionType.ArrayIndex)
return Visit(Expression.MakeIndex(node.Left, null, new[] { node.Right }));
return Visit(Expression.MakeIndex(node.Left, null, [node.Right]));
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
}
protected override Expression VisitIndex(IndexExpression node)
{
if (node.Indexer == AvaloniaObjectIndexer)
if (node.Indexer == BindingExpressionVisitorMembers.AvaloniaObjectIndexer)
{
var property = GetValue<AvaloniaProperty>(node.Arguments[0]);
return Add(node.Object, node, new AvaloniaPropertyAccessorNode(property));
return Add(node.Object, node, x => x.Property(property, CreateAvaloniaPropertyAccessor));
}
else
else if (node.Object?.Type.IsArray == true)
{
var indexes = node.Arguments.Select(GetValue<int>).ToArray();
return Add(node.Object, node, x => x.ArrayElement(indexes, node.Type));
}
else if (node.Indexer?.GetMethod is not null &&
node.Arguments.Count == 1 &&
node.Arguments[0].Type == typeof(int))
{
return Add(node.Object, node, new ExpressionTreeIndexerNode(node));
var getMethod = node.Indexer.GetMethod;
var setMethod = node.Indexer.SetMethod;
var index = GetValue<int>(node.Arguments[0]);
var info = new ClrPropertyInfo(
CommonPropertyNames.IndexerName,
x => getMethod.Invoke(x, new object[] { index }),
setMethod is not null ? (o, v) => setMethod.Invoke(o, new[] { index, v }) : null,
getMethod.ReturnType);
return Add(node.Object, node, x => x.Property(
info,
(weakRef, propInfo) => CreateIndexerPropertyAccessor(weakRef, propInfo, index)));
}
else if (node.Indexer?.GetMethod is not null)
{
var getMethod = node.Indexer.GetMethod;
var setMethod = node.Indexer?.SetMethod;
var indexes = node.Arguments.Select(GetValue<object>).ToArray();
var info = new ClrPropertyInfo(
CommonPropertyNames.IndexerName,
x => getMethod.Invoke(x, indexes),
setMethod is not null ? (o, v) => setMethod.Invoke(o, indexes.Append(v).ToArray()) : null,
getMethod.ReturnType);
return Add(node.Object, node, x => x.Property(
info,
CreateInpcPropertyAccessor));
}
throw new ExpressionParseException(0, $"Invalid indexer in binding expression: {node.NodeType}.");
}
protected override Expression VisitMember(MemberExpression node)
{
switch (node.Member.MemberType)
return node.Member.MemberType switch
{
case MemberTypes.Property:
return Add(node.Expression, node, new DynamicPluginPropertyAccessorNode(node.Member.Name, acceptsNull: false));
default:
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
}
MemberTypes.Property => AddPropertyNode(node),
_ => throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}."),
};
}
protected override Expression VisitMethodCall(MethodCallExpression node)
@ -90,20 +133,43 @@ internal class BindingExpressionVisitor<TIn> : ExpressionVisitor
else if (method.Name == MultiDimensionalArrayGetterMethodName &&
node.Object is not null)
{
var expression = Expression.MakeIndex(node.Object, null, node.Arguments);
return Add(node.Object, node, new ExpressionTreeIndexerNode(expression));
var indexes = node.Arguments.Select(GetValue<int>).ToArray();
return Add(node.Object, node, x => x.ArrayElement(indexes, node.Type));
}
else if (method.Name.StartsWith(StreamBindingExtensions.StreamBindingName) &&
method.DeclaringType == typeof(StreamBindingExtensions))
{
var instance = node.Method.IsStatic ? node.Arguments[0] : node.Object;
Add(instance, node, new DynamicPluginStreamNode());
return node;
var instanceType = instance?.Type;
var genericArgs = method.GetGenericArguments();
var genericArg = genericArgs.Length > 0 ? genericArgs[0] : typeof(object);
if (instanceType == typeof(Task) ||
(instanceType?.IsGenericType == true &&
instanceType.GetGenericTypeDefinition() == typeof(Task<>) &&
genericArg.IsAssignableFrom(instanceType.GetGenericArguments()[0])))
{
var builderMethod = typeof(CompiledBindingPathBuilder)
.GetMethod(nameof(CompiledBindingPathBuilder.StreamTask))!
.MakeGenericMethod(genericArg);
return Add(instance, node, x => builderMethod.Invoke(x, null));
}
else if (typeof(IObservable<>).MakeGenericType(genericArg).IsAssignableFrom(instance?.Type))
{
var builderMethod = typeof(CompiledBindingPathBuilder)
.GetMethod(nameof(CompiledBindingPathBuilder.StreamObservable))!
.MakeGenericMethod(genericArg);
return Add(instance, node, x => builderMethod.Invoke(x, null));
}
}
else if (method == CreateDelegateMethod)
else if (method == BindingExpressionVisitorMembers.CreateDelegateMethod)
{
var accessor = new DynamicPluginPropertyAccessorNode(GetValue<MethodInfo>(node.Object!).Name, acceptsNull: false);
return Add(node.Arguments[1], node, accessor);
var methodInfo = GetValue<MethodInfo>(node.Object!);
var delegateType = GetValue<Type>(node.Arguments[0]);
return Add(node.Arguments[1], node, x => x.Method(
methodInfo.MethodHandle,
delegateType.TypeHandle,
acceptsNull: false));
}
throw new ExpressionParseException(0, $"Invalid method call in binding expression: '{node.Method.DeclaringType}.{node.Method.Name}'.");
@ -120,20 +186,26 @@ internal class BindingExpressionVisitor<TIn> : ExpressionVisitor
{
if (node.NodeType == ExpressionType.Not && node.Type == typeof(bool))
{
return Add(node.Operand, node, new LogicalNotNode());
return Add(node.Operand, node, x => x.Not());
}
else if (node.NodeType == ExpressionType.Convert)
{
if (node.Operand.Type.IsAssignableFrom(node.Type))
// Allow reference type casts (both upcasts and downcasts) but reject value type conversions
if (!node.Type.IsValueType && !node.Operand.Type.IsValueType &&
(node.Type.IsAssignableFrom(node.Operand.Type) || node.Operand.Type.IsAssignableFrom(node.Type)))
{
// Ignore inheritance casts
return _head = base.VisitUnary(node);
var castMethod = typeof(CompiledBindingPathBuilder)
.GetMethod(nameof(CompiledBindingPathBuilder.TypeCast))!
.MakeGenericMethod(node.Type);
return Add(node.Operand, node, x => castMethod.Invoke(x, null));
}
}
else if (node.NodeType == ExpressionType.TypeAs)
{
// Ignore as operator.
return _head = base.VisitUnary(node);
var castMethod = typeof(CompiledBindingPathBuilder)
.GetMethod(nameof(CompiledBindingPathBuilder.TypeCast))!
.MakeGenericMethod(node.Type);
return Add(node.Operand, node, x => castMethod.Invoke(x, null));
}
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
@ -146,7 +218,7 @@ internal class BindingExpressionVisitor<TIn> : ExpressionVisitor
protected override CatchBlock VisitCatchBlock(CatchBlock node)
{
throw new ExpressionParseException(0, $"Catch blocks are not allowed in binding expressions.");
throw new ExpressionParseException(0, "Catch blocks are not allowed in binding expressions.");
}
protected override Expression VisitConditional(ConditionalExpression node)
@ -156,17 +228,17 @@ internal class BindingExpressionVisitor<TIn> : ExpressionVisitor
protected override Expression VisitDynamic(DynamicExpression node)
{
throw new ExpressionParseException(0, $"Dynamic expressions are not allowed in binding expressions.");
throw new ExpressionParseException(0, "Dynamic expressions are not allowed in binding expressions.");
}
protected override ElementInit VisitElementInit(ElementInit node)
{
throw new ExpressionParseException(0, $"Element init expressions are not valid in a binding expression.");
throw new ExpressionParseException(0, "Element init expressions are not valid in a binding expression.");
}
protected override Expression VisitGoto(GotoExpression node)
{
throw new ExpressionParseException(0, $"Goto expressions not supported in binding expressions.");
throw new ExpressionParseException(0, "Goto expressions are not supported in binding expressions.");
}
protected override Expression VisitInvocation(InvocationExpression node)
@ -191,7 +263,7 @@ internal class BindingExpressionVisitor<TIn> : ExpressionVisitor
protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
{
throw new ExpressionParseException(0, $"Member assignments not supported in binding expressions.");
throw new ExpressionParseException(0, "Member assignments not supported in binding expressions.");
}
protected override Expression VisitSwitch(SwitchExpression node)
@ -209,17 +281,69 @@ internal class BindingExpressionVisitor<TIn> : ExpressionVisitor
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
}
private Expression Add(Expression? instance, Expression expression, ExpressionNode node)
private Expression Add(Expression? instance, Expression expression, Action<CompiledBindingPathBuilder> build)
{
var visited = Visit(instance);
if (visited != _head)
{
throw new ExpressionParseException(
0,
0,
$"Unable to parse '{expression}': expected an instance of '{_head}' but got '{visited}'.");
_nodes.Add(node);
}
build(_builder);
return _head = expression;
}
private Expression AddPropertyNode(MemberExpression node)
{
// Check if it's an AvaloniaProperty accessed via CLR wrapper
if (typeof(AvaloniaObject).IsAssignableFrom(node.Expression?.Type) &&
AvaloniaPropertyRegistry.Instance.FindRegistered(node.Expression.Type, node.Member.Name) is { } avaloniaProperty)
{
return Add(
node.Expression,
node,
x => x.Property(avaloniaProperty, CreateAvaloniaPropertyAccessor));
}
else
{
var property = (PropertyInfo)node.Member;
var info = new ClrPropertyInfo(
property.Name,
CreateGetter(property),
CreateSetter(property),
property.PropertyType);
return Add(node.Expression, node, x => x.Property(info, CreateInpcPropertyAccessor));
}
}
private static Func<object, object>? CreateGetter(PropertyInfo info)
{
if (info.GetMethod == null)
return null;
var target = Expression.Parameter(typeof(object), "target");
return Expression.Lambda<Func<object, object>>(
Expression.Convert(Expression.Call(Expression.Convert(target, info.DeclaringType!), info.GetMethod),
typeof(object)),
target)
.Compile();
}
private static Action<object, object?>? CreateSetter(PropertyInfo info)
{
if (info.SetMethod == null)
return null;
var target = Expression.Parameter(typeof(object), "target");
var value = Expression.Parameter(typeof(object), "value");
return Expression.Lambda<Action<object, object?>>(
Expression.Call(Expression.Convert(target, info.DeclaringType!), info.SetMethod,
Expression.Convert(value, info.SetMethod.GetParameters()[0].ParameterType)),
target, value)
.Compile();
}
private static T GetValue<T>(Expression expr)
{
if (expr is ConstantExpression constant)
@ -232,4 +356,168 @@ internal class BindingExpressionVisitor<TIn> : ExpressionVisitor
var type = method.DeclaringType;
return type?.GetRuntimeProperties().FirstOrDefault(prop => prop.GetMethod == method);
}
// Accessor factory methods
private static IPropertyAccessor CreateInpcPropertyAccessor(WeakReference<object?> target, IPropertyInfo property)
=> new InpcPropertyAccessor(target, property);
private static IPropertyAccessor CreateAvaloniaPropertyAccessor(WeakReference<object?> target, IPropertyInfo property)
=> new AvaloniaPropertyAccessor(
new WeakReference<AvaloniaObject?>((AvaloniaObject?)(target.TryGetTarget(out var o) ? o : null)),
(AvaloniaProperty)property);
private static IPropertyAccessor CreateIndexerPropertyAccessor(WeakReference<object?> target, IPropertyInfo property, int argument)
=> new IndexerAccessor(target, property, argument);
// Accessor implementations
private class AvaloniaPropertyAccessor : PropertyAccessorBase, IWeakEventSubscriber<AvaloniaPropertyChangedEventArgs>
{
private readonly WeakReference<AvaloniaObject?> _reference;
private readonly AvaloniaProperty _property;
public AvaloniaPropertyAccessor(WeakReference<AvaloniaObject?> reference, AvaloniaProperty property)
{
_reference = reference ?? throw new ArgumentNullException(nameof(reference));
_property = property ?? throw new ArgumentNullException(nameof(property));
}
public override Type PropertyType => _property.PropertyType;
public override object? Value => _reference.TryGetTarget(out var instance) ? instance?.GetValue(_property) : null;
public override bool SetValue(object? value, BindingPriority priority)
{
if (!_property.IsReadOnly && _reference.TryGetTarget(out var instance))
{
instance.SetValue(_property, value, priority);
return true;
}
return false;
}
public void OnEvent(object? sender, WeakEvent ev, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == _property)
PublishValue(Value);
}
protected override void SubscribeCore()
{
if (_reference.TryGetTarget(out var reference) && reference is not null)
{
PublishValue(reference.GetValue(_property));
WeakEvents.AvaloniaPropertyChanged.Subscribe(reference, this);
}
}
protected override void UnsubscribeCore()
{
if (_reference.TryGetTarget(out var reference) && reference is not null)
WeakEvents.AvaloniaPropertyChanged.Unsubscribe(reference, this);
}
}
private class InpcPropertyAccessor : PropertyAccessorBase, IWeakEventSubscriber<PropertyChangedEventArgs>
{
protected readonly WeakReference<object?> _reference;
private readonly IPropertyInfo _property;
public InpcPropertyAccessor(WeakReference<object?> reference, IPropertyInfo property)
{
_reference = reference ?? throw new ArgumentNullException(nameof(reference));
_property = property ?? throw new ArgumentNullException(nameof(property));
}
public override Type PropertyType => _property.PropertyType;
public override object? Value => _reference.TryGetTarget(out var o) ? _property.Get(o) : null;
public override bool SetValue(object? value, BindingPriority priority)
{
if (_property.CanSet && _reference.TryGetTarget(out var o))
{
_property.Set(o, value);
SendCurrentValue();
return true;
}
return false;
}
public void OnEvent(object? sender, WeakEvent ev, PropertyChangedEventArgs e)
{
if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName))
SendCurrentValue();
}
protected override void SubscribeCore()
{
SendCurrentValue();
if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
WeakEvents.ThreadSafePropertyChanged.Subscribe(inpc, this);
}
protected override void UnsubscribeCore()
{
if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
WeakEvents.ThreadSafePropertyChanged.Unsubscribe(inpc, this);
}
protected void SendCurrentValue()
{
try
{
PublishValue(Value);
}
catch (Exception e)
{
PublishValue(new BindingNotification(e, BindingErrorType.Error));
}
}
}
private class IndexerAccessor : InpcPropertyAccessor, IWeakEventSubscriber<NotifyCollectionChangedEventArgs>
{
private readonly int _index;
public IndexerAccessor(WeakReference<object?> target, IPropertyInfo basePropertyInfo, int argument)
: base(target, basePropertyInfo)
{
_index = argument;
}
protected override void SubscribeCore()
{
base.SubscribeCore();
if (_reference.TryGetTarget(out var o) && o is INotifyCollectionChanged incc)
WeakEvents.CollectionChanged.Subscribe(incc, this);
}
protected override void UnsubscribeCore()
{
base.UnsubscribeCore();
if (_reference.TryGetTarget(out var o) && o is INotifyCollectionChanged incc)
WeakEvents.CollectionChanged.Unsubscribe(incc, this);
}
public void OnEvent(object? sender, WeakEvent ev, NotifyCollectionChangedEventArgs args)
{
if (ShouldNotifyListeners(args))
SendCurrentValue();
}
private bool ShouldNotifyListeners(NotifyCollectionChangedEventArgs e)
{
return e.Action switch
{
NotifyCollectionChangedAction.Add => _index >= e.NewStartingIndex,
NotifyCollectionChangedAction.Remove => _index >= e.OldStartingIndex,
NotifyCollectionChangedAction.Replace => _index >= e.NewStartingIndex &&
_index < e.NewStartingIndex + e.NewItems!.Count,
NotifyCollectionChangedAction.Move => (_index >= e.NewStartingIndex &&
_index < e.NewStartingIndex + e.NewItems!.Count) ||
(_index >= e.OldStartingIndex &&
_index < e.OldStartingIndex + e.OldItems!.Count),
NotifyCollectionChangedAction.Reset => true,
_ => false
};
}
}
}

20
src/Avalonia.Base/Data/Core/Parsers/BindingExpressionVisitorMembers.cs

@ -0,0 +1,20 @@
using System;
using System.Reflection;
namespace Avalonia.Data.Core.Parsers;
/// <summary>
/// Stores reflection members used by <see cref="BindingExpressionVisitor{TIn}"/> outside of the
/// generic class to avoid duplication for each generic instantiation.
/// </summary>
internal static class BindingExpressionVisitorMembers
{
static BindingExpressionVisitorMembers()
{
AvaloniaObjectIndexer = typeof(AvaloniaObject).GetProperty(CommonPropertyNames.IndexerName, [typeof(AvaloniaProperty)])!;
CreateDelegateMethod = typeof(MethodInfo).GetMethod(nameof(MethodInfo.CreateDelegate), [typeof(Type), typeof(object)])!;
}
public static readonly PropertyInfo AvaloniaObjectIndexer;
public static readonly MethodInfo CreateDelegateMethod;
}

13
src/Avalonia.Base/Data/ReflectionBinding.cs

@ -8,6 +8,7 @@ using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Data.Core.Parsers;
using Avalonia.Metadata;
using Avalonia.Utilities;
namespace Avalonia.Data
@ -34,17 +35,6 @@ namespace Avalonia.Data
{
Path = path;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReflectionBinding"/> class.
/// </summary>
/// <param name="path">The binding path.</param>
/// <param name="mode">The binding mode.</param>
public ReflectionBinding(string path, BindingMode mode)
{
Path = path;
Mode = mode;
}
/// <summary>
/// Gets or sets the amount of time, in milliseconds, to wait before updating the binding
@ -95,6 +85,7 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the binding path.
/// </summary>
[ConstructorArgument("path")]
public string Path { get; set; } = "";
/// <summary>

1
src/Avalonia.Base/Data/TemplateBinding.cs

@ -48,6 +48,7 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the name of the source property on the templated parent.
/// </summary>
[ConstructorArgument("property")]
[InheritDataTypeFrom(InheritDataTypeFromScopeKind.ControlTemplate)]
public AvaloniaProperty? Property { get; set; }

12
src/Avalonia.Base/Diagnostics/ObsoletionMessages.cs

@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Diagnostics;
internal static class ObsoletionMessages
{
public const string MayBeRemovedInAvalonia12 = "This API may be removed in Avalonia 12. If you depend on this API, please open an issue with details of your use-case.";
}

37
src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs

@ -1,37 +0,0 @@
using System;
using System.Collections.Generic;
using Avalonia.Metadata;
using Avalonia.Styling;
namespace Avalonia.Diagnostics;
[PrivateApi]
[Unstable("Use StyledElementExtensions.GetValueStoreDiagnostic() instead")]
public class StyleDiagnostics
{
/// <summary>
/// Currently applied styles.
/// </summary>
public IReadOnlyList<AppliedStyle> AppliedStyles { get; }
public StyleDiagnostics(IReadOnlyList<AppliedStyle> appliedStyles)
{
AppliedStyles = appliedStyles;
}
}
[PrivateApi]
[Unstable("Use StyledElementExtensions.GetValueStoreDiagnostic() instead")]
public sealed class AppliedStyle
{
private readonly StyleInstance _instance;
internal AppliedStyle(StyleInstance instance)
{
_instance = instance;
}
public bool HasActivator => _instance.HasActivator;
public bool IsActive => _instance.IsActive();
public StyleBase Style => (StyleBase)_instance.Source;
}

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

@ -39,7 +39,4 @@ internal class StyleValueFrameDiagnostic : IValueFrameDiagnostic
}
}
}
[Unstable("Compatibility with 11.x")]
public AppliedStyle AsAppliedStyle() => new AppliedStyle(_styleInstance);
}

24
src/Avalonia.Base/Diagnostics/StyledElementExtensions.cs

@ -1,24 +0,0 @@
using System;
using System.Linq;
using Avalonia.Metadata;
using Avalonia.Styling;
namespace Avalonia.Diagnostics;
/// <summary>
/// Defines diagnostic extensions on <see cref="StyledElement"/>s.
/// </summary>
[PrivateApi]
public static class StyledElementExtensions
{
[Obsolete("Use AvaloniaObjectExtensions.GetValueStoreDiagnostic instead", true)]
public static StyleDiagnostics GetStyleDiagnostics(this StyledElement styledElement)
{
var diagnostics = styledElement.GetValueStore().GetStoreDiagnostic();
return new StyleDiagnostics(diagnostics.AppliedFrames
.OfType<StyleValueFrameDiagnostic>()
.Select(f => f.AsAppliedStyle())
.ToArray());
}
}

3
src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs → src/Avalonia.Base/IOptionalFeatureProvider.cs

@ -1,8 +1,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
// TODO12: move to Avalonia namespace.
namespace Avalonia.Platform;
namespace Avalonia;
public interface IOptionalFeatureProvider
{

61
src/Avalonia.Base/Input/DataFormats.cs

@ -1,56 +1,11 @@
using System;
using System.ComponentModel;
using Avalonia.Input.Platform;
namespace Avalonia.Input
{
public static class DataFormats
{
/// <summary>
/// Dataformat for plaintext
/// </summary>
[Obsolete($"Use {nameof(DataFormat)}.{nameof(DataFormat.Text)} instead.")]
public static readonly string Text = nameof(Text);
namespace Avalonia.Input;
/// <summary>
/// Dataformat for one or more files.
/// </summary>
[Obsolete($"Use {nameof(DataFormat)}.{nameof(DataFormat.File)} instead.")]
public static readonly string Files = nameof(Files);
/// <summary>
/// Dataformat for one or more filenames
/// </summary>
/// <remarks>
/// This data format is supported only on desktop platforms.
/// </remarks>
[Obsolete($"Use {nameof(DataFormat)}.{nameof(DataFormat.File)} instead."), EditorBrowsable(EditorBrowsableState.Never)]
public static readonly string FileNames = nameof(FileNames);
#pragma warning disable CS0618 // Type or member is obsolete
internal static DataFormat ToDataFormat(string format)
{
if (format == Text)
return DataFormat.Text;
if (format == Files || format == FileNames)
return DataFormat.File;
return DataFormat.CreateBytesPlatformFormat(format);
}
internal static string ToString(DataFormat format)
{
if (DataFormat.Text.Equals(format))
return Text;
if (DataFormat.File.Equals(format))
return Files;
return format.Identifier;
}
#pragma warning restore CS0618 // Type or member is obsolete
}
}
// TODO13: remove
/// <summary>
/// This class does not do anything anymore.
/// Use <see cref="DataFormat"/> instead.
/// </summary>
[Obsolete($"Use {nameof(DataFormat)} instead", true)]
public static class DataFormats;

45
src/Avalonia.Base/Input/DataObject.cs

@ -1,40 +1,11 @@
using System;
using System.Collections.Generic;
namespace Avalonia.Input
{
/// <summary>
/// Specific and mutable implementation of the IDataObject interface.
/// </summary>
[Obsolete($"Use {nameof(DataTransfer)} instead")]
public class DataObject : IDataObject
{
private readonly Dictionary<string, object> _items = new();
namespace Avalonia.Input;
/// <inheritdoc />
public bool Contains(string dataFormat)
{
return _items.ContainsKey(dataFormat);
}
/// <inheritdoc />
public object? Get(string dataFormat)
{
return _items.TryGetValue(dataFormat, out var item) ? item : null;
}
/// <inheritdoc />
public IEnumerable<string> GetDataFormats()
{
return _items.Keys;
}
/// <summary>
/// Sets a value to the internal store of the data object with <see cref="DataFormats"/> as a key.
/// </summary>
public void Set(string dataFormat, object value)
{
_items[dataFormat] = value;
}
}
}
// TODO13: remove
/// <summary>
/// This class does not do anything anymore.
/// Use <see cref="DataTransfer"/> instead.
/// </summary>
[Obsolete($"Use {nameof(DataTransfer)} instead", true)]
public sealed class DataObject;

54
src/Avalonia.Base/Input/DataObjectExtensions.cs

@ -1,54 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Avalonia.Platform.Storage;
#pragma warning disable CS0618 // Type or member is obsolete
namespace Avalonia.Input
{
// TODO12: remove
public static class DataObjectExtensions
{
/// <summary>
/// Returns a list of files if the DataObject contains files or filenames.
/// <seealso cref="DataFormats.Files"/>.
/// </summary>
/// <returns>
/// Collection of storage items - files or folders. If format isn't available, returns null.
/// </returns>
public static IEnumerable<IStorageItem>? GetFiles(this IDataObject dataObject)
{
return dataObject.Get(DataFormats.Files) as IEnumerable<IStorageItem>;
}
/// <summary>
/// Returns a list of filenames if the DataObject contains filenames.
/// <seealso cref="DataFormats.FileNames"/>
/// </summary>
/// <returns>
/// Collection of file names. If format isn't available, returns null.
/// </returns>
[System.Obsolete("Use GetFiles, this method is supported only on desktop platforms."), EditorBrowsable(EditorBrowsableState.Never)]
public static IEnumerable<string>? GetFileNames(this IDataObject dataObject)
{
return (dataObject.Get(DataFormats.FileNames) as IEnumerable<string>)
?? dataObject.GetFiles()?
.Select(f => f.TryGetLocalPath())
.Where(p => !string.IsNullOrEmpty(p))
.OfType<string>();
}
/// <summary>
/// Returns the dragged text if the DataObject contains any text.
/// <seealso cref="DataFormats.Text"/>
/// </summary>
/// <returns>
/// A text string. If format isn't available, returns null.
/// </returns>
public static string? GetText(this IDataObject dataObject)
{
return dataObject.Get(DataFormats.Text) as string;
}
}
}

9
src/Avalonia.Base/Input/DataTransferExtensions.cs

@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Input.Platform;
using Avalonia.Media.Imaging;
using Avalonia.Platform.Storage;
@ -14,11 +12,6 @@ namespace Avalonia.Input;
/// </summary>
public static class DataTransferExtensions
{
[Obsolete]
internal static IDataObject ToLegacyDataObject(this IDataTransfer dataTransfer)
=> (dataTransfer as DataObjectToDataTransferWrapper)?.DataObject
?? new DataTransferToDataObjectWrapper(dataTransfer);
/// <summary>
/// Gets whether a <see cref="IDataTransfer"/> supports a specific format.
/// </summary>

10
src/Avalonia.Base/Input/DragDrop.cs

@ -122,16 +122,6 @@ namespace Avalonia.Input
element.RemoveHandler(DropEvent, handler);
}
/// <summary>
/// Starts a dragging operation with the given <see cref="IDataObject"/> and returns the applied drop effect from the target.
/// <seealso cref="DataObject"/>
/// </summary>
[Obsolete($"Use {nameof(DoDragDropAsync)} instead.")]
public static Task<DragDropEffects> DoDragDrop(PointerEventArgs triggerEvent, IDataObject data, DragDropEffects allowedEffects)
{
return DoDragDropAsync(triggerEvent, new DataObjectToDataTransferWrapper(data), allowedEffects);
}
/// <summary>
/// Starts a dragging operation with the given <see cref="IDataTransfer"/> and returns the applied drop effect from the target.
/// <seealso cref="DataTransfer"/>

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

@ -1,5 +1,4 @@
using System;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Metadata;
@ -9,16 +8,11 @@ namespace Avalonia.Input
{
private readonly Interactive _target;
private readonly Point _targetLocation;
[Obsolete] private IDataObject? _legacyDataObject;
public DragDropEffects DragEffects { get; set; }
public IDataTransfer DataTransfer { get; }
[Obsolete($"Use {nameof(DataTransfer)} instead.")]
public IDataObject Data
=> _legacyDataObject ??= DataTransfer.ToLegacyDataObject();
public KeyModifiers KeyModifiers { get; }
public Point GetPosition(Visual relativeTo)
@ -31,17 +25,6 @@ namespace Avalonia.Input
return _target.TranslatePoint(_targetLocation, relativeTo) ?? new Point(0, 0);
}
[Obsolete($"Use the constructor accepting a {nameof(IDataTransfer)} instance instead.")]
public DragEventArgs(
RoutedEvent<DragEventArgs> routedEvent,
IDataObject data,
Interactive target,
Point targetLocation,
KeyModifiers keyModifiers)
: this(routedEvent, new DataObjectToDataTransferWrapper(data), target, targetLocation, keyModifiers)
{
}
[Unstable("This constructor might be removed in 12.0. For unit testing, consider using DragDrop.DoDragDrop or IHeadlessWindow.DragDrop.")]
public DragEventArgs(
RoutedEvent<DragEventArgs> routedEvent,

32
src/Avalonia.Base/Input/IDataObject.cs

@ -1,32 +0,0 @@
using System;
using System.Collections.Generic;
namespace Avalonia.Input
{
/// <summary>
/// Interface to access information about the data of a drag-and-drop operation.
/// </summary>
[Obsolete($"Use {nameof(IDataTransfer)} or {nameof(IAsyncDataTransfer)} instead")]
public interface IDataObject
{
/// <summary>
/// Lists all formats which are present in the DataObject.
/// <seealso cref="DataFormats"/>
/// </summary>
IEnumerable<string> GetDataFormats();
/// <summary>
/// Checks whether a given DataFormat is present in this object
/// <seealso cref="DataFormats"/>
/// </summary>
bool Contains(string dataFormat);
/// <summary>
/// Tries to get the data of the given DataFormat.
/// </summary>
/// <returns>
/// Object data. If format isn't available, returns null.
/// </returns>
object? Get(string dataFormat);
}
}

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

@ -34,6 +34,8 @@ namespace Avalonia.Input
_pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
}
internal Pointer Pointer => _pointer;
internal static TMouseDevice GetOrCreatePrimary<TMouseDevice>() where TMouseDevice : MouseDevice, new()
{
if (_primary is TMouseDevice device)

55
src/Avalonia.Base/Input/Platform/Clipboard.cs

@ -1,9 +1,4 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Compatibility;
using Avalonia.Platform.Storage;
using System.Threading.Tasks;
namespace Avalonia.Input.Platform;
@ -15,12 +10,6 @@ internal sealed class Clipboard(IClipboardImpl clipboardImpl) : IClipboard
private readonly IClipboardImpl _clipboardImpl = clipboardImpl;
private IAsyncDataTransfer? _lastDataTransfer;
Task<string?> IClipboard.GetTextAsync()
=> this.TryGetTextAsync();
Task IClipboard.SetTextAsync(string? text)
=> this.SetValueAsync(DataFormat.Text, text);
public Task ClearAsync()
{
_lastDataTransfer?.Dispose();
@ -29,10 +18,6 @@ internal sealed class Clipboard(IClipboardImpl clipboardImpl) : IClipboard
return _clipboardImpl.ClearAsync();
}
[Obsolete($"Use {nameof(SetDataAsync)} instead.")]
Task IClipboard.SetDataObjectAsync(IDataObject data)
=> SetDataAsync(new DataObjectToDataTransferWrapper(data));
public Task SetDataAsync(IAsyncDataTransfer? dataTransfer)
{
if (dataTransfer is null)
@ -47,47 +32,9 @@ internal sealed class Clipboard(IClipboardImpl clipboardImpl) : IClipboard
public Task FlushAsync()
=> _clipboardImpl is IFlushableClipboardImpl flushable ? flushable.FlushAsync() : Task.CompletedTask;
async Task<string[]> IClipboard.GetFormatsAsync()
{
var dataTransfer = await TryGetDataAsync();
return dataTransfer is null ? [] : dataTransfer.Formats.Select(DataFormats.ToString).ToArray();
}
[Obsolete($"Use {nameof(TryGetDataAsync)} instead.")]
async Task<object?> IClipboard.GetDataAsync(string format)
{
// No ConfigureAwait(false) here: we want TryGetXxxAsync() below to be called on the initial thread.
using var dataTransfer = await TryGetDataAsync();
if (dataTransfer is null)
return null;
if (format == DataFormats.Text)
return await dataTransfer.TryGetTextAsync().ConfigureAwait(false);
if (format == DataFormats.Files)
return await dataTransfer.TryGetFilesAsync().ConfigureAwait(false);
if (format == DataFormats.FileNames)
{
return (await dataTransfer.TryGetFilesAsync().ConfigureAwait(false))
?.Select(file => file.TryGetLocalPath())
.Where(path => path is not null)
.ToArray();
}
return null;
}
public Task<IAsyncDataTransfer?> TryGetDataAsync()
=> _clipboardImpl.TryGetDataAsync();
[Obsolete($"Use {nameof(TryGetInProcessDataAsync)} instead.")]
async Task<IDataObject?> IClipboard.TryGetInProcessDataObjectAsync()
{
var dataTransfer = await TryGetInProcessDataAsync().ConfigureAwait(false);
return (dataTransfer as DataObjectToDataTransferWrapper)?.DataObject;
}
public async Task<IAsyncDataTransfer?> TryGetInProcessDataAsync()
{
if (_lastDataTransfer is null || _clipboardImpl is not IOwnedClipboardImpl ownedClipboardImpl)

76
src/Avalonia.Base/Input/Platform/DataObjectToDataTransferItemWrapper.cs

@ -1,76 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using Avalonia.Compatibility;
namespace Avalonia.Input.Platform;
/// <summary>
/// Wraps a legacy <see cref="IDataObject"/> into a <see cref="IDataTransferItem"/>.
/// </summary>
[Obsolete]
internal sealed class DataObjectToDataTransferItemWrapper(
IDataObject dataObject,
DataFormat[] formats,
string[] formatStrings)
: PlatformDataTransferItem
{
private readonly IDataObject _dataObject = dataObject;
private readonly DataFormat[] _formats = formats;
private readonly string[] _formatStrings = formatStrings;
protected override DataFormat[] ProvideFormats()
=> _formats;
protected override object? TryGetRawCore(DataFormat format)
{
var index = Array.IndexOf(Formats, format);
if (index < 0)
return null;
// We should never have DataFormat.File here, it's been handled by DataObjectToDataTransferWrapper.
Debug.Assert(!DataFormat.File.Equals(format));
var formatString = _formatStrings[index];
var data = _dataObject.Get(formatString);
if (DataFormat.Text.Equals(format))
return Convert.ToString(data) ?? string.Empty;
if (format is DataFormat<string>)
return Convert.ToString(data);
if (format is DataFormat<byte[]>)
return ConvertLegacyDataToBytes(format, data);
return null;
}
private static byte[]? ConvertLegacyDataToBytes(DataFormat format, object? data)
{
switch (data)
{
case null:
return null;
case byte[] bytes:
return bytes;
case string str:
return OperatingSystemEx.IsWindows() || OperatingSystemEx.IsMacOS() || OperatingSystemEx.IsIOS() ?
Encoding.Unicode.GetBytes(str) :
Encoding.UTF8.GetBytes(str);
case Stream stream:
var length = (int)(stream.Length - stream.Position);
var buffer = new byte[length];
stream.ReadExactly(buffer, 0, length);
return buffer;
default:
return null;
}
}
}

95
src/Avalonia.Base/Input/Platform/DataObjectToDataTransferWrapper.cs

@ -1,95 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO;
namespace Avalonia.Input.Platform;
#pragma warning disable CS0618 // Type or member is obsolete: usages of IDataObject and DataFormats
// TODO12: remove
/// <summary>
/// Wraps a legacy <see cref="IDataObject"/> into a <see cref="IDataTransfer"/>.
/// </summary>
[Obsolete]
internal sealed class DataObjectToDataTransferWrapper(IDataObject dataObject)
: PlatformDataTransfer
{
public IDataObject DataObject { get; } = dataObject;
protected override DataFormat[] ProvideFormats()
=> DataObject.GetDataFormats().Select(DataFormats.ToDataFormat).Distinct().ToArray();
protected override PlatformDataTransferItem[] ProvideItems()
{
var items = new List<PlatformDataTransferItem>();
var nonFileFormats = new List<DataFormat>();
var nonFileFormatStrings = new List<string>();
var hasFiles = false;
foreach (var formatString in DataObject.GetDataFormats())
{
var format = DataFormats.ToDataFormat(formatString);
if (formatString == DataFormats.Files)
{
if (hasFiles)
continue;
// This is not ideal as we're reading the filenames ahead of time to generate the appropriate items.
// We don't really care about that for this legacy wrapper.
if (DataObject.Get(formatString) is IEnumerable<IStorageItem> storageItems)
{
hasFiles = true;
foreach (var storageItem in storageItems)
items.Add(PlatformDataTransferItem.Create(DataFormat.File, storageItem));
}
}
else if (formatString == DataFormats.FileNames)
{
if (hasFiles)
continue;
if (DataObject.Get(formatString) is IEnumerable<string> fileNames)
{
hasFiles = true;
foreach (var fileName in fileNames)
{
if (StorageProviderHelpers.TryCreateBclStorageItem(fileName) is { } storageItem)
items.Add(PlatformDataTransferItem.Create(DataFormat.File, storageItem));
}
}
}
else
{
nonFileFormats.Add(format);
nonFileFormatStrings.Add(formatString);
}
}
if (nonFileFormats.Count > 0)
{
Debug.Assert(nonFileFormats.Count == nonFileFormatStrings.Count);
// Single item containing all formats except for DataFormat.File.
items.Add(new DataObjectToDataTransferItemWrapper(
DataObject,
nonFileFormats.ToArray(),
nonFileFormatStrings.ToArray()));
}
return items.ToArray();
}
[SuppressMessage(
"ReSharper",
"SuspiciousTypeConversion.Global",
Justification = "IDisposable may be implemented externally by the IDataObject instance.")]
public override void Dispose()
=> (DataObject as IDisposable)?.Dispose();
}

42
src/Avalonia.Base/Input/Platform/DataTransferToDataObjectWrapper.cs

@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Platform.Storage;
namespace Avalonia.Input.Platform;
/// <summary>
/// Wraps a <see cref="IDataTransfer"/> into a legacy <see cref="IDataObject"/>.
/// </summary>
[Obsolete]
internal sealed class DataTransferToDataObjectWrapper(IDataTransfer dataTransfer) : IDataObject
{
public IDataTransfer DataTransfer { get; } = dataTransfer;
public IEnumerable<string> GetDataFormats()
=> DataTransfer.Formats.Select(DataFormats.ToString);
public bool Contains(string dataFormat)
=> DataTransfer.Contains(DataFormats.ToDataFormat(dataFormat));
public object? Get(string dataFormat)
{
if (dataFormat == DataFormats.Text)
return DataTransfer.TryGetText();
if (dataFormat == DataFormats.Files)
return DataTransfer.TryGetFiles();
if (dataFormat == DataFormats.FileNames)
{
return DataTransfer
.TryGetFiles()
?.Select(file => file.TryGetLocalPath())
.Where(path => path is not null)
.ToArray();
}
return null;
}
}

55
src/Avalonia.Base/Input/Platform/IClipboard.cs

@ -1,4 +1,3 @@
using System;
using System.Threading.Tasks;
using Avalonia.Metadata;
@ -10,41 +9,11 @@ namespace Avalonia.Input.Platform
[NotClientImplementable]
public interface IClipboard
{
// TODO12: remove, ClipboardExtensions.TryGetTextAsync exists
/// <summary>
/// Returns a string containing the text data on the clipboard.
/// </summary>
/// <returns>A string containing text data, or null if no corresponding text data is available.</returns>
[Obsolete($"Use {nameof(ClipboardExtensions)}.{nameof(ClipboardExtensions.TryGetTextAsync)} instead")]
Task<string?> GetTextAsync();
// TODO12: remove, ClipboardExtensions.SetTextAsync exists
/// <summary>
/// Places a text on the clipboard.
/// </summary>
/// <param name="text">The text value to set.</param>
/// <remarks>
/// <para>By calling this method, the clipboard will get cleared of any possible previous data.</para>
/// <para>
/// If <paramref name="text"/> is null or empty, nothing will get placed on the clipboard and this method
/// will be equivalent to <see cref="ClearAsync"/>.
/// </para>
/// </remarks>
Task SetTextAsync(string? text);
/// <summary>
/// Clears any data from the system clipboard.
/// </summary>
Task ClearAsync();
/// <summary>
/// Places a specified non-persistent data object on the system Clipboard.
/// </summary>
/// <param name="data">A data object (an object that implements <see cref="IDataObject"/>) to place on the system Clipboard.</param>
/// <exception cref="System.ArgumentNullException"><paramref name="data"/> is null.</exception>
[Obsolete($"Use {nameof(SetDataAsync)} instead.")]
Task SetDataObjectAsync(IDataObject data);
/// <summary>
/// Places a data object on the clipboard.
/// The data object is responsible for providing supported formats and data upon request.
@ -69,20 +38,6 @@ namespace Avalonia.Input.Platform
/// <remarks>This method is only supported on the Windows platform. This method will do nothing on other platforms.</remarks>
Task FlushAsync();
/// <summary>
/// Get list of available Clipboard format.
/// </summary>
[Obsolete($"Use {nameof(ClipboardExtensions.GetDataFormatsAsync)} instead.")]
Task<string[]> GetFormatsAsync();
/// <summary>
/// Retrieves data in a specified format from the Clipboard.
/// </summary>
/// <param name="format">A string that specifies the format of the data to retrieve. For a set of predefined data formats, see the <see cref="DataFormats"/> class.</param>
/// <returns></returns>
[Obsolete($"Use {nameof(TryGetDataAsync)} instead.")]
Task<object?> GetDataAsync(string format);
/// <summary>
/// Retrieves data from the clipboard.
/// </summary>
@ -95,16 +50,6 @@ namespace Avalonia.Input.Platform
/// </remarks>
Task<IAsyncDataTransfer?> TryGetDataAsync();
/// <summary>
/// If clipboard contains the IDataObject that was set by a previous call to <see cref="SetDataObjectAsync(Avalonia.Input.IDataObject)"/>,
/// return said IDataObject instance. Otherwise, return null.
/// Note that not every platform supports that method, on unsupported platforms this method will always return
/// null
/// </summary>
/// <returns></returns>
[Obsolete($"Use {nameof(TryGetInProcessDataAsync)} instead.")]
Task<IDataObject?> TryGetInProcessDataObjectAsync();
/// <summary>
/// Retrieves the exact instance of a <see cref="IAsyncDataTransfer"/> previously placed on the clipboard
/// by <see cref="SetDataAsync"/>, if any.

9
src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs

@ -1,5 +1,4 @@
using System;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Avalonia.Metadata;
namespace Avalonia.Input.Platform
@ -7,12 +6,6 @@ namespace Avalonia.Input.Platform
[NotClientImplementable]
public interface IPlatformDragSource
{
[Obsolete($"Use {nameof(DoDragDropAsync)} instead.")]
Task<DragDropEffects> DoDragDrop(
PointerEventArgs triggerEvent,
IDataObject data,
DragDropEffects allowedEffects);
Task<DragDropEffects> DoDragDropAsync(
PointerEventArgs triggerEvent,
IDataTransfer dataTransfer,

24
src/Avalonia.Base/Input/Pointer.cs

@ -54,31 +54,38 @@ namespace Avalonia.Input
internal void Capture(IInputElement? control, CaptureSource source)
{
var oldCapture = Captured;
if (oldCapture == control)
var oldSource = CaptureSource;
// If a handler marks Implicit capture as handled, we still want them to have another chance if the element is captured explicitly.
if (oldCapture == control && oldSource == source)
return;
var oldVisual = oldCapture as Visual;
var newVisual = control as Visual;
IInputElement? commonParent = null;
if (oldVisual != null)
if (oldVisual != null || newVisual != null)
{
commonParent = FindCommonParent(control, oldCapture);
foreach (var notifyTarget in oldVisual.GetSelfAndVisualAncestors().OfType<IInputElement>())
var visual = oldVisual ?? newVisual!; // We want the capture to be cancellable even if there is no currently captured element.
foreach (var notifyTarget in visual.GetSelfAndVisualAncestors().OfType<IInputElement>())
{
if (notifyTarget == commonParent)
break;
var args = new PointerCaptureChangingEventArgs(notifyTarget, this, control, source);
notifyTarget.RaiseEvent(args);
if (args.Handled)
return;
if (notifyTarget == commonParent)
break;
}
}
if (oldVisual != null)
oldVisual.DetachedFromVisualTree -= OnCaptureDetached;
Captured = control;
CaptureSource = source;
if (source != CaptureSource.Platform)
// However, we still want to notify the platform only if the captured element actually changed.
if (oldCapture != control && source != CaptureSource.Platform)
PlatformCapture(control);
if (oldVisual != null)
@ -89,7 +96,7 @@ namespace Avalonia.Input
notifyTarget.RaiseEvent(new PointerCaptureLostEventArgs(notifyTarget, this));
}
if (Captured is Visual newVisual)
if (newVisual != null)
newVisual.DetachedFromVisualTree += OnCaptureDetached;
if (Captured != null)
@ -115,6 +122,7 @@ namespace Avalonia.Input
public IInputElement? Captured { get; private set; }
public PointerType Type { get; }
public bool IsPrimary { get; }
/// <summary>
@ -124,6 +132,8 @@ namespace Avalonia.Input
public bool IsGestureRecognitionSkipped { get; set; }
internal CaptureSource CaptureSource { get; private set; } = CaptureSource.Platform;
public void Dispose()
{
if (Captured != null)

22
src/Avalonia.Base/Input/Raw/RawDragEvent.cs

@ -1,40 +1,20 @@
using System;
using Avalonia.Input.Platform;
using Avalonia.Metadata;
using Avalonia.Metadata;
namespace Avalonia.Input.Raw
{
[PrivateApi]
public class RawDragEvent : RawInputEventArgs
{
[Obsolete] private IDataObject? _legacyDataObject;
public Point Location { get; set; }
public IDataTransfer DataTransfer { get; }
[Obsolete($"Use {nameof(DataTransfer)} instead.")]
public IDataObject Data
=> _legacyDataObject ??= DataTransfer.ToLegacyDataObject();
public DragDropEffects Effects { get; set; }
public RawDragEventType Type { get; }
public KeyModifiers KeyModifiers { get; }
[Obsolete($"Use the constructor accepting a {nameof(IDataTransfer)} instance instead.")]
public RawDragEvent(IDragDropDevice inputDevice,
RawDragEventType type,
IInputRoot root,
Point location,
IDataObject data,
DragDropEffects effects,
RawInputModifiers modifiers)
: this(inputDevice, type, root, location, new DataObjectToDataTransferWrapper(data), effects, modifiers)
{
}
public RawDragEvent(
IDragDropDevice inputDevice,
RawDragEventType type,

33
src/Avalonia.Base/Input/Raw/RawKeyEventArgs.cs

@ -1,4 +1,3 @@
using System;
using Avalonia.Metadata;
namespace Avalonia.Input.Raw
@ -12,21 +11,6 @@ namespace Avalonia.Input.Raw
[PrivateApi]
public class RawKeyEventArgs : RawInputEventArgs
{
[Obsolete("Use the overload that takes a physical key and key symbol instead.")]
public RawKeyEventArgs(
IKeyboardDevice device,
ulong timestamp,
IInputRoot root,
RawKeyEventType type,
Key key,
RawInputModifiers modifiers)
: this(device, timestamp, root, type, key, modifiers, PhysicalKey.None, KeyDeviceType.Keyboard, null)
{
Key = key;
Type = type;
Modifiers = modifiers;
}
public RawKeyEventArgs(
IInputDevice device,
ulong timestamp,
@ -35,24 +19,13 @@ namespace Avalonia.Input.Raw
Key key,
RawInputModifiers modifiers,
PhysicalKey physicalKey,
string? keySymbol)
: this(device, timestamp, root, type, key, modifiers, physicalKey, KeyDeviceType.Keyboard, keySymbol) { }
public RawKeyEventArgs(
IInputDevice device,
ulong timestamp,
IInputRoot root,
RawKeyEventType type,
Key key,
RawInputModifiers modifiers,
PhysicalKey physicalKey,
KeyDeviceType keyDeviceType,
string? keySymbol)
string? keySymbol,
KeyDeviceType keyDeviceType = KeyDeviceType.Keyboard)
: base(device, timestamp, root)
{
Type = type;
Key = key;
Modifiers = modifiers;
Type = type;
PhysicalKey = physicalKey;
KeySymbol = keySymbol;
KeyDeviceType = keyDeviceType;

12
src/Avalonia.Base/Layout/Layoutable.cs

@ -951,17 +951,5 @@ namespace Avalonia.Layout
{
return new Size(Math.Max(size.Width, 0), Math.Max(size.Height, 0));
}
internal override void SynchronizeCompositionProperties()
{
base.SynchronizeCompositionProperties();
if (CompositionVisual is { } visual)
{
// If the visual isn't using layout rounding, it's possible that antialiasing renders to pixels
// outside the current bounds. Extend the dirty rect by 1px in all directions in this case.
visual.ShouldExtendDirtyRect = !UseLayoutRounding;
}
}
}
}

114
src/Avalonia.Base/Media/BitmapCache.cs

@ -0,0 +1,114 @@
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Media;
/// <summary>
/// Represents the behavior of caching a visual element or tree of elements as bitmap surfaces.
/// </summary>
public class BitmapCache : CacheMode
{
private CompositionBitmapCache? _current;
public static readonly StyledProperty<double> RenderAtScaleProperty = AvaloniaProperty.Register<BitmapCache, double>(
nameof(RenderAtScale), 1);
/// <summary>
/// Use the RenderAtScale property to render the BitmapCache at a multiple of the normal bitmap size.
/// The normal size is determined by the local size of the element.
///
/// Values greater than 1 increase the resolution of the bitmap relative to the native resolution of the element,
/// and values less than 1 decrease the resolution.
/// For example, if the RenderAtScale property is set to 2.0, and you apply a scale transform that
/// enlarges the content by a factor of 2, the content will have the same visual quality as the same content
/// with RenderAtScale set to 1.0 and a transform scale of 1.
///
/// When RenderAtScale is set to 0, no bitmap is rendered. Negative values are clamped to 0.
///
/// If you change this value, the cache is regenerated at the appropriate new resolution.
/// </summary>
public double RenderAtScale
{
get => GetValue(RenderAtScaleProperty);
set => SetValue(RenderAtScaleProperty, value);
}
public static readonly StyledProperty<bool> SnapsToDevicePixelsProperty = AvaloniaProperty.Register<BitmapCache, bool>(
nameof(SnapsToDevicePixels));
/// <summary>
/// Set the SnapsToDevicePixels property when the cache displays content that requires pixel-alignment to render correctly.
/// This is the case for text with subpixel antialiasing. If you set the EnableClearType property to true,
/// consider setting SnapsToDevicePixels to true to ensure proper rendering.
///
/// When the SnapsToDevicePixels property is set to false,
/// you can move and scale the cached element by a fraction of a pixel.
///
/// When the SnapsToDevicePixels property is set to true,
/// the bitmap cache is aligned with pixel boundaries of the destination.
/// If you move or scale the cached element by a fraction of a pixel,
/// the bitmap snaps to the pixel grid
/// . In this case, the top-left corner of the bitmap is rounded up and snapped to the pixel grid,
/// but the bottom-right corner is on a fractional pixel boundary.
/// </summary>
public bool SnapsToDevicePixels
{
get => GetValue(SnapsToDevicePixelsProperty);
set => SetValue(SnapsToDevicePixelsProperty, value);
}
public static readonly StyledProperty<bool> EnableClearTypeProperty = AvaloniaProperty.Register<BitmapCache, bool>(
nameof(EnableClearType));
/// <summary>
/// Set the EnableClearType property to allow subpixel text to be rendered in the cache.
/// When the EnableClearType property is true, your application MUST render all
/// of its subpixel text on an opaque background.
///
/// When the EnableClearType property is false, text in the cache is rendered with grayscale antialiasing.
///
/// ClearType text requires correct pixel alignment of rendered characters,
/// so you should set the SnapsToDevicePixels property to true.
/// If you do not set this property, the content may not blend correctly.
///
/// Use the EnableClearType property when you know the cache is rendered on pixel boundaries,
/// so it is safe to cache ClearType text. This situation occurs commonly in text-scrolling scenarios.
/// </summary>
public bool EnableClearType
{
get => GetValue(EnableClearTypeProperty);
set => SetValue(EnableClearTypeProperty, value);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (change.IsEffectiveValueChange && _current != null)
{
if (change.Property == RenderAtScaleProperty)
_current.RenderAtScale = RenderAtScale;
else if (change.Property == SnapsToDevicePixelsProperty)
_current.SnapsToDevicePixels = SnapsToDevicePixels;
else if (change.Property == EnableClearTypeProperty)
_current.EnableClearType = EnableClearType;
}
base.OnPropertyChanged(change);
}
// We currently only allow visual to be attached to one compositor at a time, so keep it simple for now
internal override CompositionCacheMode GetForCompositor(Compositor c)
{
// TODO: Make it to be a multi-compositor resource once we support visuals being attached to multiple
// compositor instances (e. g. referenced via visual brush from a different WASM toplevel).
if(_current?.Compositor != c)
{
_current = new CompositionBitmapCache(c, new ServerCompositionBitmapCache(c.Server));
_current.EnableClearType = EnableClearType;
_current.RenderAtScale = RenderAtScale;
_current.SnapsToDevicePixels = SnapsToDevicePixels;
}
return _current;
}
}

21
src/Avalonia.Base/Media/CacheMode.cs

@ -0,0 +1,21 @@
using System;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Drawing;
namespace Avalonia.Media;
/// <summary>
/// Represents cached content modes for graphics acceleration features.
/// </summary>
public abstract class CacheMode : StyledElement
{
// We currently only allow visual to be attached to one compositor at a time, so keep it simple for now
internal abstract CompositionCacheMode GetForCompositor(Compositor c);
public static CacheMode Parse(string s)
{
if(s == "BitmapCache")
return new BitmapCache();
throw new ArgumentException("Unknown CacheMode: " + s);
}
}

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

@ -472,13 +472,6 @@ namespace Avalonia.Media
return ((uint)A << 24) | ((uint)R << 16) | ((uint)G << 8) | (uint)B;
}
/// <inheritdoc cref="Color.ToUInt32"/>
[Obsolete("Use Color.ToUInt32() instead."), EditorBrowsable(EditorBrowsableState.Never)]
public uint ToUint32()
{
return ToUInt32();
}
/// <summary>
/// Returns the HSL color model equivalent of this RGB color.
/// </summary>

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

@ -432,15 +432,8 @@ namespace Avalonia.Media
_states.Push(new RestoreState(this, RestoreState.PushedStateType.TextOptions));
return new PushedState(this);
}
protected abstract void PushTextOptionsCore(TextOptions textOptions);
[Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)]
public PushedState PushPreTransform(Matrix matrix) => PushTransform(matrix);
[Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)]
public PushedState PushPostTransform(Matrix matrix) => PushTransform(matrix);
[Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)]
public PushedState PushTransformContainer() => PushTransform(Matrix.Identity);
protected abstract void PushTextOptionsCore(TextOptions textOptions);
protected abstract void PushTransformCore(Matrix matrix);

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

@ -20,8 +20,6 @@ namespace Avalonia.Media
/// </summary>
RelativePoint GradientOrigin { get; }
[Obsolete("Use RadiusX/RadiusY")] public double Radius { get; }
/// <summary>
/// Gets the horizontal radius of the outermost circle of the radial gradient.
/// </summary>

4
src/Avalonia.Base/Media/Imaging/Bitmap.cs

@ -291,9 +291,7 @@ namespace Avalonia.Media.Imaging
{
get
{
// TODO12: We should probably make PlatformImpl to be nullable or make it possible to check
// and fix IRef<T> in general (right now Item is not nullable while it internally is)
if (PlatformImpl.Item == null!)
if (!PlatformImpl.IsAlive)
return null;
return PlatformImpl;
}

2
src/Avalonia.Base/Media/Immutable/ImmutableRadialGradientBrush.cs

@ -85,7 +85,5 @@ namespace Avalonia.Media.Immutable
/// <inheritdoc/>
public RelativeScalar RadiusY { get; }
[Obsolete("Use RadiusX/RadiusY")] public double Radius => RadiusX.Scalar;
}
}

40
src/Avalonia.Base/Media/RadialGradientBrush.cs

@ -27,15 +27,6 @@ namespace Avalonia.Media
AvaloniaProperty.Register<RadialGradientBrush, RelativePoint>(
nameof(GradientOrigin),
RelativePoint.Center);
/// <summary>
/// Defines the <see cref="Radius"/> property.
/// </summary>
[Obsolete("Use RadiusX/RadiusY, note that those properties use _relative_ values, so Radius=0.55 would become RadiusX=55% RadiusY=55%. Radius property is always relative even if the rest of the brush uses absolute values.")]
public static readonly StyledProperty<double> RadiusProperty =
AvaloniaProperty.Register<RadialGradientBrush, double>(
nameof(Radius),
0.5);
/// <summary>
/// Defines the <see cref="RadiusX"/> property.
@ -74,9 +65,6 @@ namespace Avalonia.Media
/// Gets or sets the horizontal radius of the outermost circle of the radial
/// gradient.
/// </summary>
#pragma warning disable CS0618 // Type or member is obsolete
[DependsOn(nameof(Radius))]
#pragma warning restore CS0618 // Type or member is obsolete
public RelativeScalar RadiusX
{
get { return GetValue(RadiusXProperty); }
@ -87,25 +75,11 @@ namespace Avalonia.Media
/// Gets or sets the vertical radius of the outermost circle of the radial
/// gradient.
/// </summary>
#pragma warning disable CS0618 // Type or member is obsolete
[DependsOn(nameof(Radius))]
#pragma warning restore CS0618 // Type or member is obsolete
public RelativeScalar RadiusY
{
get { return GetValue(RadiusYProperty); }
set { SetValue(RadiusYProperty, value); }
}
/// <summary>
/// Gets or sets the horizontal and vertical radius of the outermost circle of the radial
/// gradient.
/// </summary>
[Obsolete("Use RadiusX/RadiusY, note that those properties use _relative_ values, so Radius=0.55 would become RadiusX=55% RadiusY=55%. Radius property is always relative even if the rest of the brush uses absolute values.")]
public double Radius
{
get { return GetValue(RadiusProperty); }
set { SetValue(RadiusProperty, value); }
}
/// <inheritdoc/>
public override IImmutableBrush ToImmutable()
@ -121,19 +95,5 @@ namespace Avalonia.Media
base.SerializeChanges(c, writer);
ServerCompositionSimpleRadialGradientBrush.SerializeAllChanges(writer, Center, GradientOrigin, RadiusX, RadiusY);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
#pragma warning disable CS0618 // Type or member is obsolete: compatibility code for Radius
if (change.IsEffectiveValueChange && change.Property == RadiusProperty)
{
var compatibilityValue = new RelativeScalar(Radius, RelativeUnit.Relative);
SetCurrentValue(RadiusXProperty, compatibilityValue);
SetCurrentValue(RadiusYProperty, compatibilityValue);
}
#pragma warning restore CS0618 // Type or member is obsolete
base.OnPropertyChanged(change);
}
}
}

4
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@ -1,4 +1,4 @@
// ReSharper disable ForCanBeConvertedToForeach
// ReSharper disable ForCanBeConvertedToForeach
using System;
using System.Buffers;
using System.Collections.Generic;
@ -662,7 +662,7 @@ namespace Avalonia.Media.TextFormatting
clusterLength = shapedTextCharacters.GlyphRun.Metrics.FirstCluster + currentRun.Length - currentInfo.GlyphCluster;
}
if (currentWidth + clusterWidth > paragraphWidth)
if (MathUtilities.GreaterThan(currentWidth + clusterWidth, paragraphWidth))
{
if (runLength == 0 && measuredLength == 0)
{

4
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Media.TextFormatting.Unicode;
@ -1423,7 +1423,7 @@ namespace Avalonia.Media.TextFormatting
var overhangLeading = inkBounds.Left;
//The width of overhanging pixels at the end of the natural bounds. Positive value means we are inside.
var overhangTrailing = widthIncludingWhitespace - inkBounds.Right;
var hasOverflowed = width > _paragraphWidth;
var hasOverflowed = MathUtilities.GreaterThan(width, _paragraphWidth);
var start = GetParagraphOffsetX(width, widthIncludingWhitespace);

17
src/Avalonia.Base/Metadata/ConstructorArgumentAttribute.cs

@ -0,0 +1,17 @@
using System;
namespace Avalonia.Metadata;
/// <summary>
/// Indicates that a property corresponds to a named parameter in the constructor.
/// </summary>
/// <param name="name">The name of the parameter in the constructor.</param>
/// <remarks>This attribute is used for XAML.</remarks>
[AttributeUsage(AttributeTargets.Property)]
public sealed class ConstructorArgumentAttribute(string name) : Attribute
{
/// <summary>
/// Gets the name of the parameter in the constructor.
/// </summary>
public string Name { get; } = name;
}

8
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

@ -221,8 +221,9 @@ namespace Avalonia.Platform
/// </summary>
/// <param name="pixelSize">The size, in pixels, of the render target</param>
/// <param name="scaling">The scaling which will be reported by IBitmap.Dpi</param>
/// <param name="enableTextAntialiasing">Specifies if text antialiasing should be enabled</param>
/// <returns></returns>
IDrawingContextLayerImpl CreateOffscreenRenderTarget(PixelSize pixelSize, double scaling);
IDrawingContextLayerImpl CreateOffscreenRenderTarget(PixelSize pixelSize, Vector scaling, bool enableTextAntialiasing);
/// <summary>
/// Indicates that the context is no longer usable. This method should be thread-safe
@ -233,5 +234,10 @@ namespace Avalonia.Platform
/// Exposes features that should be available for consumption while context isn't active (e. g. from the UI thread)
/// </summary>
IReadOnlyDictionary<Type, object> PublicFeatures { get; }
/// <summary>
/// Maximum supported offscreen render target pixel size, or null if no limit
/// </summary>
public PixelSize? MaxOffscreenRenderTargetPixelSize { get; }
}
}

47
src/Avalonia.Base/Platform/IRenderTarget.cs

@ -1,6 +1,5 @@
using System;
using Avalonia.Metadata;
using Avalonia.Rendering;
namespace Avalonia.Platform
{
@ -13,51 +12,29 @@ namespace Avalonia.Platform
[PrivateApi]
public interface IRenderTarget : IDisposable
{
/// <summary>
/// Creates an <see cref="IDrawingContextImpl"/> for a rendering session.
/// </summary>
/// <param name="useScaledDrawing">Apply DPI reported by the render target as a hidden transform matrix</param>
IDrawingContextImpl CreateDrawingContext(bool useScaledDrawing);
/// <summary>
/// Indicates if the render target is no longer usable and needs to be recreated
/// </summary>
public bool IsCorrupted { get; }
}
bool IsCorrupted { get; }
[PrivateApi, Obsolete("Use IRenderTarget2", true)]
// TODO12: Remove
public interface IRenderTargetWithProperties : IRenderTarget
{
RenderTargetProperties Properties { get; }
}
[PrivateApi]
// TODO12: Merge into IRenderTarget
public interface IRenderTarget2 : IRenderTarget
{
/// <summary>
/// Gets the properties of the render target.
/// </summary>
RenderTargetProperties Properties { get; }
/// <summary>
/// Creates an <see cref="IDrawingContextImpl"/> for a rendering session.
/// </summary>
/// <param name="useScaledDrawing">Apply DPI reported by the render target as a hidden transform matrix</param>
IDrawingContextImpl CreateDrawingContext(bool useScaledDrawing);
/// <summary>
/// Creates an <see cref="IDrawingContextImpl"/> for a rendering session.
/// </summary>
/// <param name="expectedPixelSize">The pixel size of the surface</param>
/// <param name="properties">Returns various properties about the returned drawing context</param>
IDrawingContextImpl CreateDrawingContext(PixelSize expectedPixelSize,
out RenderTargetDrawingContextProperties properties);
}
internal static class RenderTargetExtensions
{
public static IDrawingContextImpl CreateDrawingContextWithProperties(
this IRenderTarget renderTarget,
IDrawingContextImpl CreateDrawingContext(
PixelSize expectedPixelSize,
out RenderTargetDrawingContextProperties properties)
{
if (renderTarget is IRenderTarget2 target)
return target.CreateDrawingContext(expectedPixelSize, out properties);
properties = default;
return renderTarget.CreateDrawingContext(false);
}
out RenderTargetDrawingContextProperties properties);
}
}

6
src/Avalonia.Base/Platform/ISurfaceOrientation.cs

@ -0,0 +1,6 @@
namespace Avalonia.Platform;
internal interface ISurfaceOrientation
{
SurfaceOrientation Orientation { get; }
}

70
src/Avalonia.Base/Platform/LtrbRect.cs

@ -23,6 +23,9 @@ public struct LtrbRect
{
public double Left, Top, Right, Bottom;
public double Width => Right - Left;
public double Height => Bottom - Top;
internal LtrbRect(double x, double y, double right, double bottom)
{
Left = x;
@ -40,9 +43,37 @@ public struct LtrbRect
Bottom = rc.Bottom;
}
internal bool IsZeroSize => Left == Right && Top == Bottom;
internal static LtrbRect Infinite { get; } = new(double.NegativeInfinity, double.NegativeInfinity,
double.PositiveInfinity, double.PositiveInfinity);
internal bool IsWellOrdered => Left <= Right && Top <= Bottom;
internal bool IsZeroSize => Left == Right || Top == Bottom;
internal bool IsEmpty => IsZeroSize;
internal LtrbRect? NullIfZeroSize() => IsZeroSize ? null : this;
internal LtrbRect Intersect(LtrbRect rect)
internal LtrbRect? IntersectOrNull(LtrbRect rect)
{
var newLeft = (rect.Left > Left) ? rect.Left : Left;
var newTop = (rect.Top > Top) ? rect.Top : Top;
var newRight = (rect.Right < Right) ? rect.Right : Right;
var newBottom = (rect.Bottom < Bottom) ? rect.Bottom : Bottom;
if ((newRight > newLeft) && (newBottom > newTop))
{
return new LtrbRect(newLeft, newTop, newRight, newBottom);
}
else
{
return default;
}
}
internal LtrbRect IntersectOrEmpty(LtrbRect rect)
{
var newLeft = (rect.Left > Left) ? rect.Left : Left;
var newTop = (rect.Top > Top) ? rect.Top : Top;
@ -118,7 +149,7 @@ public struct LtrbRect
/// <summary>
/// Perform _WPF-like_ union operation
/// </summary>
private LtrbRect FullUnionCore(LtrbRect rect)
public LtrbRect Union(LtrbRect rect)
{
var x1 = Math.Min(Left, rect.Left);
var x2 = Math.Max(Right, rect.Right);
@ -134,7 +165,7 @@ public struct LtrbRect
return right;
if (right == null)
return left;
return right.Value.FullUnionCore(left.Value);
return right.Value.Union(left.Value);
}
internal static LtrbRect? FullUnion(LtrbRect? left, Rect? right)
@ -143,7 +174,7 @@ public struct LtrbRect
return left;
if (left == null)
return new(right.Value);
return left.Value.FullUnionCore(new(right.Value));
return left.Value.Union(new(right.Value));
}
public override bool Equals(object? obj)
@ -165,6 +196,18 @@ public struct LtrbRect
return hash;
}
}
public override string ToString() => $"{Left}:{Top}-{Right}:{Bottom} ({Width}x{Height})";
public bool Contains(Point point)
{
return point.X >= Left && point.X <= Right && point.Y >= Top && point.Y <= Bottom;
}
public bool Contains(LtrbRect rect)
{
return rect.Left >= Left && rect.Right <= Right && rect.Top >= Top && rect.Bottom <= Bottom;
}
}
/// <summary>
@ -184,7 +227,7 @@ public struct LtrbRect
public struct LtrbPixelRect
{
public int Left, Top, Right, Bottom;
internal LtrbPixelRect(int x, int y, int right, int bottom)
{
Left = x;
@ -218,16 +261,10 @@ public struct LtrbPixelRect
return new(x1, y1, x2, y2);
}
internal Rect ToRectWithNoScaling() => new(Left, Top, (Right - Left), (Bottom - Top));
internal bool Contains(int x, int y)
{
return x >= Left && x <= Right && y >= Top && y <= Bottom;
}
internal static LtrbPixelRect FromRectWithNoScaling(LtrbRect rect) =>
new((int)rect.Left, (int)rect.Top, (int)Math.Ceiling(rect.Right),
(int)Math.Ceiling(rect.Bottom));
public static bool operator ==(LtrbPixelRect left, LtrbPixelRect right)=>
left.Left == right.Left && left.Top == right.Top && left.Right == right.Right && left.Bottom == right.Bottom;
@ -259,10 +296,9 @@ public struct LtrbPixelRect
}
internal Rect ToRectUnscaled() => new(Left, Top, Right - Left, Bottom - Top);
internal static LtrbPixelRect FromRectUnscaled(LtrbRect rect)
{
return new LtrbPixelRect((int)rect.Left, (int)rect.Top, (int)Math.Ceiling(rect.Right),
internal LtrbRect ToLtrbRectUnscaled() => new(Left, Top, Right, Bottom);
internal static LtrbPixelRect FromRectUnscaled(LtrbRect rect) =>
new((int)rect.Left, (int)rect.Top, (int)Math.Ceiling(rect.Right),
(int)Math.Ceiling(rect.Bottom));
}
}

9
src/Avalonia.Base/Platform/SurfaceOrientation.cs

@ -0,0 +1,9 @@
namespace Avalonia.Platform;
public enum SurfaceOrientation
{
Rotation0,
Rotation90,
Rotation180,
Rotation270,
}

2
src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleCompositionBrush.cs

@ -49,7 +49,7 @@ namespace Avalonia.Rendering.Composition.Server
partial class ServerCompositionSimpleRadialGradientBrush : IRadialGradientBrush
{
public double Radius => RadiusX.Scalar;
}
partial class ServerCompositionSimpleSolidColorBrush : ISolidColorBrush

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

@ -180,11 +180,12 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester
CompositionTarget.PixelSize = PixelSize.FromSizeRounded(_root.ClientSize, _root.RenderScaling);
CompositionTarget.Scaling = _root.RenderScaling;
var commit = _compositor.RequestCommitAsync();
var commit = _compositor.RequestCompositionBatchCommitAsync();
if (!_queuedSceneInvalidation)
{
_queuedSceneInvalidation = true;
commit.ContinueWith(_ => Dispatcher.UIThread.Post(() =>
// Updated hit-test information is available after full render
commit.Rendered.ContinueWith(_ => Dispatcher.UIThread.Post(() =>
{
_queuedSceneInvalidation = false;
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize)));

10
src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs

@ -12,6 +12,7 @@ public abstract class CompositionCustomVisualHandler
private ServerCompositionCustomVisual? _host;
private bool _inRender;
private Rect _currentTransformedClip;
private Matrix _currentTransform;
public virtual void OnMessage(object message)
{
@ -27,6 +28,7 @@ public abstract class CompositionCustomVisualHandler
{
_inRender = true;
_currentTransformedClip = currentTransformedClip;
_currentTransform = drawingContext.CurrentTransform;
try
{
OnRender(drawingContext);
@ -97,14 +99,14 @@ public abstract class CompositionCustomVisualHandler
protected bool RenderClipContains(Point pt)
{
VerifyInRender();
pt *= _host!.GlobalTransformMatrix;
return _currentTransformedClip.Contains(pt) && _host.Root!.DirtyRects.Contains(pt);
pt = pt.Transform(_currentTransform);
return _currentTransformedClip.Contains(pt);
}
protected bool RenderClipIntersectes(Rect rc)
{
VerifyInRender();
rc = rc.TransformToAABB(_host!.GlobalTransformMatrix);
return _currentTransformedClip.Intersects(rc) && _host.Root!.DirtyRects.Intersects(new (rc));
rc = rc.TransformToAABB(_currentTransform);
return _currentTransformedClip.Intersects(rc);
}
}

5
src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs

@ -25,6 +25,10 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
get => _drawList;
set
{
// Nothing to do
if (value == null && _drawList == null)
return;
_drawList?.Dispose();
_drawList = value;
_drawListChanged = true;
@ -46,6 +50,7 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
internal CompositionDrawListVisual(Compositor compositor, ServerCompositionDrawListVisual server, Visual visual) : base(compositor, server)
{
Visual = visual;
CustomHitTestCountInSubTree = visual is ICustomHitTest ? 1 : 0;
}
internal override bool HitTest(Point pt)

6
src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs

@ -101,12 +101,6 @@ public interface ICompositionGpuImportedObject : IAsyncDisposable
/// </summary>
Task ImportCompleted { get; }
/// <inheritdoc cref="ImportCompleted"/>
/// <seealso cref="ImportCompleted">ImportCompleted (recommended replacement)</seealso>
[Obsolete("Please use ICompositionGpuImportedObject.ImportCompleted instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
Task ImportCompeted { get; }
/// <summary>
/// Indicates if the device context this instance is associated with is no longer available
/// </summary>

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

Loading…
Cancel
Save