Browse Source

Merge branch 'skiasharp3-compat' into xy-focus-skiasharp-3

xy-focus-skiasharp-3
Max Katz 2 years ago
parent
commit
1a7f3737e7
  1. 2
      native/Avalonia.Native/src/OSX/app.mm
  2. 1
      src/Avalonia.Base/Threading/Dispatcher.Queue.cs
  3. 112
      src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
  4. 6
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  5. 11
      src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs
  6. 3
      src/Avalonia.Native/avn.idl
  7. 27
      src/Avalonia.ReactiveUI/ReactiveUserControl.cs
  8. 34
      src/Avalonia.ReactiveUI/ReactiveWindow.cs
  9. 1
      src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml
  10. 19
      src/Avalonia.Themes.Fluent/Controls/RefreshVisualizer.xaml
  11. 19
      src/Avalonia.Themes.Simple/Controls/RefreshVisualizer.xaml
  12. 12
      src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs
  13. 38
      src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKCanvas.cs
  14. 54
      src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKImageFilter.cs
  15. 38
      src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKPath.cs
  16. 8
      src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.cs
  17. 4
      src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs
  18. 6
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  19. 2
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  20. 6
      src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs
  21. 39
      tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

2
native/Avalonia.Native/src/OSX/app.mm

@ -70,7 +70,7 @@ ComPtr<IAvnApplicationEvents> _events;
{
auto array = CreateAvnStringArray(urls);
_events->FilesOpened(array);
_events->UrlsOpened(array);
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender

1
src/Avalonia.Base/Threading/Dispatcher.Queue.cs

@ -194,7 +194,6 @@ public partial class Dispatcher
if (Now - backgroundJobExecutionStartedAt.Value > _maximumInputStarvationTime)
{
_signaled = true;
RequestBackgroundProcessing();
return;
}

112
src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs

@ -129,70 +129,58 @@ namespace Avalonia.Controls
if (_root != null)
{
_content = Content as Control;
if (_content == null)
OnOrientationChanged();
if (_root != null && _content != null)
{
_content = new PathIcon()
{
Height = DefaultIndicatorSize,
Width = DefaultIndicatorSize,
Name = "PART_Icon"
};
_root.Children.Insert(0, _content);
_content.VerticalAlignment = Layout.VerticalAlignment.Center;
_content.HorizontalAlignment = Layout.HorizontalAlignment.Center;
_content.Loaded += (s, e) =>
{
var composition = ElementComposition.GetElementVisual(_content);
UpdateContent();
}
}
}
if(composition == null)
return;
private void OnContentLoaded(object? s, RoutedEventArgs e)
{
if (_content == null)
return;
var composition = ElementComposition.GetElementVisual(_content);
var compositor = composition.Compositor;
composition.Opacity = 0;
if (composition == null) return;
var smoothRotationAnimation
= compositor.CreateScalarKeyFrameAnimation();
smoothRotationAnimation.Target = "RotationAngle";
smoothRotationAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", new LinearEasing());
smoothRotationAnimation.Duration = TimeSpan.FromMilliseconds(100);
var compositor = composition.Compositor;
composition.Opacity = 0;
var opacityAnimation
= compositor.CreateScalarKeyFrameAnimation();
opacityAnimation.Target = "Opacity";
opacityAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", new LinearEasing());
opacityAnimation.Duration = TimeSpan.FromMilliseconds(100);
var smoothRotationAnimation = compositor.CreateScalarKeyFrameAnimation();
smoothRotationAnimation.Target = "RotationAngle";
smoothRotationAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", new LinearEasing());
smoothRotationAnimation.Duration = TimeSpan.FromMilliseconds(100);
var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
offsetAnimation.Target = "Offset";
offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", new LinearEasing());
offsetAnimation.Duration = TimeSpan.FromMilliseconds(150);
var opacityAnimation = compositor.CreateScalarKeyFrameAnimation();
opacityAnimation.Target = "Opacity";
opacityAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", new LinearEasing());
opacityAnimation.Duration = TimeSpan.FromMilliseconds(100);
var scaleAnimation
= compositor.CreateVector3KeyFrameAnimation();
scaleAnimation.Target = "Scale";
scaleAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", new LinearEasing());
scaleAnimation.Duration = TimeSpan.FromMilliseconds(100);
var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
offsetAnimation.Target = "Offset";
offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", new LinearEasing());
offsetAnimation.Duration = TimeSpan.FromMilliseconds(150);
var animation = compositor.CreateImplicitAnimationCollection();
animation["RotationAngle"] = smoothRotationAnimation;
animation["Offset"] = offsetAnimation;
animation["Scale"] = scaleAnimation;
animation["Opacity"] = opacityAnimation;
var scaleAnimation = compositor.CreateVector3KeyFrameAnimation();
scaleAnimation.Target = "Scale";
scaleAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", new LinearEasing());
scaleAnimation.Duration = TimeSpan.FromMilliseconds(100);
composition.ImplicitAnimations = animation;
var animation = compositor.CreateImplicitAnimationCollection();
animation["RotationAngle"] = smoothRotationAnimation;
animation["Offset"] = offsetAnimation;
animation["Scale"] = scaleAnimation;
animation["Opacity"] = opacityAnimation;
UpdateContent();
};
SetCurrentValue(ContentProperty, _content);
}
else
{
RaisePropertyChanged(ContentProperty, null, Content, Data.BindingPriority.Style, false);
}
}
OnOrientationChanged();
composition.ImplicitAnimations = animation;
UpdateContent();
}
@ -229,6 +217,7 @@ namespace Avalonia.Controls
visualizerVisual.Offset = IsPullDirectionVertical ?
new Vector3D(visualizerVisual.Offset.X, 0, 0) :
new Vector3D(0, visualizerVisual.Offset.Y, 0);
visual.Offset = visualizerVisual.Offset;
_content.InvalidateMeasure();
break;
case RefreshVisualizerState.Interacting:
@ -353,14 +342,27 @@ namespace Avalonia.Controls
}
else if (change.Property == ContentProperty)
{
if (change.OldValue is Control c)
{
c.Loaded -= OnContentLoaded;
_root?.Children.Remove(c);
}
_content = change.NewValue as Control;
if (_content != null)
{
_content.Loaded += OnContentLoaded;
}
if (_root != null && _content != null)
{
_root.Children.Insert(0, _content);
_content.VerticalAlignment = Layout.VerticalAlignment.Center;
_content.HorizontalAlignment = Layout.HorizontalAlignment.Center;
}
UpdateContent();
UpdateContent();
}
}
else if (change.Property == OrientationProperty)
{

6
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -430,6 +430,12 @@ namespace Avalonia.Controls
root.LayoutManager.ExecuteLayoutPass();
}
// During the previous BringIntoView, the scroll width extent might have been out of date if
// elements have different widths. Because of that, the ScrollViewer might not scroll to the correct offset.
// After the previous BringIntoView, Y offset should be correct and an extra layout pass has been executed,
// hence the width extent should be correct now, and we can try to scroll again.
scrollToElement.BringIntoView();
_scrollToElement = null;
_scrollToIndex = -1;
return scrollToElement;

