Browse Source

Merge branch 'master' into fixes/ListBox_Space_Handling

pull/11844/head
Max Katz 3 years ago
committed by GitHub
parent
commit
036476ebba
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      samples/AppWithoutLifetime/MainWindow.axaml.cs
  2. 6
      samples/AppWithoutLifetime/Sub.axaml.cs
  3. 9
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  4. 12
      samples/ControlCatalog.Android/MainActivity.cs
  5. 66
      samples/ControlCatalog.Android/Resources/drawable-night-v31/avalonia_anim.xml
  6. 71
      samples/ControlCatalog.Android/Resources/drawable-v31/avalonia_anim.xml
  7. 21
      samples/ControlCatalog.Android/Resources/values-v31/styles.xml
  8. 11
      samples/ControlCatalog.Android/Resources/values/styles.xml
  9. 35
      samples/ControlCatalog.Android/SplashActivity.cs
  10. 2
      samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs
  11. 3
      samples/ControlCatalog.NetCore/Program.cs
  12. 3
      samples/ControlCatalog/App.xaml
  13. 4
      samples/ControlCatalog/App.xaml.cs
  14. 5
      samples/MobileSandbox.Android/MainActivity.cs
  15. 10
      samples/MobileSandbox.Android/MobileSandbox.Android.csproj
  16. 66
      samples/MobileSandbox.Android/Resources/drawable-night-v31/avalonia_anim.xml
  17. 71
      samples/MobileSandbox.Android/Resources/drawable-v31/avalonia_anim.xml
  18. 4
      samples/MobileSandbox.Android/Resources/values-night/colors.xml
  19. 16
      samples/MobileSandbox.Android/Resources/values-v31/styles.xml
  20. 7
      samples/MobileSandbox.Android/Resources/values/styles.xml
  21. 17
      samples/MobileSandbox.Android/SplashActivity.cs
  22. 4
      samples/SafeAreaDemo.Android/MainActivity.cs
  23. 66
      samples/SafeAreaDemo.Android/Resources/drawable-night-v31/avalonia_anim.xml
  24. 71
      samples/SafeAreaDemo.Android/Resources/drawable-v31/avalonia_anim.xml
  25. 4
      samples/SafeAreaDemo.Android/Resources/values-night/colors.xml
  26. 16
      samples/SafeAreaDemo.Android/Resources/values-v31/styles.xml
  27. 7
      samples/SafeAreaDemo.Android/Resources/values/styles.xml
  28. 13
      samples/SafeAreaDemo.Android/SafeAreaDemo.Android.csproj
  29. 30
      samples/SafeAreaDemo.Android/SplashActivity.cs
  30. 6
      samples/SafeAreaDemo/Views/MainView.xaml.cs
  31. 74
      src/Android/Avalonia.Android/AndroidPlatform.cs
  32. 60
      src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
  33. 88
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  34. 40
      src/Android/Avalonia.Android/AvaloniaSplashActivity.cs
  35. 4
      src/Avalonia.Base/CombinedGeometry.cs
  36. 1
      src/Avalonia.Base/Input/Pointer.cs
  37. 2
      src/Avalonia.Base/Input/TextInputEventArgs.cs
  38. 2
      src/Avalonia.Base/Media/EllipseGeometry.cs
  39. 7
      src/Avalonia.Base/Media/Geometry.cs
  40. 4
      src/Avalonia.Base/Media/GeometryGroup.cs
  41. 2
      src/Avalonia.Base/Media/LineGeometry.cs
  42. 4
      src/Avalonia.Base/Media/MediaContext.cs
  43. 4
      src/Avalonia.Base/Media/PathGeometry.cs
  44. 4
      src/Avalonia.Base/Media/PlatformGeometry.cs
  45. 2
      src/Avalonia.Base/Media/PolylineGeometry.cs
  46. 2
      src/Avalonia.Base/Media/RectangleGeometry.cs
  47. 4
      src/Avalonia.Base/Media/StreamGeometry.cs
  48. 2
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  49. 13
      src/Avalonia.Base/Point.cs
  50. 4
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  51. 1
      src/Avalonia.Base/Rendering/RenderLoop.cs
  52. 5
      src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
  53. 14
      src/Avalonia.Base/Vector.cs
  54. 14
      src/Avalonia.Controls.DataGrid/DataGridColumn.cs
  55. 7
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  56. 2
      src/Avalonia.Controls/AppBuilder.cs
  57. 37
      src/Avalonia.Controls/Control.cs
  58. 12
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  59. 6
      src/Avalonia.Controls/ToggleSwitch.cs
  60. 2
      src/Avalonia.Diagnostics/Diagnostics/Views/PropertyValueEditorView.cs
  61. 145
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml
  62. 46
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs
  63. 6
      src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs
  64. 24
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  65. 6
      src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
  66. 2
      src/Avalonia.Native/PopupImpl.cs
  67. 2
      src/Avalonia.Native/WindowImpl.cs
  68. 5
      src/Avalonia.Native/WindowImplBase.cs
  69. 7
      src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs
  70. 18
      src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs
  71. 47
      src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml
  72. 2
      src/Avalonia.Themes.Fluent/FluentTheme.xaml
  73. 35
      src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs
  74. 3
      src/Avalonia.Themes.Simple/Controls/TransitioningContentControl.xaml
  75. 12
      src/Avalonia.X11/Glx/GlxPlatformFeature.cs
  76. 2
      src/Avalonia.X11/X11Clipboard.cs
  77. 83
      src/Avalonia.X11/X11Platform.cs
  78. 4
      src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs
  79. 18
      src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
  80. 59
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  81. 101
      src/Windows/Avalonia.Win32/Win32GlManager.cs
  82. 95
      src/Windows/Avalonia.Win32/Win32Platform.cs
  83. 140
      src/Windows/Avalonia.Win32/Win32PlatformOptions.cs
  84. 2
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs
  85. 1
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs
  86. 31
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs
  87. 1
      src/tools/Avalonia.Generators/Avalonia.Generators.csproj
  88. 13
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs
  89. 2
      tests/Avalonia.Base.UnitTests/Media/GeometryTests.cs
  90. 31
      tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs
  91. 1
      tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj

5
samples/AppWithoutLifetime/MainWindow.axaml.cs

@ -17,10 +17,11 @@ public partial class MainWindow : Window
AvaloniaXamlLoader.Load(this);
}
protected override void OnLoaded()
/// <inheritdoc/>
protected override void OnLoaded(RoutedEventArgs e)
{
this.AttachDevTools();
base.OnLoaded();
base.OnLoaded(e);
}
public void Open(object sender, RoutedEventArgs e)

6
samples/AppWithoutLifetime/Sub.axaml.cs

@ -1,5 +1,6 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
namespace AppWithoutLifetime;
@ -16,9 +17,10 @@ public partial class Sub : Window
AvaloniaXamlLoader.Load(this);
}
protected override void OnLoaded()
/// <inheritdoc/>
protected override void OnLoaded(RoutedEventArgs e)
{
this.AttachDevTools();
base.OnLoaded();
base.OnLoaded(e);
}
}

9
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@ -10,6 +10,11 @@
<AndroidPackageFormat>apk</AndroidPackageFormat>
<MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\drawable-night-v31\avalonia_anim.xml" />
<None Remove="Resources\drawable-v31\avalonia_anim.xml" />
<None Remove="Resources\values-v31\styles.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="..\..\build\Assets\Icon.png">
<Link>Resources\drawable\Icon.png</Link>
@ -37,6 +42,10 @@
<AndroidEnvironment Condition="'$(IsEmulator)'!='True'" Include="environment.device.txt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Android\Avalonia.Android\Avalonia.Android.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />

12
samples/ControlCatalog.Android/MainActivity.cs

@ -5,8 +5,16 @@ using Avalonia.Android;
namespace ControlCatalog.Android
{
[Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.Main", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
public class MainActivity : AvaloniaMainActivity
[Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
public class MainActivity : AvaloniaMainActivity<App>
{
protected override Avalonia.AppBuilder CustomizeAppBuilder(Avalonia.AppBuilder builder)
{
return base.CustomizeAppBuilder(builder)
.AfterSetup(_ =>
{
Pages.EmbedSample.Implementation = new EmbedSampleAndroid();
});
}
}
}

66
samples/ControlCatalog.Android/Resources/drawable-night-v31/avalonia_anim.xml

@ -0,0 +1,66 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<group
android:name="wrapper"
android:translateX="21"
android:translateY="21">
<group android:name="group">
<path
android:name="path"
android:pathData="M 74.853 85.823 L 75.368 85.823 C 80.735 85.823 85.144 81.803 85.761 76.602 L 85.836 41.76 C 85.225 18.593 66.254 0 42.939 0 C 19.24 0 0.028 19.212 0.028 42.912 C 0.028 66.357 18.831 85.418 42.18 85.823 L 74.853 85.823 Z"
android:strokeWidth="1"/>
<path
android:name="path_1"
android:pathData="M 43.059 14.614 C 29.551 14.614 18.256 24.082 15.445 36.743 C 18.136 37.498 20.109 39.968 20.109 42.899 C 20.109 45.831 18.136 48.301 15.445 49.055 C 18.256 61.716 29.551 71.184 43.059 71.184 C 47.975 71.184 52.599 69.93 56.628 67.723 L 56.628 70.993 L 71.344 70.993 L 71.344 44.072 C 71.357 43.714 71.344 43.26 71.344 42.899 C 71.344 27.278 58.68 14.614 43.059 14.614 Z M 29.51 42.899 C 29.51 35.416 35.576 29.35 43.059 29.35 C 50.541 29.35 56.607 35.416 56.607 42.899 C 56.607 50.382 50.541 56.448 43.059 56.448 C 35.576 56.448 29.51 50.382 29.51 42.899 Z"
android:strokeWidth="1"
android:fillType="evenOdd"/>
<path
android:name="path_2"
android:pathData="M 18.105 42.88 C 18.105 45.38 16.078 47.407 13.579 47.407 C 11.079 47.407 9.052 45.38 9.052 42.88 C 9.052 40.381 11.079 38.354 13.579 38.354 C 16.078 38.354 18.105 40.381 18.105 42.88 Z"
android:strokeWidth="1"/>
</group>
</group>
</vector>
</aapt:attr>
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:duration="1000"
android:valueFrom="#00ffffff"
android:valueTo="#161c2d"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
<target android:name="path_1">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:duration="1000"
android:valueFrom="#00ffffff"
android:valueTo="#f9f9fb"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
<target android:name="path_2">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:duration="1000"
android:valueFrom="#00ffffff"
android:valueTo="#f9f9fb"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
</animated-vector>

71
samples/ControlCatalog.Android/Resources/drawable-v31/avalonia_anim.xml

@ -0,0 +1,71 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<group
android:name="wrapper"
android:translateX="21"
android:translateY="21">
<group android:name="group">
<path
android:name="path"
android:pathData="M 74.853 85.823 L 75.368 85.823 C 80.735 85.823 85.144 81.803 85.761 76.602 L 85.836 41.76 C 85.225 18.593 66.254 0 42.939 0 C 19.24 0 0.028 19.212 0.028 42.912 C 0.028 66.357 18.831 85.418 42.18 85.823 L 74.853 85.823 Z"
android:fillColor="#00ffffff"
android:strokeWidth="1"/>
<path
android:name="path_1"
android:pathData="M 43.059 14.614 C 29.551 14.614 18.256 24.082 15.445 36.743 C 18.136 37.498 20.109 39.968 20.109 42.899 C 20.109 45.831 18.136 48.301 15.445 49.055 C 18.256 61.716 29.551 71.184 43.059 71.184 C 47.975 71.184 52.599 69.93 56.628 67.723 L 56.628 70.993 L 71.344 70.993 L 71.344 44.072 C 71.357 43.714 71.344 43.26 71.344 42.899 C 71.344 27.278 58.68 14.614 43.059 14.614 Z M 29.51 42.899 C 29.51 35.416 35.576 29.35 43.059 29.35 C 50.541 29.35 56.607 35.416 56.607 42.899 C 56.607 50.382 50.541 56.448 43.059 56.448 C 35.576 56.448 29.51 50.382 29.51 42.899 Z"
android:fillColor="#00ffffff"
android:strokeWidth="1"
android:fillType="evenOdd"/>
<path
android:name="path_2"
android:pathData="M 18.105 42.88 C 18.105 45.38 16.078 47.407 13.579 47.407 C 11.079 47.407 9.052 45.38 9.052 42.88 C 9.052 40.381 11.079 38.354 13.579 38.354 C 16.078 38.354 18.105 40.381 18.105 42.88 Z"
android:fillColor="#00ffffff"
android:strokeWidth="1"/>
</group>
</group>
</vector>
</aapt:attr>
<target android:name="path_2">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:startOffset="100"
android:duration="900"
android:valueFrom="#00ffffff"
android:valueTo="#161c2d"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:duration="500"
android:valueFrom="#00ffffff"
android:valueTo="#f9f9fb"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
<target android:name="path_1">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:startOffset="100"
android:duration="900"
android:valueFrom="#00ffffff"
android:valueTo="#161c2d"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
</animated-vector>

21
samples/ControlCatalog.Android/Resources/values-v31/styles.xml

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<style name="MyTheme">
</style>
<style name="MyTheme.NoActionBar" parent="@style/Theme.AppCompat.NoActionBar">
<item name="android:windowActionBar">false</item>
<item name="android:windowBackground">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowSplashScreenBackground">@color/splash_background</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/avalonia_anim</item>
<item name="android:windowSplashScreenAnimationDuration">1000</item>
<item name="postSplashScreenTheme">@style/MyTheme.Main</item>
</style>
<style name="MyTheme.Main"
parent ="MyTheme.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
</style>
</resources>

11
samples/ControlCatalog.Android/Resources/values/styles.xml

@ -6,16 +6,7 @@
<style name="MyTheme.NoActionBar" parent="@style/Theme.AppCompat.DayNight.NoActionBar">
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
</style>
<style name="MyTheme.Splash" parent ="MyTheme.NoActionBar">
<item name="android:windowBackground">@drawable/splash_screen</item>
<item name="android:windowContentOverlay">@null</item>
</style>
<style name="MyTheme.Main" parent ="MyTheme.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
</style>
</resources>

35
samples/ControlCatalog.Android/SplashActivity.cs

@ -1,35 +0,0 @@
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Avalonia.Android;
namespace ControlCatalog.Android
{
[Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
public class SplashActivity : AvaloniaSplashActivity<App>
{
protected override Avalonia.AppBuilder CustomizeAppBuilder(Avalonia.AppBuilder builder)
{
return base.CustomizeAppBuilder(builder)
.AfterSetup(_ =>
{
Pages.EmbedSample.Implementation = new EmbedSampleAndroid();
});
}
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
}
protected override void OnResume()
{
base.OnResume();
StartActivity(new Intent(Application.Context, typeof(MainActivity)));
Finish();
}
}
}

2
samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs

@ -39,7 +39,7 @@ internal unsafe class WinApi
[DllImport("user32.dll", SetLastError = true)]
public static extern bool DestroyWindow(IntPtr hwnd);
[DllImport("kernel32.dll")]
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "LoadLibraryW", ExactSpelling = true)]
public static extern IntPtr LoadLibrary(string lib);

