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/Pages/CompositionPage.axaml.cs b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs index 0d3061f361..5bf46510dc 100644 --- a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs +++ b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs @@ -144,8 +144,8 @@ public partial class CompositionPage : UserControl { if(_solidVisual == null) return; - _solidVisual.Size = new Vector2((float)v.Bounds.Width / 3, (float)v.Bounds.Height / 3); - _solidVisual.Offset = new Vector3((float)v.Bounds.Width / 3, (float)v.Bounds.Height / 3, 0); + _solidVisual.Size = new (v.Bounds.Width / 3, v.Bounds.Height / 3); + _solidVisual.Offset = new (v.Bounds.Width / 3, v.Bounds.Height / 3, 0); } v.AttachedToVisualTree += delegate { @@ -164,7 +164,7 @@ public partial class CompositionPage : UserControl animation.Direction = PlaybackDirection.Alternate; _solidVisual.StartAnimation("Color", animation); - _solidVisual.AnchorPoint = new Vector2(0, 0); + _solidVisual.AnchorPoint = new (0, 0); var scale = _solidVisual.Compositor.CreateVector3KeyFrameAnimation(); scale.Duration = TimeSpan.FromSeconds(5); @@ -195,8 +195,8 @@ public partial class CompositionPage : UserControl if (_customVisual == null) return; var h = (float)Math.Min(v.Bounds.Height, v.Bounds.Width / 3); - _customVisual.Size = new Vector2((float)v.Bounds.Width, h); - _customVisual.Offset = new Vector3(0, (float)(v.Bounds.Height - h) / 2, 0); + _customVisual.Size = new (v.Bounds.Width, h); + _customVisual.Offset = new (0, (v.Bounds.Height - h) / 2, 0); } v.AttachedToVisualTree += delegate { diff --git a/samples/ControlCatalog/Pages/GesturePage.cs b/samples/ControlCatalog/Pages/GesturePage.cs index cc4429f414..c81ba4fb17 100644 --- a/samples/ControlCatalog/Pages/GesturePage.cs +++ b/samples/ControlCatalog/Pages/GesturePage.cs @@ -13,7 +13,7 @@ namespace ControlCatalog.Pages public class GesturePage : UserControl { private bool _isInit; - private float _currentScale; + private double _currentScale; public GesturePage() { @@ -53,7 +53,7 @@ namespace ControlCatalog.Pages if(compositionVisual!= null) { _currentScale = 1; - compositionVisual.Scale = new Vector3(1,1,1); + compositionVisual.Scale = new (1,1,1); compositionVisual.Offset = default; image.InvalidateMeasure(); } @@ -69,7 +69,7 @@ namespace ControlCatalog.Pages } _currentScale = 1; - Vector3 currentOffset = default; + Vector3D currentOffset = default; CompositionVisual? compositionVisual = null; @@ -133,11 +133,11 @@ namespace ControlCatalog.Pages if (compositionVisual != null && _currentScale != 1) { - currentOffset += new Vector3((float)e.Delta.X, (float)e.Delta.Y, 0); + currentOffset += new Vector3D(e.Delta.X, e.Delta.Y, 0); var currentSize = control.Bounds.Size * _currentScale; - currentOffset = new Vector3((float)MathUtilities.Clamp(currentOffset.X, 0, currentSize.Width - control.Bounds.Width), + currentOffset = new Vector3D(MathUtilities.Clamp(currentOffset.X, 0, currentSize.Width - control.Bounds.Width), (float)MathUtilities.Clamp(currentOffset.Y, 0, currentSize.Height - control.Bounds.Height), 0); @@ -157,7 +157,7 @@ namespace ControlCatalog.Pages var ball = control.FindLogicalDescendantOfType(); - Vector3 defaultOffset = default; + Vector3D defaultOffset = default; CompositionVisual? ballCompositionVisual = null; @@ -181,11 +181,11 @@ namespace ControlCatalog.Pages control.AddHandler(Gestures.PullGestureEvent, (s, e) => { - Vector3 center = new((float)control.Bounds.Center.X, (float)control.Bounds.Center.Y, 0); + Vector3D center = new((float)control.Bounds.Center.X, (float)control.Bounds.Center.Y, 0); InitComposition(ball!); if (ballCompositionVisual != null) { - ballCompositionVisual.Offset = defaultOffset + new System.Numerics.Vector3((float)e.Delta.X * 0.4f, (float)e.Delta.Y * 0.4f, 0) * (inverse ? -1 : 1); + ballCompositionVisual.Offset = defaultOffset + new Vector3D(e.Delta.X * 0.4f, e.Delta.Y * 0.4f, 0) * (inverse ? -1 : 1); e.Handled = true; } diff --git a/samples/GpuInterop/DrawingSurfaceDemoBase.cs b/samples/GpuInterop/DrawingSurfaceDemoBase.cs index aad813ea82..367436e5a5 100644 --- a/samples/GpuInterop/DrawingSurfaceDemoBase.cs +++ b/samples/GpuInterop/DrawingSurfaceDemoBase.cs @@ -48,7 +48,7 @@ public abstract class DrawingSurfaceDemoBase : Control, IGpuDemo Surface = _compositor.CreateDrawingSurface(); _visual = _compositor.CreateSurfaceVisual(); - _visual.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height); + _visual.Size = new (Bounds.Width, Bounds.Height); _visual.Surface = Surface; ElementComposition.SetElementChildVisual(this, _visual); var (res, info) = await DoInitialize(_compositor, Surface); @@ -72,7 +72,7 @@ public abstract class DrawingSurfaceDemoBase : Control, IGpuDemo if (root == null) return; - _visual!.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height); + _visual!.Size = new (Bounds.Width, Bounds.Height); var size = PixelSize.FromSize(Bounds.Size, root.RenderScaling); RenderFrame(size); if (SupportsDisco && Disco > 0) 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/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index 6929f192c7..f96abcac96 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,4 +1,11 @@ + + + + + + + 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/Animation/AnimationInstance`1.cs b/src/Avalonia.Base/Animation/AnimationInstance`1.cs index 92d2c2c8b5..9d4575b9ad 100644 --- a/src/Avalonia.Base/Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Base/Animation/AnimationInstance`1.cs @@ -58,6 +58,9 @@ namespace Avalonia.Animation if (_animation.Duration < TimeSpan.Zero) throw new InvalidOperationException("Duration value cannot be negative."); + if (_animation.Delay < TimeSpan.Zero) + throw new InvalidOperationException("Delay value cannot be negative."); + _easeFunc = _animation.Easing; _speedRatioConv = 1d / _animation.SpeedRatio; @@ -151,72 +154,74 @@ namespace Avalonia.Animation var iterDelay = _iterationDelay.Ticks * _speedRatioConv; var initDelay = _initialDelay.Ticks * _speedRatioConv; - if (indexTime > 0 & indexTime <= initDelay) + // This conditional checks if the time given is the very start/zero + // and when we have an active delay time. + if (initDelay > 0 && indexTime <= initDelay) { DoDelay(); + return; } - else + + // Calculate timebases. + var iterationTime = iterDuration + iterDelay; + var opsTime = indexTime - initDelay; + var playbackTime = opsTime % iterationTime; + + _currentIteration = (ulong)(opsTime / iterationTime); + + // Stop animation when the current iteration is beyond the iteration count or + // when the duration is set to zero while animating and snap to the last iterated value. + if (_currentIteration + 1 > _iterationCount || _duration == TimeSpan.Zero) { - // Calculate timebases. - var iterationTime = iterDuration + iterDelay; - var opsTime = indexTime - initDelay; - var playbackTime = opsTime % iterationTime; + var easedTime = _easeFunc!.Ease(_playbackReversed ? 0.0 : 1.0); + _lastInterpValue = _interpolator(easedTime, _neutralValue); + DoComplete(); + } - _currentIteration = (ulong)(opsTime / iterationTime); + if (playbackTime <= iterDuration) + { + // Normalize time for interpolation. + var normalizedTime = playbackTime / iterDuration; - // Stop animation when the current iteration is beyond the iteration count or - // when the duration is set to zero while animating and snap to the last iterated value. - if (_currentIteration + 1 > _iterationCount || _duration == TimeSpan.Zero) - { - var easedTime = _easeFunc!.Ease(_playbackReversed ? 0.0 : 1.0); - _lastInterpValue = _interpolator(easedTime, _neutralValue); - DoComplete(); - } + // Check if normalized time needs to be reversed according to PlaybackDirection - if (playbackTime <= iterDuration) - { - // Normalize time for interpolation. - var normalizedTime = playbackTime / iterDuration; - - // Check if normalized time needs to be reversed according to PlaybackDirection - - switch (_playbackDirection) - { - case PlaybackDirection.Normal: - _playbackReversed = false; - break; - case PlaybackDirection.Reverse: - _playbackReversed = true; - break; - case PlaybackDirection.Alternate: - _playbackReversed = _currentIteration % 2 != 0; - break; - case PlaybackDirection.AlternateReverse: - _playbackReversed = _currentIteration % 2 == 0; - break; - default: - throw new InvalidOperationException($"Animation direction value is unknown: {_playbackDirection}"); - } - - if (_playbackReversed) - normalizedTime = 1 - normalizedTime; - - // Ease and interpolate - var easedTime = _easeFunc!.Ease(normalizedTime); - _lastInterpValue = _interpolator(easedTime, _neutralValue); - - PublishNext(_lastInterpValue); - } - else if (playbackTime > iterDuration & - playbackTime <= iterationTime & - iterDelay > 0) + switch (_playbackDirection) { - // The last iteration's trailing delay should be skipped. - if (_currentIteration + 1 < _iterationCount) - DoDelay(); - else - DoComplete(); + case PlaybackDirection.Normal: + _playbackReversed = false; + break; + case PlaybackDirection.Reverse: + _playbackReversed = true; + break; + case PlaybackDirection.Alternate: + _playbackReversed = _currentIteration % 2 != 0; + break; + case PlaybackDirection.AlternateReverse: + _playbackReversed = _currentIteration % 2 == 0; + break; + default: + throw new InvalidOperationException( + $"Animation direction value is unknown: {_playbackDirection}"); } + + if (_playbackReversed) + normalizedTime = 1 - normalizedTime; + + // Ease and interpolate + var easedTime = _easeFunc!.Ease(normalizedTime); + _lastInterpValue = _interpolator(easedTime, _neutralValue); + + PublishNext(_lastInterpValue); + } + else if (playbackTime > iterDuration && + playbackTime <= iterationTime && + iterDelay > 0) + { + // The last iteration's trailing delay should be skipped. + if (_currentIteration + 1 < _iterationCount) + DoDelay(); + else + DoComplete(); } } 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/Media/BoxShadow.cs b/src/Avalonia.Base/Media/BoxShadow.cs index 91529353ed..a0e1873d30 100644 --- a/src/Avalonia.Base/Media/BoxShadow.cs +++ b/src/Avalonia.Base/Media/BoxShadow.cs @@ -73,40 +73,44 @@ namespace Avalonia.Media public override string ToString() { var sb = StringBuilderCache.Acquire(); + ToString(sb); + return StringBuilderCache.GetStringAndRelease(sb); + } + internal void ToString(StringBuilder sb) + { if (this == default) { - return "none"; + sb.Append("none"); + return; } if (IsInset) { - sb.Append("inset"); + sb.Append("inset "); } if (OffsetX != 0.0) { - sb.AppendFormat(" {0}", OffsetX.ToString(CultureInfo.InvariantCulture)); + sb.AppendFormat("{0} ", OffsetX.ToString(CultureInfo.InvariantCulture)); } if (OffsetY != 0.0) { - sb.AppendFormat(" {0}", OffsetY.ToString(CultureInfo.InvariantCulture)); + sb.AppendFormat("{0} ", OffsetY.ToString(CultureInfo.InvariantCulture)); } - + if (Blur != 0.0) { - sb.AppendFormat(" {0}", Blur.ToString(CultureInfo.InvariantCulture)); + sb.AppendFormat("{0} ", Blur.ToString(CultureInfo.InvariantCulture)); } if (Spread != 0.0) { - sb.AppendFormat(" {0}", Spread.ToString(CultureInfo.InvariantCulture)); + sb.AppendFormat("{0} ", Spread.ToString(CultureInfo.InvariantCulture)); } - sb.AppendFormat(" {0}", Color.ToString()); - - return StringBuilderCache.GetStringAndRelease(sb); + sb.AppendFormat("{0}", Color.ToString()); } public static unsafe BoxShadow Parse(string s) diff --git a/src/Avalonia.Base/Media/BoxShadows.cs b/src/Avalonia.Base/Media/BoxShadows.cs index 385f73f703..3ddd8e0a95 100644 --- a/src/Avalonia.Base/Media/BoxShadows.cs +++ b/src/Avalonia.Base/Media/BoxShadows.cs @@ -39,20 +39,20 @@ namespace Avalonia.Media public override string ToString() { - var sb = StringBuilderCache.Acquire(); - if (Count == 0) { return "none"; } + var sb = StringBuilderCache.Acquire(); foreach (var boxShadow in this) { - sb.AppendFormat("{0} ", boxShadow.ToString()); + boxShadow.ToString(sb); + sb.Append(','); + sb.Append(' '); } - + sb.Remove(sb.Length - 2, 2); return StringBuilderCache.GetStringAndRelease(sb); - } [EditorBrowsable(EditorBrowsableState.Never)] 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/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/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/Composition/Animations/Interpolators.cs b/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs index e19c0ff098..0df21c2c1d 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs @@ -19,6 +19,12 @@ namespace Avalonia.Rendering.Composition.Animations public static ScalarInterpolator Instance { get; } = new ScalarInterpolator(); } + class DoubleInterpolator : IInterpolator + { + public double Interpolate(double @from, double to, float progress) => @from + (to - @from) * progress; + + public static DoubleInterpolator Instance { get; } = new (); + } class Vector2Interpolator : IInterpolator { @@ -28,6 +34,15 @@ namespace Avalonia.Rendering.Composition.Animations public static Vector2Interpolator Instance { get; } = new Vector2Interpolator(); } + class VectorInterpolator : IInterpolator + { + public Vector Interpolate(Vector @from, Vector to, float progress) + => new(DoubleInterpolator.Instance.Interpolate(from.X, to.X, progress), + DoubleInterpolator.Instance.Interpolate(from.Y, to.Y, progress)); + + public static VectorInterpolator Instance { get; } = new (); + } + class Vector3Interpolator : IInterpolator { public Vector3 Interpolate(Vector3 @from, Vector3 to, float progress) @@ -36,6 +51,16 @@ namespace Avalonia.Rendering.Composition.Animations public static Vector3Interpolator Instance { get; } = new Vector3Interpolator(); } + class Vector3DInterpolator : IInterpolator + { + public Vector3D Interpolate(Vector3D @from, Vector3D to, float progress) + => new(DoubleInterpolator.Instance.Interpolate(from.X, to.X, progress), + DoubleInterpolator.Instance.Interpolate(from.Y, to.Y, progress), + DoubleInterpolator.Instance.Interpolate(from.Z, to.Z, progress)); + + public static Vector3DInterpolator Instance { get; } = new (); + } + class Vector4Interpolator : IInterpolator { public Vector4 Interpolate(Vector4 @from, Vector4 to, float progress) diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index 61a3f7e831..368743f1b5 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -241,8 +241,8 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester continue; // TODO: Optimize all of that by moving to the Visual itself, so we won't have to recalculate every time - comp.Offset = new Vector3((float)visual.Bounds.Left, (float)visual.Bounds.Top, 0); - comp.Size = new Vector2((float)visual.Bounds.Width, (float)visual.Bounds.Height); + comp.Offset = new (visual.Bounds.Left, visual.Bounds.Top, 0); + comp.Size = new (visual.Bounds.Width, visual.Bounds.Height); comp.Visible = visual.IsVisible; comp.Opacity = (float)visual.Opacity; comp.ClipToBounds = visual.ClipToBounds; @@ -269,7 +269,7 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester renderTransform *= (-offset) * visual.RenderTransform.Value * (offset); } - comp.TransformMatrix = MatrixUtils.ToMatrix4x4(renderTransform); + comp.TransformMatrix = renderTransform; try { diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs b/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs index b7ed6fe612..598d4163d1 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs @@ -28,7 +28,7 @@ public abstract class CompositionCustomVisualHandler _host.Compositor.VerifyAccess(); } - protected Vector2 EffectiveSize + protected Vector EffectiveSize { get { diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs index 91d2fb59de..121d11bf2a 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs @@ -70,7 +70,7 @@ namespace Avalonia.Rendering.Composition return false; } - var m33 = MatrixUtils.ToMatrix(m.Value); + var m33 = m.Value; return m33.TryInvert(out matrix); } diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs index 896ae4fc3a..2fdf163153 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs @@ -16,6 +16,7 @@ namespace Avalonia.Rendering.Composition.Expressions private readonly DelegateExpressionFfi _registry; static float Lerp(float a, float b, float p) => p * (b - a) + a; + static double Lerp(double a, double b, double p) => p * (b - a) + a; static Matrix3x2 Inverse(Matrix3x2 m) { @@ -34,6 +35,13 @@ namespace Avalonia.Rendering.Composition.Expressions var t = MathUtilities.Clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); return t * t * (3.0f - 2.0f * t); } + + static double SmoothStep(double edge0, double edge1, double x) + { + var t = MathUtilities.Clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); + return t * t * (3.0f - 2.0f * t); + } + static Vector2 SmoothStep(Vector2 edge0, Vector2 edge1, Vector2 x) { @@ -43,6 +51,15 @@ namespace Avalonia.Rendering.Composition.Expressions ); } + + static Vector SmoothStep(Vector edge0, Vector edge1, Vector x) + { + return new ( + SmoothStep(edge0.X, edge1.X, x.X), + SmoothStep(edge0.Y, edge1.Y, x.Y) + ); + } + static Vector3 SmoothStep(Vector3 edge0, Vector3 edge1, Vector3 x) { return new Vector3( @@ -52,6 +69,15 @@ namespace Avalonia.Rendering.Composition.Expressions ); } + + static Vector3D SmoothStep(Vector3D edge0, Vector3D edge1, Vector3D x) + { + return new ( + SmoothStep(edge0.X, edge1.X, x.X), + SmoothStep(edge0.Y, edge1.Y, x.Y), + SmoothStep(edge0.Z, edge1.Z, x.Z) + ); + } static Vector4 SmoothStep(Vector4 edge0, Vector4 edge1, Vector4 x) { @@ -69,7 +95,9 @@ namespace Avalonia.Rendering.Composition.Expressions { {"Abs", (float f) => Math.Abs(f)}, {"Abs", (Vector2 v) => Vector2.Abs(v)}, + {"Abs", (Vector v) => v.Abs()}, {"Abs", (Vector3 v) => Vector3.Abs(v)}, + {"Abs", (Vector3D v) => v.Abs()}, {"Abs", (Vector4 v) => Vector4.Abs(v)}, {"ACos", (float f) => (float) Math.Acos(f)}, @@ -79,7 +107,9 @@ namespace Avalonia.Rendering.Composition.Expressions {"Clamp", (float a1, float a2, float a3) => MathUtilities.Clamp(a1, a2, a3)}, {"Clamp", (Vector2 a1, Vector2 a2, Vector2 a3) => Vector2.Clamp(a1, a2, a3)}, + {"Clamp", (Vector a1, Vector a2, Vector a3) => Vector.Clamp(a1, a2, a3)}, {"Clamp", (Vector3 a1, Vector3 a2, Vector3 a3) => Vector3.Clamp(a1, a2, a3)}, + {"Clamp", (Vector3D a1, Vector3D a2, Vector3D a3) => Vector3D.Clamp(a1, a2, a3)}, {"Clamp", (Vector4 a1, Vector4 a2, Vector4 a3) => Vector4.Clamp(a1, a2, a3)}, {"Concatenate", (Quaternion a1, Quaternion a2) => Quaternion.Concatenate(a1, a2)}, @@ -109,11 +139,15 @@ namespace Avalonia.Rendering.Composition.Expressions }, {"Distance", (Vector2 a1, Vector2 a2) => Vector2.Distance(a1, a2)}, + {"Distance", (Vector a1, Vector a2) => Vector.Distance(a1, a2)}, {"Distance", (Vector3 a1, Vector3 a2) => Vector3.Distance(a1, a2)}, + {"Distance", (Vector3D a1, Vector3D a2) => Vector3D.Distance(a1, a2)}, {"Distance", (Vector4 a1, Vector4 a2) => Vector4.Distance(a1, a2)}, {"DistanceSquared", (Vector2 a1, Vector2 a2) => Vector2.DistanceSquared(a1, a2)}, + {"DistanceSquared", (Vector a1, Vector a2) => Vector.DistanceSquared(a1, a2)}, {"DistanceSquared", (Vector3 a1, Vector3 a2) => Vector3.DistanceSquared(a1, a2)}, + {"DistanceSquared", (Vector3D a1, Vector3D a2) => Vector3D.DistanceSquared(a1, a2)}, {"DistanceSquared", (Vector4 a1, Vector4 a2) => Vector4.DistanceSquared(a1, a2)}, {"Floor", (float v) => (float) Math.Floor(v)}, @@ -123,18 +157,24 @@ namespace Avalonia.Rendering.Composition.Expressions {"Length", (Vector2 a1) => a1.Length()}, + {"Length", (Vector a1) => a1.Length}, {"Length", (Vector3 a1) => a1.Length()}, + {"Length", (Vector3D a1) => a1.Length}, {"Length", (Vector4 a1) => a1.Length()}, {"Length", (Quaternion a1) => a1.Length()}, {"LengthSquared", (Vector2 a1) => a1.LengthSquared()}, + {"LengthSquared", (Vector a1) => a1*a1}, {"LengthSquared", (Vector3 a1) => a1.LengthSquared()}, + {"LengthSquared", (Vector3D a1) => Vector3D.Dot(a1, a1)}, {"LengthSquared", (Vector4 a1) => a1.LengthSquared()}, {"LengthSquared", (Quaternion a1) => a1.LengthSquared()}, {"Lerp", (float a1, float a2, float a3) => Lerp(a1, a2, a3)}, {"Lerp", (Vector2 a1, Vector2 a2, float a3) => Vector2.Lerp(a1, a2, a3)}, + {"Lerp", (Vector a1, Vector a2, float a3) => new Vector(Lerp(a1.X, a2.X, a3), Lerp(a1.Y, a2.Y, a3))}, {"Lerp", (Vector3 a1, Vector3 a2, float a3) => Vector3.Lerp(a1, a2, a3)}, + {"Lerp", (Vector3D a1, Vector3D a2, float a3) => new Vector3D(Lerp(a1.X, a2.X, a3), Lerp(a1.Y, a2.Y, a3), Lerp(a1.Z, a2.Z, a3))}, {"Lerp", (Vector4 a1, Vector4 a2, float a3) => Vector4.Lerp(a1, a2, a3)}, @@ -173,24 +213,31 @@ namespace Avalonia.Rendering.Composition.Expressions {"Max", (float a1, float a2) => Math.Max(a1, a2)}, {"Max", (Vector2 a1, Vector2 a2) => Vector2.Max(a1, a2)}, + {"Max", (Vector a1, Vector a2) => Vector.Max(a1, a2)}, {"Max", (Vector3 a1, Vector3 a2) => Vector3.Max(a1, a2)}, + {"Max", (Vector3D a1, Vector3D a2) => Vector3D.Max(a1, a2)}, {"Max", (Vector4 a1, Vector4 a2) => Vector4.Max(a1, a2)}, {"Min", (float a1, float a2) => Math.Min(a1, a2)}, {"Min", (Vector2 a1, Vector2 a2) => Vector2.Min(a1, a2)}, + {"Min", (Vector a1, Vector a2) => Vector.Min(a1, a2)}, {"Min", (Vector3 a1, Vector3 a2) => Vector3.Min(a1, a2)}, + {"Min", (Vector3D a1, Vector3D a2) => Vector3D.Min(a1, a2)}, {"Min", (Vector4 a1, Vector4 a2) => Vector4.Min(a1, a2)}, {"Mod", (float a, float b) => a % b}, {"Normalize", (Quaternion a) => Quaternion.Normalize(a)}, {"Normalize", (Vector2 a) => Vector2.Normalize(a)}, + {"Normalize", (Vector a) => Vector.Normalize(a)}, {"Normalize", (Vector3 a) => Vector3.Normalize(a)}, + {"Normalize", (Vector3D a) => Vector3D.Normalize(a)}, {"Normalize", (Vector4 a) => Vector4.Normalize(a)}, {"Pow", (float a, float b) => (float) Math.Pow(a, b)}, {"Quaternion.CreateFromAxisAngle", (Vector3 a, float b) => Quaternion.CreateFromAxisAngle(a, b)}, + {"Quaternion.CreateFromAxisAngle", (Vector3D a, float b) => Quaternion.CreateFromAxisAngle(a.ToVector3(), b)}, {"Quaternion", (float a, float b, float c, float d) => new Quaternion(a, b, c, d)}, {"Round", (float a) => (float) Math.Round(a)}, @@ -198,14 +245,18 @@ namespace Avalonia.Rendering.Composition.Expressions {"Scale", (Matrix3x2 a, float b) => a * b}, {"Scale", (Matrix4x4 a, float b) => a * b}, {"Scale", (Vector2 a, float b) => a * b}, + {"Scale", (Vector a, float b) => a * b}, {"Scale", (Vector3 a, float b) => a * b}, + {"Scale", (Vector3D a, float b) => Vector3D.Multiply(a, b)}, {"Scale", (Vector4 a, float b) => a * b}, {"Sin", (float a) => (float) Math.Sin(a)}, {"SmoothStep", (float a1, float a2, float a3) => SmoothStep(a1, a2, a3)}, {"SmoothStep", (Vector2 a1, Vector2 a2, Vector2 a3) => SmoothStep(a1, a2, a3)}, + {"SmoothStep", (Vector a1, Vector a2, Vector a3) => SmoothStep(a1, a2, a3)}, {"SmoothStep", (Vector3 a1, Vector3 a2, Vector3 a3) => SmoothStep(a1, a2, a3)}, + {"SmoothStep", (Vector3D a1, Vector3D a2, Vector3D a3) => SmoothStep(a1, a2, a3)}, {"SmoothStep", (Vector4 a1, Vector4 a2, Vector4 a3) => SmoothStep(a1, a2, a3)}, // I have no idea how to do a spherical interpolation for a scalar value, so we are doing a linear one @@ -222,9 +273,13 @@ namespace Avalonia.Rendering.Composition.Expressions {"Transform", (Vector2 a, Matrix3x2 b) => Vector2.Transform(a, b)}, {"Transform", (Vector3 a, Matrix4x4 b) => Vector3.Transform(a, b)}, - {"Vector2", (float a, float b) => new Vector2(a, b)}, - {"Vector3", (float a, float b, float c) => new Vector3(a, b, c)}, + {"Vector2", (float a, float b) => new Vector(a, b)}, + {"Vector2", (double a, double b) => new Vector(a, b)}, + {"Vector3", (float a, float b, float c) => new Vector3D(a, b, c)}, + {"Vector3", (double a, double b, double c) => new Vector3D(a, b, c)}, {"Vector3", (Vector2 v2, float z) => new Vector3(v2, z)}, + {"Vector3", (Vector v2, float z) => new Vector3D(v2.X, v2.Y, z)}, + {"Vector3", (Vector v2, double z) => new Vector3D(v2.X, v2.Y, z)}, {"Vector4", (float a, float b, float c, float d) => new Vector4(a, b, c, d)}, {"Vector4", (Vector2 v2, float z, float w) => new Vector4(v2, z, w)}, {"Vector4", (Vector3 v3, float w) => new Vector4(v3, w)}, diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs index c15487065c..ad8d370478 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs @@ -49,6 +49,46 @@ namespace Avalonia.Rendering.Composition.Expressions } } + return CallWithCast(countGroup, arguments, out result, false); + } + + bool CallWithCast(List countGroup, IReadOnlyList arguments, out ExpressionVariant result, bool anyCast) + { + result = default; + foreach (var record in countGroup) + { + var match = true; + for (var c = 0; c < arguments.Count; c++) + { + var parameter = record.Types[c]; + var arg = arguments[c].Type; + if (parameter != arg) + { + var canCast = (parameter == VariantType.Double && arg == VariantType.Scalar) + || (parameter == VariantType.Vector3D && arg == VariantType.Vector3) + || (parameter == VariantType.Vector && arg == VariantType.Vector2) + || (anyCast && ( + (arg == VariantType.Double && parameter == VariantType.Scalar) + || (arg == VariantType.Vector3D && parameter == VariantType.Vector3) + || (arg == VariantType.Vector && parameter == VariantType.Vector2) + )); + if (!canCast) + { + match = false; + break; + } + } + } + + if (match) + { + result = record.Delegate(arguments); + return true; + } + } + + if (anyCast == false) + return CallWithCast(countGroup, arguments, out result, true); return false; } @@ -75,8 +115,11 @@ namespace Avalonia.Rendering.Composition.Expressions { [typeof(bool)] = VariantType.Boolean, [typeof(float)] = VariantType.Scalar, + [typeof(double)] = VariantType.Double, [typeof(Vector2)] = VariantType.Vector2, + [typeof(Vector)] = VariantType.Vector, [typeof(Vector3)] = VariantType.Vector3, + [typeof(Vector3D)] = VariantType.Vector3D, [typeof(Vector4)] = VariantType.Vector4, [typeof(Matrix3x2)] = VariantType.Matrix3x2, [typeof(Matrix4x4)] = VariantType.Matrix4x4, diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs index 21f14283b5..a97b959fe1 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs @@ -17,6 +17,8 @@ namespace Avalonia.Rendering.Composition.Expressions Vector2, Vector3, Vector4, + Vector, + Vector3D, AvaloniaMatrix, Matrix3x2, Matrix4x4, @@ -38,6 +40,8 @@ namespace Avalonia.Rendering.Composition.Expressions [FieldOffset(4)] public Vector2 Vector2; [FieldOffset(4)] public Vector3 Vector3; [FieldOffset(4)] public Vector4 Vector4; + [FieldOffset(4)] public Vector Vector; + [FieldOffset(4)] public Vector3D Vector3D; [FieldOffset(4)] public Matrix AvaloniaMatrix; [FieldOffset(4)] public Matrix3x2 Matrix3x2; [FieldOffset(4)] public Matrix4x4 Matrix4x4; @@ -55,6 +59,15 @@ namespace Avalonia.Rendering.Composition.Expressions return Vector2.Y; return default; } + + if (Type == VariantType.Vector) + { + if (ReferenceEquals(property, "X")) + return Vector.X; + if (ReferenceEquals(property, "Y")) + return Vector.Y; + return default; + } if (Type == VariantType.Vector3) { @@ -78,6 +91,29 @@ namespace Avalonia.Rendering.Composition.Expressions return new Vector2(Vector3.Z, Vector3.Y); return default; } + + if (Type == VariantType.Vector3D) + { + if (ReferenceEquals(property, "X")) + return Vector3D.X; + if (ReferenceEquals(property, "Y")) + return Vector3D.Y; + if (ReferenceEquals(property, "Z")) + return Vector3D.Z; + if(ReferenceEquals(property, "XY")) + return new Vector(Vector3D.X, Vector3D.Y); + if(ReferenceEquals(property, "YX")) + return new Vector(Vector3D.Y, Vector3D.X); + if(ReferenceEquals(property, "XZ")) + return new Vector(Vector3D.X, Vector3D.Z); + if(ReferenceEquals(property, "ZX")) + return new Vector(Vector3D.Z, Vector3D.X); + if(ReferenceEquals(property, "YZ")) + return new Vector(Vector3D.Y, Vector3D.Z); + if(ReferenceEquals(property, "ZY")) + return new Vector(Vector3D.Z, Vector3D.Y); + return default; + } if (Type == VariantType.Vector4) { @@ -115,14 +151,20 @@ namespace Avalonia.Rendering.Composition.Expressions return AvaloniaMatrix.M11; if (ReferenceEquals(property, "M12")) return AvaloniaMatrix.M12; + if (ReferenceEquals(property, "M13")) + return AvaloniaMatrix.M13; if (ReferenceEquals(property, "M21")) return AvaloniaMatrix.M21; if (ReferenceEquals(property, "M22")) return AvaloniaMatrix.M22; + if (ReferenceEquals(property, "M23")) + return AvaloniaMatrix.M23; if (ReferenceEquals(property, "M31")) return AvaloniaMatrix.M31; if (ReferenceEquals(property, "M32")) return AvaloniaMatrix.M32; + if (ReferenceEquals(property, "M33")) + return AvaloniaMatrix.M33; return default; } @@ -220,7 +262,13 @@ namespace Avalonia.Rendering.Composition.Expressions Type = VariantType.Vector2, Vector2 = value }; - + + public static implicit operator ExpressionVariant(Vector value) => + new ExpressionVariant + { + Type = VariantType.Vector, + Vector = value + }; public static implicit operator ExpressionVariant(Vector3 value) => new ExpressionVariant @@ -228,6 +276,13 @@ namespace Avalonia.Rendering.Composition.Expressions Type = VariantType.Vector3, Vector3 = value }; + + public static implicit operator ExpressionVariant(Vector3D value) => + new ExpressionVariant + { + Type = VariantType.Vector3D, + Vector3D = value + }; public static implicit operator ExpressionVariant(Vector4 value) => @@ -285,10 +340,16 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Vector2) return left.Vector2 + right.Vector2; + + if (left.Type == VariantType.Vector) + return left.Vector + right.Vector; if (left.Type == VariantType.Vector3) return left.Vector3 + right.Vector3; + if (left.Type == VariantType.Vector3D) + return Avalonia.Vector3D.Add(left.Vector3D, right.Vector3D); + if (left.Type == VariantType.Vector4) return left.Vector4 + right.Vector4; @@ -317,10 +378,16 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Vector2) return left.Vector2 - right.Vector2; + + if (left.Type == VariantType.Vector) + return left.Vector - right.Vector; if (left.Type == VariantType.Vector3) return left.Vector3 - right.Vector3; + if (left.Type == VariantType.Vector3D) + return Vector3D.Add(left.Vector3D, -right.Vector3D); + if (left.Type == VariantType.Vector4) return left.Vector4 - right.Vector4; @@ -347,9 +414,15 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Vector2) return -left.Vector2; + + if (left.Type == VariantType.Vector) + return -left.Vector; if (left.Type == VariantType.Vector3) return -left.Vector3; + + if (left.Type == VariantType.Vector3D) + return -left.Vector3D; if (left.Type == VariantType.Vector4) return -left.Vector4; @@ -383,14 +456,29 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Vector2 && right.Type == VariantType.Vector2) return left.Vector2 * right.Vector2; + if (left.Type == VariantType.Vector && right.Type == VariantType.Vector) + return Vector.Multiply(left.Vector, right.Vector); + if (left.Type == VariantType.Vector2 && right.Type == VariantType.Scalar) return left.Vector2 * right.Scalar; + + if (left.Type == VariantType.Vector && right.Type == VariantType.Scalar) + return left.Vector * right.Scalar; + + if (left.Type == VariantType.Vector && right.Type == VariantType.Double) + return left.Vector * right.Double; if (left.Type == VariantType.Vector3 && right.Type == VariantType.Vector3) return left.Vector3 * right.Vector3; + + if (left.Type == VariantType.Vector3D && right.Type == VariantType.Vector3D) + return Vector3D.Multiply(left.Vector3D, right.Vector3D); if (left.Type == VariantType.Vector3 && right.Type == VariantType.Scalar) return left.Vector3 * right.Scalar; + + if (left.Type == VariantType.Vector3D && right.Type == VariantType.Scalar) + return Vector3D.Multiply(left.Vector3D, right.Scalar); if (left.Type == VariantType.Vector4 && right.Type == VariantType.Vector4) return left.Vector4 * right.Vector4; @@ -436,15 +524,33 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Vector2 && right.Type == VariantType.Vector2) return left.Vector2 / right.Vector2; + if (left.Type == VariantType.Vector && right.Type == VariantType.Vector) + return Vector.Divide(left.Vector, right.Vector); + if (left.Type == VariantType.Vector2 && right.Type == VariantType.Scalar) return left.Vector2 / right.Scalar; + + if (left.Type == VariantType.Vector && right.Type == VariantType.Scalar) + return left.Vector / right.Scalar; + + if (left.Type == VariantType.Vector && right.Type == VariantType.Double) + return left.Vector / right.Scalar; if (left.Type == VariantType.Vector3 && right.Type == VariantType.Vector3) return left.Vector3 / right.Vector3; + if (left.Type == VariantType.Vector3D && right.Type == VariantType.Vector3D) + return Vector3D.Divide(left.Vector3D, right.Vector3D); + if (left.Type == VariantType.Vector3 && right.Type == VariantType.Scalar) return left.Vector3 / right.Scalar; + if (left.Type == VariantType.Vector3D && right.Type == VariantType.Scalar) + return Avalonia.Vector3D.Divide(left.Vector3D, right.Scalar); + + if (left.Type == VariantType.Vector3D && right.Type == VariantType.Double) + return Avalonia.Vector3D.Divide(left.Vector3D, right.Double); + if (left.Type == VariantType.Vector4 && right.Type == VariantType.Vector4) return left.Vector4 / right.Vector4; @@ -471,9 +577,15 @@ namespace Avalonia.Rendering.Composition.Expressions if (Type == VariantType.Vector2) return Vector2 == right.Vector2; - + + if (Type == VariantType.Vector) + return Vector == right.Vector; + if (Type == VariantType.Vector3) return Vector3 == right.Vector3; + + if (Type == VariantType.Vector3D) + return Vector3D == right.Vector3D; if (Type == VariantType.Vector4) return Vector4 == right.Vector4; @@ -571,6 +683,11 @@ namespace Avalonia.Rendering.Composition.Expressions res = (T) (object) Scalar; return true; } + if (Type == VariantType.Double) + { + res = (T)(object)Scalar; + return true; + } } if (typeof(T) == typeof(double)) @@ -580,6 +697,12 @@ namespace Avalonia.Rendering.Composition.Expressions res = (T) (object) Double; return true; } + + if (Type == VariantType.Scalar) + { + res = (T)(object)(float)Double; + return true; + } } if (typeof(T) == typeof(Vector2)) @@ -589,6 +712,27 @@ namespace Avalonia.Rendering.Composition.Expressions res = (T) (object) Vector2; return true; } + + if (Type == VariantType.Vector) + { + res = (T) (object) Vector.ToVector2(); + return true; + } + } + + if (typeof(T) == typeof(Vector)) + { + if (Type == VariantType.Vector) + { + res = (T) (object) Vector; + return true; + } + + if (Type == VariantType.Vector2) + { + res = (T)(object)new Vector(Vector2); + return true; + } } if (typeof(T) == typeof(Vector3)) @@ -598,6 +742,26 @@ namespace Avalonia.Rendering.Composition.Expressions res = (T) (object) Vector3; return true; } + if (Type == VariantType.Vector3D) + { + res = (T) (object) Vector3D.ToVector3(); + return true; + } + } + + if (typeof(T) == typeof(Vector3D)) + { + if (Type == VariantType.Vector3D) + { + res = (T) (object) Vector3D; + return true; + } + + if (Type == VariantType.Vector3) + { + res = (T)(object)new Vector3D(Vector3); + return true; + } } if (typeof(T) == typeof(Vector4)) @@ -668,9 +832,15 @@ namespace Avalonia.Rendering.Composition.Expressions if (typeof(T) == typeof(Vector2)) return (Vector2) (object) v; + + if (typeof(T) == typeof(Vector)) + return (Vector) (object) v; if (typeof(T) == typeof(Vector3)) return (Vector3) (object) v; + + if (typeof(T) == typeof(Vector3D)) + return (Vector3D) (object) v; if (typeof(T) == typeof(Vector4)) return (Vector4) (object) v; @@ -709,8 +879,12 @@ namespace Avalonia.Rendering.Composition.Expressions return Double.ToString(CultureInfo.InvariantCulture); if (Type == VariantType.Vector2) return Vector2.ToString(); + if (Type == VariantType.Vector) + return Vector.ToString(); if (Type == VariantType.Vector3) return Vector3.ToString(); + if (Type == VariantType.Vector3D) + return Vector3D.ToString(); if (Type == VariantType.Vector4) return Vector4.ToString(); if (Type == VariantType.Quaternion) diff --git a/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs b/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs index 3d8d5bae78..714895cd71 100644 --- a/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs +++ b/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs @@ -6,41 +6,46 @@ namespace Avalonia.Rendering.Composition { static class MatrixUtils { - public static Matrix4x4 ComputeTransform(Vector2 size, Vector2 anchorPoint, Vector3 centerPoint, - Matrix4x4 transformMatrix, Vector3 scale, float rotationAngle, Quaternion orientation, Vector3 offset) + public static Matrix ComputeTransform(Vector size, Vector anchorPoint, Vector3D centerPoint, + Matrix transformMatrix, Vector3D scale, float rotationAngle, Quaternion orientation, Vector3D offset) { // The math here follows the *observed* UWP behavior since there are no docs on how it's supposed to work - - var anchor = size * anchorPoint; - var mat = Matrix4x4.CreateTranslation(-anchor.X, -anchor.Y, 0); - var center = new Vector3(centerPoint.X, centerPoint.Y, centerPoint.Z); + var anchor = Vector.Multiply(size, anchorPoint); + var mat = Matrix.CreateTranslation(-anchor.X, -anchor.Y); + + var center = new Vector3D(centerPoint.X, centerPoint.Y, centerPoint.Z); if (!transformMatrix.IsIdentity) mat = transformMatrix * mat; - if (scale != new Vector3(1, 1, 1)) - mat *= Matrix4x4.CreateScale(scale, center); + if (scale != new Vector3D(1, 1, 1)) + mat *= ToMatrix(Matrix4x4.CreateScale(scale.ToVector3(), center.ToVector3())); //TODO: RotationAxis support if (rotationAngle != 0) - mat *= Matrix4x4.CreateRotationZ(rotationAngle, center); + mat *= ToMatrix(Matrix4x4.CreateRotationZ(rotationAngle, center.ToVector3())); if (orientation != Quaternion.Identity) { if (centerPoint != default) { - mat *= Matrix4x4.CreateTranslation(-center) - * Matrix4x4.CreateFromQuaternion(orientation) - * Matrix4x4.CreateTranslation(center); + mat *= ToMatrix(Matrix4x4.CreateTranslation(-center.ToVector3()) + * Matrix4x4.CreateFromQuaternion(orientation) + * Matrix4x4.CreateTranslation(center.ToVector3())); } else - mat *= Matrix4x4.CreateFromQuaternion(orientation); + mat *= ToMatrix(Matrix4x4.CreateFromQuaternion(orientation)); } if (offset != default) - mat *= Matrix4x4.CreateTranslation(offset); + { + if (offset.Z == 0) + mat *= Matrix.CreateTranslation(offset.X, offset.Y); + else + mat *= ToMatrix(Matrix4x4.CreateTranslation(offset.ToVector3())); + } return mat; } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs index b9e6833d21..c63e7917b3 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs @@ -81,10 +81,9 @@ namespace Avalonia.Rendering.Composition.Server // If we only have translation and scale, just scale the padding if (CombinedTransformMatrix is { - M12: 0, M13: 0, M14: 0, - M21: 0, M23: 0, M24: 0, - M31: 0, M32: 0, M34: 0, - M43: 0, M44: 1 + M12: 0, M13: 0, + M21: 0, M23: 0, + M31: 0, M32: 0 }) padding = new Thickness(padding.Left * CombinedTransformMatrix.M11, padding.Top * CombinedTransformMatrix.M22, diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index 0eeffda123..3ff1bf2b98 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -51,7 +51,7 @@ namespace Avalonia.Rendering.Composition.Server canvas.PushClip(AdornedVisual._combinedTransformedClipBounds); } var transform = GlobalTransformMatrix; - canvas.PostTransform = MatrixUtils.ToMatrix(transform); + canvas.PostTransform = transform; canvas.Transform = Matrix.Identity; if (Effect != null) @@ -71,7 +71,7 @@ namespace Avalonia.Rendering.Composition.Server RenderCore(canvas, currentTransformedClip); // Hack to force invalidation of SKMatrix - canvas.PostTransform = MatrixUtils.ToMatrix(transform); + canvas.PostTransform = transform; canvas.Transform = Matrix.Identity; if (OpacityMaskBrush != null) @@ -106,8 +106,8 @@ namespace Avalonia.Rendering.Composition.Server return ref _readback2; } - public Matrix4x4 CombinedTransformMatrix { get; private set; } = Matrix4x4.Identity; - public Matrix4x4 GlobalTransformMatrix { get; private set; } + public Matrix CombinedTransformMatrix { get; private set; } = Matrix.Identity; + public Matrix GlobalTransformMatrix { get; private set; } public record struct UpdateResult(Rect? Bounds, bool InvalidatedOld, bool InvalidatedNew) { @@ -134,12 +134,12 @@ namespace Avalonia.Rendering.Composition.Server { CombinedTransformMatrix = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint, // HACK: Ignore RenderTransform set by the adorner layer - AdornedVisual != null ? Matrix4x4.Identity : TransformMatrix, + AdornedVisual != null ? Matrix.Identity : TransformMatrix, Scale, RotationAngle, Orientation, Offset); _combinedTransformDirty = false; } - var parentTransform = (AdornedVisual ?? Parent)?.GlobalTransformMatrix ?? Matrix4x4.Identity; + var parentTransform = (AdornedVisual ?? Parent)?.GlobalTransformMatrix ?? Matrix.Identity; var newTransform = CombinedTransformMatrix * parentTransform; @@ -148,7 +148,7 @@ namespace Avalonia.Rendering.Composition.Server if (GlobalTransformMatrix != newTransform) { _isBackface = Vector3.Transform( - new Vector3(0, 0, float.PositiveInfinity), GlobalTransformMatrix).Z <= 0; + new Vector3(0, 0, float.PositiveInfinity), MatrixUtils.ToMatrix4x4(GlobalTransformMatrix)).Z <= 0; positionChanged = true; } @@ -179,14 +179,14 @@ namespace Avalonia.Rendering.Composition.Server TransformedOwnContentBounds = default; else TransformedOwnContentBounds = - ownBounds.TransformToAABB(MatrixUtils.ToMatrix(GlobalTransformMatrix)); + ownBounds.TransformToAABB(GlobalTransformMatrix); } if (_clipSizeDirty || positionChanged) { _transformedClipBounds = ClipToBounds ? new Rect(new Size(Size.X, Size.Y)) - .TransformToAABB(MatrixUtils.ToMatrix(GlobalTransformMatrix)) + .TransformToAABB(GlobalTransformMatrix) : null; _clipSizeDirty = false; @@ -249,7 +249,7 @@ namespace Avalonia.Rendering.Composition.Server /// public struct ReadbackData { - public Matrix4x4 Matrix; + public Matrix Matrix; public ulong Revision; public long TargetId; public bool Visible; diff --git a/src/Avalonia.Base/Rendering/Composition/Visual.cs b/src/Avalonia.Base/Rendering/Composition/Visual.cs index 6d6818256a..f4f1f3e8d9 100644 --- a/src/Avalonia.Base/Rendering/Composition/Visual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Visual.cs @@ -33,7 +33,7 @@ namespace Avalonia.Rendering.Composition } } - internal Matrix4x4? TryGetServerGlobalTransform() + internal Matrix? TryGetServerGlobalTransform() { if (Root == null) return null; 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/Styling/Activators/NthChildActivator.cs b/src/Avalonia.Base/Styling/Activators/NthChildActivator.cs index 8bdcec2e53..8fe0bb2537 100644 --- a/src/Avalonia.Base/Styling/Activators/NthChildActivator.cs +++ b/src/Avalonia.Base/Styling/Activators/NthChildActivator.cs @@ -1,6 +1,4 @@ -#nullable enable -using System; -using Avalonia.LogicalTree; +using Avalonia.LogicalTree; namespace Avalonia.Styling.Activators { @@ -14,7 +12,7 @@ namespace Avalonia.Styling.Activators private readonly int _step; private readonly int _offset; private readonly bool _reversed; - private int _index = -1; + private int? _index; public NthChildActivator( ILogical control, @@ -30,7 +28,7 @@ namespace Avalonia.Styling.Activators protected override bool EvaluateIsActive() { - var index = _index >= 0 ? _index : _provider.GetChildIndex(_control); + var index = _index ?? _provider.GetChildIndex(_control); return NthChildSelector.Evaluate(index, _provider, _step, _offset, _reversed).IsMatch; } @@ -50,8 +48,7 @@ namespace Avalonia.Styling.Activators // 1. Subscribed child index was changed // 2. Child indexes were reset // 3. We're a reversed (nth-last-child) selector and total count has changed - if ((e.Child == _control || e.Action == ChildIndexChangedAction.ChildIndexesReset) || - (_reversed && e.Action == ChildIndexChangedAction.TotalCountChanged)) + switch (e.Action) { // We're using the _index field to pass the index of the child to EvaluateIsActive // *only* when the active state is re-evaluated via this event handler. The docs @@ -65,16 +62,17 @@ namespace Avalonia.Styling.Activators // IChildIndexProvider.GetChildIndex. This is because this event can be fired during // the process of realizing an element of a virtualized list; in this case calling // GetChildIndex may not return the correct index as the element isn't yet realized. - _index = e.Index; - ReevaluateIsActive(); - _index = -1; + case ChildIndexChangedAction.ChildIndexChanged when e.Child == _control: + _index = e.Index; + ReevaluateIsActive(); + _index = null; + break; + case ChildIndexChangedAction.ChildIndexesReset: + case ChildIndexChangedAction.TotalCountChanged when _reversed: + _index = null; + ReevaluateIsActive(); + break; } } - - private void TotalCountChanged(object? sender, EventArgs e) - { - if (_reversed) - ReevaluateIsActive(); - } } } diff --git a/src/Avalonia.Base/Styling/Activators/StyleActivatorBase.cs b/src/Avalonia.Base/Styling/Activators/StyleActivatorBase.cs index 06848a7524..3dbf927b86 100644 --- a/src/Avalonia.Base/Styling/Activators/StyleActivatorBase.cs +++ b/src/Avalonia.Base/Styling/Activators/StyleActivatorBase.cs @@ -6,9 +6,14 @@ namespace Avalonia.Styling.Activators internal abstract class StyleActivatorBase : IStyleActivator { private IStyleActivatorSink? _sink; - private bool _value; + private bool? _value; - public bool GetIsActive() => _value = EvaluateIsActive(); + public bool GetIsActive() + { + var value = EvaluateIsActive(); + _value ??= value; + return value; + } public bool IsSubscribed => _sink is not null; @@ -63,7 +68,7 @@ namespace Avalonia.Styling.Activators /// protected bool ReevaluateIsActive() { - var value = EvaluateIsActive(); + var value = GetIsActive(); if (value != _value) { 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 166ae6b93b..15722901a6 100644 --- a/src/Avalonia.Base/Vector.cs +++ b/src/Avalonia.Base/Vector.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Numerics; #if !BUILDTASK using Avalonia.Animation.Animators; #endif @@ -353,5 +354,52 @@ namespace Avalonia x = this._x; y = this._y; } + + internal Vector2 ToVector2() => new Vector2((float)X, (float)Y); + + internal Vector(Vector2 v) : this(v.X, v.Y) + { + + } + + /// + /// Returns a vector whose elements are the absolute values of each of the specified vector's elements. + /// + /// + public Vector Abs() => new(Math.Abs(X), Math.Abs(Y)); + + /// + /// Restricts a vector between a minimum and a maximum value. + /// + public static Vector Clamp(Vector value, Vector min, Vector max) => + Min(Max(value, min), max); + + /// + /// Returns a vector whose elements are the maximum of each of the pairs of elements in two specified vectors + /// + 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. + /// + public static double Distance(Vector value1, Vector value2) => Math.Sqrt(DistanceSquared(value1, value2)); + + /// + /// Returns the Euclidean distance squared between two specified points + /// + public static double DistanceSquared(Vector value1, Vector value2) + { + var difference = value1 - value2; + return Dot(difference, difference); + } + + public static implicit operator Vector(Vector2 v) => new(v); } } diff --git a/src/Avalonia.Base/Vector3D.cs b/src/Avalonia.Base/Vector3D.cs new file mode 100644 index 0000000000..ec6463c987 --- /dev/null +++ b/src/Avalonia.Base/Vector3D.cs @@ -0,0 +1,145 @@ +using System; +using System.Globalization; +using System.Numerics; +using Avalonia.Rendering.Composition.Expressions; +using Avalonia.Utilities; + +namespace Avalonia; + +public readonly record struct Vector3D(double X, double Y, double Z) +{ + /// + /// Parses a string. + /// + /// The string. + /// The . + public static Vector3D Parse(string s) + { + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Vector.")) + { + return new Vector3D( + tokenizer.ReadDouble(), + tokenizer.ReadDouble(), + tokenizer.ReadDouble() + ); + } + } + + internal Vector3 ToVector3() => new Vector3((float)X, (float)Y, (float)Z); + + internal Vector3D(Vector3 v) : this(v.X, v.Y, v.Z) + { + + } + + public static implicit operator Vector3D(Vector3 vector) => new(vector); + + /// + /// Calculates the dot product of two vectors. + /// + public static double Dot(Vector3D vector1, Vector3D vector2) => + (vector1.X * vector2.X) + + (vector1.Y * vector2.Y) + + (vector1.Z * vector2.Z); + + /// + /// Adds the second to the first vector + /// + public static Vector3D Add(Vector3D left, Vector3D right) => + new Vector3D(left.X + right.X, left.Y + right.Y, left.Z + right.Z); + + /// + /// Adds the second to the first vector + /// + public static Vector3D operator +(Vector3D left, Vector3D right) => Add(left, right); + + /// + /// Subtracts the second from the first vector + /// + public static Vector3D Substract(Vector3D left, Vector3D right) => + new Vector3D(left.X - right.X, left.Y - right.Y, left.Z - right.Z); + + /// + /// Subtracts the second from the first vector + /// + public static Vector3D operator -(Vector3D left, Vector3D right) => Substract(left, right); + + /// + /// Negates the vector + /// + public static Vector3D operator -(Vector3D v) => new(-v.X, -v.Y, -v.Z); + + /// + /// Multiplies the first vector by the second. + /// + public static Vector3D Multiply(Vector3D left, Vector3D right) => + new(left.X * right.X, left.Y * right.Y, left.Z * right.Z); + + /// + /// Multiplies the vector by the given scalar. + /// + public static Vector3D Multiply(Vector3D left, double right) => + new(left.X * right, left.Y * right, left.Z * right); + + /// + /// Multiplies the vector by the given scalar. + /// + public static Vector3D operator *(Vector3D left, double right) => Multiply(left, right); + + /// + /// Divides the first vector by the second. + /// + public static Vector3D Divide(Vector3D left, Vector3D right) => + new(left.X / right.X, left.Y / right.Y, left.Z / right.Z); + + /// + /// Divides the vector by the given scalar. + /// + public static Vector3D Divide(Vector3D left, double right) => + new(left.X / right, left.Y / right, left.Z / right); + + /// Returns a vector whose elements are the absolute values of each of the specified vector's elements. + public Vector3D Abs() => new(Math.Abs(X), Math.Abs(Y), Math.Abs(Z)); + + /// + /// Restricts a vector between a minimum and a maximum value. + /// + public static Vector3D Clamp(Vector3D value, Vector3D min, Vector3D max) => + Min(Max(value, min), max); + + /// + /// Returns a vector whose elements are the maximum of each of the pairs of elements in two specified vectors + /// + public static Vector3D Max(Vector3D left, Vector3D right) => + new(Math.Max(left.X, right.X), Math.Max(left.Y, right.Y), Math.Max(left.Z, right.Z)); + + /// + /// Returns a vector whose elements are the minimum of each of the pairs of elements in two specified vectors + /// + public static Vector3D Min(Vector3D left, Vector3D right) => + new(Math.Min(left.X, right.X), Math.Min(left.Y, right.Y), Math.Min(left.Z, right.Z)); + + /// + /// Length of the vector. + /// + public double Length => Math.Sqrt(Dot(this, this)); + + /// + /// Returns a normalized version of this vector. + /// + public static Vector3D Normalize(Vector3D value) => Divide(value, value.Length); + + /// + /// Computes the squared Euclidean distance between the two given points. + /// + public static double DistanceSquared(Vector3D value1, Vector3D value2) + { + var difference = Vector3D.Substract(value1, value2); + return Dot(difference, difference); + } + + /// + /// Computes the Euclidean distance between the two given points. + /// + public static double Distance(Vector3D value1, Vector3D value2) => Math.Sqrt(DistanceSquared(value1, value2)); +} \ No newline at end of file diff --git a/src/Avalonia.Base/composition-schema.xml b/src/Avalonia.Base/composition-schema.xml index 97b2686584..05aea3808e 100644 --- a/src/Avalonia.Base/composition-schema.xml +++ b/src/Avalonia.Base/composition-schema.xml @@ -21,14 +21,14 @@ - - - - + + + + - - + + @@ -57,10 +57,13 @@ + + + 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}"> - + - /// 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/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 06427991f8..83aa88a7b6 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -23,7 +23,7 @@ namespace Avalonia.Controls /// Displays a collection of items. /// [PseudoClasses(":empty", ":singleitem")] - public class ItemsControl : TemplatedControl, IChildIndexProvider, IScrollSnapPointsInfo + public class ItemsControl : TemplatedControl, IChildIndexProvider { /// /// The default value for the property. @@ -67,18 +67,6 @@ namespace Avalonia.Controls public static readonly StyledProperty DisplayMemberBindingProperty = AvaloniaProperty.Register(nameof(DisplayMemberBinding)); - /// - /// Defines the property. - /// - public static readonly StyledProperty AreHorizontalSnapPointsRegularProperty = - AvaloniaProperty.Register(nameof(AreHorizontalSnapPointsRegular)); - - /// - /// Defines the property. - /// - public static readonly StyledProperty AreVerticalSnapPointsRegularProperty = - AvaloniaProperty.Register(nameof(AreVerticalSnapPointsRegular)); - /// /// Gets or sets the to use for binding to the display member of each item. /// @@ -249,64 +237,6 @@ namespace Avalonia.Controls /// public event EventHandler? ContainerClearing; - /// - public event EventHandler HorizontalSnapPointsChanged - { - add - { - if (_itemsPresenter != null) - { - _itemsPresenter.HorizontalSnapPointsChanged += value; - } - } - - remove - { - if (_itemsPresenter != null) - { - _itemsPresenter.HorizontalSnapPointsChanged -= value; - } - } - } - - /// - public event EventHandler VerticalSnapPointsChanged - { - add - { - if (_itemsPresenter != null) - { - _itemsPresenter.VerticalSnapPointsChanged += value; - } - } - - remove - { - if (_itemsPresenter != null) - { - _itemsPresenter.VerticalSnapPointsChanged -= value; - } - } - } - - /// - /// Gets or sets whether the horizontal snap points for the are equidistant from each other. - /// - public bool AreHorizontalSnapPointsRegular - { - get => GetValue(AreHorizontalSnapPointsRegularProperty); - set => SetValue(AreHorizontalSnapPointsRegularProperty, value); - } - - /// - /// Gets or sets whether the vertical snap points for the are equidistant from each other. - /// - public bool AreVerticalSnapPointsRegular - { - get => GetValue(AreVerticalSnapPointsRegularProperty); - set => SetValue(AreVerticalSnapPointsRegularProperty, value); - } - /// /// Gets a default recycle key that can be used when an supports /// a single container type. @@ -896,19 +826,5 @@ namespace Avalonia.Controls count = ItemsView.Count; return true; } - - /// - public IReadOnlyList GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment) - { - return _itemsPresenter?.GetIrregularSnapPoints(orientation, snapPointsAlignment) ?? new List(); - } - - /// - public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset) - { - offset = 0; - - return _itemsPresenter?.GetRegularSnapPoints(orientation, snapPointsAlignment, out offset) ?? 0; - } } } diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index 06069a897e..5f8d4a561d 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -215,7 +215,7 @@ namespace Avalonia.Controls /// /// Transformation matrix corresponding to _matrixTransform. /// - private Matrix _transformation; + private Matrix _transformation = Matrix.Identity; private IDisposable? _transformChangedEvent; /// @@ -256,13 +256,16 @@ namespace Avalonia.Controls /// private void ApplyLayoutTransform() { - if (LayoutTransform == null) - return; - // Get the transform matrix and apply it - _transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound); + var matrix = LayoutTransform is null ? + Matrix.Identity : + RoundMatrix(LayoutTransform.Value, DecimalsAfterRound); + + if (_transformation == matrix) + return; - _matrixTransform.Matrix = _transformation; + _transformation = matrix; + _matrixTransform.Matrix = matrix; // New transform means re-layout is necessary InvalidateMeasure(); diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 1bb53f90a8..40d6b6b0f4 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -394,6 +394,14 @@ namespace Avalonia.Controls _isEmbeddedInMenu = parent?.FindLogicalAncestorOfType(true) != null; } + /// + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + TryUpdateCanExecute(); + } + protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { // This will cause the hotkey manager to dispose the observer and the reference to this control diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index 9610be088b..63e512492e 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs @@ -11,7 +11,7 @@ namespace Avalonia.Controls.Presenters /// /// Presents items inside an . /// - public class ItemsPresenter : Control, ILogicalScrollable, IScrollSnapPointsInfo + public class ItemsPresenter : Control, ILogicalScrollable { /// /// Defines the property. @@ -21,37 +21,8 @@ namespace Avalonia.Controls.Presenters private PanelContainerGenerator? _generator; private ILogicalScrollable? _logicalScrollable; - private IScrollSnapPointsInfo? _scrollSnapPointsInfo; private EventHandler? _scrollInvalidated; - /// - /// Defines the property. - /// - public static readonly StyledProperty AreHorizontalSnapPointsRegularProperty = - AvaloniaProperty.Register(nameof(AreHorizontalSnapPointsRegular)); - - /// - /// Defines the property. - /// - public static readonly StyledProperty AreVerticalSnapPointsRegularProperty = - AvaloniaProperty.Register(nameof(AreVerticalSnapPointsRegular)); - - /// - /// Defines the event. - /// - public static readonly RoutedEvent HorizontalSnapPointsChangedEvent = - RoutedEvent.Register( - nameof(HorizontalSnapPointsChanged), - RoutingStrategies.Bubble); - - /// - /// Defines the event. - /// - public static readonly RoutedEvent VerticalSnapPointsChangedEvent = - RoutedEvent.Register( - nameof(VerticalSnapPointsChanged), - RoutingStrategies.Bubble); - event EventHandler? ILogicalScrollable.ScrollInvalidated { add => _scrollInvalidated += value; @@ -107,47 +78,12 @@ namespace Avalonia.Controls.Presenters } } - /// - /// Occurs when the measurements for horizontal snap points change. - /// - public event EventHandler? HorizontalSnapPointsChanged - { - add => AddHandler(HorizontalSnapPointsChangedEvent, value); - remove => RemoveHandler(HorizontalSnapPointsChangedEvent, value); - } - - /// - /// Occurs when the measurements for vertical snap points change. - /// - public event EventHandler? VerticalSnapPointsChanged - { - add => AddHandler(VerticalSnapPointsChangedEvent, value); - remove => RemoveHandler(VerticalSnapPointsChangedEvent, value); - } - bool ILogicalScrollable.IsLogicalScrollEnabled => _logicalScrollable?.IsLogicalScrollEnabled ?? false; Size ILogicalScrollable.ScrollSize => _logicalScrollable?.ScrollSize ?? default; Size ILogicalScrollable.PageScrollSize => _logicalScrollable?.PageScrollSize ?? default; Size IScrollable.Extent => _logicalScrollable?.Extent ?? default; Size IScrollable.Viewport => _logicalScrollable?.Viewport ?? default; - /// - /// Gets or sets whether the horizontal snap points for the are equidistant from each other. - /// - public bool AreHorizontalSnapPointsRegular - { - get { return GetValue(AreHorizontalSnapPointsRegularProperty); } - set { SetValue(AreHorizontalSnapPointsRegularProperty, value); } - } - - /// - /// Gets or sets whether the vertical snap points for the are equidistant from each other. - /// - public bool AreVerticalSnapPointsRegular - { - get { return GetValue(AreVerticalSnapPointsRegularProperty); } - set { SetValue(AreVerticalSnapPointsRegularProperty, value); } - } public override sealed void ApplyTemplate() { @@ -167,36 +103,14 @@ namespace Avalonia.Controls.Presenters Panel.TemplatedParent = TemplatedParent; Panel.IsItemsHost = true; - _scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo; LogicalChildren.Add(Panel); VisualChildren.Add(Panel); - if (_scrollSnapPointsInfo != null) - { - _scrollSnapPointsInfo.AreVerticalSnapPointsRegular = AreVerticalSnapPointsRegular; - _scrollSnapPointsInfo.AreHorizontalSnapPointsRegular = AreHorizontalSnapPointsRegular; - } - if (Panel is VirtualizingPanel v) v.Attach(ItemsControl); else CreateSimplePanelGenerator(); - if (Panel is IScrollSnapPointsInfo scrollSnapPointsInfo) - { - scrollSnapPointsInfo.VerticalSnapPointsChanged += (s, e) => - { - e.RoutedEvent = VerticalSnapPointsChangedEvent; - RaiseEvent(e); - }; - - scrollSnapPointsInfo.HorizontalSnapPointsChanged += (s, e) => - { - e.RoutedEvent = HorizontalSnapPointsChangedEvent; - RaiseEvent(e); - }; - } - _logicalScrollable = Panel as ILogicalScrollable; if (_logicalScrollable is not null) @@ -240,16 +154,6 @@ namespace Avalonia.Controls.Presenters ResetState(); InvalidateMeasure(); } - else if(change.Property == AreHorizontalSnapPointsRegularProperty) - { - if (_scrollSnapPointsInfo != null) - _scrollSnapPointsInfo.AreHorizontalSnapPointsRegular = AreHorizontalSnapPointsRegular; - } - else if (change.Property == AreVerticalSnapPointsRegularProperty) - { - if (_scrollSnapPointsInfo != null) - _scrollSnapPointsInfo.AreVerticalSnapPointsRegular = AreVerticalSnapPointsRegular; - } } internal void Refresh() @@ -303,27 +207,5 @@ namespace Avalonia.Controls.Presenters } private void OnLogicalScrollInvalidated(object? sender, EventArgs e) => _scrollInvalidated?.Invoke(this, e); - - public IReadOnlyList GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment) - { - if(Panel is IScrollSnapPointsInfo scrollSnapPointsInfo) - { - return scrollSnapPointsInfo.GetIrregularSnapPoints(orientation, snapPointsAlignment); - } - - return new List(); - } - - public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset) - { - if (Panel is IScrollSnapPointsInfo scrollSnapPointsInfo) - { - return scrollSnapPointsInfo.GetRegularSnapPoints(orientation, snapPointsAlignment, out offset); - } - - offset = 0; - - return 0; - } } } diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 1c9dd79b13..bd3d2b5171 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -7,6 +7,7 @@ using Avalonia.Input.GestureRecognizers; using Avalonia.Utilities; using Avalonia.VisualTree; using System.Linq; +using Avalonia.Layout; namespace Avalonia.Controls.Presenters { @@ -99,6 +100,8 @@ namespace Avalonia.Controls.Presenters private double _horizontalSnapPointOffset; private CompositeDisposable? _ownerSubscriptions; private ScrollViewer? _owner; + private IScrollSnapPointsInfo? _scrollSnapPointsInfo; + private bool _isSnapPointsUpdated; /// /// Initializes static members of the class. @@ -377,6 +380,13 @@ namespace Avalonia.Controls.Presenters CanVerticallyScroll ? double.PositiveInfinity : availableSize.Height); Child.Measure(constraint); + + if (!_isSnapPointsUpdated) + { + _isSnapPointsUpdated = true; + UpdateSnapPoints(); + } + return Child.DesiredSize.Constrain(availableSize); } @@ -463,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; @@ -570,7 +588,12 @@ namespace Avalonia.Controls.Presenters private void OnScrollGestureInertiaStartingEnded(object? sender, ScrollGestureInertiaStartingEventArgs e) { - if (Content is not IScrollSnapPointsInfo) + var scrollable = Content; + + if (Content is ItemsControl itemsControl) + scrollable = itemsControl.Presenter?.Panel; + + if (scrollable is not IScrollSnapPointsInfo) return; if (_scrollGestureSnapPoints == null) @@ -676,22 +699,6 @@ namespace Avalonia.Controls.Presenters _owner?.SetCurrentValue(OffsetProperty, change.GetNewValue()); } - else if (change.Property == ContentProperty) - { - if (change.OldValue is IScrollSnapPointsInfo oldSnapPointsInfo) - { - oldSnapPointsInfo.VerticalSnapPointsChanged -= ScrollSnapPointsInfoSnapPointsChanged; - oldSnapPointsInfo.HorizontalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged; - } - - if (Content is IScrollSnapPointsInfo scrollSnapPointsInfo) - { - scrollSnapPointsInfo.VerticalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged; - scrollSnapPointsInfo.HorizontalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged; - } - - UpdateSnapPoints(); - } else if (change.Property == ChildProperty) { ChildChanged(change); @@ -875,7 +882,9 @@ namespace Avalonia.Controls.Presenters private void UpdateSnapPoints() { - if (Content is IScrollSnapPointsInfo scrollSnapPointsInfo) + var scrollable = GetScrollSnapPointsInfo(Content); + + if (scrollable is IScrollSnapPointsInfo scrollSnapPointsInfo) { _areVerticalSnapPointsRegular = scrollSnapPointsInfo.AreVerticalSnapPointsRegular; _areHorizontalSnapPointsRegular = scrollSnapPointsInfo.AreHorizontalSnapPointsRegular; @@ -910,7 +919,9 @@ namespace Avalonia.Controls.Presenters private Vector SnapOffset(Vector offset) { - if(Content is not IScrollSnapPointsInfo) + var scrollable = GetScrollSnapPointsInfo(Content); + + if(scrollable is null) return offset; var diff = GetAlignedDiff(); @@ -1012,5 +1023,37 @@ namespace Avalonia.Controls.Presenters } return snapPoints[Math.Min(point, snapPoints.Count - 1)]; } + + private IScrollSnapPointsInfo? GetScrollSnapPointsInfo(object? content) + { + var scrollable = content; + + if (Content is ItemsControl itemsControl) + scrollable = itemsControl.Presenter?.Panel; + + if (Content is ItemsPresenter itemsPresenter) + scrollable = itemsPresenter.Panel; + + var snapPointsInfo = scrollable as IScrollSnapPointsInfo; + + if(snapPointsInfo != _scrollSnapPointsInfo) + { + if(_scrollSnapPointsInfo != null) + { + _scrollSnapPointsInfo.VerticalSnapPointsChanged -= ScrollSnapPointsInfoSnapPointsChanged; + _scrollSnapPointsInfo.HorizontalSnapPointsChanged -= ScrollSnapPointsInfoSnapPointsChanged; + } + + _scrollSnapPointsInfo = snapPointsInfo; + + if(_scrollSnapPointsInfo != null) + { + _scrollSnapPointsInfo.VerticalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged; + _scrollSnapPointsInfo.HorizontalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged; + } + } + + return snapPointsInfo; + } } } diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs index 39ff8e3a92..c49043ea46 100644 --- a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs +++ b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs @@ -223,7 +223,7 @@ namespace Avalonia.Controls var visualizerVisual = ElementComposition.GetElementVisual(this); if (visual != null && contentVisual != null && visualizerVisual != null) { - contentVisual.CenterPoint = new Vector3((float)(_content.Bounds.Width / 2), (float)(_content.Bounds.Height / 2), 0); + contentVisual.CenterPoint = new Vector3D((_content.Bounds.Width / 2), (_content.Bounds.Height / 2), 0); switch (RefreshVisualizerState) { case RefreshVisualizerState.Idle: @@ -236,43 +236,43 @@ namespace Avalonia.Controls contentVisual.Opacity = MinimumIndicatorOpacity; contentVisual.RotationAngle = _startingRotationAngle; visualizerVisual.Offset = IsPullDirectionVertical ? - new Vector3(visualizerVisual.Offset.X, 0, 0) : - new Vector3(0, visualizerVisual.Offset.Y, 0); + new Vector3D(visualizerVisual.Offset.X, 0, 0) : + new Vector3D(0, visualizerVisual.Offset.Y, 0); _content.InvalidateMeasure(); break; case RefreshVisualizerState.Interacting: _played = false; contentVisual.Opacity = MinimumIndicatorOpacity; contentVisual.RotationAngle = (float)(_startingRotationAngle + _interactionRatio * 2 * Math.PI); - Vector3 offset = default; + Vector3D offset = default; if (IsPullDirectionVertical) { - offset = new Vector3(0, (float)(_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0); + offset = new Vector3D(0, (_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0); } else { - offset = new Vector3((float)(_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0); + offset = new Vector3D((_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0); } visual.Offset = offset; visualizerVisual.Offset = IsPullDirectionVertical ? - new Vector3(visualizerVisual.Offset.X, offset.Y, 0) : - new Vector3(offset.X, visualizerVisual.Offset.Y, 0); + new Vector3D(visualizerVisual.Offset.X, offset.Y, 0) : + new Vector3D(offset.X, visualizerVisual.Offset.Y, 0); break; case RefreshVisualizerState.Pending: contentVisual.Opacity = 1; contentVisual.RotationAngle = _startingRotationAngle + (float)(2 * Math.PI); if (IsPullDirectionVertical) { - offset = new Vector3(0, (float)(_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0); + offset = new Vector3D(0, (_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0); } else { - offset = new Vector3((float)(_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0); + offset = new Vector3D((_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0); } visual.Offset = offset; visualizerVisual.Offset = IsPullDirectionVertical ? - new Vector3(visualizerVisual.Offset.X, offset.Y, 0) : - new Vector3(offset.X, visualizerVisual.Offset.Y, 0); + new Vector3D(visualizerVisual.Offset.X, offset.Y, 0) : + new Vector3D(offset.X, visualizerVisual.Offset.Y, 0); if (!_played) { @@ -301,18 +301,18 @@ namespace Avalonia.Controls * (IsPullDirectionFar ? -1f : 1f); if (IsPullDirectionVertical) { - offset = new Vector3(0, (float)(_executingRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0); + offset = new Vector3D(0, (_executingRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0); } else { - offset = new Vector3((float)(_executingRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0); + offset = new Vector3D((_executingRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0); } visual.Offset = offset; - contentVisual.Offset += IsPullDirectionVertical ? new Vector3(0, (float)(translationRatio * root.Bounds.Height), 0) : - new Vector3((float)(translationRatio * root.Bounds.Width), 0, 0); + contentVisual.Offset += IsPullDirectionVertical ? new Vector3D(0, (translationRatio * root.Bounds.Height), 0) : + new Vector3D((translationRatio * root.Bounds.Width), 0, 0); visualizerVisual.Offset = IsPullDirectionVertical ? - new Vector3(visualizerVisual.Offset.X, offset.Y, 0) : - new Vector3(offset.X, visualizerVisual.Offset.Y, 0); + new Vector3D(visualizerVisual.Offset.X, offset.Y, 0) : + new Vector3D(offset.X, visualizerVisual.Offset.Y, 0); break; case RefreshVisualizerState.Peeking: contentVisual.Opacity = 1; 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.Controls/Utils/VirtualizingSnapPointsList.cs b/src/Avalonia.Controls/Utils/VirtualizingSnapPointsList.cs new file mode 100644 index 0000000000..f92756c59b --- /dev/null +++ b/src/Avalonia.Controls/Utils/VirtualizingSnapPointsList.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Primitives; +using Avalonia.Layout; + +namespace Avalonia.Controls.Utils +{ + internal class VirtualizingSnapPointsList : IReadOnlyList + { + private const int ExtraCount = 2; + private readonly RealizedStackElements _realizedElements; + private readonly Orientation _orientation; + private readonly Orientation _parentOrientation; + private readonly SnapPointsAlignment _snapPointsAlignment; + private readonly double _size; + private readonly int _start = -1; + private readonly int _end; + + public VirtualizingSnapPointsList(RealizedStackElements realizedElements, int count, Orientation orientation, Orientation parentOrientation, SnapPointsAlignment snapPointsAlignment, double size) + { + _realizedElements = realizedElements; + _orientation = orientation; + _parentOrientation = parentOrientation; + _snapPointsAlignment = snapPointsAlignment; + _size = size; + if (parentOrientation == orientation) + { + _start = Math.Max(0, _realizedElements.FirstIndex - ExtraCount); + _end = Math.Min(count - 1, _realizedElements.LastIndex + ExtraCount); + } + } + + public double this[int index] + { + get + { + if(index < 0 || index >= Count) + throw new ArgumentOutOfRangeException("index"); + + index += _start; + + double snapPoint = 0; + var averageElementSize = _size; + + Control? container; + switch (_orientation) + { + case Orientation.Horizontal: + container = _realizedElements.GetElement(index); + if (container != null) + { + switch (_snapPointsAlignment) + { + case SnapPointsAlignment.Near: + snapPoint = container.Bounds.Left; + break; + case SnapPointsAlignment.Center: + snapPoint = container.Bounds.Center.X; + break; + case SnapPointsAlignment.Far: + snapPoint = container.Bounds.Right; + break; + } + } + else + { + var ind = index; + if (index > _realizedElements.LastIndex) + { + ind -= _realizedElements.LastIndex + 1; + } + snapPoint = ind * averageElementSize; + switch (_snapPointsAlignment) + { + case SnapPointsAlignment.Center: + snapPoint += averageElementSize / 2; + break; + case SnapPointsAlignment.Far: + snapPoint += averageElementSize; + break; + } + if (index > _realizedElements.LastIndex) + { + var lastElement = _realizedElements.GetElement(_realizedElements.LastIndex); + if (lastElement != null) + { + snapPoint += lastElement.Bounds.Right; + } + } + } + break; + case Orientation.Vertical: + container = _realizedElements.GetElement(index); + if (container != null) + { + switch (_snapPointsAlignment) + { + case SnapPointsAlignment.Near: + snapPoint = container.Bounds.Top; + break; + case SnapPointsAlignment.Center: + snapPoint = container.Bounds.Center.Y; + break; + case SnapPointsAlignment.Far: + snapPoint = container.Bounds.Bottom; + break; + } + } + else + { + var ind = index; + if(index > _realizedElements.LastIndex) + { + ind -= _realizedElements.LastIndex + 1; + } + snapPoint = ind * averageElementSize; + switch (_snapPointsAlignment) + { + case SnapPointsAlignment.Center: + snapPoint += averageElementSize / 2; + break; + case SnapPointsAlignment.Far: + snapPoint += averageElementSize; + break; + } + if (index > _realizedElements.LastIndex) + { + var lastElement = _realizedElements.GetElement(_realizedElements.LastIndex); + if (lastElement != null) + { + snapPoint += lastElement.Bounds.Bottom; + } + } + } + break; + } + + return snapPoint; + } + } + + public int Count => _parentOrientation != _orientation ? 0 : _end - _start + 1; + + public IEnumerator GetEnumerator() + { + for (var i = 0; i < Count; i++) + yield return this[i]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs index b234686f5e..6a3c7e17ee 100644 --- a/src/Avalonia.Controls/VirtualizingStackPanel.cs +++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs @@ -776,107 +776,10 @@ namespace Avalonia.Controls /// public IReadOnlyList GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment) { - var snapPoints = new List(); + if(_realizedElements == null) + return new List(); - switch (orientation) - { - case Orientation.Horizontal: - if (AreHorizontalSnapPointsRegular) - throw new InvalidOperationException(); - if (Orientation == Orientation.Horizontal) - { - var averageElementSize = EstimateElementSizeU(); - double snapPoint = 0; - for (var i = 0; i < Items.Count; i++) - { - var container = ContainerFromIndex(i); - if (container != null) - { - switch (snapPointsAlignment) - { - case SnapPointsAlignment.Near: - snapPoint = container.Bounds.Left; - break; - case SnapPointsAlignment.Center: - snapPoint = container.Bounds.Center.X; - break; - case SnapPointsAlignment.Far: - snapPoint = container.Bounds.Right; - break; - } - } - else - { - if (snapPoint == 0) - { - switch (snapPointsAlignment) - { - case SnapPointsAlignment.Center: - snapPoint = averageElementSize / 2; - break; - case SnapPointsAlignment.Far: - snapPoint = averageElementSize; - break; - } - } - else - snapPoint += averageElementSize; - } - - snapPoints.Add(snapPoint); - } - } - break; - case Orientation.Vertical: - if (AreVerticalSnapPointsRegular) - throw new InvalidOperationException(); - if (Orientation == Orientation.Vertical) - { - var averageElementSize = EstimateElementSizeU(); - double snapPoint = 0; - for (var i = 0; i < Items.Count; i++) - { - var container = ContainerFromIndex(i); - if (container != null) - { - switch (snapPointsAlignment) - { - case SnapPointsAlignment.Near: - snapPoint = container.Bounds.Top; - break; - case SnapPointsAlignment.Center: - snapPoint = container.Bounds.Center.Y; - break; - case SnapPointsAlignment.Far: - snapPoint = container.Bounds.Bottom; - break; - } - } - else - { - if (snapPoint == 0) - { - switch (snapPointsAlignment) - { - case SnapPointsAlignment.Center: - snapPoint = averageElementSize / 2; - break; - case SnapPointsAlignment.Far: - snapPoint = averageElementSize; - break; - } - } - else - snapPoint += averageElementSize; - } - - snapPoints.Add(snapPoint); - } - } - break; - } - - return snapPoints; + return new VirtualizingSnapPointsList(_realizedElements, ItemsControl?.ItemsSource?.Count() ?? 0, orientation, Orientation, snapPointsAlignment, EstimateElementSizeU()); } /// diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/PropertyValueEditorView.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/PropertyValueEditorView.cs index 7b58671996..f6e9842ac8 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/PropertyValueEditorView.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/PropertyValueEditorView.cs @@ -57,42 +57,11 @@ namespace Avalonia.Diagnostics.Views if (Property?.PropertyType is not { } propertyType) return null; - TControl CreateControl(AvaloniaProperty valueProperty, - IValueConverter? converter = null, - Action? init = null, - AvaloniaProperty? readonlyProperty = null) - where TControl : Control, new() - { - var control = new TControl(); - var bindingMode = Property.IsReadonly ? BindingMode.OneWay : BindingMode.TwoWay; - - init?.Invoke(control); - - control.Bind(valueProperty, - new Binding(nameof(Property.Value), bindingMode) - { - Source = Property, - Converter = converter ?? new ValueConverter(), - ConverterParameter = propertyType - }).DisposeWith(_cleanup); - - if (readonlyProperty != null) - { - control[readonlyProperty] = Property.IsReadonly; - } - else - { - control.IsEnabled = !Property.IsReadonly; - } - - return control; - } - if (propertyType == typeof(bool)) return CreateControl(ToggleButton.IsCheckedProperty); //TODO: Infinity, NaN not working with NumericUpDown - if (propertyType.IsPrimitive && propertyType != typeof(float) && propertyType != typeof(double)) + if (IsValidNumeric(propertyType)) return CreateControl( NumericUpDown.ValueProperty, new ValueToDecimalConverter(), @@ -139,7 +108,8 @@ namespace Avalonia.Diagnostics.Views ColorView.ColorProperty, new Binding(nameof(Property.Value), BindingMode.TwoWay) { - Source = Property, Converter = Color2Brush + Source = Property, + Converter = Color2Brush }) .DisposeWith(_cleanup); @@ -261,6 +231,74 @@ namespace Avalonia.Diagnostics.Views } return tb; + + TControl CreateControl(AvaloniaProperty valueProperty, + IValueConverter? converter = null, + Action? init = null, + AvaloniaProperty? readonlyProperty = null) + where TControl : Control, new() + { + var control = new TControl(); + var bindingMode = Property.IsReadonly ? BindingMode.OneWay : BindingMode.TwoWay; + + init?.Invoke(control); + + control.Bind(valueProperty, + new Binding(nameof(Property.Value), bindingMode) + { + Source = Property, + Converter = converter ?? new ValueConverter(), + ConverterParameter = propertyType + }).DisposeWith(_cleanup); + + if (readonlyProperty != null) + { + control[readonlyProperty] = Property.IsReadonly; + } + else + { + control.IsEnabled = !Property.IsReadonly; + } + + return control; + } + + static bool IsValidNumeric(Type? type) + { + if (type == null || type.IsEnum == true) + { + return false; + } + var typeCode = Type.GetTypeCode(type); + if (typeCode == TypeCode.Object) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + typeCode = Type.GetTypeCode(Nullable.GetUnderlyingType(type)); + } + else + { + return false; + } + } + switch (typeCode) + { + case TypeCode.Byte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.SByte: + case TypeCode.Single: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + return true; + default: + return false; + } + } + + } //HACK: ValueConverter that skips first target update 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"> + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -