Browse Source

Merge branch 'master' into issues/4806

Andrey Kunchev 5 years ago
parent
commit
ec9ea5b0d0
17 changed files with 288 additions and 118 deletions
  1. 4 75
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  2. 2 2
      src/Avalonia.Controls/MenuItem.cs
  3. 15 3
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  4. 61 0
      src/Avalonia.Visuals/Animation/CompositePageTransition.cs
  5. 21 8
      src/Avalonia.Visuals/Animation/CrossFade.cs
  6. 21 23
      src/Avalonia.Visuals/Animation/PageSlide.cs
  7. 37 6
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  8. 4 0
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  9. 123 1
      tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
  10. BIN
      tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png
  11. BIN
      tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png
  12. BIN
      tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png
  13. BIN
      tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png
  14. BIN
      tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png
  15. BIN
      tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png
  16. BIN
      tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png
  17. BIN
      tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png

+ 4 - 75
src/Avalonia.Controls/Calendar/CalendarItem.cs

@@ -36,11 +36,7 @@ namespace Avalonia.Controls.Primitives
         private Button _headerButton;
         private Button _nextButton;
         private Button _previousButton;
-        private Grid _monthView;
-        private Grid _yearView;
         private ITemplate<IControl> _dayTitleTemplate;
-        private CalendarButton _lastCalendarButton;
-        private CalendarDayButton _lastCalendarDayButton;
         
         private DateTime _currentMonth;
         private bool _isMouseLeftButtonDown = false;
@@ -160,38 +156,12 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Gets the Grid that hosts the content when in month mode.
         /// </summary>
-        internal Grid MonthView
-        {
-            get { return _monthView; }
-            private set
-            {
-                if (_monthView != null)
-                    _monthView.PointerLeave -= MonthView_MouseLeave;
-
-                _monthView = value;
-
-                if (_monthView != null)
-                    _monthView.PointerLeave += MonthView_MouseLeave;
-            }
-        }
+        internal Grid MonthView { get; set; }
         /// <summary>
         /// Gets the Grid that hosts the content when in year or decade mode.
         /// </summary>
-        internal Grid YearView
-        {
-            get { return _yearView; }
-            private set
-            {
-                if (_yearView != null)
-                    _yearView.PointerLeave -= YearView_MouseLeave;
-
-                _yearView = value;
-
-                if (_yearView != null)
-                    _yearView.PointerLeave += YearView_MouseLeave;
-            }
-        }
-
+        internal Grid YearView { get; set; }
+        
         private void PopulateGrids()
         {
             if (MonthView != null)
@@ -226,7 +196,6 @@ namespace Avalonia.Controls.Primitives
                         cell.CalendarDayButtonMouseDown += Cell_MouseLeftButtonDown;
                         cell.CalendarDayButtonMouseUp += Cell_MouseLeftButtonUp;
                         cell.PointerEnter += Cell_MouseEnter;
-                        cell.PointerLeave += Cell_MouseLeave;
                         cell.Click += Cell_Click;
                         children.Add(cell);
                     }
@@ -256,7 +225,6 @@ namespace Avalonia.Controls.Primitives
                         month.CalendarLeftMouseButtonDown += Month_CalendarButtonMouseDown;
                         month.CalendarLeftMouseButtonUp += Month_CalendarButtonMouseUp;
                         month.PointerEnter += Month_MouseEnter;
-                        month.PointerLeave += Month_MouseLeave;
                         children.Add(month);
                     }
                 }
@@ -937,17 +905,7 @@ namespace Avalonia.Controls.Primitives
                 }
             }
         }