3
samples/ControlCatalog.NetCore/Program.cs

@ -110,8 +110,7 @@ namespace ControlCatalog.NetCore
{
builder.With(new Win32PlatformOptions()
{
UseLowLatencyDxgiSwapChain = true,
UseWindowsUIComposition = false
CompositionMode = new [] { Win32CompositionMode.LowLatencyDxgiSwapChain }
});
return builder.StartWithClassicDesktopLifetime(args);
}

3
samples/ControlCatalog/App.xaml

@ -28,6 +28,9 @@
</ResourceDictionary.ThemeDictionaries>
<!-- Styles attached dynamically depending on current theme (simple or fluent) -->
<FluentTheme x:Key="FluentTheme">
</FluentTheme>
<SimpleTheme x:Key="SimpleTheme" />
<StyleInclude x:Key="DataGridFluent" Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />
<StyleInclude x:Key="DataGridSimple" Source="avares://Avalonia.Controls.DataGrid/Themes/Simple.xaml" />
<StyleInclude x:Key="ColorPickerFluent" Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml" />

4
samples/ControlCatalog/App.xaml.cs

@ -30,8 +30,8 @@ namespace ControlCatalog
AvaloniaXamlLoader.Load(this);
_fluentTheme = new FluentTheme();
_simpleTheme = new SimpleTheme();
_fluentTheme = (FluentTheme)Resources["FluentTheme"]!;
_simpleTheme = (SimpleTheme)Resources["SimpleTheme"]!;
_colorPickerFluent = (IStyle)Resources["ColorPickerFluent"]!;
_colorPickerSimple = (IStyle)Resources["ColorPickerSimple"]!;
_dataGridFluent = (IStyle)Resources["DataGridFluent"]!;

5
samples/MobileSandbox.Android/MainActivity.cs