11
src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs

@ -13,12 +13,21 @@ namespace Avalonia.Native
void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls)
{
((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray());
}
void IAvnApplicationEvents.UrlsOpened(IAvnStringArray urls)
{
// Raise the urls opened event to be compatible with legacy behavior.
((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray());
if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime)
{
foreach (var url in urls.ToStringArray())
{
lifetime.RaiseUrl(new Uri(url));
if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri))
{
lifetime.RaiseUrl(uri);
}
}
}
}

3
src/Avalonia.Native/avn.idl

@ -1085,7 +1085,8 @@ interface IAvnNativeControlHostTopLevelAttachment : IUnknown
[uuid(6575b5af-f27a-4609-866c-f1f014c20f79)]
interface IAvnApplicationEvents : IUnknown
{
void FilesOpened (IAvnStringArray* urls);
void FilesOpened (IAvnStringArray* args);
void UrlsOpened (IAvnStringArray* urls);
bool TryShutdown();
void OnReopen ();
void OnHide ();

27
src/Avalonia.ReactiveUI/ReactiveUserControl.cs

@ -1,8 +1,4 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia;
using Avalonia.VisualTree;
using Avalonia.Controls;
using ReactiveUI;
@ -29,7 +25,6 @@ namespace Avalonia.ReactiveUI
// This WhenActivated block calls ViewModel's WhenActivated
// block if the ViewModel implements IActivatableViewModel.
this.WhenActivated(disposables => { });
this.GetObservable(ViewModelProperty).Subscribe(OnViewModelChanged);
}
/// <summary>
@ -47,21 +42,23 @@ namespace Avalonia.ReactiveUI
set => ViewModel = (TViewModel?)value;
}
protected override void OnDataContextChanged(EventArgs e)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnDataContextChanged(e);
ViewModel = DataContext as TViewModel;
}
base.OnPropertyChanged(change);
private void OnViewModelChanged(object? value)
{
if (value == null)
if (change.Property == DataContextProperty)
{
ClearValue(DataContextProperty);
if (Object.ReferenceEquals(change.OldValue, ViewModel))
{
SetCurrentValue(ViewModelProperty, change.NewValue);
}
}
else if (DataContext != value)
else if (change.Property == ViewModelProperty)
{
DataContext = value;
if (Object.ReferenceEquals(change.OldValue, DataContext))
{
SetCurrentValue(DataContextProperty, change.NewValue);
}
}
}
}