-        internal void Cell_MouseLeave(object sender, PointerEventArgs e)
-        {
-            if (_isMouseLeftButtonDown)
-            {
-                CalendarDayButton b = (CalendarDayButton)sender;
-                // The button is in Pressed state. Change the state to normal.
-                if (e.Pointer.Captured == b)
-                    e.Pointer.Capture(null);
-                _lastCalendarDayButton = b;
-            }
-        }
+        
         internal void Cell_MouseLeftButtonDown(object sender, PointerPressedEventArgs e)
         {
             if (Owner != null)
@@ -1207,35 +1165,6 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
-        private void Month_MouseLeave(object sender, PointerEventArgs e)
-        {
-            if (_isMouseLeftButtonDownYearView)
-            {
-                CalendarButton b = (CalendarButton)sender;
-                // The button is in Pressed state. Change the state to normal.
-                if (e.Pointer.Captured == b)
-                    e.Pointer.Capture(null);
-                //b.ReleaseMouseCapture();
-
-                _lastCalendarButton = b;
-            }
-        }
-        private void MonthView_MouseLeave(object sender, PointerEventArgs e)
-        {
-            if (_lastCalendarDayButton != null)
-            {
-                e.Pointer.Capture(_lastCalendarDayButton);
-            }
-        }
-
-        private void YearView_MouseLeave(object sender, PointerEventArgs e)
-        {
-            if (_lastCalendarButton != null)
-            {
-                e.Pointer.Capture(_lastCalendarButton);
-            }
-        }
-        
         internal void UpdateDisabled(bool isEnabled)
         {
             PseudoClasses.Set(":calendardisabled", !isEnabled);

+ 2 - 2
src/Avalonia.Controls/MenuItem.cs

@@ -101,7 +101,7 @@ namespace Avalonia.Controls
 
         private ICommand? _command;
         private bool _commandCanExecute = true;
-        private Popup _popup;
+        private Popup? _popup;
 
         /// <summary>
         /// Initializes static members of the <see cref="MenuItem"/> class.
@@ -145,7 +145,7 @@ namespace Avalonia.Controls
                 {
                     var parent = x as Control;
                     return parent?.GetObservable(DefinitionBase.PrivateSharedSizeScopeProperty) ??
-                        Observable.Return<DefinitionBase.SharedSizeScope>(null);
+                        Observable.Return<DefinitionBase.SharedSizeScope?>(null);
                 });
 
             this.Bind(DefinitionBase.PrivateSharedSizeScopeProperty, parentSharedSizeScope);

+ 15 - 3
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@@ -148,6 +148,7 @@ namespace Avalonia.Controls.Platform
             {
                 case Key.Up:
                 case Key.Down:
+                {
                     if (item?.IsTopLevel == true)
                     {
                         if (item.HasSubMenu && !item.IsSubMenuOpen)
@@ -161,8 +162,10 @@ namespace Avalonia.Controls.Platform
                         goto default;
                     }
                     break;
+                }
 
                 case Key.Left:
+                {
                     if (item?.Parent is IMenuItem parent && !parent.IsTopLevel && parent.IsSubMenuOpen)
                     {
                         parent.Close();
@@ -174,8 +177,10 @@ namespace Avalonia.Controls.Platform
                         goto default;
                     }
                     break;
+                }
 
                 case Key.Right:
+                {
                     if (item != null && !item.IsTopLevel && item.HasSubMenu)
                     {
                         Open(item, true);
@@ -186,8 +191,10 @@ namespace Avalonia.Controls.Platform
                         goto default;
                     }
                     break;
+                }
 
                 case Key.Enter:
+                {
                     if (item != null)
                     {
                         if (!item.HasSubMenu)
@@ -202,12 +209,14 @@ namespace Avalonia.Controls.Platform
                         e.Handled = true;
                     }
                     break;
+                }
 
                 case Key.Escape:
-                    if (item?.Parent != null)
+                {
+                    if (item?.Parent is IMenuElement parent)
                     {
-                        item.Parent.Close();
-                        item.Parent.Focus();
+                        parent.Close();
+                        parent.Focus();
                     }
                     else
                     {
@@ -216,8 +225,10 @@ namespace Avalonia.Controls.Platform
 
                     e.Handled = true;
                     break;
+                }
 
                 default:
+                {
                     var direction = e.Key.ToNavigationDirection();
 
                     if (direction.HasValue)
@@ -246,6 +257,7 @@ namespace Avalonia.Controls.Platform
                     }
 
                     break;
+                }
             }
 
             if (!e.Handled && item?.Parent is IMenuItem parentItem)

+ 61 - 0
src/Avalonia.Visuals/Animation/CompositePageTransition.cs

@@ -0,0 +1,61 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Avalonia.Metadata;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Defines a composite page transition that can be used to combine multiple transitions.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// Instantiate the <see cref="CompositePageTransition" /> in XAML and initialize the
+    /// <see cref="Transitions" /> property in order to have many animations triggered at once.
+    /// For example, you can combine <see cref="CrossFade"/> and <see cref="PageSlide"/>.
+    /// <code>
+    /// <![CDATA[
+    /// <reactiveUi:RoutedViewHost Router="{Binding Router}">
+    ///   <reactiveUi:RoutedViewHost.PageTransition>
+    ///     <CompositePageTransition>
+    ///       <PageSlide Duration="0.5" />
+    ///       <CrossFade Duration="0.5" />
+    ///     </CompositePageTransition>
+    ///   </reactiveUi:RoutedViewHost.PageTransition>
+    /// </reactiveUi:RoutedViewHost>
+    /// ]]>
+    /// </code>
+    /// </para>
+    /// </remarks>
+    public class CompositePageTransition : IPageTransition
+    {
+        /// <summary>
+        /// Gets or sets the transitions to be executed. Can be defined from XAML.
+        /// </summary>
+        [Content]
+        public List<IPageTransition> PageTransitions { get; set; } = new List<IPageTransition>();
+
+        /// <summary>
+        /// Starts the animation.
+        /// </summary>
+        /// <param name="from">
+        /// The control that is being transitioned away from. May be null.
+        /// </param>
+        /// <param name="to">
+        /// The control that is being transitioned to. May be null.
+        /// </param>
+        /// <param name="forward">
+        /// Defines the direction of the transition.
+        /// </param>
+        /// <returns>
+        /// A <see cref="Task"/> that tracks the progress of the animation.
+        /// </returns>
+        public Task Start(Visual from, Visual to, bool forward)
+        {
+            var transitionTasks = PageTransitions
+                .Select(transition => transition.Start(from, to, forward))
+                .ToList();
+            return Task.WhenAll(transitionTasks);
+        }
+    }
+}