@ -1,12 +1,11 @@
using Android.App;
using Android.Content.PM;
using Avalonia;
using Avalonia.Android;
namespace MobileSandbox.Android
{
[Activity(Label = "MobileSandbox.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
public class MainActivity : AvaloniaMainActivity
[Activity(Label = "MobileSandbox.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
public class MainActivity : AvaloniaMainActivity<App>
{
}
}

10
samples/MobileSandbox.Android/MobileSandbox.Android.csproj

@ -10,6 +10,12 @@
<AndroidPackageFormat>apk</AndroidPackageFormat>
<MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\drawable-night-v31\avalonia_anim.xml" />
<None Remove="Resources\drawable-v31\avalonia_anim.xml" />
<None Remove="Resources\values-v31\styles.xml" />
<None Remove="Resources\values-night\colors.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="..\..\build\Assets\Icon.png">
<Link>Resources\drawable\Icon.png</Link>
@ -37,6 +43,10 @@
<AndroidEnvironment Condition="'$(IsEmulator)'!='True'" Include="environment.device.txt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Android\Avalonia.Android\Avalonia.Android.csproj" />
<ProjectReference Include="..\MobileSandbox\MobileSandbox.csproj" />

66
samples/MobileSandbox.Android/Resources/drawable-night-v31/avalonia_anim.xml

@ -0,0 +1,66 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<group
android:name="wrapper"
android:translateX="21"
android:translateY="21">
<group android:name="group">
<path
android:name="path"
android:pathData="M 74.853 85.823 L 75.368 85.823 C 80.735 85.823 85.144 81.803 85.761 76.602 L 85.836 41.76 C 85.225 18.593 66.254 0 42.939 0 C 19.24 0 0.028 19.212 0.028 42.912 C 0.028 66.357 18.831 85.418 42.18 85.823 L 74.853 85.823 Z"
android:strokeWidth="1"/>
<path
android:name="path_1"
android:pathData="M 43.059 14.614 C 29.551 14.614 18.256 24.082 15.445 36.743 C 18.136 37.498 20.109 39.968 20.109 42.899 C 20.109 45.831 18.136 48.301 15.445 49.055 C 18.256 61.716 29.551 71.184 43.059 71.184 C 47.975 71.184 52.599 69.93 56.628 67.723 L 56.628 70.993 L 71.344 70.993 L 71.344 44.072 C 71.357 43.714 71.344 43.26 71.344 42.899 C 71.344 27.278 58.68 14.614 43.059 14.614 Z M 29.51 42.899 C 29.51 35.416 35.576 29.35 43.059 29.35 C 50.541 29.35 56.607 35.416 56.607 42.899 C 56.607 50.382 50.541 56.448 43.059 56.448 C 35.576 56.448 29.51 50.382 29.51 42.899 Z"
android:strokeWidth="1"
android:fillType="evenOdd"/>
<path
android:name="path_2"
android:pathData="M 18.105 42.88 C 18.105 45.38 16.078 47.407 13.579 47.407 C 11.079 47.407 9.052 45.38 9.052 42.88 C 9.052 40.381 11.079 38.354 13.579 38.354 C 16.078 38.354 18.105 40.381 18.105 42.88 Z"
android:strokeWidth="1"/>
</group>
</group>
</vector>
</aapt:attr>
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:duration="1000"
android:valueFrom="#00ffffff"
android:valueTo="#161c2d"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
<target android:name="path_1">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:duration="1000"
android:valueFrom="#00ffffff"
android:valueTo="#f9f9fb"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
<target android:name="path_2">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:duration="1000"
android:valueFrom="#00ffffff"
android:valueTo="#f9f9fb"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
</animated-vector>

71
samples/MobileSandbox.Android/Resources/drawable-v31/avalonia_anim.xml

@ -0,0 +1,71 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<group
android:name="wrapper"
android:translateX="21"
android:translateY="21">
<group android:name="group">
<path
android:name="path"
android:pathData="M 74.853 85.823 L 75.368 85.823 C 80.735 85.823 85.144 81.803 85.761 76.602 L 85.836 41.76 C 85.225 18.593 66.254 0 42.939 0 C 19.24 0 0.028 19.212 0.028 42.912 C 0.028 66.357 18.831 85.418 42.18 85.823 L 74.853 85.823 Z"
android:fillColor="#00ffffff"
android:strokeWidth="1"/>
<path
android:name="path_1"
android:pathData="M 43.059 14.614 C 29.551 14.614 18.256 24.082 15.445 36.743 C 18.136 37.498 20.109 39.968 20.109 42.899 C 20.109 45.831 18.136 48.301 15.445 49.055 C 18.256 61.716 29.551 71.184 43.059 71.184 C 47.975 71.184 52.599 69.93 56.628 67.723 L 56.628 70.993 L 71.344 70.993 L 71.344 44.072 C 71.357 43.714 71.344 43.26 71.344 42.899 C 71.344 27.278 58.68 14.614 43.059 14.614 Z M 29.51 42.899 C 29.51 35.416 35.576 29.35 43.059 29.35 C 50.541 29.35 56.607 35.416 56.607 42.899 C 56.607 50.382 50.541 56.448 43.059 56.448 C 35.576 56.448 29.51 50.382 29.51 42.899 Z"
android:fillColor="#00ffffff"
android:strokeWidth="1"
android:fillType="evenOdd"/>
<path
android:name="path_2"
android:pathData="M 18.105 42.88 C 18.105 45.38 16.078 47.407 13.579 47.407 C 11.079 47.407 9.052 45.38 9.052 42.88 C 9.052 40.381 11.079 38.354 13.579 38.354 C 16.078 38.354 18.105 40.381 18.105 42.88 Z"
android:fillColor="#00ffffff"
android:strokeWidth="1"/>
</group>
</group>
</vector>
</aapt:attr>
<target android:name="path_2">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:startOffset="100"
android:duration="900"
android:valueFrom="#00ffffff"
android:valueTo="#161c2d"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:duration="500"
android:valueFrom="#00ffffff"
android:valueTo="#f9f9fb"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
<target android:name="path_1">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:startOffset="100"
android:duration="900"
android:valueFrom="#00ffffff"
android:valueTo="#161c2d"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
</animated-vector>

4
samples/MobileSandbox.Android/Resources/values-night/colors.xml

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="splash_background">#212121</color>
</resources>

16
samples/MobileSandbox.Android/Resources/values-v31/styles.xml

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<style name="MyTheme">
</style>
<style name="MyTheme.NoActionBar" parent="@style/Theme.AppCompat.NoActionBar">
<item name="android:windowActionBar">false</item>
<item name="android:windowBackground">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowSplashScreenBackground">@color/splash_background</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/avalonia_anim</item>
<item name="android:windowSplashScreenAnimationDuration">1000</item>
</style>
</resources>

7
samples/MobileSandbox.Android/Resources/values/styles.xml

@ -6,12 +6,7 @@
<style name="MyTheme.NoActionBar" parent="@style/Theme.AppCompat.NoActionBar">
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
</style>
<style name="MyTheme.Splash" parent ="MyTheme.NoActionBar">
<item name="android:windowBackground">@drawable/splash_screen</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
</style>
</resources>

17
samples/MobileSandbox.Android/SplashActivity.cs

@ -1,17 +0,0 @@
using Android.App;
using Android.Content;
using Avalonia.Android;
namespace MobileSandbox.Android
{
[Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
public class SplashActivity : AvaloniaSplashActivity<App>
{
protected override void OnResume()
{
base.OnResume();
StartActivity(new Intent(Application.Context, typeof(MainActivity)));
}
}
}

4
samples/SafeAreaDemo.Android/MainActivity.cs

@ -4,8 +4,8 @@ using Avalonia.Android;
namespace SafeAreaDemo.Android
{
[Activity(Label = "SafeAreaDemo.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
public class MainActivity : AvaloniaMainActivity
[Activity(Label = "SafeAreaDemo.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
public class MainActivity : AvaloniaMainActivity<App>
{
}
}

66
samples/SafeAreaDemo.Android/Resources/drawable-night-v31/avalonia_anim.xml

@ -0,0 +1,66 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<group
android:name="wrapper"
android:translateX="21"
android:translateY="21">
<group android:name="group">
<path
android:name="path"
android:pathData="M 74.853 85.823 L 75.368 85.823 C 80.735 85.823 85.144 81.803 85.761 76.602 L 85.836 41.76 C 85.225 18.593 66.254 0 42.939 0 C 19.24 0 0.028 19.212 0.028 42.912 C 0.028 66.357 18.831 85.418 42.18 85.823 L 74.853 85.823 Z"
android:strokeWidth="1"/>
<path
android:name="path_1"
android:pathData="M 43.059 14.614 C 29.551 14.614 18.256 24.082 15.445 36.743 C 18.136 37.498 20.109 39.968 20.109 42.899 C 20.109 45.831 18.136 48.301 15.445 49.055 C 18.256 61.716 29.551 71.184 43.059 71.184 C 47.975 71.184 52.599 69.93 56.628 67.723 L 56.628 70.993 L 71.344 70.993 L 71.344 44.072 C 71.357 43.714 71.344 43.26 71.344 42.899 C 71.344 27.278 58.68 14.614 43.059 14.614 Z M 29.51 42.899 C 29.51 35.416 35.576 29.35 43.059 29.35 C 50.541 29.35 56.607 35.416 56.607 42.899 C 56.607 50.382 50.541 56.448 43.059 56.448 C 35.576 56.448 29.51 50.382 29.51 42.899 Z"
android:strokeWidth="1"
android:fillType="evenOdd"/>
<path
android:name="path_2"
android:pathData="M 18.105 42.88 C 18.105 45.38 16.078 47.407 13.579 47.407 C 11.079 47.407 9.052 45.38 9.052 42.88 C 9.052 40.381 11.079 38.354 13.579 38.354 C 16.078 38.354 18.105 40.381 18.105 42.88 Z"
android:strokeWidth="1"/>
</group>
</group>
</vector>
</aapt:attr>
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:duration="1000"
android:valueFrom="#00ffffff"
android:valueTo="#161c2d"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
<target android:name="path_1">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:duration="1000"
android:valueFrom="#00ffffff"
android:valueTo="#f9f9fb"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
<target android:name="path_2">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:duration="1000"
android:valueFrom="#00ffffff"
android:valueTo="#f9f9fb"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
</animated-vector>

71
samples/SafeAreaDemo.Android/Resources/drawable-v31/avalonia_anim.xml

@ -0,0 +1,71 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<group
android:name="wrapper"
android:translateX="21"
android:translateY="21">
<group android:name="group">
<path
android:name="path"
android:pathData="M 74.853 85.823 L 75.368 85.823 C 80.735 85.823 85.144 81.803 85.761 76.602 L 85.836 41.76 C 85.225 18.593 66.254 0 42.939 0 C 19.24 0 0.028 19.212 0.028 42.912 C 0.028 66.357 18.831 85.418 42.18 85.823 L 74.853 85.823 Z"
android:fillColor="#00ffffff"
android:strokeWidth="1"/>
<path
android:name="path_1"
android:pathData="M 43.059 14.614 C 29.551 14.614 18.256 24.082 15.445 36.743 C 18.136 37.498 20.109 39.968 20.109 42.899 C 20.109 45.831 18.136 48.301 15.445 49.055 C 18.256 61.716 29.551 71.184 43.059 71.184 C 47.975 71.184 52.599 69.93 56.628 67.723 L 56.628 70.993 L 71.344 70.993 L 71.344 44.072 C 71.357 43.714 71.344 43.26 71.344 42.899 C 71.344 27.278 58.68 14.614 43.059 14.614 Z M 29.51 42.899 C 29.51 35.416 35.576 29.35 43.059 29.35 C 50.541 29.35 56.607 35.416 56.607 42.899 C 56.607 50.382 50.541 56.448 43.059 56.448 C 35.576 56.448 29.51 50.382 29.51 42.899 Z"
android:fillColor="#00ffffff"
android:strokeWidth="1"
android:fillType="evenOdd"/>
<path
android:name="path_2"
android:pathData="M 18.105 42.88 C 18.105 45.38 16.078 47.407 13.579 47.407 C 11.079 47.407 9.052 45.38 9.052 42.88 C 9.052 40.381 11.079 38.354 13.579 38.354 C 16.078 38.354 18.105 40.381 18.105 42.88 Z"
android:fillColor="#00ffffff"
android:strokeWidth="1"/>
</group>
</group>
</vector>
</aapt:attr>
<target android:name="path_2">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:startOffset="100"
android:duration="900"
android:valueFrom="#00ffffff"
android:valueTo="#161c2d"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:duration="500"
android:valueFrom="#00ffffff"
android:valueTo="#f9f9fb"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
<target android:name="path_1">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillColor"
android:startOffset="100"
android:duration="900"
android:valueFrom="#00ffffff"
android:valueTo="#161c2d"
android:valueType="colorType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
</animated-vector>

4
samples/SafeAreaDemo.Android/Resources/values-night/colors.xml

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="splash_background">#212121</color>
</resources>

16
samples/SafeAreaDemo.Android/Resources/values-v31/styles.xml

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<style name="MyTheme">
</style>
<style name="MyTheme.NoActionBar" parent="@style/Theme.AppCompat.NoActionBar">
<item name="android:windowActionBar">false</item>
<item name="android:windowBackground">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowSplashScreenBackground">@color/splash_background</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/avalonia_anim</item>
<item name="android:windowSplashScreenAnimationDuration">1000</item>
</style>
</resources>

7
samples/SafeAreaDemo.Android/Resources/values/styles.xml

@ -6,12 +6,7 @@
<style name="MyTheme.NoActionBar" parent="@style/Theme.AppCompat.NoActionBar">
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
</style>
<style name="MyTheme.Splash" parent ="MyTheme.NoActionBar">
<item name="android:windowBackground">@drawable/splash_screen</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
</style>
</resources>

13
samples/SafeAreaDemo.Android/SafeAreaDemo.Android.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0-android</TargetFramework>
@ -12,13 +12,14 @@
</PropertyGroup>
<ItemGroup>
<AndroidResource Include="Icon.png">
<Link>Resources\drawable\Icon.png</Link>
</AndroidResource>
<ProjectReference Include="..\SafeAreaDemo\SafeAreaDemo.csproj" />
<ProjectReference Include="..\..\src\Android\Avalonia.Android\Avalonia.Android.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SafeAreaDemo\SafeAreaDemo.csproj" />
<ProjectReference Include="..\..\src\Android\Avalonia.Android\Avalonia.Android.csproj" />
<AndroidResource Include="..\..\build\Assets\Icon.png">
<Link>Resources\drawable\Icon.png</Link>
</AndroidResource>
</ItemGroup>
</Project>

30
samples/SafeAreaDemo.Android/SplashActivity.cs

@ -1,30 +0,0 @@
using Android.App;
using Android.Content;
using Android.OS;
using Avalonia;
using Avalonia.Android;
using Application = Android.App.Application;
namespace SafeAreaDemo.Android
{
[Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
public class SplashActivity : AvaloniaSplashActivity<App>
{
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{
return base.CustomizeAppBuilder(builder);
}
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
}
protected override void OnResume()
{
base.OnResume();
StartActivity(new Intent(Application.Context, typeof(MainActivity)));
}
}
}

6
samples/SafeAreaDemo/Views/MainView.xaml.cs

@ -1,4 +1,5 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using SafeAreaDemo.ViewModels;
@ -11,9 +12,10 @@ namespace SafeAreaDemo.Views
AvaloniaXamlLoader.Load(this);
}
protected override void OnLoaded()
/// <inheritdoc/>
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded();
base.OnLoaded(e);
var insetsManager = TopLevel.GetTopLevel(this)?.InsetsManager;
if (insetsManager != null && DataContext is MainViewModel viewModel)

74
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Android;
using Avalonia.Android.Platform;
@ -22,6 +24,39 @@ namespace Avalonia
.UseSkia();
}
}
/// <summary>
/// Represents the rendering mode for platform graphics.
/// </summary>
public enum AndroidRenderingMode
{
/// <summary>
/// Avalonia is rendered into a framebuffer.
/// </summary>
Software = 1,
/// <summary>
/// Enables android EGL rendering.
/// </summary>
Egl = 2
}
public sealed class AndroidPlatformOptions
{
/// <summary>
/// Gets or sets Avalonia rendering modes with fallbacks.
/// The first element in the array has the highest priority.
/// The default value is: <see cref="AndroidRenderingMode.Egl"/>, <see cref="AndroidRenderingMode.Software"/>.
/// </summary>
/// <remarks>
/// If application should work on as wide range of devices as possible, at least add <see cref="AndroidRenderingMode.Software"/> as a fallback value.
/// </remarks>
/// <exception cref="System.InvalidOperationException">Thrown if no values were matched.</exception>
public IReadOnlyList<AndroidRenderingMode> RenderingMode { get; set; } = new[]
{
AndroidRenderingMode.Egl, AndroidRenderingMode.Software
};
}
}
namespace Avalonia.Android
@ -47,18 +82,39 @@ namespace Avalonia.Android
.Bind<IRenderTimer>().ToConstant(new ChoreographerTimer())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
if (Options.UseGpu)
var graphics = InitializeGraphics(Options);
if (graphics is not null)
{
EglPlatformGraphics.TryInitialize();
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(graphics);
}
Compositor = new Compositor(AvaloniaLocator.Current.GetService<IPlatformGraphics>());
Compositor = new Compositor(graphics);
}
}
private static IPlatformGraphics InitializeGraphics(AndroidPlatformOptions opts)
{
if (opts.RenderingMode is null || !opts.RenderingMode.Any())
{
throw new InvalidOperationException($"{nameof(AndroidPlatformOptions)}.{nameof(AndroidPlatformOptions.RenderingMode)} must not be empty or null");
}
public sealed class AndroidPlatformOptions
{
public bool UseDeferredRendering { get; set; } = false;
public bool UseGpu { get; set; } = true;
foreach (var renderingMode in opts.RenderingMode)
{
if (renderingMode == AndroidRenderingMode.Software)
{
return null;
}
if (renderingMode == AndroidRenderingMode.Egl)
{
if (EglPlatformGraphics.TryCreate() is { } egl)
{
return egl;
}
}
}
throw new InvalidOperationException($"{nameof(AndroidPlatformOptions)}.{nameof(AndroidPlatformOptions.RenderingMode)} has a value of \"{string.Join(", ", opts.RenderingMode)}\", but no options were applied.");
}
}
}

60
src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Android
{
partial class AvaloniaMainActivity<TApp> where TApp : Application, new()
{
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid();
private static AppBuilder? s_appBuilder;
internal static object ViewContent;
public object Content
{
get
{
return ViewContent;
}
set
{
ViewContent = value;
if (View != null)
View.Content = value;
}
}
protected AppBuilder CreateAppBuilder()
{
var builder = AppBuilder.Configure<TApp>();
return CustomizeAppBuilder(builder);
}
private void InitializeApp()
{
if (s_appBuilder == null)
{
var builder = CreateAppBuilder();
builder.SetupWithLifetime(new SingleViewLifetime());
s_appBuilder = builder;
}
View = new AvaloniaView(this);
if (ViewContent != null)
{
View.Content = ViewContent;
}
if (Avalonia.Application.Current.ApplicationLifetime is SingleViewLifetime lifetime)
{
lifetime.View = View;
}
}
}
}

88
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@ -11,27 +11,49 @@ using AndroidX.AppCompat.App;
namespace Avalonia.Android
{
public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler, IActivityNavigationService
public class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler, IActivityNavigationService
{
internal static object ViewContent;
public Action<int, Result, Intent> ActivityResult { get; set; }
public Action<int, string[], Permission[]> RequestPermissionsResult { get; set; }
public event EventHandler<AndroidBackRequestedEventArgs> BackRequested;
public override void OnBackPressed()
{
var eventArgs = new AndroidBackRequestedEventArgs();
BackRequested?.Invoke(this, eventArgs);
if (!eventArgs.Handled)
{
base.OnBackPressed();
}
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
ActivityResult?.Invoke(requestCode, resultCode, data);
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
RequestPermissionsResult?.Invoke(requestCode, permissions, grantResults);
}
}
public abstract partial class AvaloniaMainActivity<TApp> : AvaloniaMainActivity where TApp : Application, new()
{
internal AvaloniaView View;
private GlobalLayoutListener _listener;
protected override void OnCreate(Bundle savedInstanceState)
{
View = new AvaloniaView(this);
if (ViewContent != null)
{
View.Content = ViewContent;
}
InitializeApp();
if (Avalonia.Application.Current.ApplicationLifetime is SingleViewLifetime lifetime)
{
lifetime.View = View;
}
base.OnCreate(savedInstanceState);
SetContentView(View);
@ -41,20 +63,6 @@ namespace Avalonia.Android
View.ViewTreeObserver?.AddOnGlobalLayoutListener(_listener);
}
public object Content
{
get
{
return ViewContent;
}
set
{
ViewContent = value;
if (View != null)
View.Content = value;
}
}
protected override void OnResume()
{
base.OnResume();
@ -66,20 +74,6 @@ namespace Avalonia.Android
}
}
public event EventHandler<AndroidBackRequestedEventArgs> BackRequested;
public override void OnBackPressed()
{
var eventArgs = new AndroidBackRequestedEventArgs();
BackRequested?.Invoke(this, eventArgs);
if (!eventArgs.Handled)
{
base.OnBackPressed();
}
}
protected override void OnDestroy()
{
View.Content = null;
@ -89,20 +83,6 @@ namespace Avalonia.Android
base.OnDestroy();
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
ActivityResult?.Invoke(requestCode, resultCode, data);
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
RequestPermissionsResult?.Invoke(requestCode, permissions, grantResults);
}
class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
{
private AvaloniaView _view;

40
src/Android/Avalonia.Android/AvaloniaSplashActivity.cs

@ -1,40 +0,0 @@
using Android.OS;
using AndroidX.AppCompat.App;
namespace Avalonia.Android
{
public abstract class AvaloniaSplashActivity : AppCompatActivity
{
protected abstract AppBuilder CreateAppBuilder();
private static AppBuilder s_appBuilder;
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
if (s_appBuilder == null)
{
var builder = CreateAppBuilder();
var lifetime = new SingleViewLifetime();
builder.SetupWithLifetime(lifetime);
s_appBuilder = builder;
}
}
}
public abstract class AvaloniaSplashActivity<TApp> : AvaloniaSplashActivity where TApp : Application, new()
{
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid();
protected override AppBuilder CreateAppBuilder()
{
var builder = AppBuilder.Configure<TApp>();
return CustomizeAppBuilder(builder);
}
}
}

4
src/Avalonia.Base/CombinedGeometry.cs

@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.Text;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Media
{
public enum GeometryCombineMode
@ -147,7 +145,7 @@ namespace Avalonia.Media
return new CombinedGeometry(GeometryCombineMode, Geometry1, Geometry2, Transform);
}
protected override IGeometryImpl? CreateDefiningGeometry()
private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var g1 = Geometry1;
var g2 = Geometry2;

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

@ -88,7 +88,6 @@ namespace Avalonia.Input
/// Captures pointer input to the specified gesture recognizer.
/// </summary>
/// <param name="gestureRecognizer">The gesture recognizer.</param>
/// </remarks>
internal void CaptureGestureRecognizer(GestureRecognizer? gestureRecognizer)
{
if (CapturedGestureRecognizer != gestureRecognizer)

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

@ -4,6 +4,6 @@ namespace Avalonia.Input
{
public class TextInputEventArgs : RoutedEventArgs
{
public string? Text { get; init; }
public string? Text { get; set; }
}
}

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

@ -135,7 +135,7 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
protected override IGeometryImpl? CreateDefiningGeometry()
private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();

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

@ -28,6 +28,11 @@ namespace Avalonia.Media
TransformProperty.Changed.AddClassHandler<Geometry>((x,e) => x.TransformChanged(e));
}
internal Geometry()
{
}
/// <summary>
/// Raised when the geometry changes.
/// </summary>
@ -134,7 +139,7 @@ namespace Avalonia.Media
/// Creates the platform implementation of the geometry, without the transform applied.
/// </summary>
/// <returns></returns>
protected abstract IGeometryImpl? CreateDefiningGeometry();
private protected abstract IGeometryImpl? CreateDefiningGeometry();
/// <summary>
/// Invalidates the platform implementation of the geometry.

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

@ -1,8 +1,6 @@
using Avalonia.Metadata;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Media
{
/// <summary>
@ -72,7 +70,7 @@ namespace Avalonia.Media
newChildren.Parent = this;
}
protected override IGeometryImpl? CreateDefiningGeometry()
private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
if (_children.Count > 0)
{

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

@ -68,7 +68,7 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
protected override IGeometryImpl? CreateDefiningGeometry()
private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();

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

@ -213,10 +213,10 @@ internal partial class MediaContext : ICompositorScheduler
}
/// <summary>
/// Executes the <param name="callback">callback</param> in the next iteration of the current UI-thread
/// Executes the <paramref name="callback">callback</paramref> in the next iteration of the current UI-thread
/// render loop / layout pass that.
/// </summary>
/// <param name="callback">Code to execute.</param>
/// <param name="callback"></param>
public void BeginInvokeOnRender(Action callback)
{
if (_invokeOnRenderCallbacks == null)

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

@ -43,7 +43,7 @@ namespace Avalonia.Media
/// </summary>
/// <param name="pathData">The s.</param>
/// <returns></returns>
public static new PathGeometry Parse(string pathData)
public new static PathGeometry Parse(string pathData)
{
var pathGeometry = new PathGeometry();
@ -81,7 +81,7 @@ namespace Avalonia.Media
set { SetValue(FillRuleProperty, value); }
}
protected override IGeometryImpl? CreateDefiningGeometry()
private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var figures = Figures;

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

@ -2,7 +2,7 @@
namespace Avalonia.Media
{
internal class PlatformGeometry : Geometry
internal sealed class PlatformGeometry : Geometry
{
private readonly IGeometryImpl _geometryImpl;
@ -16,7 +16,7 @@ namespace Avalonia.Media
return new PlatformGeometry(_geometryImpl);
}
protected override IGeometryImpl? CreateDefiningGeometry()
private protected override IGeometryImpl? CreateDefiningGeometry()
{
return _geometryImpl;
}

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

@ -74,7 +74,7 @@ namespace Avalonia.Media
return new PolylineGeometry(Points, IsFilled);
}
protected override IGeometryImpl? CreateDefiningGeometry()
private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
var geometry = factory.CreateStreamGeometry();

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

@ -47,7 +47,7 @@ namespace Avalonia.Media
/// <inheritdoc/>
public override Geometry Clone() => new RectangleGeometry(Rect);
protected override IGeometryImpl? CreateDefiningGeometry()
private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();

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

@ -31,7 +31,7 @@ namespace Avalonia.Media
/// </summary>
/// <param name="s">The string.</param>
/// <returns>A <see cref="StreamGeometry"/>.</returns>
public static new StreamGeometry Parse(string s)
public new static StreamGeometry Parse(string s)
{
var streamGeometry = new StreamGeometry();
@ -62,7 +62,7 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
protected override IGeometryImpl? CreateDefiningGeometry()
private protected override IGeometryImpl? CreateDefiningGeometry()
{
if (_impl == null)
{

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

@ -161,7 +161,7 @@ namespace Avalonia.Platform
void PopGeometryClip();
/// <summary>
/// Get Fetaure from type
/// <summary>
/// Attempts to get an optional feature from the drawing context implementation
/// </summary>

13
src/Avalonia.Base/Point.cs

@ -164,6 +164,19 @@ namespace Avalonia
/// <returns>The resulting point.</returns>
public static Point operator *(Point point, Matrix matrix) => matrix.Transform(point);
/// <summary>
/// Computes the Euclidean distance between the two given points.
/// </summary>
/// <param name="value1">The first point.</param>
/// <param name="value2">The second point.</param>
/// <returns>The Euclidean distance.</returns>
public static double Distance(Point value1, Point value2)
{
double distanceSquared = ((value2.X - value1.X) * (value2.X - value1.X)) +
((value2.Y - value1.Y) * (value2.Y - value1.Y));
return Math.Sqrt(distanceSquared);
}
/// <summary>
/// Parses a <see cref="Point"/> string.
/// </summary>

4
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@ -216,7 +216,7 @@ namespace Avalonia.PropertyStore
valueChanged = !EqualityComparer<T>.Default.Equals(Value, v);
Value = v;
Priority = priority;
if (_uncommon is not null)
if (!isCoercedDefaultValue && _uncommon is not null)
_uncommon._uncoercedValue = value;
}
@ -225,7 +225,7 @@ namespace Avalonia.PropertyStore
baseValueChanged = !EqualityComparer<T>.Default.Equals(_baseValue, v);
_baseValue = v;
BasePriority = priority;
if (_uncommon is not null)
if (!isCoercedDefaultValue && _uncommon is not null)
_uncommon._uncoercedBaseValue = value;
}

1
src/Avalonia.Base/Rendering/RenderLoop.cs

@ -42,7 +42,6 @@ namespace Avalonia.Rendering
/// Initializes a new instance of the <see cref="RenderLoop"/> class.
/// </summary>
/// <param name="timer">The render timer.</param>
/// <param name="dispatcher">The UI thread dispatcher.</param>
public RenderLoop(IRenderTimer timer)
{
_timer = timer;

5
src/Avalonia.Base/Threading/Dispatcher.Invoke.cs

@ -581,11 +581,6 @@ public partial class Dispatcher
/// <param name="action">
/// A Func&lt;Task&lt;TResult&gt;&gt; delegate to invoke through the dispatcher.
/// </param>
/// <param name="priority">
/// The priority that determines in what order the specified
/// callback is invoked relative to the other pending operations
/// in the Dispatcher.
/// </param>
/// <returns>
/// An task that completes after the task returned from callback finishes
/// </returns>

14
src/Avalonia.Base/Vector.cs

@ -359,7 +359,7 @@ namespace Avalonia
internal Vector(Vector2 v) : this(v.X, v.Y)
{
}
/// <summary>
@ -379,21 +379,27 @@ namespace Avalonia
/// </summary>
public static Vector Max(Vector left, Vector right) =>
new(Math.Max(left.X, right.X), Math.Max(left.Y, right.Y));
/// <summary>
/// Returns a vector whose elements are the minimum of each of the pairs of elements in two specified vectors
/// </summary>
public static Vector Min(Vector left, Vector right) =>
new(Math.Min(left.X, right.X), Math.Min(left.Y, right.Y));
/// <summary>
/// Computes the Euclidean distance between the two given points.
/// </summary>
/// <param name="value1">The first point.</param>
/// <param name="value2">The second point.</param>
/// <returns>The Euclidean distance.</returns>
public static double Distance(Vector value1, Vector value2) => Math.Sqrt(DistanceSquared(value1, value2));
/// <summary>
/// Returns the Euclidean distance squared between two specified points
/// </summary>
/// <param name="value1">The first point.</param>
/// <param name="value2">The second point.</param>
/// <returns>The Euclidean distance squared.</returns>
public static double DistanceSquared(Vector value1, Vector value2)
{
var difference = value1 - value2;

14
src/Avalonia.Controls.DataGrid/DataGridColumn.cs

@ -38,7 +38,7 @@ namespace Avalonia.Controls
private ICellEditBinding _editBinding;
private IBinding _clipboardContentBinding;
private ControlTheme _cellTheme;
private readonly Classes _cellStyleClasses = new Classes();
private Classes _cellStyleClasses;
/// <summary>
/// Initializes a new instance of the <see cref="T:Avalonia.Controls.DataGridColumn" /> class.
@ -393,17 +393,7 @@ namespace Avalonia.Controls
}
}
public Classes CellStyleClasses
{
get => _cellStyleClasses;
set
{
if(_cellStyleClasses != value)
{
_cellStyleClasses.Replace(value);
}
}
}
public Classes CellStyleClasses => _cellStyleClasses ??= new();
/// <summary>
/// Backing field for CellTheme property.

7
src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml

@ -42,9 +42,10 @@
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<x:Double x:Key="ListAccentLowOpacity">0.6</x:Double>
<x:Double x:Key="ListAccentMediumOpacity">0.8</x:Double>
<x:Double x:Key="DataGridSortIconMinWidth">32</x:Double>
<StreamGeometry x:Key="DataGridSortIconDescendingPath">M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z</StreamGeometry>
<StreamGeometry x:Key="DataGridSortIconAscendingPath">M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z</StreamGeometry>
@ -174,7 +175,7 @@
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" MinWidth="32" />
<ColumnDefinition Width="Auto" MinWidth="{DynamicResource DataGridSortIconMinWidth}" />
</Grid.ColumnDefinitions>
<ContentPresenter Content="{TemplateBinding Content}"
@ -512,7 +513,7 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid ColumnDefinitions="Auto,*,Auto"
<Grid ColumnDefinitions="Auto,*,Auto"
RowDefinitions="Auto,*,Auto,Auto"
ClipToBounds="True">
<DataGridColumnHeader Name="PART_TopLeftCornerHeader"

2
src/Avalonia.Controls/AppBuilder.cs

@ -151,7 +151,7 @@ namespace Avalonia
}
throw new InvalidOperationException(
$"Unable to create AppBuilder from type {entryPointType.Name}." +
$"Unable to create AppBuilder from type \"{entryPointType.FullName}\". " +
$"Input type either needs to have BuildAvaloniaApp -> AppBuilder method or inherit Application type.");
}

37
src/Avalonia.Controls/Control.cs

@ -316,7 +316,8 @@ namespace Avalonia.Controls
((ILogical)this).IsAttachedToLogicalTree)
{
_isLoaded = true;
OnLoaded();
OnLoaded(new RoutedEventArgs(LoadedEvent, this));
}
}
@ -333,28 +334,36 @@ namespace Avalonia.Controls
_loadedQueue.Remove(this);
_isLoaded = false;
OnUnloaded();
OnUnloaded(new RoutedEventArgs(UnloadedEvent, this));
}
}
/// <summary>
/// Invoked just before the <see cref="Loaded"/> event.
/// </summary>
protected virtual void OnLoaded()
/// <param name="e">The event args.</param>
protected virtual void OnLoaded(RoutedEventArgs e)
{
var eventArgs = new RoutedEventArgs(LoadedEvent);
eventArgs.Source = null;
RaiseEvent(eventArgs);
RaiseEvent(e);
}
/// <summary>
/// Invoked just before the <see cref="Unloaded"/> event.
/// </summary>
protected virtual void OnUnloaded()
/// <param name="e">The event args.</param>
protected virtual void OnUnloaded(RoutedEventArgs e)
{
var eventArgs = new RoutedEventArgs(UnloadedEvent);
eventArgs.Source = null;
RaiseEvent(eventArgs);
RaiseEvent(e);
}
/// <summary>
/// Invoked just before the <see cref="SizeChanged"/> event.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnSizeChanged(SizeChangedEventArgs e)
{
RaiseEvent(e);
}
/// <inheritdoc/>
@ -435,6 +444,10 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Returns a new, type-specific <see cref="AutomationPeer"/> implementation for the control.
/// </summary>
/// <returns>The type-specific <see cref="AutomationPeer"/> implementation.</returns>
protected virtual AutomationPeer OnCreateAutomationPeer()
{
return new NoneAutomationPeer(this);
@ -459,6 +472,7 @@ namespace Avalonia.Controls
return _automationPeer;
}
/// <inheritdoc/>
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
@ -473,6 +487,7 @@ namespace Avalonia.Controls
}
}
/// <inheritdoc/>
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
@ -531,7 +546,7 @@ namespace Avalonia.Controls
previousSize: new Size(oldValue.Width, oldValue.Height),
newSize: new Size(newValue.Width, newValue.Height));
RaiseEvent(sizeChangedEventArgs);
OnSizeChanged(sizeChangedEventArgs);
}
}
}

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