34
src/Avalonia.ReactiveUI/ReactiveWindow.cs

@ -1,8 +1,4 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia;
using Avalonia.VisualTree;
using Avalonia.Controls;
using ReactiveUI;
@ -29,8 +25,6 @@ namespace Avalonia.ReactiveUI
// This WhenActivated block calls ViewModel's WhenActivated
// block if the ViewModel implements IActivatableViewModel.
this.WhenActivated(disposables => { });
this.GetObservable(DataContextProperty).Subscribe(OnDataContextChanged);
this.GetObservable(ViewModelProperty).Subscribe(OnViewModelChanged);
}
/// <summary>
@ -48,27 +42,23 @@ namespace Avalonia.ReactiveUI
set => ViewModel = (TViewModel?)value;
}
private void OnDataContextChanged(object? value)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (value is TViewModel viewModel)
{
ViewModel = viewModel;
}
else
{
ViewModel = null;
}
}
base.OnPropertyChanged(change);
private void OnViewModelChanged(object? value)
{
if (value == null)
if (change.Property == DataContextProperty)
{
ClearValue(DataContextProperty);
if (Object.ReferenceEquals(change.OldValue, ViewModel))
{
SetCurrentValue(ViewModelProperty, change.NewValue);
}
}
else if (DataContext != value)
else if (change.Property == ViewModelProperty)
{
DataContext = value;
if (Object.ReferenceEquals(change.OldValue, DataContext))
{
SetCurrentValue(DataContextProperty, change.NewValue);
}
}
}
}

1
src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml

@ -13,6 +13,7 @@
<Thickness x:Key="TextControlThemePadding">10,6,6,5</Thickness>
<sys:Double x:Key="IconElementThemeHeight">20</sys:Double>
<sys:Double x:Key="IconElementThemeWidth">20</sys:Double>
<sys:Double x:Key="RefreshVisualizerIndicatorSize">24</sys:Double>
<Thickness x:Key="ButtonPadding">8,5,8,6</Thickness>
<!-- Override system shape defaults -->

