diff --git a/native/Avalonia.Native/src/OSX/app.mm b/native/Avalonia.Native/src/OSX/app.mm index 7630de1d0b..d3d3d938e9 100644 --- a/native/Avalonia.Native/src/OSX/app.mm +++ b/native/Avalonia.Native/src/OSX/app.mm @@ -70,7 +70,7 @@ ComPtr _events; { auto array = CreateAvnStringArray(urls); - _events->FilesOpened(array); + _events->UrlsOpened(array); } - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender diff --git a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs index 151d3d5fd6..a4f48f35f5 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs @@ -194,7 +194,6 @@ public partial class Dispatcher if (Now - backgroundJobExecutionStartedAt.Value > _maximumInputStarvationTime) { - _signaled = true; RequestBackgroundProcessing(); return; } diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs index ea15aa992d..b7647df444 100644 --- a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs +++ b/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) { diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs index feb40d7dd9..f702f09e8a 100644 --- a/src/Avalonia.Controls/VirtualizingStackPanel.cs +++ b/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; diff --git a/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs b/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs index 3365da7d36..212088d4cd 100644 --- a/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs +++ b/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); + } } } } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 7791426c0b..27da2fc894 100644 --- a/src/Avalonia.Native/avn.idl +++ b/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 (); diff --git a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs index b0978dc3f6..94090f2614 100644 --- a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs +++ b/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); } /// @@ -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); + } } } } diff --git a/src/Avalonia.ReactiveUI/ReactiveWindow.cs b/src/Avalonia.ReactiveUI/ReactiveWindow.cs index 14e9353096..727c22f016 100644 --- a/src/Avalonia.ReactiveUI/ReactiveWindow.cs +++ b/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); } /// @@ -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); + } } } } diff --git a/src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml index 6f12f1d306..5328a71b3c 100644 --- a/src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml @@ -13,6 +13,7 @@ 10,6,6,5 20 20 + 24 8,5,8,6 diff --git a/src/Avalonia.Themes.Fluent/Controls/RefreshVisualizer.xaml b/src/Avalonia.Themes.Fluent/Controls/RefreshVisualizer.xaml index a9ca8a0a5c..c5f993bb0e 100644 --- a/src/Avalonia.Themes.Fluent/Controls/RefreshVisualizer.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/RefreshVisualizer.xaml @@ -11,19 +11,20 @@ Value="{DynamicResource RefreshVisualizerBackground}"/> + + + - - - - + Background="{TemplateBinding Background}"/> diff --git a/src/Avalonia.Themes.Simple/Controls/RefreshVisualizer.xaml b/src/Avalonia.Themes.Simple/Controls/RefreshVisualizer.xaml index 09f15e4b2a..fe9f291794 100644 --- a/src/Avalonia.Themes.Simple/Controls/RefreshVisualizer.xaml +++ b/src/Avalonia.Themes.Simple/Controls/RefreshVisualizer.xaml @@ -13,19 +13,20 @@ Value="{DynamicResource RefreshVisualizerBackground}"/> + + + - - - - + Background="{TemplateBinding Background}"/> diff --git a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs index 6607bcf539..f534c3f27b 100644 --- a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs +++ b/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; diff --git a/src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKCanvas.cs b/src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKCanvas.cs new file mode 100644 index 0000000000..0483c3ff28 --- /dev/null +++ b/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); + } +} diff --git a/src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKImageFilter.cs b/src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKImageFilter.cs new file mode 100644 index 0000000000..493aa16736 --- /dev/null +++ b/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? s_sk3FilterBlurFactory; + private static Func? 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)Delegate.CreateDelegate(typeof(Func), 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) + Delegate.CreateDelegate(typeof(Func), null, method); + } + + return s_sk3FilterDropShadowFactory(dropOffsetX, dropOffsetY, sigma, f, color); + } + } +} diff --git a/src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKPath.cs b/src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKPath.cs new file mode 100644 index 0000000000..73561aec34 --- /dev/null +++ b/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); + } +} diff --git a/src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.cs b/src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.cs new file mode 100644 index 0000000000..a09b6452c2 --- /dev/null +++ b/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; +} diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs index babc547209..d9c2c27496 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs +++ b/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; diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index c3778d8426..2778c664ce 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/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()); } } diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index c695e8ba41..7dce7b764a 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/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); } } diff --git a/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs b/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs index 2dee8c318c..9e74610163 100644 --- a/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs +++ b/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); } } diff --git a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs index a34db7d979..b72f2376de 100644 --- a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs +++ b/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((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()) + { + 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 GetRealizedIndexes(VirtualizingStackPanel target, ItemsControl itemsControl) { return target.GetRealizedElements()