@ -7,7 +7,7 @@ using Avalonia.Input.GestureRecognizers;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using System.Linq;
using Avalonia.Interactivity;
using Avalonia.Layout;
namespace Avalonia.Controls.Presenters
{
@ -473,7 +473,15 @@ namespace Avalonia.Controls.Presenters
}
Viewport = finalSize;
Extent = Child!.Bounds.Size.Inflate(Child.Margin);
var childMargin = Child!.Margin;
if (Child.UseLayoutRounding)
{
var scale = LayoutHelper.GetLayoutScale(Child);
childMargin = LayoutHelper.RoundLayoutThickness(childMargin, scale, scale);
}
Extent = Child!.Bounds.Size.Inflate(childMargin);
_isAnchorElementDirty = true;
return finalSize;

6
src/Avalonia.Controls/ToggleSwitch.cs

@ -3,6 +3,7 @@ using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
namespace Avalonia.Controls
@ -200,9 +201,10 @@ namespace Avalonia.Controls
}
}
protected override void OnLoaded()
/// <inheritdoc/>
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded();
base.OnLoaded(e);
UpdateKnobTransitions();
}

2
src/Avalonia.Diagnostics/Diagnostics/Views/PropertyValueEditorView.cs

@ -265,7 +265,7 @@ namespace Avalonia.Diagnostics.Views
static bool IsValidNumeric(Type? type)
{
if (type == null)
if (type == null || type.IsEnum == true)
{
return false;
}

145
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml

@ -6,106 +6,71 @@
MinWidth="430"
MinHeight="475"
Title="About Avalonia"
Background="Purple"
FontFamily="/Assets/Roboto-Light.ttf#Roboto"
x:Class="Avalonia.Dialogs.AboutAvaloniaDialog"
x:DataType="dialogs:AboutAvaloniaDialog">
<Window.Styles>
<Style>
<Style.Resources>
<DrawingGroup x:Key="AvaloniaLogo">
<GeometryDrawing Geometry="m 150.66581 0.66454769 c -54.77764 0 -101.0652 38.86360031 -112.62109 90.33008031 a 26.1 26.1 0 0 1 18.92187 25.070312 26.1 26.1 0 0 1 -18.91992 25.08202 c 11.56024 51.46073 57.8456 90.31837 112.61914 90.31837 63.37832 0 115.40039 -52.02207 115.40039 -115.40039 0 -63.378322 -52.02207 -115.40039231 -115.40039 -115.40039231 z m 0 60.00000031 c 30.95192 0 55.40039 24.44847 55.40039 55.400392 0 30.9519 -24.44847 55.40039 -55.40039 55.40039 -30.95191 0 -55.40039 -24.44848 -55.40039 -55.40039 0 -30.951922 24.44848 -55.400392 55.40039 -55.400392 z">
<GeometryDrawing.Brush>
<LinearGradientBrush StartPoint="272,411" EndPoint="435,248">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#B0B0B0" Offset="0" />
<GradientStop Color="#FFFFFF" Offset="0.6784" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Brush="#B0B0B0">
<GeometryDrawing.Geometry>
<EllipseGeometry Rect="9.6,95.8,40.6,40.6" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="White">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="206.06355, 114.56503,60,116.2" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF8B44AC" Geometry="M215 246.5h1.5a30 30 0 0 0 29.8-26.5l.2-100a123.2 123.2 0 1 0-125.4 126.5H215Z"/>
<GeometryDrawing Brush="#FFF9F9FB" Geometry="M123.7 42a81.3 81.3 0 0 0-79.3 63.5 18.4 18.4 0 0 1 0 35.4 81.3 81.3 0 0 0 118.2 53.6v9.4H205v-80.7A81.2 81.2 0 0 0 123.7 42Zm-39 81.2a39 39 0 1 1 77.9 0 39 39 0 0 1-77.8 0Z"/>
<GeometryDrawing Brush="#FFF9F9FB" Geometry="M52 123.2a13 13 0 1 1-26 0 13 13 0 0 1 26 0Z"/>
</DrawingGroup>
</Style.Resources>
</Style>
<Style Selector="Rectangle.Abstract">
<Setter Property="Fill" Value="White" />
<Setter Property="Width" Value="750" />
<Setter Property="Height" Value="700" />
</Style>
<Style Selector="Button.Hyperlink">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Margin" Value="-5"/>
<Setter Property="Foreground" Value="#419df2" />
<Setter Property="Command" Value="{Binding OpenBrowser}" />
<Setter Property="Content" Value="{Binding $self.CommandParameter}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Cursor" Value="Hand" />
</Style>
</Window.Styles>
<Grid Background="#4A255D">
<Canvas>
<Rectangle Classes="Abstract" Canvas.Top="90" Opacity="0.132">
<Rectangle.RenderTransform>
<RotateTransform Angle="-2" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Classes="Abstract" Canvas.Top="95" Opacity="0.3">
<Rectangle.RenderTransform>
<RotateTransform Angle="-4" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Classes="Abstract" Canvas.Top="100" Opacity="0.3">
<Rectangle.RenderTransform>
<RotateTransform Angle="-8" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Classes="Abstract" Canvas.Top="105" Opacity="0.7">
<Rectangle.RenderTransform>
<RotateTransform Angle="-12" />
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="18">
<Border Height="70" Width="70">
<Image>
<Image.Source>
<DrawingImage Drawing="{DynamicResource AvaloniaLogo}"/>
</Image.Source>
</Image>
</Border>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10,-10,0,0">
<TextBlock Text="{Binding Version, StringFormat=Avalonia {0}}" FontSize="40" Foreground="White" />
<TextBlock Text="Development Build" IsVisible="{Binding IsDevelopmentBuild}" Margin="0,-10,0,0" FontSize="15" Foreground="White" />
</StackPanel>
</StackPanel>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Spacing="20" Margin="10 60 10 0">
<TextBlock Text="This product is built with the Avalonia cross-platform UI Framework. &#x0A;&#x0A;Avalonia is made possible by the generous support of it's contributors and community." TextWrapping="Wrap" TextAlignment="Center" HorizontalAlignment="Center" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
<TextBlock Text="Main source repository | " />
<Button Classes="Hyperlink" CommandParameter="https://github.com/AvaloniaUI/Avalonia/" />
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
<TextBlock Text="Documentation and Information | " />
<Button Classes="Hyperlink" CommandParameter="https://avaloniaui.net/" />
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
<TextBlock Text="Chat Room | " />
<Button Classes="Hyperlink" CommandParameter="https://t.me/Avalonia" />
</StackPanel>
</StackPanel>
<StackPanel VerticalAlignment="Bottom" Margin="10">
<TextBlock Text="© 2022 The Avalonia Project" TextWrapping="Wrap" HorizontalAlignment="Center" />
</StackPanel>
<Grid RowDefinitions="Auto, *" Margin="35,35,35,15">
<!-- Header -->
<Grid RowDefinitions="*, Auto, Auto">
<Image Width="130"
Margin="0, 0,0, 20"
Grid.Row="0">
<Image.Source>
<DrawingImage Drawing="{DynamicResource AvaloniaLogo}"/>
</Image.Source>
</Image>
<TextBlock Text="Avalonia"
FontWeight="Bold"
HorizontalAlignment="Center"
FontSize="32"
Grid.Row="1"/>
<TextBlock Text="{Binding Version}"
FontWeight="Regular"
HorizontalAlignment="Center"
FontSize="14"
Grid.Row="2"/>
</Grid>
<Grid RowDefinitions="Auto, Auto, *, Auto" Grid.Row="1" Margin="0, 30,0,0">
<TextBlock Grid.Row="0"
Text="This product is built with Avalonia, a cross-platform UI framework."
TextWrapping="Wrap"/>
<TextBlock Grid.Row="1"
Text="Avalonia is made possible by the generous support of its contributors and community."
TextWrapping="Wrap"
Margin="0,15,0,0"/>
<Button Grid.Row="2" Background="#8B44AC"
Foreground="White"
VerticalAlignment="Center"
Padding="12"
Content="Learn more about Avalonia"
Click="Button_OnClick"
HorizontalAlignment="Center"/>
<TextBlock Grid.Row="4" VerticalAlignment="Bottom"
Text="{Binding Copyright}"
HorizontalAlignment="Center"/>
</Grid>
</Grid>
</Window>

46
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs

@ -3,6 +3,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
namespace Avalonia.Dialogs
@ -11,35 +12,19 @@ namespace Avalonia.Dialogs
{
private static readonly Version s_version = typeof(AboutAvaloniaDialog).Assembly.GetName().Version;
public static string Version { get; } = s_version.ToString(2);
public static string Version { get; } = $@"v{s_version.ToString(2)}";
public static bool IsDevelopmentBuild { get; } = s_version.Revision == 999;
public static string Copyright { get; } = $"© {DateTime.Now.Year} The Avalonia Project";
public AboutAvaloniaDialog()
{
AvaloniaXamlLoader.Load(this);
DataContext = this;
}
public static void OpenBrowser(string url)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
// If no associated application/json MimeType is found xdg-open opens retrun error
// but it tries to open it anyway using the console editor (nano, vim, other..)
ShellExec($"xdg-open {url}", waitForExit: false);
}
else
{
using Process process = Process.Start(new ProcessStartInfo
{
FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? url : "open",
Arguments = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"{url}" : "",
CreateNoWindow = true,
UseShellExecute = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
});
}
}
private static void ShellExec(string cmd, bool waitForExit = true)
{
@ -64,5 +49,26 @@ namespace Avalonia.Dialogs
}
}
}
private void Button_OnClick(object sender, RoutedEventArgs e)
{
var url = "https://www.avaloniaui.net/";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
// If no associated application/json MimeType is found xdg-open opens retrun error
// but it tries to open it anyway using the console editor (nano, vim, other..)
ShellExec($"xdg-open {url}", waitForExit: false);
}
else
{
using Process process = Process.Start(new ProcessStartInfo
{
FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? url : "open",
Arguments = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"{url}" : "",
CreateNoWindow = true,
UseShellExecute = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
});
}
}
}
}