19
src/Avalonia.Themes.Fluent/Controls/RefreshVisualizer.xaml

@ -11,19 +11,20 @@
Value="{DynamicResource RefreshVisualizerBackground}"/>
<Setter Property="Foreground"
Value="{DynamicResource RefreshVisualizerForeground}"/>
<Setter Property="Content">
<Template>
<PathIcon Name="PART_Icon"
Data="M18.6195264,3.31842271 C19.0080059,3.31842271 19.3290603,3.60710385 19.3798716,3.9816481 L19.3868766,4.08577298 L19.3868766,6.97963208 C19.3868766,7.36811161 19.0981955,7.68916605 18.7236513,7.73997735 L18.6195264,7.74698235 L15.7256673,7.74698235 C15.3018714,7.74698235 14.958317,7.40342793 14.958317,6.97963208 C14.958317,6.59115255 15.2469981,6.27009811 15.6215424,6.21928681 L15.7256673,6.21228181 L16.7044011,6.21182461 C13.7917384,3.87107476 9.52212532,4.05209336 6.81933829,6.75488039 C3.92253872,9.65167996 3.92253872,14.34832 6.81933829,17.2451196 C9.71613786,20.1419192 14.4127779,20.1419192 17.3095775,17.2451196 C19.0725398,15.4821573 19.8106555,12.9925923 19.3476248,10.58925 C19.2674502,10.173107 19.5398064,9.77076216 19.9559494,9.69058758 C20.3720923,9.610413 20.7744372,9.88276918 20.8546118,10.2989121 C21.4129973,13.1971899 20.5217103,16.2033812 18.3947747,18.3303168 C14.8986373,21.8264542 9.23027854,21.8264542 5.73414113,18.3303168 C2.23800371,14.8341794 2.23800371,9.16582064 5.73414113,5.66968323 C9.05475132,2.34907304 14.3349409,2.18235834 17.8523166,5.16953912 L17.8521761,4.08577298 C17.8521761,3.66197713 18.1957305,3.31842271 18.6195264,3.31842271 Z"
Width="{DynamicResource RefreshVisualizerIndicatorSize}"
Height="{DynamicResource RefreshVisualizerIndicatorSize}"
/>
</Template>
</Setter>
<Setter Property="Template">
<ControlTemplate>
<Grid Name="PART_Root"
MinHeight="80"
Background="{TemplateBinding Background}">
<Grid.Styles>
<Style Selector="PathIcon#PART_Icon">
<Setter Property="Data"
Value="M18.6195264,3.31842271 C19.0080059,3.31842271 19.3290603,3.60710385 19.3798716,3.9816481 L19.3868766,4.08577298 L19.3868766,6.97963208 C19.3868766,7.36811161 19.0981955,7.68916605 18.7236513,7.73997735 L18.6195264,7.74698235 L15.7256673,7.74698235 C15.3018714,7.74698235 14.958317,7.40342793 14.958317,6.97963208 C14.958317,6.59115255 15.2469981,6.27009811 15.6215424,6.21928681 L15.7256673,6.21228181 L16.7044011,6.21182461 C13.7917384,3.87107476 9.52212532,4.05209336 6.81933829,6.75488039 C3.92253872,9.65167996 3.92253872,14.34832 6.81933829,17.2451196 C9.71613786,20.1419192 14.4127779,20.1419192 17.3095775,17.2451196 C19.0725398,15.4821573 19.8106555,12.9925923 19.3476248,10.58925 C19.2674502,10.173107 19.5398064,9.77076216 19.9559494,9.69058758 C20.3720923,9.610413 20.7744372,9.88276918 20.8546118,10.2989121 C21.4129973,13.1971899 20.5217103,16.2033812 18.3947747,18.3303168 C14.8986373,21.8264542 9.23027854,21.8264542 5.73414113,18.3303168 C2.23800371,14.8341794 2.23800371,9.16582064 5.73414113,5.66968323 C9.05475132,2.34907304 14.3349409,2.18235834 17.8523166,5.16953912 L17.8521761,4.08577298 C17.8521761,3.66197713 18.1957305,3.31842271 18.6195264,3.31842271 Z">
</Setter>
</Style>
</Grid.Styles>
</Grid>
Background="{TemplateBinding Background}"/>
</ControlTemplate>
</Setter>
</ControlTheme>