+ 21 - 8
src/Avalonia.Visuals/Animation/CrossFade.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Threading.Tasks;
+using Avalonia.Animation.Easings;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
 
@@ -74,14 +75,26 @@ namespace Avalonia.Animation
         /// </summary>
         public TimeSpan Duration
         {
-            get
-            {
-                return _fadeOutAnimation.Duration;
-            }
-            set
-            {
-                _fadeOutAnimation.Duration = _fadeInAnimation.Duration = value;
-            }
+            get => _fadeOutAnimation.Duration;
+            set => _fadeOutAnimation.Duration = _fadeInAnimation.Duration = value;
+        }
+
+        /// <summary>
+        /// Gets or sets element entrance easing.
+        /// </summary>
+        public Easing FadeInEasing
+        {
+            get => _fadeInAnimation.Easing;
+            set => _fadeInAnimation.Easing = value;
+        }
+
+        /// <summary>
+        /// Gets or sets element exit easing.
+        /// </summary>
+        public Easing FadeOutEasing
+        {
+            get => _fadeOutAnimation.Easing;
+            set => _fadeOutAnimation.Easing = value;
         }
 
         /// <summary>

+ 21 - 23
src/Avalonia.Visuals/Animation/PageSlide.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Threading.Tasks;
+using Avalonia.Animation.Easings;
 using Avalonia.Media;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
@@ -48,6 +49,16 @@ namespace Avalonia.Animation
         /// Gets the duration of the animation.
         /// </summary>
         public SlideAxis Orientation { get; set; }
+        
+        /// <summary>
+        /// Gets or sets element entrance easing.
+        /// </summary>
+        public Easing SlideInEasing { get; set; } = new LinearEasing();
+        
+        /// <summary>
+        /// Gets or sets element exit easing.
+        /// </summary>
+        public Easing SlideOutEasing { get; set; } = new LinearEasing();
 
         /// <summary>
         /// Starts the animation.