6
src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs

@ -18,9 +18,9 @@ namespace Avalonia.Dialogs.Internal
return;
}
_patterns = filter.Patterns?
.Select(e => new Regex(Regex.Escape(e).Replace(@"\*", ".*").Replace(@"\?", "."), RegexOptions.Singleline | RegexOptions.IgnoreCase))
.ToArray();
_patterns = filter.Patterns?
.Select(e => new Regex("^" + Regex.Escape(e).Replace("\\*", ".*") + "$", RegexOptions.Singleline | RegexOptions.IgnoreCase))
.ToArray();
}
public bool Match(string filename)

24
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -123,20 +123,18 @@ namespace Avalonia.Native
hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers));
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(hotkeys);
if (_options.UseGpu)
// TODO: add software and metal support via RenderingMode options param
try
{
_platformGl = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay());
AvaloniaLocator.CurrentMutable
.Bind<IPlatformGraphics>().ToConstant(_platformGl);
}
catch (Exception)
{
try
{
_platformGl = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay());
AvaloniaLocator.CurrentMutable
.Bind<IPlatformGraphics>().ToConstant(_platformGl);
}
catch (Exception)
{
// ignored
}
// ignored
}
Compositor = new Compositor(_platformGl, true);

6
src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Native;
@ -30,11 +31,6 @@ namespace Avalonia
/// </summary>
public class AvaloniaNativePlatformOptions
{
/// <summary>
/// Determines whether to use GPU for rendering in your project. The default value is true.
/// </summary>
public bool UseGpu { get; set; } = true;
/// <summary>
/// Embeds popups to the window when set to true. The default value is false.
/// </summary>

2
src/Avalonia.Native/PopupImpl.cs

@ -15,7 +15,7 @@ namespace Avalonia.Native
public PopupImpl(IAvaloniaNativeFactory factory,
AvaloniaNativePlatformOptions opts,
AvaloniaNativeGlPlatformGraphics glFeature,
IWindowBaseImpl parent) : base(factory, opts, glFeature)
IWindowBaseImpl parent) : base(factory, glFeature)
{
_opts = opts;
_glFeature = glFeature;

2
src/Avalonia.Native/WindowImpl.cs

@ -24,7 +24,7 @@ namespace Avalonia.Native
private bool _canResize = true;
internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
AvaloniaNativeGlPlatformGraphics glFeature) : base(factory, opts, glFeature)
AvaloniaNativeGlPlatformGraphics glFeature) : base(factory, glFeature)
{
_opts = opts;
_glFeature = glFeature;

5
src/Avalonia.Native/WindowImplBase.cs

@ -67,11 +67,10 @@ namespace Avalonia.Native
private PlatformBehaviorInhibition _platformBehaviorInhibition;
private WindowTransparencyLevel _transparencyLevel = WindowTransparencyLevel.None;
internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
AvaloniaNativeGlPlatformGraphics glFeature)
internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativeGlPlatformGraphics glFeature)
{
_factory = factory;
_gpu = opts.UseGpu && glFeature != null;
_gpu = glFeature != null;
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
_mouse = new MouseDevice();

7
src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs

@ -16,13 +16,6 @@ namespace Avalonia.OpenGL.Egl
_display = display;
}
public static void TryInitialize()
{
var feature = TryCreate();
if (feature != null)
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(feature);
}
public static EglPlatformGraphics? TryCreate() => TryCreate(() => new EglDisplay(new EglDisplayCreationOptions
{
Egl = new EglInterface(),

18
src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs

@ -10,12 +10,18 @@ internal class ColorPaletteResourcesCollection : AvaloniaDictionary<ThemeVariant
public ColorPaletteResourcesCollection() : base(2)
{
this.ForEachItem(
(_, x) =>
(key, x) =>
{
if (Owner is not null)
{
x.PropertyChanged += Palette_PropertyChanged;
}
if (key != ThemeVariant.Dark && key != ThemeVariant.Light)
{
throw new InvalidOperationException(
$"{nameof(FluentTheme)}.{nameof(FluentTheme.Palettes)} only supports Light and Dark variants.");
}
},
(_, x) =>
{
@ -30,9 +36,13 @@ internal class ColorPaletteResourcesCollection : AvaloniaDictionary<ThemeVariant
public bool HasResources => Count > 0;
public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
{
theme ??= ThemeVariant.Default;
if (base.TryGetValue(theme, out var paletteResources)
&& paletteResources.TryGetResource(key, theme, out value))
if (theme == null || theme == ThemeVariant.Default)
{
theme = ThemeVariant.Light;
}
if (base.TryGetValue(theme, out var themePaletteResources)
&& themePaletteResources.TryGetResource(key, theme, out value))
{
return true;
}

47
src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml

@ -1,22 +1,25 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:ClassModifier="internal">
<Style Selector="TextBlock" >
<Setter Property="FontSize" Value="14" />
</Style>
<Styles.Resources>
<x:Double x:Key="ControlContentThemeFontSize">14</x:Double>
<x:Double x:Key="ContentControlFontSize">14</x:Double>
<x:Double x:Key="TextControlThemeMinHeight">24</x:Double>
<Thickness x:Key="TextControlThemePadding">2,2,6,1</Thickness>
<x:Double x:Key="ListViewItemMinHeight">32</x:Double>
<x:Double x:Key="TreeViewItemMinHeight">24</x:Double>
<Thickness x:Key="TimePickerHostPadding">0,1,0,2</Thickness>
<Thickness x:Key="DatePickerHostPadding">0,1,0,2</Thickness>
<Thickness x:Key="DatePickerHostMonthPadding">9,0,0,1</Thickness>
<Thickness x:Key="ComboBoxEditableTextPadding">10,0,30,0</Thickness>
<x:Double x:Key="ComboBoxMinHeight">24</x:Double>
<Thickness x:Key="ComboBoxPadding">12,1,0,3</Thickness>
<x:Double x:Key="NavigationViewItemOnLeftMinHeight">32</x:Double>
</Styles.Resources>
</Styles>
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:ClassModifier="internal">
<x:Double x:Key="ControlContentThemeFontSize">14</x:Double>
<x:Double x:Key="ContentControlFontSize">14</x:Double>
<x:Double x:Key="TextControlThemeMinHeight">24</x:Double>
<x:Double x:Key="TextControlThemeMinWidth">32</x:Double>
<Thickness x:Key="TextControlThemePadding">4, 2</Thickness>
<Thickness x:Key="ListBoxItemPadding">4,2</Thickness>
<x:Double x:Key="ListViewItemMinHeight">24</x:Double>
<Thickness x:Key="ComboBoxItemThemePadding">11,5,11,7</Thickness>
<Thickness x:Key="MenuFlyoutItemThemePaddingNarrow">4 4 8 4</Thickness>
<Thickness x:Key="MenuBarItemPadding">5, 2</Thickness>
<x:Double x:Key="TreeViewItemMinHeight">24</x:Double>
<Thickness x:Key="TimePickerHostPadding">0,1,0,2</Thickness>
<Thickness x:Key="DatePickerHostPadding">0,1,0,2</Thickness>
<Thickness x:Key="DatePickerHostMonthPadding">9,0,0,1</Thickness>
<Thickness x:Key="ComboBoxEditableTextPadding">10,0,30,0</Thickness>
<x:Double x:Key="ComboBoxMinHeight">24</x:Double>
<Thickness x:Key="ComboBoxPadding">12,1,0,3</Thickness>
<x:Double x:Key="NavigationViewItemOnLeftMinHeight">32</x:Double>
<x:Double x:Key="TabItemMinHeight">28</x:Double>
<Thickness x:Key="TabItemHeaderMargin">6, 0</Thickness>
<Thickness x:Key="ButtonPadding">6,4</Thickness>
</ResourceDictionary>

2
src/Avalonia.Themes.Fluent/FluentTheme.xaml

@ -18,7 +18,7 @@
</ResourceDictionary.MergedDictionaries>
<!-- These are not part of MergedDictionaries so we can add or remove them easier -->
<StyleInclude x:Key="CompactStyles" Source="/DensityStyles/Compact.xaml" />
<ResourceInclude x:Key="CompactStyles" Source="/DensityStyles/Compact.xaml" />
</ResourceDictionary>
</Styles.Resources>

35
src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs

@ -16,9 +16,10 @@ namespace Avalonia.Themes.Fluent
/// <summary>
/// Includes the fluent theme in an application.
/// </summary>
public class FluentTheme : Styles
public class FluentTheme : Styles, IResourceNode
{
private readonly Styles _compactStyles;
private readonly ResourceDictionary _compactStyles;
private DensityStyle _densityStyle;
/// <summary>
/// Initializes a new instance of the <see cref="FluentTheme"/> class.
@ -28,9 +29,7 @@ namespace Avalonia.Themes.Fluent
{
AvaloniaXamlLoader.Load(sp, this);
_compactStyles = (Styles)GetAndRemove("CompactStyles");
EnsureCompactStyles();
_compactStyles = (ResourceDictionary)GetAndRemove("CompactStyles");
Palettes = Resources.MergedDictionaries.OfType<ColorPaletteResourcesCollection>().FirstOrDefault()
?? throw new InvalidOperationException("FluentTheme was initialized with missing ColorPaletteResourcesCollection.");
@ -43,17 +42,17 @@ namespace Avalonia.Themes.Fluent
return val;
}
}
public static readonly StyledProperty<DensityStyle> DensityStyleProperty =
AvaloniaProperty.Register<FluentTheme, DensityStyle>(nameof(DensityStyle));
public static readonly DirectProperty<FluentTheme, DensityStyle> DensityStyleProperty = AvaloniaProperty.RegisterDirect<FluentTheme, DensityStyle>(
nameof(DensityStyle), o => o.DensityStyle, (o, v) => o.DensityStyle = v);
/// <summary>
/// Gets or sets the density style of the fluent theme (normal, compact).
/// </summary>
public DensityStyle DensityStyle
{
get => GetValue(DensityStyleProperty);
set => SetValue(DensityStyleProperty, value);
get => _densityStyle;
set => SetAndRaise(DensityStyleProperty, ref _densityStyle, value);
}
public IDictionary<ThemeVariant, ColorPaletteResources> Palettes { get; }
@ -64,20 +63,20 @@ namespace Avalonia.Themes.Fluent
if (change.Property == DensityStyleProperty)
{
EnsureCompactStyles();
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
}
private void EnsureCompactStyles()
bool IResourceNode.TryGetResource(object key, ThemeVariant? theme, out object? value)
{
if (DensityStyle == DensityStyle.Compact)
// DensityStyle dictionary should be checked first
if (_densityStyle == DensityStyle.Compact
&& _compactStyles.TryGetResource(key, theme, out value))
{
Add(_compactStyles);
}
else
{
Remove(_compactStyles);
return true;
}
return base.TryGetResource(key, theme, out value);
}
}
}

3
src/Avalonia.Themes.Simple/Controls/TransitioningContentControl.xaml

@ -13,11 +13,10 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" />
<ContentPresenter Name="PART_TransitionContentPresenter"
<ContentPresenter Name="PART_ContentPresenter2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"

12
src/Avalonia.X11/Glx/GlxPlatformFeature.cs

@ -15,18 +15,6 @@ namespace Avalonia.X11.Glx
IPlatformGraphicsContext IPlatformGraphics.CreateContext() => Display.CreateContext();
public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException();
public static bool TryInitialize(X11Info x11, IList<GlVersion> glProfiles)
{
var feature = TryCreate(x11, glProfiles);
if (feature != null)
{
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(feature);
return true;
}
return false;
}
public static GlxPlatformGraphics TryCreate(X11Info x11, IList<GlVersion> glProfiles)
{

2
src/Avalonia.X11/X11Clipboard.cs

@ -224,7 +224,7 @@ namespace Avalonia.X11
private Task<object> SendDataRequest(IntPtr format)
{
if (_requestedDataTcs == null || _requestedFormatsTcs.Task.IsCompleted)
if (_requestedDataTcs == null || _requestedDataTcs.Task.IsCompleted)
_requestedDataTcs = new TaskCompletionSource<object>();
XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, format, format, _handle, IntPtr.Zero);
return _requestedDataTcs.Task;

83
src/Avalonia.X11/X11Platform.cs

@ -93,17 +93,13 @@ namespace Avalonia.X11
XI2 = xi2;
}
if (options.UseGpu)
var graphics = InitializeGraphics(options, Info);
if (graphics is not null)
{
if (options.UseEGL)
EglPlatformGraphics.TryInitialize();
else
GlxPlatformGraphics.TryInitialize(Info, Options.GlProfiles);
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(graphics);
}
var gl = AvaloniaLocator.Current.GetService<IPlatformGraphics>();
Compositor = new Compositor(gl);
Compositor = new Compositor(graphics);
}
public IntPtr DeferredDisplay { get; set; }
@ -185,25 +181,84 @@ namespace Avalonia.X11
return false;
}
private static IPlatformGraphics InitializeGraphics(X11PlatformOptions opts, X11Info info)
{
if (opts.RenderingMode is null || !opts.RenderingMode.Any())
{
throw new InvalidOperationException($"{nameof(X11PlatformOptions)}.{nameof(X11PlatformOptions.RenderingMode)} must not be empty or null");
}
foreach (var renderingMode in opts.RenderingMode)
{
if (renderingMode == X11RenderingMode.Software)
{
return null;
}
if (renderingMode == X11RenderingMode.Glx)
{
if (GlxPlatformGraphics.TryCreate(info, opts.GlProfiles) is { } glx)
{
return glx;
}
}
if (renderingMode == X11RenderingMode.Egl)
{
if (EglPlatformGraphics.TryCreate() is { } egl)
{
return egl;
}
}
}
throw new InvalidOperationException($"{nameof(X11PlatformOptions)}.{nameof(X11PlatformOptions.RenderingMode)} has a value of \"{string.Join(", ", opts.RenderingMode)}\", but no options were applied.");
}
}
}
namespace Avalonia
{
/// <summary>
/// Platform-specific options which apply to Linux.
/// Represents the rendering mode for platform graphics.
/// </summary>
public class X11PlatformOptions
public enum X11RenderingMode
{
/// <summary>
/// Enables native Linux EGL when set to true. The default value is false.
/// Avalonia is rendered into a framebuffer.
/// </summary>
public bool UseEGL { get; set; }
Software = 1,
/// <summary>
/// Determines whether to use GPU for rendering in your project. The default value is true.
/// Enables Glx rendering.
/// </summary>
public bool UseGpu { get; set; } = true;
Glx = 2,
/// <summary>
/// Enables native Linux EGL rendering.
/// </summary>
Egl = 3
}
/// <summary>
/// Platform-specific options which apply to Linux.
/// </summary>
public class X11PlatformOptions
{
/// <summary>
/// Gets or sets Avalonia rendering modes with fallbacks.
/// The first element in the array has the highest priority.
/// The default value is: <see cref="X11RenderingMode.Glx"/>, <see cref="X11RenderingMode.Software"/>.
/// </summary>
/// <remarks>
/// If application should work on as wide range of devices as possible, at least add <see cref="X11RenderingMode.Software"/> as a fallback value.
/// </remarks>
/// <exception cref="System.InvalidOperationException">Thrown if no values were matched.</exception>
public IReadOnlyList<X11RenderingMode> RenderingMode { get; set; } = new[]
{
X11RenderingMode.Glx, X11RenderingMode.Software
};
/// <summary>
/// Embeds popups to the window when set to true. The default value is false.

4
src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs

@ -28,16 +28,18 @@ namespace Avalonia.Win32.DirectX
_syncLock = syncLock;
}
public static void TryCreateAndRegister()
public static bool TryCreateAndRegister()
{
try
{
TryCreateAndRegisterCore();
return true;
}
catch (Exception ex)
{
Logger.TryGet(LogEventLevel.Error, LogArea)
?.Log(null, "Unable to establish Dxgi: {0}", ex);
return false;
}
}

18
src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs

@ -324,6 +324,11 @@ namespace Avalonia.Win32.Input
if (IsActive)
{
Client.SetPreeditText(null);
if (Client.SupportsSurroundingText && Client.SurroundingText.AnchorOffset != Client.SurroundingText.CursorOffset)
{
KeyPress(Key.Delete);
}
}
IsComposing = true;
@ -393,6 +398,19 @@ namespace Avalonia.Win32.Input
return (int)(ptr.ToInt64() & 0xffffffff);
}
private void KeyPress(Key key)
{
if (_parent?.Input != null)
{
_parent.Input(new RawKeyEventArgs(KeyboardDevice.Instance!, (ulong)DateTime.Now.Ticks, _parent.Owner,
RawKeyEventType.KeyDown, key, RawInputModifiers.None));
_parent.Input(new RawKeyEventArgs(KeyboardDevice.Instance!, (ulong)DateTime.Now.Ticks, _parent.Owner,
RawKeyEventType.KeyUp, key, RawInputModifiers.None));
}
}
~Imm32InputMethod()
{
_caretManager.TryDestroy();

59
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1161,7 +1161,7 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint);
[DllImport("user32.dll", SetLastError = true)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "CreateWindowExW", ExactSpelling = true)]
public static extern IntPtr CreateWindowEx(
int dwExStyle,
uint lpClassName,
@ -1215,16 +1215,16 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern int GetMessageTime();
[DllImport("kernel32.dll")]
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetModuleHandleW", ExactSpelling = true)]
public static extern IntPtr GetModuleHandle(string? lpModuleName);
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(SystemMetric smIndex);
[DllImport("user32.dll", SetLastError = true)]
[DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowLongPtrW", ExactSpelling = true)]
public static extern uint GetWindowLongPtr(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowLong")]
[DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowLongW", ExactSpelling = true)]
public static extern uint GetWindowLong32b(IntPtr hWnd, int nIndex);
public static uint GetWindowLong(IntPtr hWnd, int nIndex)
@ -1239,10 +1239,10 @@ namespace Avalonia.Win32.Interop
}
}
[DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLong")]
[DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLongW", ExactSpelling = true)]
private static extern uint SetWindowLong32b(IntPtr hWnd, int nIndex, uint value);
[DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLongPtr")]
[DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLongPtrW", ExactSpelling = true)]
private static extern IntPtr SetWindowLong64b(IntPtr hWnd, int nIndex, IntPtr value);
public static uint SetWindowLong(IntPtr hWnd, int nIndex, uint value)
@ -1310,7 +1310,7 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool KillTimer(IntPtr hWnd, IntPtr uIDEvent);
[DllImport("user32.dll")]
[DllImport("user32.dll", EntryPoint = "LoadCursorW", ExactSpelling = true)]
public static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName);
[DllImport("user32.dll")]
@ -1319,7 +1319,7 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool DestroyIcon(IntPtr hIcon);
[DllImport("user32.dll")]
[DllImport("user32.dll", EntryPoint = "PeekMessageW", ExactSpelling = true)]
public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
[DllImport("user32")]
@ -1334,7 +1334,7 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool ReleaseCapture();
[DllImport("user32.dll", SetLastError = true)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "RegisterWindowMessageW", ExactSpelling = true)]
public static extern uint RegisterWindowMessage(string lpString);
[DllImport("user32.dll")]
@ -1415,7 +1415,7 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool TranslateMessage(ref MSG lpMsg);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "UnregisterClassW", ExactSpelling = true)]
public static extern bool UnregisterClass(string lpClassName, IntPtr hInstance);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetWindowTextW")]
@ -1439,10 +1439,10 @@ namespace Avalonia.Win32.Interop
[DllImport("shell32", CharSet = CharSet.Auto)]
public static extern int Shell_NotifyIcon(NIM dwMessage, NOTIFYICONDATA lpData);
[DllImport("user32.dll", EntryPoint = "SetClassLongPtr")]
[DllImport("user32.dll", EntryPoint = "SetClassLongPtrW", ExactSpelling = true)]
private static extern IntPtr SetClassLong64(IntPtr hWnd, ClassLongIndex nIndex, IntPtr dwNewLong);
[DllImport("user32.dll", EntryPoint = "SetClassLong")]
[DllImport("user32.dll", EntryPoint = "SetClassLongW", ExactSpelling = true)]
private static extern IntPtr SetClassLong32(IntPtr hWnd, ClassLongIndex nIndex, IntPtr dwNewLong);
public static IntPtr SetClassLong(IntPtr hWnd, ClassLongIndex nIndex, IntPtr dwNewLong)
@ -1463,10 +1463,10 @@ namespace Avalonia.Win32.Interop
return new IntPtr(GetClassLongPtr32(hWnd, nIndex));
}
[DllImport("user32.dll", EntryPoint = "GetClassLong")]
[DllImport("user32.dll", EntryPoint = "GetClassLongW", ExactSpelling = true)]
public static extern uint GetClassLongPtr32(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "GetClassLongPtr")]
[DllImport("user32.dll", EntryPoint = "GetClassLongPtrW", ExactSpelling = true)]
public static extern IntPtr GetClassLongPtr64(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "SetCursor")]
@ -1527,10 +1527,10 @@ namespace Avalonia.Win32.Interop
[DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GlobalFree(IntPtr hMem);
[DllImport("kernel32.dll", SetLastError = true)]
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "LoadLibraryW", ExactSpelling = true)]
public static extern IntPtr LoadLibrary(string fileName);
[DllImport("kernel32.dll", SetLastError = true)]
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "LoadLibraryExW", ExactSpelling = true)]
public static extern IntPtr LoadLibraryEx(string fileName, IntPtr hFile, int flags);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
@ -1645,7 +1645,7 @@ namespace Avalonia.Win32.Interop
[DllImport("opengl32.dll", CharSet = CharSet.Ansi)]
public static extern IntPtr wglGetProcAddress(string name);
[DllImport("kernel32.dll", SetLastError = true)]
[DllImport("kernel32.dll", SetLastError = true, EntryPoint = "CreateFileMappingW", ExactSpelling = true)]
public static extern IntPtr CreateFileMapping(IntPtr hFile,
IntPtr lpFileMappingAttributes,
uint flProtect,
@ -1668,16 +1668,16 @@ namespace Avalonia.Win32.Interop
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
internal static extern void ReleaseStgMedium(ref STGMEDIUM medium);
[DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)]
[DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "GetClipboardFormatNameW", ExactSpelling = true)]
public static extern int GetClipboardFormatName(int format, StringBuilder lpString, int cchMax);
[DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)]
[DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "RegisterClipboardFormatW", ExactSpelling = true)]
public static extern int RegisterClipboardFormat(string format);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GlobalSize(IntPtr hGlobal);
[DllImport("shell32.dll", BestFitMapping = false, CharSet = CharSet.Auto)]
[DllImport("shell32.dll", BestFitMapping = false, CharSet = CharSet.Unicode, EntryPoint = "DragQueryFileW", ExactSpelling = true)]
public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder? lpszFile, int cch);
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)]
@ -1827,8 +1827,21 @@ namespace Avalonia.Win32.Interop
return result;
}
[DllImport("user32.dll")]
internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);
internal static int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data)
{
var user32 = LoadLibrary("user32.dll");
var pfnSetWindowCompositionAttribute = (delegate* unmanaged[Stdcall]<IntPtr, WindowCompositionAttributeData*, int>)GetProcAddress(user32, nameof(SetWindowCompositionAttribute));
if (pfnSetWindowCompositionAttribute == null)
{
// This preserves the same behavior as using the DllImport attribute.
throw new EntryPointNotFoundException("The unsupported SetWindowCompositionAttribute-function has been removed from the operating system.");
}
fixed (WindowCompositionAttributeData* pData = &data)
{
return pfnSetWindowCompositionAttribute(hwnd, pData);
}
}
[Flags]
public enum GCS : uint
@ -1893,7 +1906,7 @@ namespace Avalonia.Win32.Interop
[DllImport("imm32.dll")]
public static extern bool ImmSetCompositionFont(IntPtr hIMC, ref LOGFONT lf);
[DllImport("imm32.dll", SetLastError = false, CharSet = CharSet.Unicode)]
[DllImport("imm32.dll", SetLastError = false, CharSet = CharSet.Unicode, EntryPoint = "ImmGetCompositionStringW", ExactSpelling = true)]
public static extern int ImmGetCompositionString(IntPtr hIMC, GCS dwIndex, [Out, Optional] IntPtr lpBuf, uint dwBufLen);
public static string? ImmGetCompositionString(IntPtr hIMC, GCS dwIndex)