19
src/Avalonia.Themes.Simple/Controls/RefreshVisualizer.xaml

@ -13,19 +13,20 @@
Value="{DynamicResource RefreshVisualizerBackground}"/>
<Setter Property="Foreground"
Value="{DynamicResource RefreshVisualizerForeground}"/>
<Setter Property="Content">
<Template>
<PathIcon Name="PART_Icon"
Data="M18.6195264,3.31842271 C19.0080059,3.31842271 19.3290603,3.60710385 19.3798716,3.9816481 L19.3868766,4.08577298 L19.3868766,6.97963208 C19.3868766,7.36811161 19.0981955,7.68916605 18.7236513,7.73997735 L18.6195264,7.74698235 L15.7256673,7.74698235 C15.3018714,7.74698235 14.958317,7.40342793 14.958317,6.97963208 C14.958317,6.59115255 15.2469981,6.27009811 15.6215424,6.21928681 L15.7256673,6.21228181 L16.7044011,6.21182461 C13.7917384,3.87107476 9.52212532,4.05209336 6.81933829,6.75488039 C3.92253872,9.65167996 3.92253872,14.34832 6.81933829,17.2451196 C9.71613786,20.1419192 14.4127779,20.1419192 17.3095775,17.2451196 C19.0725398,15.4821573 19.8106555,12.9925923 19.3476248,10.58925 C19.2674502,10.173107 19.5398064,9.77076216 19.9559494,9.69058758 C20.3720923,9.610413 20.7744372,9.88276918 20.8546118,10.2989121 C21.4129973,13.1971899 20.5217103,16.2033812 18.3947747,18.3303168 C14.8986373,21.8264542 9.23027854,21.8264542 5.73414113,18.3303168 C2.23800371,14.8341794 2.23800371,9.16582064 5.73414113,5.66968323 C9.05475132,2.34907304 14.3349409,2.18235834 17.8523166,5.16953912 L17.8521761,4.08577298 C17.8521761,3.66197713 18.1957305,3.31842271 18.6195264,3.31842271 Z"
Width="24"
Height="24"
/>
</Template>
</Setter>
<Setter Property="Template">
<ControlTemplate>
<Grid Name="PART_Root"
MinHeight="80"
Background="{TemplateBinding Background}">
<Grid.Styles>
<Style Selector="PathIcon#PART_Icon">
<Setter Property="Data"
Value="M18.6195264,3.31842271 C19.0080059,3.31842271 19.3290603,3.60710385 19.3798716,3.9816481 L19.3868766,4.08577298 L19.3868766,6.97963208 C19.3868766,7.36811161 19.0981955,7.68916605 18.7236513,7.73997735 L18.6195264,7.74698235 L15.7256673,7.74698235 C15.3018714,7.74698235 14.958317,7.40342793 14.958317,6.97963208 C14.958317,6.59115255 15.2469981,6.27009811 15.6215424,6.21928681 L15.7256673,6.21228181 L16.7044011,6.21182461 C13.7917384,3.87107476 9.52212532,4.05209336 6.81933829,6.75488039 C3.92253872,9.65167996 3.92253872,14.34832 6.81933829,17.2451196 C9.71613786,20.1419192 14.4127779,20.1419192 17.3095775,17.2451196 C19.0725398,15.4821573 19.8106555,12.9925923 19.3476248,10.58925 C19.2674502,10.173107 19.5398064,9.77076216 19.9559494,9.69058758 C20.3720923,9.610413 20.7744372,9.88276918 20.8546118,10.2989121 C21.4129973,13.1971899 20.5217103,16.2033812 18.3947747,18.3303168 C14.8986373,21.8264542 9.23027854,21.8264542 5.73414113,18.3303168 C2.23800371,14.8341794 2.23800371,9.16582064 5.73414113,5.66968323 C9.05475132,2.34907304 14.3349409,2.18235834 17.8523166,5.16953912 L17.8521761,4.08577298 C17.8521761,3.66197713 18.1957305,3.31842271 18.6195264,3.31842271 Z">
</Setter>
</Style>
</Grid.Styles>
</Grid>
Background="{TemplateBinding Background}"/>
</ControlTemplate>
</Setter>
</ControlTheme>