@@ -75,18 +86,12 @@ namespace Avalonia.Animation
             {
                 var animation = new Animation
                 {
-                    Children = 
+                    Easing = SlideOutEasing,
+                    Children =
                     {
                         new KeyFrame
                         {
-                            Setters =
-                            {
-                                new Setter
-                                {
-                                    Property = translateProperty,
-                                Value = 0d
-                                }
-                            },
+                            Setters = { new Setter { Property = translateProperty, Value = 0d } },
                             Cue = new Cue(0d)
                         },
                         new KeyFrame
@@ -100,10 +105,10 @@ namespace Avalonia.Animation
                                 }
                             },
                             Cue = new Cue(1d)
-                        }                       
-                    }
+                        }
+                    },
+                    Duration = Duration
                 };
-                animation.Duration = Duration;
                 tasks.Add(animation.RunAsync(from));
             }
 
@@ -112,9 +117,9 @@ namespace Avalonia.Animation
                 to.IsVisible = true;
                 var animation = new Animation
                 {
+                    Easing = SlideInEasing,
                     Children =
                     {
-
                         new KeyFrame
                         {
                             Setters =
@@ -129,19 +134,12 @@ namespace Avalonia.Animation
                         },
                         new KeyFrame
                         {
-                            Setters =
-                            {
-                                new Setter
-                                {
-                                    Property = translateProperty,
-                                    Value = 0d
-                                }
-                            },
+                            Setters = { new Setter { Property = translateProperty, Value = 0d } },
                             Cue = new Cue(1d)
                         }
-                    }
+                    },
+                    Duration = Duration
                 };
-                animation.Duration = Duration;
                 tasks.Add(animation.RunAsync(to));
             }
 

+ 37 - 6
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@@ -583,14 +583,45 @@ namespace Avalonia.Skia
                     var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint();
                     var radius = (float)(radialGradient.Radius * targetSize.Width);
 
-                    // TODO: There is no SetAlpha in SkiaSharp
-                    //paint.setAlpha(128);
+                    var origin = radialGradient.GradientOrigin.ToPixels(targetSize).ToSKPoint();
 
-                    // would be nice to cache these shaders possibly?
-                    using (var shader =
-                        SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode))
+                    if (origin.Equals(center))
                     {
-                        paintWrapper.Paint.Shader = shader;
+                        // when the origin is the same as the center the Skia RadialGradient acts the same as D2D
+                        using (var shader =
+                            SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode))
+                        {
+                            paintWrapper.Paint.Shader = shader;
+                        }
+                    }
+                    else
+                    {
+                        // when the origin is different to the center use a two point ConicalGradient to match the behaviour of D2D
+
+                        // reverse the order of the stops to match D2D
+                        var reversedColors = new SKColor[stopColors.Length];
+                        Array.Copy(stopColors, reversedColors, stopColors.Length);
+                        Array.Reverse(reversedColors);
+
+                        // and then reverse the reference point of the stops
+                        var reversedStops = new float[stopOffsets.Length];
+                        for (var i = 0; i < stopOffsets.Length; i++)
+                        {
+                            reversedStops[i] = stopOffsets[i];
+                            if (reversedStops[i] > 0 && reversedStops[i] < 1)
+                            {
+                                reversedStops[i] = Math.Abs(1 - stopOffsets[i]);
+                            }
+                        }
+                            
+                        // compose with a background colour of the final stop to match D2D's behaviour of filling with the final color
+                        using (var shader = SKShader.CreateCompose(
+                            SKShader.CreateColor(reversedColors[0]),
+                            SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode)
+                        ))
+                        {
+                            paintWrapper.Paint.Shader = shader;
+                        }
                     }
 
                     break;

+ 4 - 0
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@@ -200,6 +200,10 @@ namespace Avalonia.Win32
                             DipFromLParam(lParam), GetMouseModifiers(wParam));
                         break;
                     }