101
src/Windows/Avalonia.Win32/Win32GlManager.cs

@ -1,3 +1,6 @@
using System;
using System.Diagnostics.Tracing;
using System.Linq;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Win32.DirectX;
@ -5,56 +8,88 @@ using Avalonia.Win32.OpenGl;
using Avalonia.Win32.OpenGl.Angle;
using Avalonia.Win32.WinRT.Composition;
namespace Avalonia.Win32
namespace Avalonia.Win32;
static class Win32GlManager
{
static class Win32GlManager
public static IPlatformGraphics? Initialize()
{
public static IPlatformGraphics? Initialize()
{
var gl = InitializeCore();
if (gl is not null)
{
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(gl);
}
var gl = InitializeCore();
return gl;
if (gl is not null)
{
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(gl);
}
return gl;
}
private static IPlatformGraphics? InitializeCore()
private static IPlatformGraphics? InitializeCore()
{
var opts = AvaloniaLocator.Current.GetService<Win32PlatformOptions>() ?? new Win32PlatformOptions();
if (opts.RenderingMode is null || !opts.RenderingMode.Any())
{
throw new InvalidOperationException($"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.RenderingMode)} must not be empty or null");
}
var opts = AvaloniaLocator.Current.GetService<Win32PlatformOptions>() ?? new Win32PlatformOptions();
if (opts.UseWgl)
foreach (var renderingMode in opts.RenderingMode)
{
if (renderingMode == Win32RenderingMode.Software)
{
var wgl = WglPlatformOpenGlInterface.TryCreate();
return wgl;
return null;
}
if (opts.AllowEglInitialization ?? Win32Platform.WindowsVersion > PlatformConstants.Windows7)
if (renderingMode == Win32RenderingMode.AngleEgl)
{
var egl = AngleWin32PlatformGraphics.TryCreate(AvaloniaLocator.Current.GetService<AngleOptions>() ??
new());
var egl = AngleWin32PlatformGraphics.TryCreate(AvaloniaLocator.Current.GetService<AngleOptions>() ?? new());
if (egl != null && egl.PlatformApi == AngleOptions.PlatformApi.DirectX11)
{
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphicsOpenGlContextFactory>()
.ToConstant(egl);
if (opts.UseWindowsUIComposition)
{
WinUiCompositorConnection.TryCreateAndRegister();
}
else if (opts.UseLowLatencyDxgiSwapChain)
{
DxgiConnection.TryCreateAndRegister();
}
TryRegisterComposition(opts);
return egl;
}
}
if (renderingMode == Win32RenderingMode.Wgl)
{
if (WglPlatformOpenGlInterface.TryCreate() is { } wgl)
{
return wgl;
}
}
}
throw new InvalidOperationException($"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.RenderingMode)} has a value of \"{string.Join(", ", opts.RenderingMode)}\", but no options were applied.");
}
private static void TryRegisterComposition(Win32PlatformOptions opts)
{
if (opts.CompositionMode is null || !opts.CompositionMode.Any())
{
throw new InvalidOperationException($"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.CompositionMode)} must not be empty or null");
}
foreach (var compositionMode in opts.CompositionMode)
{
if (compositionMode == Win32CompositionMode.RedirectionSurface)
{
return;
}
return egl;
if (compositionMode == Win32CompositionMode.WinUIComposition
&& WinUiCompositorConnection.IsSupported()
&& WinUiCompositorConnection.TryCreateAndRegister())
{
return;
}
return null;
if (compositionMode == Win32CompositionMode.LowLatencyDxgiSwapChain
&& DxgiConnection.TryCreateAndRegister())
{
return;
}
}
throw new InvalidOperationException($"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.CompositionMode)} has a value of \"{string.Join(", ", opts.CompositionMode)}\", but no options were applied.");
}
}

95
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Avalonia.Reactive;
using System.Runtime.InteropServices;
using System.Threading;
@ -10,7 +10,6 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
@ -31,78 +30,6 @@ namespace Avalonia
"Win32");
}
}
/// <summary>
/// Platform-specific options which apply to Windows.
/// </summary>
public class Win32PlatformOptions
{
/// <summary>
/// Enables ANGLE for Windows. For every Windows version that is above Windows 7, the default is true otherwise it's false.
/// </summary>
/// <remarks>
/// GPU rendering will not be enabled if this is set to false.
/// </remarks>
public bool? AllowEglInitialization { get; set; }
/// <summary>
/// Embeds popups to the window when set to true. The default value is false.
/// </summary>
public bool OverlayPopups { get; set; }
/// <summary>
/// Avalonia would try to use native Widows OpenGL when set to true. The default value is false.
/// </summary>
public bool UseWgl { get; set; }
public IList<GlVersion> WglProfiles { get; set; } = new List<GlVersion>
{
new GlVersion(GlProfileType.OpenGL, 4, 0),
new GlVersion(GlProfileType.OpenGL, 3, 2),
};
/// <summary>
/// Render Avalonia to a Texture inside the Windows.UI.Composition tree.
/// This setting is true by default.
/// </summary>
/// <remarks>
/// Supported on Windows 10 build 16299 and above. Ignored on other versions.
/// This is recommended if you need to use AcrylicBlur or acrylic in your applications.
/// </remarks>
public bool UseWindowsUIComposition { get; set; } = true;
/// <summary>
/// When <see cref="UseWindowsUIComposition"/> enabled, create rounded corner blur brushes
/// If set to null the brushes will be created using default settings (sharp corners)
/// This can be useful when you need a rounded-corner blurred Windows 10 app, or borderless Windows 11 app
/// </summary>
public float? CompositionBackdropCornerRadius { get; set; }
/// <summary>
/// When <see cref="UseLowLatencyDxgiSwapChain"/> is active, renders Avalonia through a low-latency Dxgi Swapchain.
/// Requires Feature Level 11_3 to be active, Windows 8.1+ Any Subversion.
/// This is only recommended if low input latency is desirable, and there is no need for the transparency
/// and stylings / blurrings offered by <see cref="UseWindowsUIComposition"/><br/>
/// This is mutually exclusive with
/// <see cref="UseWindowsUIComposition"/> which if active will override this setting.
/// This setting is false by default.
/// </summary>
public bool UseLowLatencyDxgiSwapChain { get; set; }
/// <summary>
/// Render directly on the UI thread instead of using a dedicated render thread.
/// Only applicable if both <see cref="UseWindowsUIComposition"/> and <see cref="UseLowLatencyDxgiSwapChain"/>
/// are false.
/// This setting is only recommended for interop with systems that must render on the UI thread, such as WPF.
/// This setting is false by default.
/// </summary>
public bool ShouldRenderOnUIThread { get; set; }
/// <summary>
/// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu
/// </summary>
public IPlatformGraphics? CustomPlatformGraphics { get; set; }
}
}
namespace Avalonia.Win32
@ -173,9 +100,23 @@ namespace Avalonia.Win32
.Bind<NonPumpingLockHelper.IHelperImpl>().ToConstant(NonPumpingWaitHelperImpl.Instance)
.Bind<IMountedVolumeInfoProvider>().ToConstant(new WindowsMountedVolumeInfoProvider())
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(s_instance);
var platformGraphics = options.CustomPlatformGraphics
?? Win32GlManager.Initialize();
IPlatformGraphics? platformGraphics;
if (options.CustomPlatformGraphics is not null)
{
if (options.CompositionMode?.Contains(Win32CompositionMode.RedirectionSurface) == false)
{
throw new InvalidOperationException(
$"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.CustomPlatformGraphics)} is only " +
$"compatible with {nameof(Win32CompositionMode)}.{nameof(Win32CompositionMode.RedirectionSurface)}");
}
platformGraphics = options.CustomPlatformGraphics;
}
else
{
platformGraphics = Win32GlManager.Initialize();
}
if (OleContext.Current != null)
AvaloniaLocator.CurrentMutable.Bind<IPlatformDragSource>().ToSingleton<DragSource>();

140
src/Windows/Avalonia.Win32/Win32PlatformOptions.cs

@ -0,0 +1,140 @@
using System.Collections.Generic;
using Avalonia.OpenGL;
using Avalonia.Platform;
namespace Avalonia;
/// <summary>
/// Represents the rendering mode for platform graphics.
/// </summary>
public enum Win32RenderingMode
{
/// <summary>
/// Avalonia is rendered into a framebuffer.
/// </summary>
Software = 1,
/// <summary>
/// Enables ANGLE EGL for Windows with GPU rendering.
/// </summary>
AngleEgl = 2,
/// <summary>
/// Avalonia would try to use native Widows OpenGL with GPU rendering.
/// </summary>
Wgl = 3
}
/// <summary>
/// Represents the Win32 window composition mode.
/// </summary>
public enum Win32CompositionMode
{
/// <summary>
/// Render Avalonia to a texture inside the Windows.UI.Composition tree.
/// </summary>
/// <remarks>
/// Supported on Windows 10 build 17134 and above. Ignored on other versions.
/// This is recommended option, as it allows window acrylic effects and high refresh rate rendering.<br/>
/// Can only be applied with <see cref="Win32PlatformOptions.RenderingMode"/>=<see cref="Win32RenderingMode.AngleEgl"/>.
/// </remarks>
WinUIComposition = 1,
// /// <summary>
// /// Render Avalonia to a texture inside the DirectComposition tree.
// /// </summary>
// /// <remarks>
// /// Supported on Windows 8 and above. Ignored on other versions.<br/>
// /// Can only be applied with <see cref="Win32PlatformOptions.RenderingMode"/>=<see cref="Win32RenderingMode.AngleEgl"/>.
// /// </remarks>
// DirectComposition = 2,
/// <summary>
/// When <see cref="LowLatencyDxgiSwapChain"/> is active, renders Avalonia through a low-latency Dxgi Swapchain.
/// </summary>
/// <remarks>
/// Requires Feature Level 11_3 to be active, Windows 8.1+ Any Subversion.
/// This is only recommended if low input latency is desirable, and there is no need for the transparency
/// and styling / blurring offered by <see cref="WinUIComposition"/>.<br/>
/// Can only be applied with <see cref="Win32PlatformOptions.RenderingMode"/>=<see cref="Win32RenderingMode.AngleEgl"/>.
/// </remarks>
LowLatencyDxgiSwapChain = 3,
/// <summary>
/// The window renders to a redirection surface.
/// </summary>
/// <remarks>
/// This option is kept only for compatibility with older systems. Some Avalonia features might not work.
/// </remarks>
RedirectionSurface,
}
/// <summary>
/// Platform-specific options which apply to Windows.
/// </summary>
public class Win32PlatformOptions
{
/// <summary>
/// Embeds popups to the window when set to true. The default value is false.
/// </summary>
public bool OverlayPopups { get; set; }
/// <summary>
/// Gets or sets Avalonia rendering modes with fallbacks.
/// The first element in the array has the highest priority.
/// The default value is: <see cref="Win32RenderingMode.AngleEgl"/>, <see cref="Win32RenderingMode.Software"/>.
/// </summary>
/// <remarks>
/// If application should work on as wide range of devices as possible, at least add <see cref="Win32RenderingMode.Software"/> as a fallback value.
/// </remarks>
/// <exception cref="System.InvalidOperationException">Thrown if no values were matched.</exception>
public IReadOnlyList<Win32RenderingMode> RenderingMode { get; set; } = new[]
{
Win32RenderingMode.AngleEgl, Win32RenderingMode.Software
};
/// <summary>
/// Gets or sets Avalonia composition modes with fallbacks.
/// The first element in the array has the highest priority.
/// The default value is: <see cref="Win32CompositionMode.WinUIComposition"/>, <see cref="Win32CompositionMode.RedirectionSurface"/>.
/// </summary>
/// <remarks>
/// If application should work on as wide range of devices as possible, at least add <see cref="Win32CompositionMode.RedirectionSurface"/> as a fallback value.
/// </remarks>
/// <exception cref="System.InvalidOperationException">Thrown if no values were matched.</exception>
public IReadOnlyList<Win32CompositionMode> CompositionMode { get; set; } = new[]
{
Win32CompositionMode.WinUIComposition, Win32CompositionMode.RedirectionSurface
};
/// <summary>
/// When <see cref="CompositionMode"/> is set to <see cref="Win32CompositionMode.WinUIComposition"/>, create rounded corner blur brushes
/// If set to null the brushes will be created using default settings (sharp corners)
/// This can be useful when you need a rounded-corner blurred Windows 10 app, or borderless Windows 11 app.
/// </summary>
public float? WinUICompositionBackdropCornerRadius { get; set; }
/// <summary>
/// Render directly on the UI thread instead of using a dedicated render thread.
/// Only applicable if <see cref="CompositionMode"/> is set to <see cref="Win32CompositionMode.RedirectionSurface"/>.
/// This setting is only recommended for interop with systems that must render on the UI thread, such as WPF.
/// This setting is false by default.
/// </summary>
public bool ShouldRenderOnUIThread { get; set; }
/// <summary>
/// Windows OpenGL profiles used when <see cref="RenderingMode"/> is set to <see cref="Win32RenderingMode.Wgl"/>.
/// This setting is 4.0 and 3.2 by default.
/// </summary>
public IList<GlVersion> WglProfiles { get; set; } = new List<GlVersion>
{
new(GlProfileType.OpenGL, 4, 0), new(GlProfileType.OpenGL, 3, 2)
};
/// <summary>
/// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu.
/// When this property set <see cref="RenderingMode"/> is ignored
/// and <see cref="CompositionMode"/> only accepts null or <see cref="Win32CompositionMode.RedirectionSurface"/>.
/// </summary>
public IPlatformGraphics? CustomPlatformGraphics { get; set; }
}