12
src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs

@ -18,18 +18,6 @@ internal class BrowserSingleViewLifetime : ISingleViewApplicationLifetime, IActi
initiallyVisible = null;
(visible ? Activated : Deactivated)?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
});
// Trigger Activated as an initial state, if web page is visible, and wasn't hidden during initialization.
if (initiallyVisible == true)
{
Dispatcher.UIThread.Invoke(() =>
{
if (initiallyVisible == true)
{
Activated?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
}
});
}
}
public AvaloniaView? View;

38
src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKCanvas.cs

@ -0,0 +1,38 @@
using System;
using System.Diagnostics.CodeAnalysis;
using SkiaSharp;
namespace Avalonia.Skia;
internal static partial class SkiaCompat
{
private delegate void CanvasSetMatrixDelegate(SKCanvas canvas, in SKMatrix matrix);
private static CanvasSetMatrixDelegate? s_canvasSetMatrix;
public static void CSetMatrix(this SKCanvas canvas, SKMatrix matrix)
{
if (IsSkiaSharp3)
{
NewCall(canvas, matrix);
}
else
{
LegacyCall(canvas, matrix);
}
[DynamicDependency("SetMatrix(SkiaSharp.SKMatrix)", typeof(SKCanvas))]
static void NewCall(SKCanvas canvas, SKMatrix matrix)
{
if (s_canvasSetMatrix is null)
{
var method = typeof(SKCanvas).GetMethod("SetMatrix", new[] { typeof(SKMatrix).MakeByRefType() })!;
s_canvasSetMatrix = (CanvasSetMatrixDelegate)Delegate.CreateDelegate(typeof(CanvasSetMatrixDelegate), method);
}
s_canvasSetMatrix(canvas, matrix);
}
static void LegacyCall(SKCanvas canvas, SKMatrix matrix) =>
canvas.SetMatrix(matrix);
}
}

54
src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKImageFilter.cs