+                // Mouse capture is lost
+                case WindowsMessage.WM_CANCELMODE:
+                    _mouseDevice.Capture(null);
+                    break;
 
                 case WindowsMessage.WM_MOUSEMOVE:
                     {

+ 123 - 1
tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs

@@ -18,7 +18,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
         public RadialGradientBrushTests() : base(@"Media\RadialGradientBrush")
         {
         }
-        
+
         [Fact]
         public async Task RadialGradientBrush_RedBlue()
         {
@@ -43,5 +43,127 @@ namespace Avalonia.Direct2D1.RenderTests.Media
             await RenderToFile(target);
             CompareImages();
         }
+
+        /// <summary>
+        /// Tests using a GradientOrigin that falls inside of the circle described by Center/Radius.
+        /// </summary>
+        [Fact]
+        public async Task RadialGradientBrush_RedBlue_Offset_Inside()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Border
+                {
+                    Background = new RadialGradientBrush
+                    {
+                        GradientStops =
+                        {
+                            new GradientStop { Color = Colors.Red, Offset = 0 },
+                            new GradientStop { Color = Colors.Blue, Offset = 1 }
+                        },
+                        GradientOrigin = new RelativePoint(0.25, 0.25, RelativeUnit.Relative)
+                    }
+                }
+            };
+
+            await RenderToFile(target);
+            CompareImages();
+        }
+
+        /// <summary>
+        /// Tests using a GradientOrigin that falls outside of the circle described by Center/Radius.
+        /// </summary>
+        [Fact]
+        public async Task RadialGradientBrush_RedBlue_Offset_Outside()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Border
+                {
+                    Background = new RadialGradientBrush
+                    {
+                        GradientStops =
+                        {
+                            new GradientStop { Color = Colors.Red, Offset = 0 },
+                            new GradientStop { Color = Colors.Blue, Offset = 1 }
+                        },
+                        GradientOrigin = new RelativePoint(0.1, 0.1, RelativeUnit.Relative)
+                    }
+                }
+            };
+
+            await RenderToFile(target);
+            CompareImages();
+        }
+
+        /// <summary>
+        /// Tests using a GradientOrigin that falls inside of the circle described by Center/Radius.
+        /// </summary>
+        [Fact]
+        public async Task RadialGradientBrush_RedGreenBlue_Offset_Inside()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Border
+                {
+                    Background = new RadialGradientBrush
+                    {
+                        GradientStops =
+                        {
+                            new GradientStop { Color = Colors.Red, Offset = 0 },
+                            new GradientStop { Color = Colors.Green, Offset = 0.5 },
+                            new GradientStop { Color = Colors.Blue, Offset = 1 }
+                        },
+                        GradientOrigin = new RelativePoint(0.25, 0.25, RelativeUnit.Relative),
+                        Center = new RelativePoint(0.5, 0.5, RelativeUnit.Relative),
+                        Radius = 0.5                        
+                    }
+                }
+            };
+
+            await RenderToFile(target);
+            CompareImages();
+        }
+
+        /// <summary>
+        /// Tests using a GradientOrigin that falls outside of the circle described by Center/Radius.
+        /// </summary>
+        [Fact]
+        public async Task RadialGradientBrush_RedGreenBlue_Offset_Outside()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Border
+                {
+                    Background = new RadialGradientBrush
+                    {
+                        GradientStops =
+                        {
+                            new GradientStop { Color = Colors.Red, Offset = 0 },
+                            new GradientStop { Color = Colors.Green, Offset = 0.25 },
+                            new GradientStop { Color = Colors.Blue, Offset = 1 }
+                        },
+                        GradientOrigin = new RelativePoint(0.1, 0.1, RelativeUnit.Relative),
+                        Center = new RelativePoint(0.5, 0.5, RelativeUnit.Relative),
+                        Radius = 0.5
+                    }
+                }
+            };
+
+            await RenderToFile(target);
+            CompareImages();
+        }
     }
 }

BIN
tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png


BIN
tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png


BIN
tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png


BIN
tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png


BIN
tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png


BIN
tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png


BIN
tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png


BIN
tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png