2
src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs

@ -23,7 +23,7 @@ namespace Avalonia.Win32.WinRT.Composition
public IDirect3D11TextureRenderTarget CreateRenderTarget(IPlatformGraphicsContext context, IntPtr d3dDevice)
{
var cornerRadius = AvaloniaLocator.Current.GetService<Win32PlatformOptions>()
?.CompositionBackdropCornerRadius;
?.WinUICompositionBackdropCornerRadius;
_window ??= new WinUiCompositedWindow(_info, _shared, cornerRadius);
_window.SetBlur(_blurEffect);

1
src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs

@ -12,6 +12,7 @@ internal class WinUiCompositionShared : IDisposable
public ICompositionBrush? MicaBrush { get; }
public object SyncRoot { get; } = new();
public static readonly Version MinWinCompositionVersion = new(10, 0, 17134);
public static readonly Version MinAcrylicVersion = new(10, 0, 15063);
public static readonly Version MinHostBackdropVersion = new(10, 0, 22000);

31
src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs

@ -112,35 +112,36 @@ internal class WinUiCompositorConnection : IRenderTimer
}
}
public static void TryCreateAndRegister()
public static bool IsSupported()
{
const int majorRequired = 10;
const int buildRequired = 17134;
var majorInstalled = Win32Platform.WindowsVersion.Major;
var buildInstalled = Win32Platform.WindowsVersion.Build;
if (majorInstalled >= majorRequired &&
buildInstalled >= buildRequired)
return Win32Platform.WindowsVersion >= WinUiCompositionShared.MinWinCompositionVersion;
}
public static bool TryCreateAndRegister()
{
if (IsSupported())
{
try
{
TryCreateAndRegisterCore();
return;
return true;
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, "WinUIComposition")
?.Log(null, "Unable to initialize WinUI compositor: {0}", e);
}
}
else
{
var osVersionNotice =
$"Windows {WinUiCompositionShared.MinWinCompositionVersion} is required. Your machine has Windows {Win32Platform.WindowsVersion} installed.";
var osVersionNotice =
$"Windows {majorRequired} Build {buildRequired} is required. Your machine has Windows {majorInstalled} Build {buildInstalled} installed.";
Logger.TryGet(LogEventLevel.Warning, "WinUIComposition")?.Log(null,
$"Unable to initialize WinUI compositor: {osVersionNotice}");
}
Logger.TryGet(LogEventLevel.Warning, "WinUIComposition")?.Log(null,
$"Unable to initialize WinUI compositor: {osVersionNotice}");
return false;
}
public WinUiCompositedWindowSurface CreateSurface(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) => new(_shared, info);

1
src/tools/Avalonia.Generators/Avalonia.Generators.csproj

@ -6,6 +6,7 @@
<DefineConstants>$(DefineConstants);XAMLX_INTERNAL</DefineConstants>
<IsPackable>true</IsPackable>
<IsRoslynComponent>true</IsRoslynComponent>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>

13
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs

@ -243,6 +243,19 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(new[] { 11 }, target.CoerceFooInvocations);
}
[Fact]
public void Second_Coerce_Of_Default_Value_Is_Passed_Uncoerced_Value()
{
var target = new Class1();
target.MinFoo = 20;
target.CoerceFooInvocations.Clear();
target.CoerceValue(Class1.FooProperty);
target.CoerceValue(Class1.FooProperty);
Assert.Equal(new[] { 11, 11 }, target.CoerceFooInvocations);
}
[Fact]
public void ClearValue_Respects_Coerced_Default_Value()
{

2
tests/Avalonia.Base.UnitTests/Media/GeometryTests.cs

@ -103,7 +103,7 @@ namespace Avalonia.Base.UnitTests.Media
throw new NotImplementedException();
}
protected override IGeometryImpl CreateDefiningGeometry()
private protected sealed override IGeometryImpl CreateDefiningGeometry()
{
return Mock.Of<IGeometryImpl>(
x => x.WithTransform(It.IsAny<Matrix>()) ==

31
tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs

@ -4,6 +4,7 @@ using System.Reactive.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Layout;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests.Presenters
@ -244,6 +245,36 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(new Size(110, 110), target.Extent);
}
[Fact]
public void Extent_Should_Include_Content_Margin_Scaled_With_Layout_Rounding()
{
var root = new TestRoot
{
LayoutScaling = 1.25,
UseLayoutRounding = true
};
var target = new ScrollContentPresenter
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Content = new Border
{
Width = 200,
Height = 200,
Margin = new Thickness(2)
}
};
root.Child = target;
target.UpdateChild();
target.Measure(new Size(1000, 1000));
target.Arrange(new Rect(0, 0, 1000, 1000));
Assert.Equal(new Size(203.2, 203.2), target.Viewport);
Assert.Equal(new Size(203.2, 203.2), target.Extent);
}
[Fact]
public void Extent_Width_Should_Be_Arrange_Width_When_CanScrollHorizontally_False()
{

1
tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj

@ -22,5 +22,4 @@
<Import Project="..\..\build\HarfBuzzSharp.props" />
<Import Project="..\..\build\XUnit.props" />
<Import Project="..\..\build\SharedVersion.props" />
<Import Project="..\..\build\AvaloniaPublicKey.props" />
</Project>

Loading…
Cancel
Save