diff --git a/samples/AppWithoutLifetime/MainWindow.axaml.cs b/samples/AppWithoutLifetime/MainWindow.axaml.cs index d27d5d5653..df3ed3cea0 100644 --- a/samples/AppWithoutLifetime/MainWindow.axaml.cs +++ b/samples/AppWithoutLifetime/MainWindow.axaml.cs @@ -17,10 +17,11 @@ public partial class MainWindow : Window AvaloniaXamlLoader.Load(this); } - protected override void OnLoaded() + /// + protected override void OnLoaded(RoutedEventArgs e) { this.AttachDevTools(); - base.OnLoaded(); + base.OnLoaded(e); } public void Open(object sender, RoutedEventArgs e) diff --git a/samples/AppWithoutLifetime/Sub.axaml.cs b/samples/AppWithoutLifetime/Sub.axaml.cs index 50c770b3a2..3a7ce787bc 100644 --- a/samples/AppWithoutLifetime/Sub.axaml.cs +++ b/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() + /// + protected override void OnLoaded(RoutedEventArgs e) { this.AttachDevTools(); - base.OnLoaded(); + base.OnLoaded(e); } } diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj index a43ea4539a..f668067f80 100644 --- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj +++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj @@ -10,6 +10,11 @@ apk true + + + + + Resources\drawable\Icon.png @@ -37,6 +42,10 @@ + + + + diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs index 486d14661e..9b5b06e5fb 100644 --- a/samples/ControlCatalog.Android/MainActivity.cs +++ b/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 { + protected override Avalonia.AppBuilder CustomizeAppBuilder(Avalonia.AppBuilder builder) + { + return base.CustomizeAppBuilder(builder) + .AfterSetup(_ => + { + Pages.EmbedSample.Implementation = new EmbedSampleAndroid(); + }); + } } } diff --git a/samples/ControlCatalog.Android/Resources/drawable-night-v31/avalonia_anim.xml b/samples/ControlCatalog.Android/Resources/drawable-night-v31/avalonia_anim.xml new file mode 100644 index 0000000000..dde4b5a7dd --- /dev/null +++ b/samples/ControlCatalog.Android/Resources/drawable-night-v31/avalonia_anim.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog.Android/Resources/drawable-v31/avalonia_anim.xml b/samples/ControlCatalog.Android/Resources/drawable-v31/avalonia_anim.xml new file mode 100644 index 0000000000..94f27d9e63 --- /dev/null +++ b/samples/ControlCatalog.Android/Resources/drawable-v31/avalonia_anim.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog.Android/Resources/values-v31/styles.xml b/samples/ControlCatalog.Android/Resources/values-v31/styles.xml new file mode 100644 index 0000000000..d5ecec4391 --- /dev/null +++ b/samples/ControlCatalog.Android/Resources/values-v31/styles.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/samples/ControlCatalog.Android/Resources/values/styles.xml b/samples/ControlCatalog.Android/Resources/values/styles.xml index 3e1270256d..6e534de2f0 100644 --- a/samples/ControlCatalog.Android/Resources/values/styles.xml +++ b/samples/ControlCatalog.Android/Resources/values/styles.xml @@ -6,16 +6,7 @@ - - - - - diff --git a/samples/ControlCatalog.Android/SplashActivity.cs b/samples/ControlCatalog.Android/SplashActivity.cs deleted file mode 100644 index a0b68b129b..0000000000 --- a/samples/ControlCatalog.Android/SplashActivity.cs +++ /dev/null @@ -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 - { - 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(); - } - } -} diff --git a/samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs b/samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs index 47d368f7a4..bd0c1bf98e 100644 --- a/samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs +++ b/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); diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 5249a4fb41..5e3e301461 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/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); } diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 64bf3e53b3..02b1242471 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -28,6 +28,9 @@ + + + diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 246fe4385f..605254c995 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/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"]!; diff --git a/samples/MobileSandbox.Android/MainActivity.cs b/samples/MobileSandbox.Android/MainActivity.cs index d65f0dec92..807afb0bd0 100644 --- a/samples/MobileSandbox.Android/MainActivity.cs +++ b/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 { } } diff --git a/samples/MobileSandbox.Android/MobileSandbox.Android.csproj b/samples/MobileSandbox.Android/MobileSandbox.Android.csproj index 94f0d31e4d..3b67d79f7b 100644 --- a/samples/MobileSandbox.Android/MobileSandbox.Android.csproj +++ b/samples/MobileSandbox.Android/MobileSandbox.Android.csproj @@ -10,6 +10,12 @@ apk true + + + + + + Resources\drawable\Icon.png @@ -37,6 +43,10 @@ + + + + diff --git a/samples/MobileSandbox.Android/Resources/drawable-night-v31/avalonia_anim.xml b/samples/MobileSandbox.Android/Resources/drawable-night-v31/avalonia_anim.xml new file mode 100644 index 0000000000..dde4b5a7dd --- /dev/null +++ b/samples/MobileSandbox.Android/Resources/drawable-night-v31/avalonia_anim.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/MobileSandbox.Android/Resources/drawable-v31/avalonia_anim.xml b/samples/MobileSandbox.Android/Resources/drawable-v31/avalonia_anim.xml new file mode 100644 index 0000000000..94f27d9e63 --- /dev/null +++ b/samples/MobileSandbox.Android/Resources/drawable-v31/avalonia_anim.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/MobileSandbox.Android/Resources/values-night/colors.xml b/samples/MobileSandbox.Android/Resources/values-night/colors.xml new file mode 100644 index 0000000000..3d47b6fc58 --- /dev/null +++ b/samples/MobileSandbox.Android/Resources/values-night/colors.xml @@ -0,0 +1,4 @@ + + + #212121 + diff --git a/samples/MobileSandbox.Android/Resources/values-v31/styles.xml b/samples/MobileSandbox.Android/Resources/values-v31/styles.xml new file mode 100644 index 0000000000..7518f078e5 --- /dev/null +++ b/samples/MobileSandbox.Android/Resources/values-v31/styles.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/samples/MobileSandbox.Android/Resources/values/styles.xml b/samples/MobileSandbox.Android/Resources/values/styles.xml index 2759d2904a..22085806da 100644 --- a/samples/MobileSandbox.Android/Resources/values/styles.xml +++ b/samples/MobileSandbox.Android/Resources/values/styles.xml @@ -6,12 +6,7 @@ - - - diff --git a/samples/MobileSandbox.Android/SplashActivity.cs b/samples/MobileSandbox.Android/SplashActivity.cs deleted file mode 100644 index ced092554d..0000000000 --- a/samples/MobileSandbox.Android/SplashActivity.cs +++ /dev/null @@ -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 - { - protected override void OnResume() - { - base.OnResume(); - - StartActivity(new Intent(Application.Context, typeof(MainActivity))); - } - } -} diff --git a/samples/SafeAreaDemo.Android/MainActivity.cs b/samples/SafeAreaDemo.Android/MainActivity.cs index b0f0a6e419..1df575eb4d 100644 --- a/samples/SafeAreaDemo.Android/MainActivity.cs +++ b/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 { } } diff --git a/samples/SafeAreaDemo.Android/Resources/drawable-night-v31/avalonia_anim.xml b/samples/SafeAreaDemo.Android/Resources/drawable-night-v31/avalonia_anim.xml new file mode 100644 index 0000000000..dde4b5a7dd --- /dev/null +++ b/samples/SafeAreaDemo.Android/Resources/drawable-night-v31/avalonia_anim.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/SafeAreaDemo.Android/Resources/drawable-v31/avalonia_anim.xml b/samples/SafeAreaDemo.Android/Resources/drawable-v31/avalonia_anim.xml new file mode 100644 index 0000000000..94f27d9e63 --- /dev/null +++ b/samples/SafeAreaDemo.Android/Resources/drawable-v31/avalonia_anim.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/SafeAreaDemo.Android/Resources/values-night/colors.xml b/samples/SafeAreaDemo.Android/Resources/values-night/colors.xml new file mode 100644 index 0000000000..3d47b6fc58 --- /dev/null +++ b/samples/SafeAreaDemo.Android/Resources/values-night/colors.xml @@ -0,0 +1,4 @@ + + + #212121 + diff --git a/samples/SafeAreaDemo.Android/Resources/values-v31/styles.xml b/samples/SafeAreaDemo.Android/Resources/values-v31/styles.xml new file mode 100644 index 0000000000..7518f078e5 --- /dev/null +++ b/samples/SafeAreaDemo.Android/Resources/values-v31/styles.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/samples/SafeAreaDemo.Android/Resources/values/styles.xml b/samples/SafeAreaDemo.Android/Resources/values/styles.xml index 2759d2904a..22085806da 100644 --- a/samples/SafeAreaDemo.Android/Resources/values/styles.xml +++ b/samples/SafeAreaDemo.Android/Resources/values/styles.xml @@ -6,12 +6,7 @@ - - - diff --git a/samples/SafeAreaDemo.Android/SafeAreaDemo.Android.csproj b/samples/SafeAreaDemo.Android/SafeAreaDemo.Android.csproj index f5d2af79d0..cc182b2b56 100644 --- a/samples/SafeAreaDemo.Android/SafeAreaDemo.Android.csproj +++ b/samples/SafeAreaDemo.Android/SafeAreaDemo.Android.csproj @@ -1,4 +1,4 @@ - + Exe net7.0-android @@ -12,13 +12,14 @@ - - Resources\drawable\Icon.png - + + - - + + Resources\drawable\Icon.png + + diff --git a/samples/SafeAreaDemo.Android/SplashActivity.cs b/samples/SafeAreaDemo.Android/SplashActivity.cs deleted file mode 100644 index 621ad1c675..0000000000 --- a/samples/SafeAreaDemo.Android/SplashActivity.cs +++ /dev/null @@ -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 - { - 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))); - } - } -} diff --git a/samples/SafeAreaDemo/Views/MainView.xaml.cs b/samples/SafeAreaDemo/Views/MainView.xaml.cs index 2b651225e7..4b8c5e5f15 100644 --- a/samples/SafeAreaDemo/Views/MainView.xaml.cs +++ b/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() + /// + protected override void OnLoaded(RoutedEventArgs e) { - base.OnLoaded(); + base.OnLoaded(e); var insetsManager = TopLevel.GetTopLevel(this)?.InsetsManager; if (insetsManager != null && DataContext is MainViewModel viewModel) diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index d5d5f211e9..144909db4b 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/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(); } } + + /// + /// Represents the rendering mode for platform graphics. + /// + public enum AndroidRenderingMode + { + /// + /// Avalonia is rendered into a framebuffer. + /// + Software = 1, + + /// + /// Enables android EGL rendering. + /// + Egl = 2 + } + + public sealed class AndroidPlatformOptions + { + /// + /// Gets or sets Avalonia rendering modes with fallbacks. + /// The first element in the array has the highest priority. + /// The default value is: , . + /// + /// + /// If application should work on as wide range of devices as possible, at least add as a fallback value. + /// + /// Thrown if no values were matched. + public IReadOnlyList RenderingMode { get; set; } = new[] + { + AndroidRenderingMode.Egl, AndroidRenderingMode.Software + }; + } } namespace Avalonia.Android @@ -47,18 +82,39 @@ namespace Avalonia.Android .Bind().ToConstant(new ChoreographerTimer()) .Bind().ToSingleton(); - if (Options.UseGpu) + var graphics = InitializeGraphics(Options); + if (graphics is not null) { - EglPlatformGraphics.TryInitialize(); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(graphics); } - - Compositor = new Compositor(AvaloniaLocator.Current.GetService()); + + 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."); + } } } diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs new file mode 100644 index 0000000000..3fcfde3ee4 --- /dev/null +++ b/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 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(); + + 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; + } + } + } +} diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs index b2cd150933..499924d8fb 100644 --- a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs +++ b/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 ActivityResult { get; set; } public Action RequestPermissionsResult { get; set; } + + public event EventHandler 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 : 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 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; diff --git a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs deleted file mode 100644 index 38038ef26c..0000000000 --- a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs +++ /dev/null @@ -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 : AvaloniaSplashActivity where TApp : Application, new() - { - protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid(); - - protected override AppBuilder CreateAppBuilder() - { - var builder = AppBuilder.Configure(); - - return CustomizeAppBuilder(builder); - } - } -} diff --git a/src/Avalonia.Base/CombinedGeometry.cs b/src/Avalonia.Base/CombinedGeometry.cs index 4b5866519b..a47e756c88 100644 --- a/src/Avalonia.Base/CombinedGeometry.cs +++ b/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; diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs index 91358712a0..5744ddf963 100644 --- a/src/Avalonia.Base/Input/Pointer.cs +++ b/src/Avalonia.Base/Input/Pointer.cs @@ -88,7 +88,6 @@ namespace Avalonia.Input /// Captures pointer input to the specified gesture recognizer. /// /// The gesture recognizer. - /// internal void CaptureGestureRecognizer(GestureRecognizer? gestureRecognizer) { if (CapturedGestureRecognizer != gestureRecognizer) diff --git a/src/Avalonia.Base/Input/TextInputEventArgs.cs b/src/Avalonia.Base/Input/TextInputEventArgs.cs index f4acd694a0..305b4d81f8 100644 --- a/src/Avalonia.Base/Input/TextInputEventArgs.cs +++ b/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; } } } diff --git a/src/Avalonia.Base/Media/EllipseGeometry.cs b/src/Avalonia.Base/Media/EllipseGeometry.cs index 84d74e888e..bb1263d8d5 100644 --- a/src/Avalonia.Base/Media/EllipseGeometry.cs +++ b/src/Avalonia.Base/Media/EllipseGeometry.cs @@ -135,7 +135,7 @@ namespace Avalonia.Media } /// - protected override IGeometryImpl? CreateDefiningGeometry() + private protected sealed override IGeometryImpl? CreateDefiningGeometry() { var factory = AvaloniaLocator.Current.GetRequiredService(); diff --git a/src/Avalonia.Base/Media/Geometry.cs b/src/Avalonia.Base/Media/Geometry.cs index 0d2311eafc..a66cd616a3 100644 --- a/src/Avalonia.Base/Media/Geometry.cs +++ b/src/Avalonia.Base/Media/Geometry.cs @@ -28,6 +28,11 @@ namespace Avalonia.Media TransformProperty.Changed.AddClassHandler((x,e) => x.TransformChanged(e)); } + internal Geometry() + { + + } + /// /// Raised when the geometry changes. /// @@ -134,7 +139,7 @@ namespace Avalonia.Media /// Creates the platform implementation of the geometry, without the transform applied. /// /// - protected abstract IGeometryImpl? CreateDefiningGeometry(); + private protected abstract IGeometryImpl? CreateDefiningGeometry(); /// /// Invalidates the platform implementation of the geometry. diff --git a/src/Avalonia.Base/Media/GeometryGroup.cs b/src/Avalonia.Base/Media/GeometryGroup.cs index 3e61413919..20bd297fc1 100644 --- a/src/Avalonia.Base/Media/GeometryGroup.cs +++ b/src/Avalonia.Base/Media/GeometryGroup.cs @@ -1,8 +1,6 @@ using Avalonia.Metadata; using Avalonia.Platform; -#nullable enable - namespace Avalonia.Media { /// @@ -72,7 +70,7 @@ namespace Avalonia.Media newChildren.Parent = this; } - protected override IGeometryImpl? CreateDefiningGeometry() + private protected sealed override IGeometryImpl? CreateDefiningGeometry() { if (_children.Count > 0) { diff --git a/src/Avalonia.Base/Media/LineGeometry.cs b/src/Avalonia.Base/Media/LineGeometry.cs index 6ac92ea33b..ced208e30b 100644 --- a/src/Avalonia.Base/Media/LineGeometry.cs +++ b/src/Avalonia.Base/Media/LineGeometry.cs @@ -68,7 +68,7 @@ namespace Avalonia.Media } /// - protected override IGeometryImpl? CreateDefiningGeometry() + private protected sealed override IGeometryImpl? CreateDefiningGeometry() { var factory = AvaloniaLocator.Current.GetRequiredService(); diff --git a/src/Avalonia.Base/Media/MediaContext.cs b/src/Avalonia.Base/Media/MediaContext.cs index 84a4a8c873..0a290052c7 100644 --- a/src/Avalonia.Base/Media/MediaContext.cs +++ b/src/Avalonia.Base/Media/MediaContext.cs @@ -213,10 +213,10 @@ internal partial class MediaContext : ICompositorScheduler } /// - /// Executes the callback in the next iteration of the current UI-thread + /// Executes the callback in the next iteration of the current UI-thread /// render loop / layout pass that. /// - /// Code to execute. + /// public void BeginInvokeOnRender(Action callback) { if (_invokeOnRenderCallbacks == null) diff --git a/src/Avalonia.Base/Media/PathGeometry.cs b/src/Avalonia.Base/Media/PathGeometry.cs index 8292afde7e..bdfbfadce4 100644 --- a/src/Avalonia.Base/Media/PathGeometry.cs +++ b/src/Avalonia.Base/Media/PathGeometry.cs @@ -43,7 +43,7 @@ namespace Avalonia.Media /// /// The s. /// - 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; diff --git a/src/Avalonia.Base/Media/PlatformGeometry.cs b/src/Avalonia.Base/Media/PlatformGeometry.cs index f25a14540f..e1488a8229 100644 --- a/src/Avalonia.Base/Media/PlatformGeometry.cs +++ b/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; } diff --git a/src/Avalonia.Base/Media/PolylineGeometry.cs b/src/Avalonia.Base/Media/PolylineGeometry.cs index b0229b6455..47cf2f48a4 100644 --- a/src/Avalonia.Base/Media/PolylineGeometry.cs +++ b/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(); var geometry = factory.CreateStreamGeometry(); diff --git a/src/Avalonia.Base/Media/RectangleGeometry.cs b/src/Avalonia.Base/Media/RectangleGeometry.cs index 0bf9eb5664..01771242a7 100644 --- a/src/Avalonia.Base/Media/RectangleGeometry.cs +++ b/src/Avalonia.Base/Media/RectangleGeometry.cs @@ -47,7 +47,7 @@ namespace Avalonia.Media /// public override Geometry Clone() => new RectangleGeometry(Rect); - protected override IGeometryImpl? CreateDefiningGeometry() + private protected sealed override IGeometryImpl? CreateDefiningGeometry() { var factory = AvaloniaLocator.Current.GetRequiredService(); diff --git a/src/Avalonia.Base/Media/StreamGeometry.cs b/src/Avalonia.Base/Media/StreamGeometry.cs index fb79488e0f..9969376896 100644 --- a/src/Avalonia.Base/Media/StreamGeometry.cs +++ b/src/Avalonia.Base/Media/StreamGeometry.cs @@ -31,7 +31,7 @@ namespace Avalonia.Media /// /// The string. /// A . - 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 } /// - protected override IGeometryImpl? CreateDefiningGeometry() + private protected override IGeometryImpl? CreateDefiningGeometry() { if (_impl == null) { diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs index a0cd2a8dcd..df3c9ebfc5 100644 --- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs @@ -161,7 +161,7 @@ namespace Avalonia.Platform void PopGeometryClip(); /// - + /// Get Fetaure from type /// /// Attempts to get an optional feature from the drawing context implementation /// diff --git a/src/Avalonia.Base/Point.cs b/src/Avalonia.Base/Point.cs index 331cce4a76..e3ea21d1eb 100644 --- a/src/Avalonia.Base/Point.cs +++ b/src/Avalonia.Base/Point.cs @@ -164,6 +164,19 @@ namespace Avalonia /// The resulting point. public static Point operator *(Point point, Matrix matrix) => matrix.Transform(point); + /// + /// Computes the Euclidean distance between the two given points. + /// + /// The first point. + /// The second point. + /// The Euclidean distance. + 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); + } + /// /// Parses a string. /// diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index 4518289335..a69d7b7094 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs @@ -216,7 +216,7 @@ namespace Avalonia.PropertyStore valueChanged = !EqualityComparer.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.Default.Equals(_baseValue, v); _baseValue = v; BasePriority = priority; - if (_uncommon is not null) + if (!isCoercedDefaultValue && _uncommon is not null) _uncommon._uncoercedBaseValue = value; } diff --git a/src/Avalonia.Base/Rendering/RenderLoop.cs b/src/Avalonia.Base/Rendering/RenderLoop.cs index aaecf5deaf..80410ba181 100644 --- a/src/Avalonia.Base/Rendering/RenderLoop.cs +++ b/src/Avalonia.Base/Rendering/RenderLoop.cs @@ -42,7 +42,6 @@ namespace Avalonia.Rendering /// Initializes a new instance of the class. /// /// The render timer. - /// The UI thread dispatcher. public RenderLoop(IRenderTimer timer) { _timer = timer; diff --git a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs index bb1663eac0..7d586e1cbd 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs @@ -581,11 +581,6 @@ public partial class Dispatcher /// /// A Func<Task<TResult>> delegate to invoke through the dispatcher. /// - /// - /// The priority that determines in what order the specified - /// callback is invoked relative to the other pending operations - /// in the Dispatcher. - /// /// /// An task that completes after the task returned from callback finishes /// diff --git a/src/Avalonia.Base/Vector.cs b/src/Avalonia.Base/Vector.cs index 15722901a6..fffe206835 100644 --- a/src/Avalonia.Base/Vector.cs +++ b/src/Avalonia.Base/Vector.cs @@ -359,7 +359,7 @@ namespace Avalonia internal Vector(Vector2 v) : this(v.X, v.Y) { - + } /// @@ -379,21 +379,27 @@ namespace Avalonia /// public static Vector Max(Vector left, Vector right) => new(Math.Max(left.X, right.X), Math.Max(left.Y, right.Y)); - + /// /// Returns a vector whose elements are the minimum of each of the pairs of elements in two specified vectors /// public static Vector Min(Vector left, Vector right) => new(Math.Min(left.X, right.X), Math.Min(left.Y, right.Y)); - + /// /// Computes the Euclidean distance between the two given points. /// + /// The first point. + /// The second point. + /// The Euclidean distance. public static double Distance(Vector value1, Vector value2) => Math.Sqrt(DistanceSquared(value1, value2)); - + /// /// Returns the Euclidean distance squared between two specified points /// + /// The first point. + /// The second point. + /// The Euclidean distance squared. public static double DistanceSquared(Vector value1, Vector value2) { var difference = value1 - value2; diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index d28c0969c4..a5695afeb7 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/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; /// /// Initializes a new instance of the 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(); /// /// Backing field for CellTheme property. diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml index 082eac60be..0cc620dae9 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml @@ -42,9 +42,10 @@ - + 0.6 0.8 + 32 M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z @@ -174,7 +175,7 @@ VerticalAlignment="{TemplateBinding VerticalContentAlignment}"> - + - AppBuilder method or inherit Application type."); } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index ae7d4f3fb4..0851c14178 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/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)); } } /// /// Invoked just before the event. /// - protected virtual void OnLoaded() + /// The event args. + protected virtual void OnLoaded(RoutedEventArgs e) { - var eventArgs = new RoutedEventArgs(LoadedEvent); - eventArgs.Source = null; - RaiseEvent(eventArgs); + RaiseEvent(e); } /// /// Invoked just before the event. /// - protected virtual void OnUnloaded() + /// The event args. + protected virtual void OnUnloaded(RoutedEventArgs e) { - var eventArgs = new RoutedEventArgs(UnloadedEvent); - eventArgs.Source = null; - RaiseEvent(eventArgs); + RaiseEvent(e); + } + + /// + /// Invoked just before the event. + /// + /// The event args. + protected virtual void OnSizeChanged(SizeChangedEventArgs e) + { + RaiseEvent(e); } /// @@ -435,6 +444,10 @@ namespace Avalonia.Controls } } + /// + /// Returns a new, type-specific implementation for the control. + /// + /// The type-specific implementation. protected virtual AutomationPeer OnCreateAutomationPeer() { return new NoneAutomationPeer(this); @@ -459,6 +472,7 @@ namespace Avalonia.Controls return _automationPeer; } + /// protected override void OnPointerReleased(PointerReleasedEventArgs e) { base.OnPointerReleased(e); @@ -473,6 +487,7 @@ namespace Avalonia.Controls } } + /// 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); } } } diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index bd694b200c..bd3d2b5171 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/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; diff --git a/src/Avalonia.Controls/ToggleSwitch.cs b/src/Avalonia.Controls/ToggleSwitch.cs index 48b068d324..dc6b946381 100644 --- a/src/Avalonia.Controls/ToggleSwitch.cs +++ b/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() + /// + protected override void OnLoaded(RoutedEventArgs e) { - base.OnLoaded(); + base.OnLoaded(e); UpdateKnobTransitions(); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/PropertyValueEditorView.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/PropertyValueEditorView.cs index aec0cb37bc..f6e9842ac8 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/PropertyValueEditorView.cs +++ b/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; } diff --git a/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml b/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml index 22362fa5f5..b58df71ba2 100644 --- a/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml +++ b/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"> + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - public class AvaloniaNativePlatformOptions { - /// - /// Determines whether to use GPU for rendering in your project. The default value is true. - /// - public bool UseGpu { get; set; } = true; - /// /// Embeds popups to the window when set to true. The default value is false. /// diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index 6b7f7e8883..6cef7ea578 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/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; diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 817fe3d080..ba96dd401a 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/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; diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 760816643e..053957f89f 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/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(); _mouse = new MouseDevice(); diff --git a/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs b/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs index 07e304febe..cf81999095 100644 --- a/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs +++ b/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().ToConstant(feature); - } - public static EglPlatformGraphics? TryCreate() => TryCreate(() => new EglDisplay(new EglDisplayCreationOptions { Egl = new EglInterface(), diff --git a/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs b/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs index 261de5497d..6eb465d399 100644 --- a/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs +++ b/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs @@ -10,12 +10,18 @@ internal class ColorPaletteResourcesCollection : AvaloniaDictionary + (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 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; } diff --git a/src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml b/src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml index d335e6f4b9..830198f6d1 100644 --- a/src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml +++ b/src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml @@ -1,22 +1,25 @@ - - - - 14 - 14 - 24 - 2,2,6,1 - 32 - 24 - 0,1,0,2 - 0,1,0,2 - 9,0,0,1 - 10,0,30,0 - 24 - 12,1,0,3 - 32 - - + + 14 + 14 + 24 + 32 + 4, 2 + 4,2 + 24 + 11,5,11,7 + 4 4 8 4 + 5, 2 + 24 + 0,1,0,2 + 0,1,0,2 + 9,0,0,1 + 10,0,30,0 + 24 + 12,1,0,3 + 32 + 28 + 6, 0 + 6,4 + diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml b/src/Avalonia.Themes.Fluent/FluentTheme.xaml index c84772aa61..f4467e40ce 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml @@ -18,7 +18,7 @@ - + diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs index 5af22dbd1d..378041356a 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs @@ -16,9 +16,10 @@ namespace Avalonia.Themes.Fluent /// /// Includes the fluent theme in an application. /// - public class FluentTheme : Styles + public class FluentTheme : Styles, IResourceNode { - private readonly Styles _compactStyles; + private readonly ResourceDictionary _compactStyles; + private DensityStyle _densityStyle; /// /// Initializes a new instance of the 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().FirstOrDefault() ?? throw new InvalidOperationException("FluentTheme was initialized with missing ColorPaletteResourcesCollection."); @@ -43,17 +42,17 @@ namespace Avalonia.Themes.Fluent return val; } } - - public static readonly StyledProperty DensityStyleProperty = - AvaloniaProperty.Register(nameof(DensityStyle)); + + public static readonly DirectProperty DensityStyleProperty = AvaloniaProperty.RegisterDirect( + nameof(DensityStyle), o => o.DensityStyle, (o, v) => o.DensityStyle = v); /// /// Gets or sets the density style of the fluent theme (normal, compact). /// public DensityStyle DensityStyle { - get => GetValue(DensityStyleProperty); - set => SetValue(DensityStyleProperty, value); + get => _densityStyle; + set => SetAndRaise(DensityStyleProperty, ref _densityStyle, value); } public IDictionary 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); } } } diff --git a/src/Avalonia.Themes.Simple/Controls/TransitioningContentControl.xaml b/src/Avalonia.Themes.Simple/Controls/TransitioningContentControl.xaml index ceccdf56c7..5f532df848 100644 --- a/src/Avalonia.Themes.Simple/Controls/TransitioningContentControl.xaml +++ b/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}" /> - Display.CreateContext(); public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException(); - - public static bool TryInitialize(X11Info x11, IList glProfiles) - { - var feature = TryCreate(x11, glProfiles); - if (feature != null) - { - AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); - return true; - } - - return false; - } public static GlxPlatformGraphics TryCreate(X11Info x11, IList glProfiles) { diff --git a/src/Avalonia.X11/X11Clipboard.cs b/src/Avalonia.X11/X11Clipboard.cs index 04d1aae194..6e18151fae 100644 --- a/src/Avalonia.X11/X11Clipboard.cs +++ b/src/Avalonia.X11/X11Clipboard.cs @@ -224,7 +224,7 @@ namespace Avalonia.X11 private Task SendDataRequest(IntPtr format) { - if (_requestedDataTcs == null || _requestedFormatsTcs.Task.IsCompleted) + if (_requestedDataTcs == null || _requestedDataTcs.Task.IsCompleted) _requestedDataTcs = new TaskCompletionSource(); XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, format, format, _handle, IntPtr.Zero); return _requestedDataTcs.Task; diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index a880e4ba1a..c70c17d523 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/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().ToConstant(graphics); } - var gl = AvaloniaLocator.Current.GetService(); - - 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 { /// - /// Platform-specific options which apply to Linux. + /// Represents the rendering mode for platform graphics. /// - public class X11PlatformOptions + public enum X11RenderingMode { /// - /// Enables native Linux EGL when set to true. The default value is false. + /// Avalonia is rendered into a framebuffer. /// - public bool UseEGL { get; set; } + Software = 1, /// - /// Determines whether to use GPU for rendering in your project. The default value is true. + /// Enables Glx rendering. /// - public bool UseGpu { get; set; } = true; + Glx = 2, + + /// + /// Enables native Linux EGL rendering. + /// + Egl = 3 + } + + /// + /// Platform-specific options which apply to Linux. + /// + public class X11PlatformOptions + { + /// + /// Gets or sets Avalonia rendering modes with fallbacks. + /// The first element in the array has the highest priority. + /// The default value is: , . + /// + /// + /// If application should work on as wide range of devices as possible, at least add as a fallback value. + /// + /// Thrown if no values were matched. + public IReadOnlyList RenderingMode { get; set; } = new[] + { + X11RenderingMode.Glx, X11RenderingMode.Software + }; /// /// Embeds popups to the window when set to true. The default value is false. diff --git a/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs b/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs index 07fb3169cb..bac2a73dd9 100644 --- a/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs +++ b/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; } } diff --git a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs index 05074cc82e..8502f5b41a 100644 --- a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs +++ b/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(); diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 39363beae3..68f3e3c670 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/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])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) diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs index c26ce5fd45..b3760a37e1 100644 --- a/src/Windows/Avalonia.Win32/Win32GlManager.cs +++ b/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().ToConstant(gl); - } + var gl = InitializeCore(); - return gl; + if (gl is not null) + { + AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl); } + + return gl; + } - private static IPlatformGraphics? InitializeCore() + private static IPlatformGraphics? InitializeCore() + { + var opts = AvaloniaLocator.Current.GetService() ?? 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() ?? 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() ?? - new()); + var egl = AngleWin32PlatformGraphics.TryCreate(AvaloniaLocator.Current.GetService() ?? new()); if (egl != null && egl.PlatformApi == AngleOptions.PlatformApi.DirectX11) { - AvaloniaLocator.CurrentMutable.Bind() - .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."); } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index e9019803be..6e727ba4d4 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/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"); } } - - /// - /// Platform-specific options which apply to Windows. - /// - public class Win32PlatformOptions - { - /// - /// Enables ANGLE for Windows. For every Windows version that is above Windows 7, the default is true otherwise it's false. - /// - /// - /// GPU rendering will not be enabled if this is set to false. - /// - public bool? AllowEglInitialization { get; set; } - - /// - /// Embeds popups to the window when set to true. The default value is false. - /// - public bool OverlayPopups { get; set; } - - /// - /// Avalonia would try to use native Widows OpenGL when set to true. The default value is false. - /// - public bool UseWgl { get; set; } - - public IList WglProfiles { get; set; } = new List - { - new GlVersion(GlProfileType.OpenGL, 4, 0), - new GlVersion(GlProfileType.OpenGL, 3, 2), - }; - - /// - /// Render Avalonia to a Texture inside the Windows.UI.Composition tree. - /// This setting is true by default. - /// - /// - /// 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. - /// - public bool UseWindowsUIComposition { get; set; } = true; - - /// - /// When 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 - /// - public float? CompositionBackdropCornerRadius { get; set; } - - /// - /// When 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
- /// This is mutually exclusive with - /// which if active will override this setting. - /// This setting is false by default. - ///
- public bool UseLowLatencyDxgiSwapChain { get; set; } - - /// - /// Render directly on the UI thread instead of using a dedicated render thread. - /// Only applicable if both and - /// 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. - /// - public bool ShouldRenderOnUIThread { get; set; } - - /// - /// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu - /// - public IPlatformGraphics? CustomPlatformGraphics { get; set; } - } } namespace Avalonia.Win32 @@ -173,9 +100,23 @@ namespace Avalonia.Win32 .Bind().ToConstant(NonPumpingWaitHelperImpl.Instance) .Bind().ToConstant(new WindowsMountedVolumeInfoProvider()) .Bind().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().ToSingleton(); diff --git a/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs new file mode 100644 index 0000000000..bbb4c37d7e --- /dev/null +++ b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs @@ -0,0 +1,140 @@ +using System.Collections.Generic; +using Avalonia.OpenGL; +using Avalonia.Platform; + +namespace Avalonia; + +/// +/// Represents the rendering mode for platform graphics. +/// +public enum Win32RenderingMode +{ + /// + /// Avalonia is rendered into a framebuffer. + /// + Software = 1, + + /// + /// Enables ANGLE EGL for Windows with GPU rendering. + /// + AngleEgl = 2, + + /// + /// Avalonia would try to use native Widows OpenGL with GPU rendering. + /// + Wgl = 3 +} + +/// +/// Represents the Win32 window composition mode. +/// +public enum Win32CompositionMode +{ + /// + /// Render Avalonia to a texture inside the Windows.UI.Composition tree. + /// + /// + /// 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.
+ /// Can only be applied with =. + ///
+ WinUIComposition = 1, + + // /// + // /// Render Avalonia to a texture inside the DirectComposition tree. + // /// + // /// + // /// Supported on Windows 8 and above. Ignored on other versions.
+ // /// Can only be applied with =. + // ///
+ // DirectComposition = 2, + + /// + /// When 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 styling / blurring offered by .
+ /// Can only be applied with =. + ///
+ LowLatencyDxgiSwapChain = 3, + + /// + /// The window renders to a redirection surface. + /// + /// + /// This option is kept only for compatibility with older systems. Some Avalonia features might not work. + /// + RedirectionSurface, +} + +/// +/// Platform-specific options which apply to Windows. +/// +public class Win32PlatformOptions +{ + /// + /// Embeds popups to the window when set to true. The default value is false. + /// + public bool OverlayPopups { get; set; } + + /// + /// Gets or sets Avalonia rendering modes with fallbacks. + /// The first element in the array has the highest priority. + /// The default value is: , . + /// + /// + /// If application should work on as wide range of devices as possible, at least add as a fallback value. + /// + /// Thrown if no values were matched. + public IReadOnlyList RenderingMode { get; set; } = new[] + { + Win32RenderingMode.AngleEgl, Win32RenderingMode.Software + }; + + /// + /// Gets or sets Avalonia composition modes with fallbacks. + /// The first element in the array has the highest priority. + /// The default value is: , . + /// + /// + /// If application should work on as wide range of devices as possible, at least add as a fallback value. + /// + /// Thrown if no values were matched. + public IReadOnlyList CompositionMode { get; set; } = new[] + { + Win32CompositionMode.WinUIComposition, Win32CompositionMode.RedirectionSurface + }; + + /// + /// When is set to , 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. + /// + public float? WinUICompositionBackdropCornerRadius { get; set; } + + /// + /// Render directly on the UI thread instead of using a dedicated render thread. + /// Only applicable if is set to . + /// 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. + /// + public bool ShouldRenderOnUIThread { get; set; } + + /// + /// Windows OpenGL profiles used when is set to . + /// This setting is 4.0 and 3.2 by default. + /// + public IList WglProfiles { get; set; } = new List + { + new(GlProfileType.OpenGL, 4, 0), new(GlProfileType.OpenGL, 3, 2) + }; + + /// + /// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu. + /// When this property set is ignored + /// and only accepts null or . + /// + public IPlatformGraphics? CustomPlatformGraphics { get; set; } +} diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs index 95ef338f08..8da154dc83 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs +++ b/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() - ?.CompositionBackdropCornerRadius; + ?.WinUICompositionBackdropCornerRadius; _window ??= new WinUiCompositedWindow(_info, _shared, cornerRadius); _window.SetBlur(_blurEffect); diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs index f17805fba3..b3a328d097 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs +++ b/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); diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs index 754af86c06..596c94d30b 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs +++ b/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); diff --git a/src/tools/Avalonia.Generators/Avalonia.Generators.csproj b/src/tools/Avalonia.Generators/Avalonia.Generators.csproj index 88128d6b45..cd1e2fcaba 100644 --- a/src/tools/Avalonia.Generators/Avalonia.Generators.csproj +++ b/src/tools/Avalonia.Generators/Avalonia.Generators.csproj @@ -6,6 +6,7 @@ $(DefineConstants);XAMLX_INTERNAL true true + true diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs index 42720cbb4c..e664829677 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs +++ b/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() { diff --git a/tests/Avalonia.Base.UnitTests/Media/GeometryTests.cs b/tests/Avalonia.Base.UnitTests/Media/GeometryTests.cs index ae8dc9dad2..6d724c86b5 100644 --- a/tests/Avalonia.Base.UnitTests/Media/GeometryTests.cs +++ b/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( x => x.WithTransform(It.IsAny()) == diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs index f6130d49d6..c7ea5c1b69 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs +++ b/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() { diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj index 8ce0b8a984..0d49f78dd9 100644 --- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj +++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj @@ -22,5 +22,4 @@ -