@ -0,0 +1,54 @@
using System;
using System.Diagnostics.CodeAnalysis;
using SkiaSharp;
namespace Avalonia.Skia;
internal static partial class SkiaCompat
{
private static Func<float, float, SKImageFilter>? s_sk3FilterBlurFactory;
private static Func<float, float, float, float, SKColor, SKImageFilter>? s_sk3FilterDropShadowFactory;
public static SKImageFilter CreateBlur(float sigmaX, float sigmaY)
{
if (IsSkiaSharp3) return NewCall(sigmaX, sigmaY);
else return LegacyCall(sigmaX, sigmaY);
static SKImageFilter LegacyCall(float sigmaX, float sigmaY) => SKImageFilter.CreateBlur(sigmaX, sigmaY);
[DynamicDependency("CreateBlur(System.Single,System.Single)", typeof(SKImageFilter))]
static SKImageFilter NewCall(float sigmaX, float sigmaY)
{
if (s_sk3FilterBlurFactory is null)
{
var method = typeof(SKImageFilter).GetMethod("CreateBlur", new[] { typeof(float), typeof(float) })!;
s_sk3FilterBlurFactory = (Func<float, float, SKImageFilter>)Delegate.CreateDelegate(typeof(Func<float, float, SKImageFilter>), null, method);
}
return s_sk3FilterBlurFactory(sigmaX, sigmaY);
}
}
public static SKImageFilter CreateDropShadow(float dropOffsetX, float dropOffsetY, float sigma, float f, SKColor color)
{
if (IsSkiaSharp3) return NewCall(dropOffsetX, dropOffsetY, sigma, f, color);
else return LegacyCall(dropOffsetX, dropOffsetY, sigma, f, color);
static SKImageFilter LegacyCall(float dropOffsetX, float dropOffsetY, float sigma, float f, SKColor color)
=> SKImageFilter.CreateDropShadow(dropOffsetX, dropOffsetY, sigma, f, color);
[DynamicDependency("CreateDropShadow(System.Single,System.Single,System.Single,System.Single,SkiaSharp.SKColor)", typeof(SKImageFilter))]
static SKImageFilter NewCall(float dropOffsetX, float dropOffsetY, float sigma, float f, SKColor color)
{
if (s_sk3FilterDropShadowFactory is null)
{
var method = typeof(SKImageFilter).GetMethod("CreateDropShadow",
new[] { typeof(float), typeof(float), typeof(float), typeof(float), typeof(SKColor) })!;
s_sk3FilterDropShadowFactory = (Func<float, float, float, float, SKColor, SKImageFilter>)
Delegate.CreateDelegate(typeof(Func<float, float, float, float, SKColor, SKImageFilter>), null, method);
}
return s_sk3FilterDropShadowFactory(dropOffsetX, dropOffsetY, sigma, f, color);
}
}
}

38
src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKPath.cs

@ -0,0 +1,38 @@
using System;
using System.Diagnostics.CodeAnalysis;
using SkiaSharp;
namespace Avalonia.Skia;
internal static partial class SkiaCompat
{
private delegate void PathTransformDelegate(SKPath canvas, in SKMatrix matrix);
private static PathTransformDelegate? s_pathTransform;
public static void CTransform(this SKPath path, ref SKMatrix matrix)
{
if (IsSkiaSharp3)
{
NewCall(path, matrix);
}
else
{
LegacyCall(path, matrix);
}
[DynamicDependency("Transform(SkiaSharp.SKMatrix)", typeof(SKPath))]
static void NewCall(SKPath path, SKMatrix matrix)
{
if (s_pathTransform is null)
{
var method = typeof(SKPath).GetMethod("Transform", new[] { typeof(SKMatrix).MakeByRefType() })!;
s_pathTransform = (PathTransformDelegate)Delegate.CreateDelegate(typeof(PathTransformDelegate), method);
}
s_pathTransform(path, matrix);
}
static void LegacyCall(SKPath path, SKMatrix matrix) =>
path.Transform(matrix);
}
}

8
src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.cs

@ -0,0 +1,8 @@
using SkiaSharp;
namespace Avalonia.Skia;
internal static partial class SkiaCompat
{
public static bool IsSkiaSharp3 { get; } = typeof(SKPath).Assembly.GetName().Version?.Major == 3;
}

4
src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs

@ -30,7 +30,7 @@ partial class DrawingContextImpl
if (blur.Radius <= 0)
return null;
var sigma = SkBlurRadiusToSigma(blur.Radius);
return SKImageFilter.CreateBlur(sigma, sigma);
return SkiaCompat.CreateBlur(sigma, sigma);
}
if (effect is IDropShadowEffect drop)
@ -41,7 +41,7 @@ partial class DrawingContextImpl
alpha *= _currentOpacity;
var color = new SKColor(drop.Color.R, drop.Color.G, drop.Color.B, (byte)Math.Max(0, Math.Min(255, alpha)));
return SKImageFilter.CreateDropShadow((float)drop.OffsetX, (float)drop.OffsetY, sigma, sigma, color);
return SkiaCompat.CreateDropShadow((float)drop.OffsetX, (float)drop.OffsetY, sigma, sigma, color);
}
return null;

