Browse Source

Merge branch 'master' into fixes/windowsIME

pull/11809/head
Benedikt Stebner 3 years ago
committed by GitHub
parent
commit
454fd84b10
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      samples/AppWithoutLifetime/MainWindow.axaml.cs
  2. 6
      samples/AppWithoutLifetime/Sub.axaml.cs
  3. 9
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  4. 12
      samples/ControlCatalog.Android/MainActivity.cs
  5. 66
      samples/ControlCatalog.Android/Resources/drawable-night-v31/avalonia_anim.xml
  6. 71
      samples/ControlCatalog.Android/Resources/drawable-v31/avalonia_anim.xml
  7. 21
      samples/ControlCatalog.Android/Resources/values-v31/styles.xml
  8. 11
      samples/ControlCatalog.Android/Resources/values/styles.xml
  9. 35
      samples/ControlCatalog.Android/SplashActivity.cs
  10. 10
      samples/ControlCatalog/Pages/CompositionPage.axaml.cs
  11. 16
      samples/ControlCatalog/Pages/GesturePage.cs
  12. 4
      samples/GpuInterop/DrawingSurfaceDemoBase.cs
  13. 5
      samples/MobileSandbox.Android/MainActivity.cs
  14. 10
      samples/MobileSandbox.Android/MobileSandbox.Android.csproj
  15. 66
      samples/MobileSandbox.Android/Resources/drawable-night-v31/avalonia_anim.xml
  16. 71
      samples/MobileSandbox.Android/Resources/drawable-v31/avalonia_anim.xml
  17. 4
      samples/MobileSandbox.Android/Resources/values-night/colors.xml
  18. 16
      samples/MobileSandbox.Android/Resources/values-v31/styles.xml
  19. 7
      samples/MobileSandbox.Android/Resources/values/styles.xml
  20. 17
      samples/MobileSandbox.Android/SplashActivity.cs
  21. 4
      samples/SafeAreaDemo.Android/MainActivity.cs
  22. 66
      samples/SafeAreaDemo.Android/Resources/drawable-night-v31/avalonia_anim.xml
  23. 71
      samples/SafeAreaDemo.Android/Resources/drawable-v31/avalonia_anim.xml
  24. 4
      samples/SafeAreaDemo.Android/Resources/values-night/colors.xml
  25. 16
      samples/SafeAreaDemo.Android/Resources/values-v31/styles.xml
  26. 7
      samples/SafeAreaDemo.Android/Resources/values/styles.xml
  27. 13
      samples/SafeAreaDemo.Android/SafeAreaDemo.Android.csproj
  28. 30
      samples/SafeAreaDemo.Android/SplashActivity.cs
  29. 6
      samples/SafeAreaDemo/Views/MainView.xaml.cs
  30. 7
      samples/Sandbox/MainWindow.axaml
  31. 60
      src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
  32. 88
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  33. 40
      src/Android/Avalonia.Android/AvaloniaSplashActivity.cs
  34. 119
      src/Avalonia.Base/Animation/AnimationInstance`1.cs
  35. 1
      src/Avalonia.Base/Input/Pointer.cs
  36. 24
      src/Avalonia.Base/Media/BoxShadow.cs
  37. 10
      src/Avalonia.Base/Media/BoxShadows.cs
  38. 4
      src/Avalonia.Base/Media/MediaContext.cs
  39. 2
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  40. 4
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  41. 25
      src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs
  42. 6
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  43. 2
      src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs
  44. 2
      src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
  45. 59
      src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs
  46. 43
      src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs
  47. 178
      src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs
  48. 33
      src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs
  49. 7
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs
  50. 20
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
  51. 2
      src/Avalonia.Base/Rendering/Composition/Visual.cs
  52. 1
      src/Avalonia.Base/Rendering/RenderLoop.cs
  53. 30
      src/Avalonia.Base/Styling/Activators/NthChildActivator.cs
  54. 11
      src/Avalonia.Base/Styling/Activators/StyleActivatorBase.cs
  55. 5
      src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
  56. 48
      src/Avalonia.Base/Vector.cs
  57. 145
      src/Avalonia.Base/Vector3D.cs
  58. 15
      src/Avalonia.Base/composition-schema.xml
  59. 14
      src/Avalonia.Controls.DataGrid/DataGridColumn.cs
  60. 7
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  61. 37
      src/Avalonia.Controls/Control.cs
  62. 86
      src/Avalonia.Controls/ItemsControl.cs
  63. 15
      src/Avalonia.Controls/LayoutTransformControl.cs
  64. 8
      src/Avalonia.Controls/MenuItem.cs
  65. 120
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  66. 83
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  67. 36
      src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
  68. 6
      src/Avalonia.Controls/ToggleSwitch.cs
  69. 156
      src/Avalonia.Controls/Utils/VirtualizingSnapPointsList.cs
  70. 103
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  71. 104
      src/Avalonia.Diagnostics/Diagnostics/Views/PropertyValueEditorView.cs
  72. 145
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml
  73. 46
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs
  74. 6
      src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs
  75. 4
      src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
  76. 2
      src/Avalonia.Themes.Fluent/Controls/ItemsControl.xaml
  77. 2
      src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
  78. 47
      src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml
  79. 2
      src/Avalonia.Themes.Fluent/FluentTheme.xaml
  80. 35
      src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs
  81. 2
      src/Avalonia.Themes.Simple/Controls/ItemsControl.xaml
  82. 2
      src/Avalonia.Themes.Simple/Controls/ListBox.xaml
  83. 3
      src/Avalonia.Themes.Simple/Controls/TransitioningContentControl.xaml
  84. 2
      src/Avalonia.X11/X11Clipboard.cs
  85. 1
      src/tools/Avalonia.Generators/Avalonia.Generators.csproj
  86. 84
      tests/Avalonia.Base.UnitTests/Animation/AnimationIterationTests.cs
  87. 13
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs
  88. 8
      tests/Avalonia.Base.UnitTests/Media/BoxShadowTests.cs
  89. 68
      tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs
  90. 19
      tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs
  91. 51
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  92. 12
      tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
  93. 31
      tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs
  94. 1
      tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj

5
samples/AppWithoutLifetime/MainWindow.axaml.cs

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

6
samples/AppWithoutLifetime/Sub.axaml.cs

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

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

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

12
samples/ControlCatalog.Android/MainActivity.cs

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

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

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

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

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

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

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

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

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

35
samples/ControlCatalog.Android/SplashActivity.cs

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

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

16
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<Border>();
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;
}

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

5
samples/MobileSandbox.Android/MainActivity.cs

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

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

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

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

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

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

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

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

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

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

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

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

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

17
samples/MobileSandbox.Android/SplashActivity.cs

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

4
samples/SafeAreaDemo.Android/MainActivity.cs

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

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

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

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

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

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

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

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

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

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

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

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

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

30
samples/SafeAreaDemo.Android/SplashActivity.cs

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

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

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

7
samples/Sandbox/MainWindow.axaml

@ -1,4 +1,11 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class="Sandbox.MainWindow">
<ScrollViewer>
<StackPanel>
<Button Margin="0 100000000000000000 0 0">0</Button>
<Button>1</Button>
</StackPanel>
</ScrollViewer>
</Window>

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

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

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

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

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

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

119
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();
}
}

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

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

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

10
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)]

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

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

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

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

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

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

25
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<double>
{
public double Interpolate(double @from, double to, float progress) => @from + (to - @from) * progress;
public static DoubleInterpolator Instance { get; } = new ();
}
class Vector2Interpolator : IInterpolator<Vector2>
{
@ -28,6 +34,15 @@ namespace Avalonia.Rendering.Composition.Animations
public static Vector2Interpolator Instance { get; } = new Vector2Interpolator();
}
class VectorInterpolator : IInterpolator<Vector>
{
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<Vector3>
{
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<Vector3D>
{
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<Vector4>
{
public Vector4 Interpolate(Vector4 @from, Vector4 to, float progress)

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

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

2
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);
}

59
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)},

43
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<FfiRecord> countGroup, IReadOnlyList<ExpressionVariant> 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,

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

33
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;
}

7
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,

20
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
/// </summary>
public struct ReadbackData
{
public Matrix4x4 Matrix;
public Matrix Matrix;
public ulong Revision;
public long TargetId;
public bool Visible;

2
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;

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

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

30
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();
}
}
}

11
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
/// </returns>
protected bool ReevaluateIsActive()
{
var value = EvaluateIsActive();
var value = GetIsActive();
if (value != _value)
{

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

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

48
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)
{
}
/// <summary>
/// Returns a vector whose elements are the absolute values of each of the specified vector's elements.
/// </summary>
/// <returns></returns>
public Vector Abs() => new(Math.Abs(X), Math.Abs(Y));
/// <summary>
/// Restricts a vector between a minimum and a maximum value.
/// </summary>
public static Vector Clamp(Vector value, Vector min, Vector max) =>
Min(Max(value, min), max);
/// <summary>
/// Returns a vector whose elements are the maximum of each of the pairs of elements in two specified vectors
/// </summary>
public static Vector Max(Vector left, Vector right) =>
new(Math.Max(left.X, right.X), Math.Max(left.Y, right.Y));
/// <summary>
/// Returns a vector whose elements are the minimum of each of the pairs of elements in two specified vectors
/// </summary>
public static Vector Min(Vector left, Vector right) =>
new(Math.Min(left.X, right.X), Math.Min(left.Y, right.Y));
/// <summary>
/// Computes the Euclidean distance between the two given points.
/// </summary>
public static double Distance(Vector value1, Vector value2) => Math.Sqrt(DistanceSquared(value1, value2));
/// <summary>
/// Returns the Euclidean distance squared between two specified points
/// </summary>
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);
}
}

145
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)
{
/// <summary>
/// Parses a <see cref="Vector"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <returns>The <see cref="Vector3D"/>.</returns>
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);
/// <summary>
/// Calculates the dot product of two vectors.
/// </summary>
public static double Dot(Vector3D vector1, Vector3D vector2) =>
(vector1.X * vector2.X)
+ (vector1.Y * vector2.Y)
+ (vector1.Z * vector2.Z);
/// <summary>
/// Adds the second to the first vector
/// </summary>
public static Vector3D Add(Vector3D left, Vector3D right) =>
new Vector3D(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
/// <summary>
/// Adds the second to the first vector
/// </summary>
public static Vector3D operator +(Vector3D left, Vector3D right) => Add(left, right);
/// <summary>
/// Subtracts the second from the first vector
/// </summary>
public static Vector3D Substract(Vector3D left, Vector3D right) =>
new Vector3D(left.X - right.X, left.Y - right.Y, left.Z - right.Z);
/// <summary>
/// Subtracts the second from the first vector
/// </summary>
public static Vector3D operator -(Vector3D left, Vector3D right) => Substract(left, right);
/// <summary>
/// Negates the vector
/// </summary>
public static Vector3D operator -(Vector3D v) => new(-v.X, -v.Y, -v.Z);
/// <summary>
/// Multiplies the first vector by the second.
/// </summary>
public static Vector3D Multiply(Vector3D left, Vector3D right) =>
new(left.X * right.X, left.Y * right.Y, left.Z * right.Z);
/// <summary>
/// Multiplies the vector by the given scalar.
/// </summary>
public static Vector3D Multiply(Vector3D left, double right) =>
new(left.X * right, left.Y * right, left.Z * right);
/// <summary>
/// Multiplies the vector by the given scalar.
/// </summary>
public static Vector3D operator *(Vector3D left, double right) => Multiply(left, right);
/// <summary>
/// Divides the first vector by the second.
/// </summary>
public static Vector3D Divide(Vector3D left, Vector3D right) =>
new(left.X / right.X, left.Y / right.Y, left.Z / right.Z);
/// <summary>
/// Divides the vector by the given scalar.
/// </summary>
public static Vector3D Divide(Vector3D left, double right) =>
new(left.X / right, left.Y / right, left.Z / right);
/// <summary>Returns a vector whose elements are the absolute values of each of the specified vector's elements.</summary>
public Vector3D Abs() => new(Math.Abs(X), Math.Abs(Y), Math.Abs(Z));
/// <summary>
/// Restricts a vector between a minimum and a maximum value.
/// </summary>
public static Vector3D Clamp(Vector3D value, Vector3D min, Vector3D max) =>
Min(Max(value, min), max);
/// <summary>
/// Returns a vector whose elements are the maximum of each of the pairs of elements in two specified vectors
/// </summary>
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));
/// <summary>
/// Returns a vector whose elements are the minimum of each of the pairs of elements in two specified vectors
/// </summary>
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));
/// <summary>
/// Length of the vector.
/// </summary>
public double Length => Math.Sqrt(Dot(this, this));
/// <summary>
/// Returns a normalized version of this vector.
/// </summary>
public static Vector3D Normalize(Vector3D value) => Divide(value, value.Length);
/// <summary>
/// Computes the squared Euclidean distance between the two given points.
/// </summary>
public static double DistanceSquared(Vector3D value1, Vector3D value2)
{
var difference = Vector3D.Substract(value1, value2);
return Dot(difference, difference);
}
/// <summary>
/// Computes the Euclidean distance between the two given points.
/// </summary>
public static double Distance(Vector3D value1, Vector3D value2) => Math.Sqrt(DistanceSquared(value1, value2));
}

15
src/Avalonia.Base/composition-schema.xml

@ -21,14 +21,14 @@
<Property Name="Clip" Type="Avalonia.Platform.IGeometryImpl?" Internal="true" />
<Property Name="ClipToBounds" Type="bool" Animated="true" DefaultValue="true"/>
<Property Name="Offset" Type="Vector3" Animated="true"/>
<Property Name="Size" Type="Vector2" Animated="true"/>
<Property Name="AnchorPoint" Type="Vector2" Animated="true"/>
<Property Name="CenterPoint" Type="Vector3" Animated="true"/>
<Property Name="Offset" Type="Vector3D" Animated="true"/>
<Property Name="Size" Type="Vector" Animated="true"/>
<Property Name="AnchorPoint" Type="Vector" Animated="true"/>
<Property Name="CenterPoint" Type="Vector3D" Animated="true"/>
<Property Name="RotationAngle" Type="float" Animated="true"/>
<Property Name="Orientation" Type="Quaternion" DefaultValue="Quaternion.Identity" Animated="true"/>
<Property Name="Scale" Type="Vector3" DefaultValue="new Vector3(1, 1, 1)" Animated="true"/>
<Property Name="TransformMatrix" Type="Matrix4x4" DefaultValue="Matrix4x4.Identity" Animated="true"/>
<Property Name="Scale" Type="Vector3D" DefaultValue="new Avalonia.Vector3D(1, 1, 1)" Animated="true"/>
<Property Name="TransformMatrix" Type="Avalonia.Matrix" DefaultValue="Avalonia.Matrix.Identity" Animated="true" Internal="true"/>
<Property Name="AdornedVisual" Type="CompositionVisual?" Internal="true" />
<Property Name="AdornerIsClipped" Type="bool" Internal="true" />
<Property Name="OpacityMaskBrush" Type="Avalonia.Media.IImmutableBrush?" Internal="true" />
@ -57,10 +57,13 @@
<Property Name="Size" Type="Size" />
</Object>
<KeyFrameAnimation Name="Scalar" Type="float"/>
<KeyFrameAnimation Name="Double" Type="double"/>
<KeyFrameAnimation Name="Boolean" Type="bool"/>
<KeyFrameAnimation Name="Color" Type="Avalonia.Media.Color"/>
<KeyFrameAnimation Type="Vector"/>
<KeyFrameAnimation Type="Vector2"/>
<KeyFrameAnimation Type="Vector3"/>
<KeyFrameAnimation Type="Vector3D"/>
<KeyFrameAnimation Type="Vector4"/>
<KeyFrameAnimation Type="Quaternion"/>
<Object Name="CompositionSimpleTransform" Internal="true" ServerOnly="true" ServerBase="SimpleServerRenderResource">

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

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

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

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

37
src/Avalonia.Controls/Control.cs

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

86
src/Avalonia.Controls/ItemsControl.cs

@ -23,7 +23,7 @@ namespace Avalonia.Controls
/// Displays a collection of items.
/// </summary>
[PseudoClasses(":empty", ":singleitem")]
public class ItemsControl : TemplatedControl, IChildIndexProvider, IScrollSnapPointsInfo
public class ItemsControl : TemplatedControl, IChildIndexProvider
{
/// <summary>
/// The default value for the <see cref="ItemsPanel"/> property.
@ -67,18 +67,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IBinding?> DisplayMemberBindingProperty =
AvaloniaProperty.Register<ItemsControl, IBinding?>(nameof(DisplayMemberBinding));
/// <summary>
/// Defines the <see cref="AreHorizontalSnapPointsRegular"/> property.
/// </summary>
public static readonly StyledProperty<bool> AreHorizontalSnapPointsRegularProperty =
AvaloniaProperty.Register<ItemsControl, bool>(nameof(AreHorizontalSnapPointsRegular));
/// <summary>
/// Defines the <see cref="AreVerticalSnapPointsRegular"/> property.
/// </summary>
public static readonly StyledProperty<bool> AreVerticalSnapPointsRegularProperty =
AvaloniaProperty.Register<ItemsControl, bool>(nameof(AreVerticalSnapPointsRegular));
/// <summary>
/// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item.
/// </summary>
@ -249,64 +237,6 @@ namespace Avalonia.Controls
/// </remarks>
public event EventHandler<ContainerClearingEventArgs>? ContainerClearing;
/// <inheritdoc />
public event EventHandler<RoutedEventArgs> HorizontalSnapPointsChanged
{
add
{
if (_itemsPresenter != null)
{
_itemsPresenter.HorizontalSnapPointsChanged += value;
}
}
remove
{
if (_itemsPresenter != null)
{
_itemsPresenter.HorizontalSnapPointsChanged -= value;
}
}
}
/// <inheritdoc />
public event EventHandler<RoutedEventArgs> VerticalSnapPointsChanged
{
add
{
if (_itemsPresenter != null)
{
_itemsPresenter.VerticalSnapPointsChanged += value;
}
}
remove
{
if (_itemsPresenter != null)
{
_itemsPresenter.VerticalSnapPointsChanged -= value;
}
}
}
/// <summary>
/// Gets or sets whether the horizontal snap points for the <see cref="ItemsControl"/> are equidistant from each other.
/// </summary>
public bool AreHorizontalSnapPointsRegular
{
get => GetValue(AreHorizontalSnapPointsRegularProperty);
set => SetValue(AreHorizontalSnapPointsRegularProperty, value);
}
/// <summary>
/// Gets or sets whether the vertical snap points for the <see cref="ItemsControl"/> are equidistant from each other.
/// </summary>
public bool AreVerticalSnapPointsRegular
{
get => GetValue(AreVerticalSnapPointsRegularProperty);
set => SetValue(AreVerticalSnapPointsRegularProperty, value);
}
/// <summary>
/// Gets a default recycle key that can be used when an <see cref="ItemsControl"/> supports
/// a single container type.
@ -896,19 +826,5 @@ namespace Avalonia.Controls
count = ItemsView.Count;
return true;
}
/// <inheritdoc />
public IReadOnlyList<double> GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment)
{
return _itemsPresenter?.GetIrregularSnapPoints(orientation, snapPointsAlignment) ?? new List<double>();
}
/// <inheritdoc />
public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset)
{
offset = 0;
return _itemsPresenter?.GetRegularSnapPoints(orientation, snapPointsAlignment, out offset) ?? 0;
}
}
}

15
src/Avalonia.Controls/LayoutTransformControl.cs

@ -215,7 +215,7 @@ namespace Avalonia.Controls
/// <summary>
/// Transformation matrix corresponding to _matrixTransform.
/// </summary>
private Matrix _transformation;
private Matrix _transformation = Matrix.Identity;
private IDisposable? _transformChangedEvent;
/// <summary>
@ -256,13 +256,16 @@ namespace Avalonia.Controls
/// </remarks>
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();

8
src/Avalonia.Controls/MenuItem.cs

@ -394,6 +394,14 @@ namespace Avalonia.Controls
_isEmbeddedInMenu = parent?.FindLogicalAncestorOfType<IMenu>(true) != null;
}
/// <inheritdoc />
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

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

@ -11,7 +11,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Presents items inside an <see cref="Avalonia.Controls.ItemsControl"/>.
/// </summary>
public class ItemsPresenter : Control, ILogicalScrollable, IScrollSnapPointsInfo
public class ItemsPresenter : Control, ILogicalScrollable
{
/// <summary>
/// Defines the <see cref="ItemsPanel"/> property.
@ -21,37 +21,8 @@ namespace Avalonia.Controls.Presenters
private PanelContainerGenerator? _generator;
private ILogicalScrollable? _logicalScrollable;
private IScrollSnapPointsInfo? _scrollSnapPointsInfo;
private EventHandler? _scrollInvalidated;
/// <summary>
/// Defines the <see cref="AreHorizontalSnapPointsRegular"/> property.
/// </summary>
public static readonly StyledProperty<bool> AreHorizontalSnapPointsRegularProperty =
AvaloniaProperty.Register<ItemsPresenter, bool>(nameof(AreHorizontalSnapPointsRegular));
/// <summary>
/// Defines the <see cref="AreVerticalSnapPointsRegular"/> property.
/// </summary>
public static readonly StyledProperty<bool> AreVerticalSnapPointsRegularProperty =
AvaloniaProperty.Register<ItemsPresenter, bool>(nameof(AreVerticalSnapPointsRegular));
/// <summary>
/// Defines the <see cref="HorizontalSnapPointsChanged"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> HorizontalSnapPointsChangedEvent =
RoutedEvent.Register<ItemsPresenter, RoutedEventArgs>(
nameof(HorizontalSnapPointsChanged),
RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="VerticalSnapPointsChanged"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> VerticalSnapPointsChangedEvent =
RoutedEvent.Register<ItemsPresenter, RoutedEventArgs>(
nameof(VerticalSnapPointsChanged),
RoutingStrategies.Bubble);
event EventHandler? ILogicalScrollable.ScrollInvalidated
{
add => _scrollInvalidated += value;
@ -107,47 +78,12 @@ namespace Avalonia.Controls.Presenters
}
}
/// <summary>
/// Occurs when the measurements for horizontal snap points change.
/// </summary>
public event EventHandler<RoutedEventArgs>? HorizontalSnapPointsChanged
{
add => AddHandler(HorizontalSnapPointsChangedEvent, value);
remove => RemoveHandler(HorizontalSnapPointsChangedEvent, value);
}
/// <summary>
/// Occurs when the measurements for vertical snap points change.
/// </summary>
public event EventHandler<RoutedEventArgs>? 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;
/// <summary>
/// Gets or sets whether the horizontal snap points for the <see cref="ItemsPresenter"/> are equidistant from each other.
/// </summary>
public bool AreHorizontalSnapPointsRegular
{
get { return GetValue(AreHorizontalSnapPointsRegularProperty); }
set { SetValue(AreHorizontalSnapPointsRegularProperty, value); }
}
/// <summary>
/// Gets or sets whether the vertical snap points for the <see cref="ItemsPresenter"/> are equidistant from each other.
/// </summary>
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<double> GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment)
{
if(Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
{
return scrollSnapPointsInfo.GetIrregularSnapPoints(orientation, snapPointsAlignment);
}
return new List<double>();
}
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;
}
}
}

83
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;
/// <summary>
/// Initializes static members of the <see cref="ScrollContentPresenter"/> 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<Vector>());
}
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;
}
}
}

36
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;

6
src/Avalonia.Controls/ToggleSwitch.cs

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

156
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<double>
{
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<double> GetEnumerator()
{
for (var i = 0; i < Count; i++)
yield return this[i];
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

103
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -776,107 +776,10 @@ namespace Avalonia.Controls
/// <inheritdoc/>
public IReadOnlyList<double> GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment)
{
var snapPoints = new List<double>();
if(_realizedElements == null)
return new List<double>();
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());
}
/// <inheritdoc/>

104
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<TControl>(AvaloniaProperty valueProperty,
IValueConverter? converter = null,
Action<TControl>? 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<CheckBox>(ToggleButton.IsCheckedProperty);
//TODO: Infinity, NaN not working with NumericUpDown
if (propertyType.IsPrimitive && propertyType != typeof(float) && propertyType != typeof(double))
if (IsValidNumeric(propertyType))
return CreateControl<NumericUpDown>(
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<TControl>(AvaloniaProperty valueProperty,
IValueConverter? converter = null,
Action<TControl>? 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

145
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml

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

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

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

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

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

4
src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs

@ -105,7 +105,7 @@ namespace Avalonia.OpenGL.Controls
}
_visual = _compositor.CreateSurfaceVisual();
_visual.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height);
_visual.Size = new Vector(Bounds.Width, Bounds.Height);
_visual.Surface = _resources.Surface;
ElementComposition.SetElementChildVisual(this, _visual);
using (_resources.Context.MakeCurrent())
@ -118,7 +118,7 @@ namespace Avalonia.OpenGL.Controls
{
if (_visual != null && change.Property == BoundsProperty)
{
_visual.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height);
_visual.Size = new Vector(Bounds.Width, Bounds.Height);
RequestNextFrameRendering();
}

2
src/Avalonia.Themes.Fluent/Controls/ItemsControl.xaml

@ -10,8 +10,6 @@
CornerRadius="{TemplateBinding CornerRadius}"
Padding="{TemplateBinding Padding}">
<ItemsPresenter Name="PART_ItemsPresenter"
AreVerticalSnapPointsRegular="{TemplateBinding AreVerticalSnapPointsRegular}"
AreHorizontalSnapPointsRegular="{TemplateBinding AreHorizontalSnapPointsRegular}"
ItemsPanel="{TemplateBinding ItemsPanel}"/>
</Border>
</ControlTemplate>

2
src/Avalonia.Themes.Fluent/Controls/ListBox.xaml

@ -40,8 +40,6 @@
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}">
<ItemsPresenter Name="PART_ItemsPresenter"
AreVerticalSnapPointsRegular="{TemplateBinding AreVerticalSnapPointsRegular}"
AreHorizontalSnapPointsRegular="{TemplateBinding AreHorizontalSnapPointsRegular}"
ItemsPanel="{TemplateBinding ItemsPanel}"
Margin="{TemplateBinding Padding}"/>
</ScrollViewer>

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

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

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

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

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

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

2
src/Avalonia.Themes.Simple/Controls/ItemsControl.xaml

@ -11,8 +11,6 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ItemsPresenter Name="PART_ItemsPresenter"
AreVerticalSnapPointsRegular="{TemplateBinding AreVerticalSnapPointsRegular}"
AreHorizontalSnapPointsRegular="{TemplateBinding AreHorizontalSnapPointsRegular}"
ItemsPanel="{TemplateBinding ItemsPanel}" />
</Border>
</ControlTemplate>

2
src/Avalonia.Themes.Simple/Controls/ListBox.xaml

@ -27,8 +27,6 @@
HorizontalSnapPointsType="{TemplateBinding (ScrollViewer.HorizontalSnapPointsType)}">
<ItemsPresenter Name="PART_ItemsPresenter"
Margin="{TemplateBinding Padding}"
AreVerticalSnapPointsRegular="{TemplateBinding AreVerticalSnapPointsRegular}"
AreHorizontalSnapPointsRegular="{TemplateBinding AreHorizontalSnapPointsRegular}"
ItemsPanel="{TemplateBinding ItemsPanel}" />
</ScrollViewer>
</Border>

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

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

2
src/Avalonia.X11/X11Clipboard.cs

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

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

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

84
tests/Avalonia.Base.UnitTests/Animation/AnimationIterationTests.cs

@ -71,11 +71,14 @@ namespace Avalonia.Base.UnitTests.Animation
var clock = new TestClock();
var animationRun = animation.RunAsync(border, clock);
border.Measure(Size.Infinity);
border.Arrange(new Rect(border.DesiredSize));
clock.Step(TimeSpan.Zero);
// Initial Delay.
clock.Step(TimeSpan.FromSeconds(1));
Assert.Equal(border.Width, 0d);
clock.Step(TimeSpan.FromSeconds(0));
Assert.Equal(100d, border.Width);
clock.Step(TimeSpan.FromSeconds(6));
@ -126,7 +129,84 @@ namespace Avalonia.Base.UnitTests.Animation
clock.Step(TimeSpan.FromSeconds(0.100d));
Assert.Equal(border.Width, 300d);
}
[Theory]
[InlineData(FillMode.Backward, 0, 0d, 0.7d)]
[InlineData(FillMode.Both, 0, 0d, 0.7d)]
[InlineData(FillMode.Forward, 100, 0d, 0.7d)]
[InlineData(FillMode.Backward, 0, 0.3d, 0.7d)]
[InlineData(FillMode.Both, 0, 0.3d, 0.7d)]
[InlineData(FillMode.Forward, 100, 0.3d, 0.7d)]
public void Check_FillMode_Start_Value(FillMode fillMode, double target, double startCue, double endCue)
{
var keyframe1 = new KeyFrame()
{
Setters = { new Setter(Layoutable.WidthProperty, 0d), }, Cue = new Cue(startCue)
};
var keyframe2 = new KeyFrame()
{
Setters = { new Setter(Layoutable.WidthProperty, 300d), }, Cue = new Cue(endCue)
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(10d),
Delay = TimeSpan.FromSeconds(5d),
FillMode = fillMode,
Children = { keyframe1, keyframe2 }
};
var border = new Border() { Height = 100d, Width = 100d, };
var clock = new TestClock();
animation.RunAsync(border, clock);
clock.Step(TimeSpan.Zero);
Assert.Equal(target, border.Width);
}
[Theory]
[InlineData(FillMode.Backward, 100, 0.3d, 1d)]
[InlineData(FillMode.Both, 300, 0.3d, 1d)]
[InlineData(FillMode.Forward, 300, 0.3d, 1d)]
[InlineData(FillMode.Backward, 100, 0.3d, 0.7d)]
[InlineData(FillMode.Both, 300, 0.3d, 0.7d)]
[InlineData(FillMode.Forward, 300, 0.3d, 0.7d)]
public void Check_FillMode_End_Value(FillMode fillMode, double target, double startCue, double endCue)
{
var keyframe1 = new KeyFrame()
{
Setters = { new Setter(Layoutable.WidthProperty, 0d), }, Cue = new Cue(0.7d)
};
var keyframe2 = new KeyFrame()
{
Setters = { new Setter(Layoutable.WidthProperty, 300d), }, Cue = new Cue(1d)
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(10d),
Delay = TimeSpan.FromSeconds(5d),
FillMode = fillMode,
Children = { keyframe1, keyframe2 }
};
var border = new Border() { Height = 100d, Width = 100d, };
var clock = new TestClock();
animation.RunAsync(border, clock);
clock.Step(TimeSpan.FromSeconds(0));
clock.Step(TimeSpan.FromSeconds(20));
Assert.Equal(target, border.Width);
}
[Fact]
public void Dispose_Subscription_Should_Stop_Animation()
{

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

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

8
tests/Avalonia.Base.UnitTests/Media/BoxShadowTests.cs

@ -41,5 +41,13 @@ namespace Avalonia.Base.UnitTests.Media
Assert.Equal(Colors.Red, parsed.Color);
}
}
[Fact]
public void BoxShadows_Should_ToString()
{
const string source = "-20 -20 60 #CCFFFFFF, 20 20 60 #33000000";
var parsed = BoxShadows.Parse(source);
Assert.Equal(source, parsed.ToString(), true);
}
}
}

68
tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs

@ -5,7 +5,7 @@ using Avalonia.Base.UnitTests.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.PropertyStore;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Moq;
@ -963,6 +963,72 @@ namespace Avalonia.Base.UnitTests.Styling
Assert.Equal(0.0, target.Double);
}
[Fact]
public void Animations_With_Activator_Trigger_Should_Be_Activated_And_Deactivated()
{
var clock = new TestClock();
var border = new Border();
var root = new TestRoot
{
Clock = clock,
Styles =
{
new Style(x => x.OfType<Border>().Not(default(Selector).Class("foo")))
{
Setters =
{
new Setter(Border.BackgroundProperty, Brushes.Yellow),
},
Animations =
{
new Avalonia.Animation.Animation
{
Duration = TimeSpan.FromSeconds(1.0),
Children =
{
new KeyFrame
{
Setters =
{
new Setter(Border.BackgroundProperty, Brushes.Green)
},
Cue = new Cue(0.0)
},
new KeyFrame
{
Setters =
{
new Setter(Border.BackgroundProperty, Brushes.Green)
},
Cue = new Cue(1.0)
}
}
}
}
},
new Style(x => x.OfType<Border>().Class("foo"))
{
Setters =
{
new Setter(Border.BackgroundProperty, Brushes.Blue),
}
}
},
Child = border
};
root.Measure(Size.Infinity);
Assert.Equal(Brushes.Yellow, border.Background);
clock.Step(TimeSpan.FromSeconds(0.5));
Assert.Equal(Brushes.Green, border.Background);
border.Classes.Add("foo");
Assert.Equal(Brushes.Blue, border.Background);
}
private class Class1 : Control
{
public static readonly StyledProperty<string> FooProperty =

19
tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs

@ -169,6 +169,25 @@ namespace Avalonia.Controls.UnitTests
new Rect(0, 100, 100, 25));
}
[Fact]
public void Bounds_On_Transform_Applied_Then_Removed_Are_Correct()
{
using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
var control = CreateWithChildAndMeasureAndTransform(
100,
25,
new RotateTransform { Angle = 90 });
Assert.Equal(new Size(25, 100), control.DesiredSize);
control.LayoutTransform = null;
control.Measure(Size.Infinity);
control.Arrange(new Rect(control.DesiredSize));
Assert.Equal(new Size(100, 25), control.DesiredSize);
}
[Fact]
public void Should_Generate_RotateTransform_90_degrees()
{

51
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
@ -1223,6 +1224,29 @@ namespace Avalonia.Controls.UnitTests
Assert.Same(item, root.FocusManager.GetFocusedElement());
}
[Fact]
public void Reads_Only_Realized_Items_From_ItemsSource()
{
using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
var data = new DataVirtualizingList();
var target = new ListBox
{
Template = ListBoxTemplate(),
ItemsSource = data,
};
Prepare(target);
var panel = Assert.IsType<VirtualizingStackPanel>(target.ItemsPanelRoot);
Assert.Equal(0, panel.FirstRealizedIndex);
Assert.Equal(9, panel.LastRealizedIndex);
Assert.Equal(
Enumerable.Range(0, 10).Select(x => $"Item{x}"),
data.GetRealizedItems());
}
private static void RaiseKeyEvent(Control target, Key key, KeyModifiers inputModifiers = 0)
{
target.RaiseEvent(new KeyEventArgs
@ -1252,5 +1276,32 @@ namespace Avalonia.Controls.UnitTests
public event NotifyCollectionChangedEventHandler CollectionChanged;
}
private class DataVirtualizingList : IList
{
private readonly List<string> _inner = new(Enumerable.Repeat<string>(null, 100));
public object this[int index]
{
get => _inner[index] = $"Item{index}";
set => throw new NotSupportedException();
}
public IEnumerable<string> GetRealizedItems() => _inner.Where(x => x is not null);
public bool IsFixedSize => true;
public bool IsReadOnly => true;
public int Count => _inner.Count;
public bool IsSynchronized => false;
public object SyncRoot => this;
public int Add(object value) => throw new NotSupportedException();
public void Clear() => throw new NotSupportedException();
public bool Contains(object value) => throw new NotImplementedException();
public void CopyTo(Array array, int index) => throw new NotImplementedException();
public IEnumerator GetEnumerator() => _inner.GetEnumerator();
public int IndexOf(object value) => throw new NotImplementedException();
public void Insert(int index, object value) => throw new NotSupportedException();
public void Remove(object value) => throw new NotSupportedException();
public void RemoveAt(int index) => throw new NotSupportedException();
}
}
}

12
tests/Avalonia.Controls.UnitTests/MenuItemTests.cs

@ -209,13 +209,13 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(0, canExecuteCallCount);
contextMenu.Open();
Assert.Equal(2, canExecuteCallCount);//2 because popup is changing logical child
Assert.Equal(3, canExecuteCallCount);// 3 because popup is changing logical child and moreover we need to invalidate again after the item is attached to the visual tree
command.RaiseCanExecuteChanged();
Assert.Equal(3, canExecuteCallCount);
Assert.Equal(4, canExecuteCallCount);
target.CommandParameter = true;
Assert.Equal(4, canExecuteCallCount);
Assert.Equal(5, canExecuteCallCount);
}
}
@ -249,13 +249,13 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(0, canExecuteCallCount);
flyout.ShowAt(button);
Assert.Equal(1, canExecuteCallCount);
Assert.Equal(2, canExecuteCallCount); // 2 because we need to invalidate after the item is attached to the visual tree
command.RaiseCanExecuteChanged();
Assert.Equal(2, canExecuteCallCount);
Assert.Equal(3, canExecuteCallCount);
target.CommandParameter = true;
Assert.Equal(3, canExecuteCallCount);
Assert.Equal(4, canExecuteCallCount);
}
}

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

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

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

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

Loading…
Cancel
Save