6
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -116,7 +116,7 @@ namespace Avalonia.Skia
{
if (!_isDisposed)
{
_context.Canvas.SetMatrix(_revertTransform);
_context.Canvas.CSetMatrix(_revertTransform);
_context._leased = false;
_isDisposed = true;
}
@ -273,7 +273,7 @@ namespace Avalonia.Skia
{
var ac = shadow.Color;
var filter = SKImageFilter.CreateBlur(SkBlurRadiusToSigma(shadow.Blur), SkBlurRadiusToSigma(shadow.Blur));
var filter = SkiaCompat.CreateBlur(SkBlurRadiusToSigma(shadow.Blur), SkBlurRadiusToSigma(shadow.Blur));
var color = new SKColor(ac.R, ac.G, ac.B, (byte)(ac.A * opacity));
paint.Reset();
@ -752,7 +752,7 @@ namespace Avalonia.Skia
transform *= _postTransform.Value;
}
Canvas.SetMatrix(transform.ToSKMatrix());
Canvas.CSetMatrix(transform.ToSKMatrix());
}
}

2
src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs

@ -156,7 +156,7 @@ namespace Avalonia.Skia
var oldMatrix = context.Canvas.TotalMatrix;
context.Canvas.ResetMatrix();
_surface.Surface.Draw(context.Canvas, 0, 0, null);
context.Canvas.SetMatrix(oldMatrix);
context.Canvas.CSetMatrix(oldMatrix);
}
}

6
src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs

@ -20,8 +20,8 @@ namespace Avalonia.Skia
var matrix = transform.ToSKMatrix();
var transformedPath = StrokePath = source.StrokePath.Clone();
transformedPath?.Transform(matrix);
transformedPath?.CTransform(ref matrix);
Bounds = transformedPath?.TightBounds.ToAvaloniaRect() ?? default;
if (ReferenceEquals(source.StrokePath, source.FillPath))
@ -29,7 +29,7 @@ namespace Avalonia.Skia
else if (source.FillPath != null)
{
FillPath = transformedPath = source.FillPath.Clone();
transformedPath.Transform(matrix);
transformedPath.CTransform(ref matrix);
}
}

39
tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

@ -6,6 +6,7 @@ using System.Collections.Specialized;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
@ -1115,6 +1116,44 @@ namespace Avalonia.Controls.UnitTests
Assert.True(container.IsVisible);
}
[Fact]
public void ScrollIntoView_With_TargetRect_Outside_Viewport_Should_Scroll_To_Item()
{
using var app = App();
var items = Enumerable.Range(0, 101).Select(x => new ItemWithHeight(x, x * 100 + 1));
var itemTemplate = new FuncDataTemplate<ItemWithHeight>((x, _) =>
new Border
{
Height = 10,
[!Layoutable.WidthProperty] = new Binding("Height"),
});
var (target, scroll, itemsControl) = CreateTarget(
items: items,
itemTemplate: itemTemplate,
styles: new[]
{
new Style(x => x.OfType<ScrollViewer>())
{
Setters =
{
new Setter(ScrollViewer.HorizontalScrollBarVisibilityProperty, ScrollBarVisibility.Visible),
}
}
});
itemsControl.ContainerPrepared += (_, ev) =>
{
ev.Container.AddHandler(Control.RequestBringIntoViewEvent, (_, e) =>
{
var dataContext = e.TargetObject.DataContext as ItemWithHeight;
e.TargetRect = new Rect(dataContext.Height - 50, 0, 50, 10);
});
};
target.ScrollIntoView(100);
Assert.Equal(9901, scroll.Offset.X);
}
private static IReadOnlyList<int> GetRealizedIndexes(VirtualizingStackPanel target, ItemsControl itemsControl)
{
return target.GetRealizedElements()

Loading…
Cancel
Save