فهرست منبع

Merge branch 'master' into Flyouts

Dan Walmsley 4 سال پیش
والد
کامیت
b581b9c2e8
60فایلهای تغییر یافته به همراه926 افزوده شده و 210 حذف شده
  1. 0 2
      readme.md
  2. 3 0
      samples/RenderDemo/MainWindow.xaml
  3. 89 0
      samples/RenderDemo/Pages/PathMeasurementPage.cs
  4. 2 2
      src/Avalonia.Base/Data/BindingValue.cs
  5. 45 9
      src/Avalonia.Base/EnumExtensions.cs
  6. 2 2
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  7. 1 1
      src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
  8. 1 1
      src/Avalonia.Controls/ComboBox.cs
  9. 8 8
      src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs
  10. 5 5
      src/Avalonia.Controls/Grid.cs
  11. 4 4
      src/Avalonia.Controls/ListBox.cs
  12. 6 6
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  13. 6 6
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  14. 18 18
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
  15. 6 6
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  16. 2 0
      src/Avalonia.Controls/ProgressBar.cs
  17. 2 2
      src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs
  18. 2 2
      src/Avalonia.Controls/TextBox.cs
  19. 5 5
      src/Avalonia.Controls/TreeView.cs
  20. 4 4
      src/Avalonia.Controls/Window.cs
  21. 8 8
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs
  22. 9 9
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs
  23. 4 4
      src/Avalonia.FreeDesktop/DBusMenuExporter.cs
  24. 2 2
      src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs
  25. 32 0
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  26. 1 1
      src/Avalonia.Input/AccessKeyHandler.cs
  27. 10 4
      src/Avalonia.Input/Gestures.cs
  28. 4 4
      src/Avalonia.Input/KeyGesture.cs
  29. 3 1
      src/Avalonia.Input/PointerEventArgs.cs
  30. 9 5
      src/Avalonia.Input/PointerPoint.cs
  31. 18 0
      src/Avalonia.Input/TappedEventArgs.cs
  32. 3 3
      src/Avalonia.Interactivity/EventRoute.cs
  33. 2 2
      src/Avalonia.Interactivity/Interactive.cs
  34. 9 0
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  35. 12 1
      src/Avalonia.Visuals/Media/DrawingContext.cs
  36. 57 0
      src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs
  37. 11 0
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  38. 39 0
      src/Avalonia.Visuals/Platform/IGeometryImpl.cs
  39. 68 0
      src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs
  40. 30 0
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  41. 14 6
      src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
  42. 9 0
      src/Avalonia.Visuals/Vector.cs
  43. 2 2
      src/Avalonia.X11/X11Window.Ime.cs
  44. 9 9
      src/Avalonia.X11/X11Window.cs
  45. 5 5
      src/Avalonia.X11/XI2Manager.cs
  46. 1 1
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs
  47. 16 0
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  48. 91 11
      src/Skia/Avalonia.Skia/GeometryImpl.cs
  49. 33 0
      src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
  50. 43 1
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  51. 33 0
      src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
  52. 4 4
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  53. 3 3
      src/Windows/Avalonia.Win32/DataObject.cs
  54. 12 12
      src/Windows/Avalonia.Win32/OleDropTarget.cs
  55. 7 7
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  56. 2 2
      src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs
  57. 52 20
      src/Windows/Avalonia.Win32/WindowImpl.cs
  58. 21 0
      tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
  59. 10 0
      tests/Avalonia.Visuals.UnitTests/VectorTests.cs
  60. 17 0
      tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

+ 0 - 2
readme.md

@@ -2,8 +2,6 @@
 <br />
 [![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) [![MyGet](https://img.shields.io/myget/avalonia-ci/vpre/Avalonia.svg?label=myget)](https://www.myget.org/gallery/avalonia-ci) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg) 
 
-<img alt="Avalonia" src="https://user-images.githubusercontent.com/6759207/84897744-cab6d800-b0ae-11ea-8214-e5174d71f5c8.png" width="400"/>
-
 ## 📖 About AvaloniaUI 
 
 Avalonia is a cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows via .NET Framework and .NET Core, Linux via Xorg, macOS. Avalonia is ready for **General-Purpose Desktop App Development**. However, there may be some bugs and breaking changes as we continue along into this project's development. 

+ 3 - 0
samples/RenderDemo/MainWindow.xaml

@@ -57,6 +57,9 @@
       <TabItem Header="LineBounds">
         <pages:LineBoundsPage />
       </TabItem>
+      <TabItem Header="Path Measurement">
+        <pages:PathMeasurementPage />
+      </TabItem>
     </TabControl>
   </DockPanel>
 </Window>

+ 89 - 0
samples/RenderDemo/Pages/PathMeasurementPage.cs

@@ -0,0 +1,89 @@
+using System;
+using System.Diagnostics;
+using System.Drawing.Drawing2D;
+using System.Security.Cryptography;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.LogicalTree;
+using Avalonia.Media;
+using Avalonia.Media.Imaging;
+using Avalonia.Media.Immutable;
+using Avalonia.Threading;
+using Avalonia.Visuals.Media.Imaging;
+
+namespace RenderDemo.Pages
+{
+    public class PathMeasurementPage : Control
+    {
+        static PathMeasurementPage()
+        {
+            AffectsRender<PathMeasurementPage>(BoundsProperty);
+        }
+
+        private RenderTargetBitmap _bitmap;
+
+        protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+            _bitmap = new RenderTargetBitmap(new PixelSize(500, 500), new Vector(96, 96));
+            base.OnAttachedToLogicalTree(e);
+        }
+
+        protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+            _bitmap.Dispose();
+            _bitmap = null;
+            base.OnDetachedFromLogicalTree(e);
+        }
+
+        readonly IPen strokePen = new ImmutablePen(Brushes.DarkBlue, 10d, null, PenLineCap.Round, PenLineJoin.Round);
+        readonly IPen strokePen1 = new ImmutablePen(Brushes.Purple, 10d, null, PenLineCap.Round, PenLineJoin.Round);
+        readonly IPen strokePen2 = new ImmutablePen(Brushes.Green, 10d, null, PenLineCap.Round, PenLineJoin.Round);
+        readonly IPen strokePen3 = new ImmutablePen(Brushes.LightBlue, 10d, null, PenLineCap.Round, PenLineJoin.Round);
+        readonly IPen strokePen4 = new ImmutablePen(Brushes.Red, 1d, null, PenLineCap.Round, PenLineJoin.Round);
+
+        public override void Render(DrawingContext context)
+        {
+            using (var ctxi = _bitmap.CreateDrawingContext(null))
+            using (var bitmapCtx = new DrawingContext(ctxi, false))
+            {
+                ctxi.Clear(default);
+
+                var basePath = new PathGeometry();
+
+                using (var basePathCtx = basePath.Open())
+                {
+                    basePathCtx.BeginFigure(new Point(20, 20), false);
+                    basePathCtx.LineTo(new Point(400, 50));
+                    basePathCtx.LineTo(new Point(80, 100));
+                    basePathCtx.LineTo(new Point(300, 150));
+                    basePathCtx.EndFigure(false);
+                }
+
+                bitmapCtx.DrawGeometry(null, strokePen, basePath);
+
+
+                var length = basePath.PlatformImpl.ContourLength;
+
+                if (basePath.PlatformImpl.TryGetSegment(length * 0.05, length * 0.2, true, out var dst1))
+                    bitmapCtx.DrawGeometry(null, strokePen1, dst1);
+
+                if (basePath.PlatformImpl.TryGetSegment(length * 0.2, length * 0.8, true, out var dst2))
+                    bitmapCtx.DrawGeometry(null, strokePen2, dst2);
+
+                if (basePath.PlatformImpl.TryGetSegment(length * 0.8, length * 0.95, true, out var dst3))
+                    bitmapCtx.DrawGeometry(null, strokePen3, dst3);
+                
+                var pathBounds = basePath.GetRenderBounds(strokePen);
+                
+                bitmapCtx.DrawRectangle(null, strokePen4, pathBounds);
+            }
+
+
+            context.DrawImage(_bitmap,
+                new Rect(0, 0, 500, 500),
+                new Rect(0, 0, 500, 500));
+             
+            base.Render(context);
+        }
+    }
+}

+ 2 - 2
src/Avalonia.Base/Data/BindingValue.cs

@@ -108,12 +108,12 @@ namespace Avalonia.Data
         /// Gets a value indicating whether the binding value represents either a binding or data
         /// validation error.
         /// </summary>
-        public bool HasError => Type.HasFlagCustom(BindingValueType.HasError);
+        public bool HasError => Type.HasAllFlags(BindingValueType.HasError);
 
         /// <summary>
         /// Gets a value indicating whether the binding value has a value.
         /// </summary>
-        public bool HasValue => Type.HasFlagCustom(BindingValueType.HasValue);
+        public bool HasValue => Type.HasAllFlags(BindingValueType.HasValue);
 
         /// <summary>
         /// Gets the type of the binding value.

+ 45 - 9
src/Avalonia.Base/EnumExtensions.cs

@@ -9,31 +9,67 @@ namespace Avalonia
     public static class EnumExtensions
     {
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static unsafe bool HasFlagCustom<T>(this T value, T flag) where T : unmanaged, Enum
+        [Obsolete("This method is obsolete. Use HasAllFlags instead.")]
+        public static bool HasFlagCustom<T>(this T value, T flag) where T : unmanaged, Enum
+            => value.HasAllFlags(flag);
+            
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static unsafe bool HasAllFlags<T>(this T value, T flags) where T : unmanaged, Enum
+        {
+            if (sizeof(T) == 1)
+            {
+                var byteValue = Unsafe.As<T, byte>(ref value);
+                var byteFlags = Unsafe.As<T, byte>(ref flags);
+                return (byteValue & byteFlags) == byteFlags;
+            }
+            else if (sizeof(T) == 2)
+            {
+                var shortValue = Unsafe.As<T, short>(ref value);
+                var shortFlags = Unsafe.As<T, short>(ref flags);
+                return (shortValue & shortFlags) == shortFlags;
+            }
+            else if (sizeof(T) == 4)
+            {
+                var intValue = Unsafe.As<T, int>(ref value);
+                var intFlags = Unsafe.As<T, int>(ref flags);
+                return (intValue & intFlags) == intFlags;
+            }
+            else if (sizeof(T) == 8)
+            {
+                var longValue = Unsafe.As<T, long>(ref value);
+                var longFlags = Unsafe.As<T, long>(ref flags);
+                return (longValue & longFlags) == longFlags;
+            }
+            else
+                throw new NotSupportedException("Enum with size of " + Unsafe.SizeOf<T>() + " are not supported");
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static unsafe bool HasAnyFlag<T>(this T value, T flags) where T : unmanaged, Enum
         {
             if (sizeof(T) == 1)
             {
                 var byteValue = Unsafe.As<T, byte>(ref value);
-                var byteFlag = Unsafe.As<T, byte>(ref flag);
-                return (byteValue & byteFlag) == byteFlag;
+                var byteFlags = Unsafe.As<T, byte>(ref flags);
+                return (byteValue & byteFlags) != 0;
             }
             else if (sizeof(T) == 2)
             {
                 var shortValue = Unsafe.As<T, short>(ref value);
-                var shortFlag = Unsafe.As<T, short>(ref flag);
-                return (shortValue & shortFlag) == shortFlag;
+                var shortFlags = Unsafe.As<T, short>(ref flags);
+                return (shortValue & shortFlags) != 0;
             }
             else if (sizeof(T) == 4)
             {
                 var intValue = Unsafe.As<T, int>(ref value);
-                var intFlag = Unsafe.As<T, int>(ref flag);
-                return (intValue & intFlag) == intFlag;
+                var intFlags = Unsafe.As<T, int>(ref flags);
+                return (intValue & intFlags) != 0;
             }
             else if (sizeof(T) == 8)
             {
                 var longValue = Unsafe.As<T, long>(ref value);
-                var longFlag = Unsafe.As<T, long>(ref flag);
-                return (longValue & longFlag) == longFlag;
+                var longFlags = Unsafe.As<T, long>(ref flags);
+                return (longValue & longFlags) != 0;
             }
             else
                 throw new NotSupportedException("Enum with size of " + Unsafe.SizeOf<T>() + " are not supported");

+ 2 - 2
src/Avalonia.Base/Utilities/TypeUtilities.cs

@@ -372,8 +372,8 @@ namespace Avalonia.Utilities
             const string implicitName = "op_Implicit";
             const string explicitName = "op_Explicit";
 
-            bool allowImplicit = operatorType.HasFlagCustom(OperatorType.Implicit);
-            bool allowExplicit = operatorType.HasFlagCustom(OperatorType.Explicit);
+            bool allowImplicit = operatorType.HasAllFlags(OperatorType.Implicit);
+            bool allowExplicit = operatorType.HasAllFlags(OperatorType.Explicit);
 
             foreach (MethodInfo method in fromType.GetMethods())
             {

+ 1 - 1
src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs

@@ -2595,7 +2595,7 @@ namespace Avalonia.Collections
         /// <returns>Whether the specified flag is set</returns>
         private bool CheckFlag(CollectionViewFlags flags)
         {
-            return _flags.HasFlagCustom(flags);
+            return _flags.HasAllFlags(flags);
         }
 
         /// <summary>

+ 1 - 1
src/Avalonia.Controls/ComboBox.cs

@@ -206,7 +206,7 @@ namespace Avalonia.Controls
                 return;
 
             if (e.Key == Key.F4 ||
-                ((e.Key == Key.Down || e.Key == Key.Up) && e.KeyModifiers.HasFlagCustom(KeyModifiers.Alt)))
+                ((e.Key == Key.Down || e.Key == Key.Up) && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt)))
             {
                 IsDropDownOpen = !IsDropDownOpen;
                 e.Handled = true;

+ 8 - 8
src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs

@@ -72,24 +72,24 @@ namespace Avalonia.Controls.Converters
                 }
             }
 
-            if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Control))
+            if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Control))
             {
                 s.Append("Ctrl");
             }
 
-            if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Shift))
+            if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Shift))
             {
                 Plus(s);
                 s.Append("Shift");
             }
 
-            if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Alt))
+            if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Alt))
             {
                 Plus(s);
                 s.Append("Alt");
             }
 
-            if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Meta))
+            if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Meta))
             {
                 Plus(s);
                 s.Append(meta);
@@ -105,22 +105,22 @@ namespace Avalonia.Controls.Converters
         {
             var s = new StringBuilder();
 
-            if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Control))
+            if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Control))
             {
                 s.Append('⌃');
             }
 
-            if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Alt))
+            if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Alt))
             {
                 s.Append('⌥');
             }
 
-            if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Shift))
+            if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Shift))
             {
                 s.Append('⇧');
             }
 
-            if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Meta))
+            if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Meta))
             {
                 s.Append('⌘');
             }

+ 5 - 5
src/Avalonia.Controls/Grid.cs

@@ -2355,7 +2355,7 @@ namespace Avalonia.Controls
         /// </summary>
         private bool CheckFlags(Flags flags)
         {
-            return _flags.HasFlagCustom(flags);
+            return _flags.HasAllFlags(flags);
         }
 
         private static void OnShowGridLinesPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
@@ -2790,10 +2790,10 @@ namespace Avalonia.Controls
             internal LayoutTimeSizeType SizeTypeU;
             internal LayoutTimeSizeType SizeTypeV;
             internal int Next;
-            internal bool IsStarU => SizeTypeU.HasFlagCustom(LayoutTimeSizeType.Star);
-            internal bool IsAutoU => SizeTypeU.HasFlagCustom(LayoutTimeSizeType.Auto);
-            internal bool IsStarV => SizeTypeV.HasFlagCustom(LayoutTimeSizeType.Star);
-            internal bool IsAutoV => SizeTypeV.HasFlagCustom(LayoutTimeSizeType.Auto);
+            internal bool IsStarU => SizeTypeU.HasAllFlags(LayoutTimeSizeType.Star);
+            internal bool IsAutoU => SizeTypeU.HasAllFlags(LayoutTimeSizeType.Auto);
+            internal bool IsStarV => SizeTypeV.HasAllFlags(LayoutTimeSizeType.Star);
+            internal bool IsAutoV => SizeTypeV.HasAllFlags(LayoutTimeSizeType.Auto);
         }
 
         /// <summary>

+ 4 - 4
src/Avalonia.Controls/ListBox.cs

@@ -135,8 +135,8 @@ namespace Avalonia.Controls
                 e.Handled = UpdateSelectionFromEventSource(
                     e.Source,
                     true,
-                    e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift),
-                    e.KeyModifiers.HasFlagCustom(KeyModifiers.Control));
+                    e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
+                    e.KeyModifiers.HasAllFlags(KeyModifiers.Control));
             }
         }
 
@@ -154,8 +154,8 @@ namespace Avalonia.Controls
                     e.Handled = UpdateSelectionFromEventSource(
                         e.Source,
                         true,
-                        e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift),
-                        e.KeyModifiers.HasFlagCustom(KeyModifiers.Control),
+                        e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
+                        e.KeyModifiers.HasAllFlags(KeyModifiers.Control),
                         point.Properties.IsRightButtonPressed);
                 }
             }

+ 6 - 6
src/Avalonia.Controls/Platform/InProcessDragSource.cs

@@ -73,20 +73,20 @@ namespace Avalonia.Platform
         {
             if (effect == DragDropEffects.Copy || effect == DragDropEffects.Move || effect == DragDropEffects.Link || effect == DragDropEffects.None)
                 return effect; // No need to check for the modifiers.
-            if (effect.HasFlagCustom(DragDropEffects.Link) && modifiers.HasFlagCustom(RawInputModifiers.Alt))
+            if (effect.HasAllFlags(DragDropEffects.Link) && modifiers.HasAllFlags(RawInputModifiers.Alt))
                 return DragDropEffects.Link;
-            if (effect.HasFlagCustom(DragDropEffects.Copy) && modifiers.HasFlagCustom(RawInputModifiers.Control))
+            if (effect.HasAllFlags(DragDropEffects.Copy) && modifiers.HasAllFlags(RawInputModifiers.Control))
                 return DragDropEffects.Copy;
             return DragDropEffects.Move;
         }
 
         private StandardCursorType GetCursorForDropEffect(DragDropEffects effects)
         {
-            if (effects.HasFlagCustom(DragDropEffects.Copy))
+            if (effects.HasAllFlags(DragDropEffects.Copy))
                 return StandardCursorType.DragCopy;
-            if (effects.HasFlagCustom(DragDropEffects.Move))
+            if (effects.HasAllFlags(DragDropEffects.Move))
                 return StandardCursorType.DragMove;
-            if (effects.HasFlagCustom(DragDropEffects.Link))
+            if (effects.HasAllFlags(DragDropEffects.Link))
                 return StandardCursorType.DragLink;
             return StandardCursorType.No;
         }
@@ -161,7 +161,7 @@ namespace Avalonia.Platform
             
             void CheckDraggingAccepted(RawInputModifiers changedMouseButton)
             {
-                if (_initialInputModifiers.Value.HasFlagCustom(changedMouseButton))
+                if (_initialInputModifiers.Value.HasAllFlags(changedMouseButton))
                 {
                     var result = RaiseEventAndUpdateCursor(RawDragEventType.Drop, e.Root, e.Position, e.InputModifiers);
                     UpdateCursor(null, DragDropEffects.None);

+ 6 - 6
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

@@ -253,8 +253,8 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
     {
         public static void ValidateEdge(this PopupAnchor edge)
         {
-            if (edge.HasFlagCustom(PopupAnchor.Left) && edge.HasFlagCustom(PopupAnchor.Right) ||
-                edge.HasFlagCustom(PopupAnchor.Top) && edge.HasFlagCustom(PopupAnchor.Bottom))
+            if (edge.HasAllFlags(PopupAnchor.Left | PopupAnchor.Right) ||
+                edge.HasAllFlags(PopupAnchor.Top | PopupAnchor.Bottom))
                 throw new ArgumentException("Opposite edges specified");
         }
 
@@ -265,10 +265,10 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
 
         public static PopupAnchor Flip(this PopupAnchor edge)
         {
-            if (edge.HasFlagCustom(PopupAnchor.HorizontalMask))
+            if (edge.HasAnyFlag(PopupAnchor.HorizontalMask))
                 edge ^= PopupAnchor.HorizontalMask;
 
-            if (edge.HasFlagCustom(PopupAnchor.VerticalMask))
+            if (edge.HasAnyFlag(PopupAnchor.VerticalMask))
                 edge ^= PopupAnchor.VerticalMask;
 
             return edge;
@@ -276,14 +276,14 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
 
         public static PopupAnchor FlipX(this PopupAnchor edge)
         {
-            if (edge.HasFlagCustom(PopupAnchor.HorizontalMask))
+            if (edge.HasAnyFlag(PopupAnchor.HorizontalMask))
                 edge ^= PopupAnchor.HorizontalMask;
             return edge;
         }
         
         public static PopupAnchor FlipY(this PopupAnchor edge)
         {
-            if (edge.HasFlagCustom(PopupAnchor.VerticalMask))
+            if (edge.HasAnyFlag(PopupAnchor.VerticalMask))
                 edge ^= PopupAnchor.VerticalMask;
             return edge;
         }

+ 18 - 18
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs

@@ -42,16 +42,16 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
         private static Point GetAnchorPoint(Rect anchorRect, PopupAnchor edge)
         {
             double x, y;
-            if (edge.HasFlagCustom(PopupAnchor.Left))
+            if (edge.HasAllFlags(PopupAnchor.Left))
                 x = anchorRect.X;
-            else if (edge.HasFlagCustom(PopupAnchor.Right))
+            else if (edge.HasAllFlags(PopupAnchor.Right))
                 x = anchorRect.Right;
             else
                 x = anchorRect.X + anchorRect.Width / 2;
             
-            if (edge.HasFlagCustom(PopupAnchor.Top))
+            if (edge.HasAllFlags(PopupAnchor.Top))
                 y = anchorRect.Y;
-            else if (edge.HasFlagCustom(PopupAnchor.Bottom))
+            else if (edge.HasAllFlags(PopupAnchor.Bottom))
                 y = anchorRect.Bottom;
             else
                 y = anchorRect.Y + anchorRect.Height / 2;
@@ -61,16 +61,16 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
         private static Point Gravitate(Point anchorPoint, Size size, PopupGravity gravity)
         {
             double x, y;
-            if (gravity.HasFlagCustom(PopupGravity.Left))
+            if (gravity.HasAllFlags(PopupGravity.Left))
                 x = -size.Width;
-            else if (gravity.HasFlagCustom(PopupGravity.Right))
+            else if (gravity.HasAllFlags(PopupGravity.Right))
                 x = 0;
             else
                 x = -size.Width / 2;
             
-            if (gravity.HasFlagCustom(PopupGravity.Top))
+            if (gravity.HasAllFlags(PopupGravity.Top))
                 y = -size.Height;
-            else if (gravity.HasFlagCustom(PopupGravity.Bottom))
+            else if (gravity.HasAllFlags(PopupGravity.Bottom))
                 y = 0;
             else
                 y = -size.Height / 2;
@@ -125,10 +125,10 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
 
             bool FitsInBounds(Rect rc, PopupAnchor edge = PopupAnchor.AllMask)
             {
-                if (edge.HasFlagCustom(PopupAnchor.Left) && rc.X < bounds.X ||
-                    edge.HasFlagCustom(PopupAnchor.Top) && rc.Y < bounds.Y ||
-                    edge.HasFlagCustom(PopupAnchor.Right) && rc.Right > bounds.Right ||
-                    edge.HasFlagCustom(PopupAnchor.Bottom) && rc.Bottom > bounds.Bottom)
+                if (edge.HasAllFlags(PopupAnchor.Left) && rc.X < bounds.X ||
+                    edge.HasAllFlags(PopupAnchor.Top) && rc.Y < bounds.Y ||
+                    edge.HasAllFlags(PopupAnchor.Right) && rc.Right > bounds.Right ||
+                    edge.HasAllFlags(PopupAnchor.Bottom) && rc.Bottom > bounds.Bottom)
                 {
                     return false;
                 }
@@ -147,7 +147,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
             // If flipping geometry and anchor is allowed and helps, use the flipped one,
             // otherwise leave it as is
             if (!FitsInBounds(geo, PopupAnchor.HorizontalMask)
-                && constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.FlipX))
+                && constraintAdjustment.HasAllFlags(PopupPositionerConstraintAdjustment.FlipX))
             {
                 var flipped = GetUnconstrained(anchor.FlipX(), gravity.FlipX());
                 if (FitsInBounds(flipped, PopupAnchor.HorizontalMask))
@@ -155,7 +155,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
             }
 
             // If sliding is allowed, try moving the rect into the bounds
-            if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.SlideX))
+            if (constraintAdjustment.HasAllFlags(PopupPositionerConstraintAdjustment.SlideX))
             {
                 geo = geo.WithX(Math.Max(geo.X, bounds.X));
                 if (geo.Right > bounds.Right)
@@ -163,7 +163,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
             }
             
             // Resize the rect horizontally if allowed.
-            if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.ResizeX))
+            if (constraintAdjustment.HasAllFlags(PopupPositionerConstraintAdjustment.ResizeX))
             {
                 var unconstrainedRect = geo;
 
@@ -186,7 +186,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
             // If flipping geometry and anchor is allowed and helps, use the flipped one,
             // otherwise leave it as is
             if (!FitsInBounds(geo, PopupAnchor.VerticalMask)
-                && constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.FlipY))
+                && constraintAdjustment.HasAllFlags(PopupPositionerConstraintAdjustment.FlipY))
             {
                 var flipped = GetUnconstrained(anchor.FlipY(), gravity.FlipY());
                 if (FitsInBounds(flipped, PopupAnchor.VerticalMask))
@@ -194,7 +194,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
             }
 
             // If sliding is allowed, try moving the rect into the bounds
-            if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.SlideY))
+            if (constraintAdjustment.HasAllFlags(PopupPositionerConstraintAdjustment.SlideY))
             {
                 geo = geo.WithY(Math.Max(geo.Y, bounds.Y));
                 if (geo.Bottom > bounds.Bottom)
@@ -202,7 +202,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
             }
 
             // Resize the rect vertically if allowed.
-            if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.ResizeY))
+            if (constraintAdjustment.HasAllFlags(PopupPositionerConstraintAdjustment.ResizeY))
             {
                 var unconstrainedRect = geo;
 

+ 6 - 6
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@@ -321,7 +321,7 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Gets a value indicating whether <see cref="SelectionMode.AlwaysSelected"/> is set.
         /// </summary>
-        protected bool AlwaysSelected => SelectionMode.HasFlagCustom(SelectionMode.AlwaysSelected);
+        protected bool AlwaysSelected => SelectionMode.HasAllFlags(SelectionMode.AlwaysSelected);
 
         /// <inheritdoc/>
         public override void BeginInit()
@@ -501,7 +501,7 @@ namespace Avalonia.Controls.Primitives
 
                 if (ItemCount > 0 &&
                     Match(keymap.SelectAll) &&
-                    SelectionMode.HasFlagCustom(SelectionMode.Multiple))
+                    SelectionMode.HasAllFlags(SelectionMode.Multiple))
                 {
                     Selection.SelectAll();
                     e.Handled = true;
@@ -530,7 +530,7 @@ namespace Avalonia.Controls.Primitives
             else if (change.Property == SelectionModeProperty && _selection is object)
             {
                 var newValue = change.NewValue.GetValueOrDefault<SelectionMode>();
-                _selection.SingleSelect = !newValue.HasFlagCustom(SelectionMode.Multiple);
+                _selection.SingleSelect = !newValue.HasAllFlags(SelectionMode.Multiple);
             }
         }
 
@@ -591,8 +591,8 @@ namespace Avalonia.Controls.Primitives
             }
 
             var mode = SelectionMode;
-            var multi = mode.HasFlagCustom(SelectionMode.Multiple);
-            var toggle = toggleModifier || mode.HasFlagCustom(SelectionMode.Toggle);
+            var multi = mode.HasAllFlags(SelectionMode.Multiple);
+            var toggle = toggleModifier || mode.HasAllFlags(SelectionMode.Toggle);
             var range = multi && rangeModifier;
 
             if (!select)
@@ -869,7 +869,7 @@ namespace Avalonia.Controls.Primitives
         {
             return new InternalSelectionModel
             {
-                SingleSelect = !SelectionMode.HasFlagCustom(SelectionMode.Multiple),
+                SingleSelect = !SelectionMode.HasAllFlags(SelectionMode.Multiple),
             };
         }
 

+ 2 - 0
src/Avalonia.Controls/ProgressBar.cs

@@ -138,6 +138,8 @@ namespace Avalonia.Controls
         static ProgressBar()
         {
             ValueProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
+            MinimumProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
+            MaximumProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
             IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
         }
 

+ 2 - 2
src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs

@@ -53,8 +53,8 @@ namespace Avalonia.Controls
         {
             return _owner.GetElementImpl(
                 index,
-                options.HasFlagCustom(ElementRealizationOptions.ForceCreate),
-                options.HasFlagCustom(ElementRealizationOptions.SuppressAutoRecycle));
+                options.HasAllFlags(ElementRealizationOptions.ForceCreate),
+                options.HasAllFlags(ElementRealizationOptions.SuppressAutoRecycle));
         }
 
         protected override object GetItemAtCore(int index) => _owner.ItemsSourceView.GetAt(index);

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

@@ -601,7 +601,7 @@ namespace Avalonia.Controls
             var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
 
             bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
-            bool DetectSelection() => e.KeyModifiers.HasFlagCustom(keymap.SelectionModifiers);
+            bool DetectSelection() => e.KeyModifiers.HasAllFlags(keymap.SelectionModifiers);
 
             if (Match(keymap.SelectAll))
             {
@@ -719,7 +719,7 @@ namespace Avalonia.Controls
             }
             else
             {
-                bool hasWholeWordModifiers = modifiers.HasFlagCustom(keymap.WholeWordTextActionModifiers);
+                bool hasWholeWordModifiers = modifiers.HasAllFlags(keymap.WholeWordTextActionModifiers);
                 switch (e.Key)
                 {
                     case Key.Left:

+ 5 - 5
src/Avalonia.Controls/TreeView.cs

@@ -412,7 +412,7 @@ namespace Avalonia.Controls
                 e.Handled = UpdateSelectionFromEventSource(
                     e.Source,
                     true,
-                    e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift));
+                    e.KeyModifiers.HasAllFlags(KeyModifiers.Shift));
             }
         }
 
@@ -521,8 +521,8 @@ namespace Avalonia.Controls
                     e.Handled = UpdateSelectionFromEventSource(
                         e.Source,
                         true,
-                        e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift),
-                        e.KeyModifiers.HasFlagCustom(KeyModifiers.Control),
+                        e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
+                        e.KeyModifiers.HasAllFlags(KeyModifiers.Control),
                         point.Properties.IsRightButtonPressed);
                 }
             }
@@ -558,8 +558,8 @@ namespace Avalonia.Controls
             }
 
             var mode = SelectionMode;
-            var toggle = toggleModifier || mode.HasFlagCustom(SelectionMode.Toggle);
-            var multi = mode.HasFlagCustom(SelectionMode.Multiple);
+            var toggle = toggleModifier || mode.HasAllFlags(SelectionMode.Toggle);
+            var multi = mode.HasAllFlags(SelectionMode.Multiple);
             var range = multi && rangeModifier && selectedContainer != null;
 
             if (rightButton)

+ 4 - 4
src/Avalonia.Controls/Window.cs

@@ -859,19 +859,19 @@ namespace Avalonia.Controls
             var constraint = clientSize;
             var maxAutoSize = PlatformImpl?.MaxAutoSizeHint ?? Size.Infinity;
 
-            if (sizeToContent.HasFlagCustom(SizeToContent.Width))
+            if (sizeToContent.HasAllFlags(SizeToContent.Width))
             {
                 constraint = constraint.WithWidth(maxAutoSize.Width);
             }
 
-            if (sizeToContent.HasFlagCustom(SizeToContent.Height))
+            if (sizeToContent.HasAllFlags(SizeToContent.Height))
             {
                 constraint = constraint.WithHeight(maxAutoSize.Height);
             }
 
             var result = base.MeasureOverride(constraint);
 
-            if (!sizeToContent.HasFlagCustom(SizeToContent.Width))
+            if (!sizeToContent.HasAllFlags(SizeToContent.Width))
             {
                 if (!double.IsInfinity(availableSize.Width))
                 {
@@ -883,7 +883,7 @@ namespace Avalonia.Controls
                 }
             }
 
-            if (!sizeToContent.HasFlagCustom(SizeToContent.Height))
+            if (!sizeToContent.HasAllFlags(SizeToContent.Height))
             {
                 if (!double.IsInfinity(availableSize.Height))
                 {

+ 8 - 8
src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs

@@ -76,13 +76,13 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx
         protected override async Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode)
         {
             FcitxKeyState state = default;
-            if (args.Modifiers.HasFlagCustom(RawInputModifiers.Control))
+            if (args.Modifiers.HasAllFlags(RawInputModifiers.Control))
                 state |= FcitxKeyState.FcitxKeyState_Ctrl;
-            if (args.Modifiers.HasFlagCustom(RawInputModifiers.Alt))
+            if (args.Modifiers.HasAllFlags(RawInputModifiers.Alt))
                 state |= FcitxKeyState.FcitxKeyState_Alt;
-            if (args.Modifiers.HasFlagCustom(RawInputModifiers.Shift))
+            if (args.Modifiers.HasAllFlags(RawInputModifiers.Shift))
                 state |= FcitxKeyState.FcitxKeyState_Shift;
-            if (args.Modifiers.HasFlagCustom(RawInputModifiers.Meta))
+            if (args.Modifiers.HasAllFlags(RawInputModifiers.Meta))
                 state |= FcitxKeyState.FcitxKeyState_Super;
 
             var type = args.Type == RawKeyEventType.KeyDown ?
@@ -126,13 +126,13 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx
         {
             var state = (FcitxKeyState)ev.state;
             KeyModifiers mods = default;
-            if (state.HasFlagCustom(FcitxKeyState.FcitxKeyState_Ctrl))
+            if (state.HasAllFlags(FcitxKeyState.FcitxKeyState_Ctrl))
                 mods |= KeyModifiers.Control;
-            if (state.HasFlagCustom(FcitxKeyState.FcitxKeyState_Alt))
+            if (state.HasAllFlags(FcitxKeyState.FcitxKeyState_Alt))
                 mods |= KeyModifiers.Alt;
-            if (state.HasFlagCustom(FcitxKeyState.FcitxKeyState_Shift))
+            if (state.HasAllFlags(FcitxKeyState.FcitxKeyState_Shift))
                 mods |= KeyModifiers.Shift;
-            if (state.HasFlagCustom(FcitxKeyState.FcitxKeyState_Super))
+            if (state.HasAllFlags(FcitxKeyState.FcitxKeyState_Super))
                 mods |= KeyModifiers.Meta;
             FireForward(new X11InputMethodForwardedKey
             {

+ 9 - 9
src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs

@@ -33,18 +33,18 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus
         {
             var state = (IBusModifierMask)k.state;
             KeyModifiers mods = default;
-            if (state.HasFlagCustom(IBusModifierMask.ControlMask))
+            if (state.HasAllFlags(IBusModifierMask.ControlMask))
                 mods |= KeyModifiers.Control;
-            if (state.HasFlagCustom(IBusModifierMask.Mod1Mask))
+            if (state.HasAllFlags(IBusModifierMask.Mod1Mask))
                 mods |= KeyModifiers.Alt;
-            if (state.HasFlagCustom(IBusModifierMask.ShiftMask))
+            if (state.HasAllFlags(IBusModifierMask.ShiftMask))
                 mods |= KeyModifiers.Shift;
-            if (state.HasFlagCustom(IBusModifierMask.Mod4Mask))
+            if (state.HasAllFlags(IBusModifierMask.Mod4Mask))
                 mods |= KeyModifiers.Meta;
             FireForward(new X11InputMethodForwardedKey
             {
                 KeyVal = (int)k.keyval,
-                Type = state.HasFlagCustom(IBusModifierMask.ReleaseMask) ? RawKeyEventType.KeyUp : RawKeyEventType.KeyDown,
+                Type = state.HasAllFlags(IBusModifierMask.ReleaseMask) ? RawKeyEventType.KeyUp : RawKeyEventType.KeyDown,
                 Modifiers = mods
             });
         }
@@ -82,13 +82,13 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus
         protected override Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode)
         {
             IBusModifierMask state = default;
-            if (args.Modifiers.HasFlagCustom(RawInputModifiers.Control))
+            if (args.Modifiers.HasAllFlags(RawInputModifiers.Control))
                 state |= IBusModifierMask.ControlMask;
-            if (args.Modifiers.HasFlagCustom(RawInputModifiers.Alt))
+            if (args.Modifiers.HasAllFlags(RawInputModifiers.Alt))
                 state |= IBusModifierMask.Mod1Mask;
-            if (args.Modifiers.HasFlagCustom(RawInputModifiers.Shift))
+            if (args.Modifiers.HasAllFlags(RawInputModifiers.Shift))
                 state |= IBusModifierMask.ShiftMask;
-            if (args.Modifiers.HasFlagCustom(RawInputModifiers.Meta))
+            if (args.Modifiers.HasAllFlags(RawInputModifiers.Meta))
                 state |= IBusModifierMask.Mod4Mask;
 
             if (args.Type == RawKeyEventType.KeyUp)

+ 4 - 4
src/Avalonia.FreeDesktop/DBusMenuExporter.cs

@@ -223,13 +223,13 @@ namespace Avalonia.FreeDesktop
                             return null;
                         var lst = new List<string>();
                         var mod = item.Gesture;
-                        if (mod.KeyModifiers.HasFlagCustom(KeyModifiers.Control))
+                        if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Control))
                             lst.Add("Control");
-                        if (mod.KeyModifiers.HasFlagCustom(KeyModifiers.Alt))
+                        if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Alt))
                             lst.Add("Alt");
-                        if (mod.KeyModifiers.HasFlagCustom(KeyModifiers.Shift))
+                        if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Shift))
                             lst.Add("Shift");
-                        if (mod.KeyModifiers.HasFlagCustom(KeyModifiers.Meta))
+                        if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Meta))
                             lst.Add("Super");
                         lst.Add(item.Gesture.Key.ToString());
                         return new[] { lst.ToArray() };

+ 2 - 2
src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs

@@ -33,11 +33,11 @@ namespace Avalonia.Headless.Vnc
                 {
                     Window?.MouseMove(pt);
                     foreach (var btn in CheckedButtons)
-                        if (_previousButtons.HasFlagCustom(btn) && !buttons.HasFlagCustom(btn))
+                        if (_previousButtons.HasAllFlags(btn) && !buttons.HasAllFlags(btn))
                             Window?.MouseUp(pt, TranslateButton(btn), modifiers);
                     
                     foreach (var btn in CheckedButtons)
-                        if (!_previousButtons.HasFlagCustom(btn) && buttons.HasFlagCustom(btn))
+                        if (!_previousButtons.HasAllFlags(btn) && buttons.HasAllFlags(btn))
                             Window?.MouseDown(pt, TranslateButton(btn), modifiers);
                     _previousButtons = buttons;
                 }, DispatcherPriority.Input);

+ 32 - 0
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@@ -104,6 +104,9 @@ namespace Avalonia.Headless
             }
 
             public Rect Bounds { get; set; }
+            
+            public double ContourLength { get; } = 0;
+            
             public virtual bool FillContains(Point point) => Bounds.Contains(point);
 
             public Rect GetRenderBounds(IPen pen)
@@ -126,6 +129,25 @@ namespace Avalonia.Headless
 
             public ITransformedGeometryImpl WithTransform(Matrix transform) =>
                 new HeadlessTransformedGeometryStub(this, transform);
+
+            public bool TryGetPointAtDistance(double distance, out Point point)
+            {
+                point = new Point();
+                return false;
+            }
+
+            public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent)
+            {
+                point = new Point();
+                tangent = new Point();
+                return false;
+            }
+
+            public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry)
+            {
+                segmentGeometry = null;
+                return false;
+            }
         }
 
         class HeadlessTransformedGeometryStub : HeadlessGeometryStub, ITransformedGeometryImpl
@@ -359,6 +381,16 @@ namespace Avalonia.Headless
 
             }
 
+            public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
+            {
+                
+            }
+
+            public void PopBitmapBlendMode()
+            {
+                
+            }
+
             public void Custom(ICustomDrawOperation custom)
             {
 

+ 1 - 1
src/Avalonia.Input/AccessKeyHandler.cs

@@ -177,7 +177,7 @@ namespace Avalonia.Input
         {
             bool menuIsOpen = MainMenu?.IsOpen == true;
 
-            if (e.KeyModifiers.HasFlagCustom(KeyModifiers.Alt) || menuIsOpen)
+            if (e.KeyModifiers.HasAllFlags(KeyModifiers.Alt) || menuIsOpen)
             {
                 // If any other key is pressed with the Alt key held down, or the main menu is open,
                 // find all controls who have registered that access key.

+ 10 - 4
src/Avalonia.Input/Gestures.cs

@@ -24,7 +24,7 @@ namespace Avalonia.Input
         public static readonly RoutedEvent<ScrollGestureEventArgs> ScrollGestureEvent =
             RoutedEvent.Register<ScrollGestureEventArgs>(
                 "ScrollGesture", RoutingStrategies.Bubble, typeof(Gestures));
- 
+
         public static readonly RoutedEvent<ScrollGestureEventArgs> ScrollGestureEndedEvent =
             RoutedEvent.Register<ScrollGestureEventArgs>(
                 "ScrollGestureEnded", RoutingStrategies.Bubble, typeof(Gestures));
@@ -89,7 +89,7 @@ namespace Avalonia.Input
                 {
                     if (s_lastPress.TryGetTarget(out var target) && target == e.Source)
                     {
-                        e.Source.RaiseEvent(new RoutedEventArgs(DoubleTappedEvent));
+                        e.Source.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e));
                     }
                 }
             }
@@ -105,8 +105,14 @@ namespace Avalonia.Input
                 {
                     if (e.InitialPressMouseButton == MouseButton.Left || e.InitialPressMouseButton == MouseButton.Right)
                     {
-                        var et = e.InitialPressMouseButton != MouseButton.Right ? TappedEvent : RightTappedEvent;
-                        e.Source.RaiseEvent(new RoutedEventArgs(et));
+                        if (e.InitialPressMouseButton == MouseButton.Right)
+                        {
+                            e.Source.RaiseEvent(new TappedEventArgs(RightTappedEvent, e));
+                        }
+                        else
+                        {
+                            e.Source.RaiseEvent(new TappedEventArgs(TappedEvent, e));
+                        }
                     }
                 }
             }

+ 4 - 4
src/Avalonia.Input/KeyGesture.cs

@@ -115,24 +115,24 @@ namespace Avalonia.Input
                 }
             }
 
-            if (KeyModifiers.HasFlagCustom(KeyModifiers.Control))
+            if (KeyModifiers.HasAllFlags(KeyModifiers.Control))
             {
                 s.Append("Ctrl");
             }
 
-            if (KeyModifiers.HasFlagCustom(KeyModifiers.Shift))
+            if (KeyModifiers.HasAllFlags(KeyModifiers.Shift))
             {
                 Plus(s);
                 s.Append("Shift");
             }
 
-            if (KeyModifiers.HasFlagCustom(KeyModifiers.Alt))
+            if (KeyModifiers.HasAllFlags(KeyModifiers.Alt))
             {
                 Plus(s);
                 s.Append("Alt");
             }
 
-            if (KeyModifiers.HasFlagCustom(KeyModifiers.Meta))
+            if (KeyModifiers.HasAllFlags(KeyModifiers.Meta))
             {
                 Plus(s);
                 s.Append("Cmd");

+ 3 - 1
src/Avalonia.Input/PointerEventArgs.cs

@@ -107,7 +107,9 @@ namespace Avalonia.Input
         None,
         Left,
         Right,
-        Middle
+        Middle,
+        XButton1,
+        XButton2
     }
 
     public class PointerPressedEventArgs : PointerEventArgs

+ 9 - 5
src/Avalonia.Input/PointerPoint.cs

@@ -31,11 +31,11 @@ namespace Avalonia.Input
         {
             PointerUpdateKind = kind;
 
-            IsLeftButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.LeftMouseButton);
-            IsMiddleButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.MiddleMouseButton);
-            IsRightButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.RightMouseButton);
-            IsXButton1Pressed = modifiers.HasFlagCustom(RawInputModifiers.XButton1MouseButton);
-            IsXButton2Pressed = modifiers.HasFlagCustom(RawInputModifiers.XButton2MouseButton);
+            IsLeftButtonPressed = modifiers.HasAllFlags(RawInputModifiers.LeftMouseButton);
+            IsMiddleButtonPressed = modifiers.HasAllFlags(RawInputModifiers.MiddleMouseButton);
+            IsRightButtonPressed = modifiers.HasAllFlags(RawInputModifiers.RightMouseButton);
+            IsXButton1Pressed = modifiers.HasAllFlags(RawInputModifiers.XButton1MouseButton);
+            IsXButton2Pressed = modifiers.HasAllFlags(RawInputModifiers.XButton2MouseButton);
 
             // The underlying input source might be reporting the previous state,
             // so make sure that we reflect the current state
@@ -90,6 +90,10 @@ namespace Avalonia.Input
                 return MouseButton.Middle;
             if (kind == PointerUpdateKind.RightButtonPressed || kind == PointerUpdateKind.RightButtonReleased)
                 return MouseButton.Right;
+            if (kind == PointerUpdateKind.XButton1Pressed || kind == PointerUpdateKind.XButton1Released)
+                return MouseButton.XButton1;
+            if (kind == PointerUpdateKind.XButton2Pressed || kind == PointerUpdateKind.XButton2Released)
+                return MouseButton.XButton2;
             return MouseButton.None;
         }
     }

+ 18 - 0
src/Avalonia.Input/TappedEventArgs.cs

@@ -0,0 +1,18 @@
+using Avalonia.Interactivity;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Input
+{
+    public class TappedEventArgs : RoutedEventArgs
+    {
+        private readonly PointerEventArgs lastPointerEventArgs;
+
+        public TappedEventArgs(RoutedEvent routedEvent, PointerEventArgs lastPointerEventArgs)
+            : base(routedEvent)
+        {
+            this.lastPointerEventArgs = lastPointerEventArgs;
+        }
+
+        public Point GetPosition(IVisual? relativeTo) => lastPointerEventArgs.GetPosition(relativeTo);
+    }
+}

+ 3 - 3
src/Avalonia.Interactivity/EventRoute.cs

@@ -88,14 +88,14 @@ namespace Avalonia.Interactivity
             }
             else
             {
-                if (_event.RoutingStrategies.HasFlagCustom(RoutingStrategies.Tunnel))
+                if (_event.RoutingStrategies.HasAllFlags(RoutingStrategies.Tunnel))
                 {
                     e.Route = RoutingStrategies.Tunnel;
                     RaiseEventImpl(e);
                     _event.InvokeRouteFinished(e);
                 }
 
-                if (_event.RoutingStrategies.HasFlagCustom(RoutingStrategies.Bubble))
+                if (_event.RoutingStrategies.HasAllFlags(RoutingStrategies.Bubble))
                 {
                     e.Route = RoutingStrategies.Bubble;
                     RaiseEventImpl(e);
@@ -159,7 +159,7 @@ namespace Avalonia.Interactivity
 
                 // Raise the event handler.
                 if (entry.Handler is object &&
-                    entry.Routes.HasFlagCustom(e.Route) &&
+                    entry.Routes.HasAllFlags(e.Route) &&
                     (!e.Handled || entry.HandledEventsToo))
                 {
                     if (entry.Adapter is object)

+ 2 - 2
src/Avalonia.Interactivity/Interactive.cs

@@ -155,8 +155,8 @@ namespace Avalonia.Interactivity
             var result = new EventRoute(e);
             var hasClassHandlers = e.HasRaisedSubscriptions;
 
-            if (e.RoutingStrategies.HasFlagCustom(RoutingStrategies.Bubble) ||
-                e.RoutingStrategies.HasFlagCustom(RoutingStrategies.Tunnel))
+            if (e.RoutingStrategies.HasAllFlags(RoutingStrategies.Bubble) ||
+                e.RoutingStrategies.HasAllFlags(RoutingStrategies.Tunnel))
             {
                 IInteractive? element = this;
 

+ 9 - 0
src/Avalonia.Visuals/ApiCompatBaseline.txt

@@ -0,0 +1,9 @@
+Compat issues with assembly Avalonia.Visuals:
+InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PopBitmapBlendMode()' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PushBitmapBlendMode(Avalonia.Visuals.Media.Imaging.BitmapBlendingMode)' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength.get()' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAndTangentAtDistance(System.Double, Avalonia.Point, Avalonia.Point)' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAtDistance(System.Double, Avalonia.Point)' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetSegment(System.Double, System.Double, System.Boolean, Avalonia.Platform.IGeometryImpl)' is present in the implementation but not in the contract.
+Total Issues: 7

+ 12 - 1
src/Avalonia.Visuals/Media/DrawingContext.cs

@@ -121,12 +121,23 @@ namespace Avalonia.Media
         /// <param name="pen">The stroke pen.</param>
         /// <param name="geometry">The geometry.</param>
         public void DrawGeometry(IBrush brush, IPen pen, Geometry geometry)
+        {
+            DrawGeometry(brush, pen, geometry.PlatformImpl);
+        }
+
+        /// <summary>
+        /// Draws a geometry.
+        /// </summary>
+        /// <param name="brush">The fill brush.</param>
+        /// <param name="pen">The stroke pen.</param>
+        /// <param name="geometry">The geometry.</param>
+        public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
         {
             Contract.Requires<ArgumentNullException>(geometry != null);
 
             if (brush != null || PenIsVisible(pen))
             {
-                PlatformImpl.DrawGeometry(brush, pen, geometry.PlatformImpl);
+                PlatformImpl.DrawGeometry(brush, pen, geometry);
             }
         }
 

+ 57 - 0
src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs

@@ -0,0 +1,57 @@
+namespace Avalonia.Visuals.Media.Imaging
+{
+    /// <summary>
+    /// Controls the way the bitmaps are drawn together.
+    /// </summary>
+    public enum BitmapBlendingMode
+    {
+        /// <summary>
+        /// Source is placed over the destination.
+        /// </summary>
+        SourceOver,
+        /// <summary>
+        /// Only the source will be present.
+        /// </summary>
+        Source,
+        /// <summary>
+        /// Only the destination will be present.
+        /// </summary>
+        Destination,
+        /// <summary>
+        /// Destination is placed over the source.
+        /// </summary>
+        DestinationOver,
+        /// <summary>
+        /// The source that overlaps the destination, replaces the destination.
+        /// </summary>
+        SourceIn,
+        /// <summary>
+        /// Destination which overlaps the source, replaces the source.
+        /// </summary>
+        DestinationIn,
+        /// <summary>
+        /// Source is placed, where it falls outside of the destination.
+        /// </summary>
+        SourceOut,
+        /// <summary>
+        /// Destination is placed, where it falls outside of the source.
+        /// </summary>
+        DestinationOut,
+        /// <summary>
+        /// Source which overlaps the destination, replaces the destination.
+        /// </summary>
+        SourceAtop,
+        /// <summary>
+        /// Destination which overlaps the source replaces the source.
+        /// </summary>
+        DestinationAtop,
+        /// <summary>
+        /// The non-overlapping regions of source and destination are combined.
+        /// </summary>
+        Xor,
+        /// <summary>
+        /// Display the sum of the source image and destination image.
+        /// </summary>
+        Plus,
+    }
+}

+ 11 - 0
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@@ -148,6 +148,17 @@ namespace Avalonia.Platform
         /// Pops the latest pushed geometry clip.
         /// </summary>
         void PopGeometryClip();
+        
+        /// <summary>
+        /// Pushes a bitmap blending value.
+        /// </summary>
+        /// <param name="blendingMode">The bitmap blending mode.</param>
+        void PushBitmapBlendMode(BitmapBlendingMode blendingMode);
+
+        /// <summary>
+        /// Pops the latest pushed bitmap blending value.
+        /// </summary>
+        void PopBitmapBlendMode();
 
         /// <summary>
         /// Adds a custom draw operation

+ 39 - 0
src/Avalonia.Visuals/Platform/IGeometryImpl.cs

@@ -11,6 +11,12 @@ namespace Avalonia.Platform
         /// Gets the geometry's bounding rectangle.
         /// </summary>
         Rect Bounds { get; }
+        
+        /// <summary>
+        /// Gets the geometry's total length as if all its contours are placed
+        /// in a straight line.
+        /// </summary>
+        double ContourLength { get; }
 
         /// <summary>
         /// Gets the geometry's bounding rectangle with the specified pen.
@@ -47,5 +53,38 @@ namespace Avalonia.Platform
         /// <param name="transform">The transform.</param>
         /// <returns>The cloned geometry.</returns>
         ITransformedGeometryImpl WithTransform(Matrix transform);
+
+        /// <summary>
+        /// Attempts to get the corresponding point at the
+        /// specified distance
+        /// </summary>
+        /// <param name="distance">The contour distance to get from.</param>
+        /// <param name="point">The point in the specified distance.</param>
+        /// <returns>If there's valid point at the specified distance.</returns>
+        bool TryGetPointAtDistance(double distance, out Point point);
+
+        /// <summary>
+        /// Attempts to get the corresponding point and
+        /// tangent from the specified distance along the
+        /// contour of the geometry.
+        /// </summary>
+        /// <param name="distance">The contour distance to get from.</param>
+        /// <param name="point">The point in the specified distance.</param>
+        /// <param name="tangent">The tangent in the specified distance.</param>
+        /// <returns>If there's valid point and tangent at the specified distance.</returns>
+        bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent);
+        
+        /// <summary>
+        /// Attempts to get the corresponding path segment
+        /// given by the two distances specified.
+        /// Imagine it like snipping a part of the current
+        /// geometry.
+        /// </summary>
+        /// <param name="startDistance">The contour distance to start snipping from.</param>
+        /// <param name="stopDistance">The contour distance to stop snipping to.</param>
+        /// <param name="startOnBeginFigure">If ture, the resulting snipped path will start with a BeginFigure call.</param>
+        /// <param name="segmentGeometry">The resulting snipped path.</param>
+        /// <returns>If the snipping operation is successful.</returns>
+        bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry);
     }
 }

+ 68 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs

@@ -0,0 +1,68 @@
+using Avalonia.Platform;
+using Avalonia.Visuals.Media.Imaging;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+    /// <summary>
+    /// A node in the scene graph which represents an bitmap blending mode push or pop.
+    /// </summary>
+    internal class BitmapBlendModeNode : IDrawOperation
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BitmapBlendModeNode"/> class that represents an
+        /// <see cref="BitmapBlendingMode"/> push.
+        /// </summary>
+        /// <param name="bitmapBlend">The <see cref="BitmapBlendingMode"/> to push.</param>
+        public BitmapBlendModeNode(BitmapBlendingMode bitmapBlend)
+        {
+            BlendingMode = bitmapBlend;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BitmapBlendNode"/> class that represents an
+        /// <see cref="BitmapBlendingMode"/> pop.
+        /// </summary>
+        public BitmapBlendModeNode()
+        {
+        }
+
+        /// <inheritdoc/>
+        public Rect Bounds => Rect.Empty;
+
+        /// <summary>
+        /// Gets the BitmapBlend to be pushed or null if the operation represents a pop.
+        /// </summary>
+        public BitmapBlendingMode? BlendingMode { get; }
+
+        /// <inheritdoc/>
+        public bool HitTest(Point p) => false;
+
+        /// <summary>
+        /// Determines if this draw operation equals another.
+        /// </summary>
+        /// <param name="opacity">The opacity of the other draw operation.</param>
+        /// <returns>True if the draw operations are the same, otherwise false.</returns>
+        /// <remarks>
+        /// The properties of the other draw operation are passed in as arguments to prevent
+        /// allocation of a not-yet-constructed draw operation object.
+        /// </remarks>
+        public bool Equals(BitmapBlendingMode? blendingMode) => BlendingMode == blendingMode;
+
+        /// <inheritdoc/>
+        public void Render(IDrawingContextImpl context)
+        {
+            if (BlendingMode.HasValue)
+            {
+                context.PushBitmapBlendMode(BlendingMode.Value);
+            }
+            else
+            {
+                context.PopBitmapBlendMode();
+            }
+        }
+
+        public void Dispose()
+        {
+        }
+    }
+}

+ 30 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@@ -253,6 +253,21 @@ namespace Avalonia.Rendering.SceneGraph
             }
         }
 
+        /// <inheritdoc/>
+        public void PopBitmapBlendMode()
+        {
+            var next = NextDrawAs<BitmapBlendModeNode>();
+
+            if (next == null || !next.Item.Equals(null))
+            {
+                Add(new BitmapBlendModeNode());
+            }
+            else
+            {
+                ++_drawOperationindex;
+            }
+        }
+        
         /// <inheritdoc/>
         public void PopOpacity()
         {
@@ -358,6 +373,21 @@ namespace Avalonia.Rendering.SceneGraph
             }
         }
 
+        /// <inheritdoc/>
+        public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
+        {
+            var next = NextDrawAs<BitmapBlendModeNode>();
+
+            if (next == null || !next.Item.Equals(blendingMode))
+            {
+                Add(new BitmapBlendModeNode(blendingMode));
+            }
+            else
+            {
+                ++_drawOperationindex;
+            }
+        }
+        
         public readonly struct UpdateState : IDisposable
         {
             public UpdateState(

+ 14 - 6
src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs

@@ -67,6 +67,14 @@ namespace Avalonia.Rendering.SceneGraph
         /// The scaling mode.
         /// </value>
         public BitmapInterpolationMode BitmapInterpolationMode { get; }
+        
+        /// <summary>
+        /// The bitmap blending mode.
+        /// </summary>
+        /// <value>
+        /// The blending mode.
+        /// </value>
+        public BitmapBlendingMode BitmapBlendingMode { get; }
 
         /// <summary>
         /// Determines if this draw operation equals another.
@@ -85,12 +93,12 @@ namespace Avalonia.Rendering.SceneGraph
         public bool Equals(Matrix transform, IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
         {
             return transform == Transform &&
-                Equals(source.Item, Source.Item) &&
-                source.Item.Version == SourceVersion &&
-                opacity == Opacity &&
-                sourceRect == SourceRect &&
-                destRect == DestRect &&
-                bitmapInterpolationMode == BitmapInterpolationMode;
+                   Equals(source.Item, Source.Item) &&
+                   source.Item.Version == SourceVersion &&
+                   opacity == Opacity &&
+                   sourceRect == SourceRect &&
+                   destRect == DestRect &&
+                   bitmapInterpolationMode == BitmapInterpolationMode;
         }
 
         /// <inheritdoc/>

+ 9 - 0
src/Avalonia.Visuals/Vector.cs

@@ -82,6 +82,15 @@ namespace Avalonia
         public static Vector operator *(Vector vector, double scale)
             => Multiply(vector, scale);
 
+        /// <summary>
+        /// Scales a vector.
+        /// </summary>
+        /// <param name="vector">The vector.</param>
+        /// <param name="scale">The scaling factor.</param>
+        /// <returns>The scaled vector.</returns>
+        public static Vector operator *(double scale, Vector vector)
+            => Multiply(vector, scale);
+
         /// <summary>
         /// Scales a vector.
         /// </summary>

+ 2 - 2
src/Avalonia.X11/X11Window.Ime.cs

@@ -96,14 +96,14 @@ namespace Avalonia.X11
 
         void HandleKeyEvent(ref XEvent ev)
         {
-            var index = ev.KeyEvent.state.HasFlagCustom(XModifierMask.ShiftMask);
+            var index = ev.KeyEvent.state.HasAllFlags(XModifierMask.ShiftMask);
 
             // We need the latin key, since it's mainly used for hotkeys, we use a different API for text anyway
             var key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 1 : 0).ToInt32();
                 
             // Manually switch the Shift index for the keypad,
             // there should be a proper way to do this
-            if (ev.KeyEvent.state.HasFlagCustom(XModifierMask.Mod2Mask)
+            if (ev.KeyEvent.state.HasAllFlags(XModifierMask.Mod2Mask)
                 && key > X11Key.Num_Lock && key <= X11Key.KP_9)
                 key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 0 : 1).ToInt32();
             

+ 9 - 9
src/Avalonia.X11/X11Window.cs

@@ -639,23 +639,23 @@ namespace Avalonia.X11
         RawInputModifiers TranslateModifiers(XModifierMask state)
         {
             var rv = default(RawInputModifiers);
-            if (state.HasFlagCustom(XModifierMask.Button1Mask))
+            if (state.HasAllFlags(XModifierMask.Button1Mask))
                 rv |= RawInputModifiers.LeftMouseButton;
-            if (state.HasFlagCustom(XModifierMask.Button2Mask))
+            if (state.HasAllFlags(XModifierMask.Button2Mask))
                 rv |= RawInputModifiers.RightMouseButton;
-            if (state.HasFlagCustom(XModifierMask.Button3Mask))
+            if (state.HasAllFlags(XModifierMask.Button3Mask))
                 rv |= RawInputModifiers.MiddleMouseButton;
-            if (state.HasFlagCustom(XModifierMask.Button4Mask))
+            if (state.HasAllFlags(XModifierMask.Button4Mask))
                 rv |= RawInputModifiers.XButton1MouseButton;
-            if (state.HasFlagCustom(XModifierMask.Button5Mask))
+            if (state.HasAllFlags(XModifierMask.Button5Mask))
                 rv |= RawInputModifiers.XButton2MouseButton;
-            if (state.HasFlagCustom(XModifierMask.ShiftMask))
+            if (state.HasAllFlags(XModifierMask.ShiftMask))
                 rv |= RawInputModifiers.Shift;
-            if (state.HasFlagCustom(XModifierMask.ControlMask))
+            if (state.HasAllFlags(XModifierMask.ControlMask))
                 rv |= RawInputModifiers.Control;
-            if (state.HasFlagCustom(XModifierMask.Mod1Mask))
+            if (state.HasAllFlags(XModifierMask.Mod1Mask))
                 rv |= RawInputModifiers.Alt;
-            if (state.HasFlagCustom(XModifierMask.Mod4Mask))
+            if (state.HasAllFlags(XModifierMask.Mod4Mask))
                 rv |= RawInputModifiers.Meta;
             return rv;
         }

+ 5 - 5
src/Avalonia.X11/XI2Manager.cs

@@ -342,13 +342,13 @@ namespace Avalonia.X11
             Type = ev->evtype;
             Timestamp = (ulong)ev->time.ToInt64();
             var state = (XModifierMask)ev->mods.Effective;
-            if (state.HasFlagCustom(XModifierMask.ShiftMask))
+            if (state.HasAllFlags(XModifierMask.ShiftMask))
                 Modifiers |= RawInputModifiers.Shift;
-            if (state.HasFlagCustom(XModifierMask.ControlMask))
+            if (state.HasAllFlags(XModifierMask.ControlMask))
                 Modifiers |= RawInputModifiers.Control;
-            if (state.HasFlagCustom(XModifierMask.Mod1Mask))
+            if (state.HasAllFlags(XModifierMask.Mod1Mask))
                 Modifiers |= RawInputModifiers.Alt;
-            if (state.HasFlagCustom(XModifierMask.Mod4Mask))
+            if (state.HasAllFlags(XModifierMask.Mod4Mask))
                 Modifiers |= RawInputModifiers.Meta;
 
             Modifiers |= ParseButtonState(ev->buttons.MaskLen, ev->buttons.Mask);
@@ -364,7 +364,7 @@ namespace Avalonia.X11
             if (Type == XiEventType.XI_ButtonPress || Type == XiEventType.XI_ButtonRelease)
                 Button = ev->detail;
             Detail = ev->detail;
-            Emulated = ev->flags.HasFlagCustom(XiDeviceEventFlags.XIPointerEmulated);
+            Emulated = ev->flags.HasAllFlags(XiDeviceEventFlags.XIPointerEmulated);
         }
     }
     

+ 1 - 1
src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs

@@ -54,7 +54,7 @@ namespace Avalonia.LinuxFramebuffer.Output
         }
 
         public PixelSize Resolution => new PixelSize(Mode.hdisplay, Mode.vdisplay);
-        public bool IsPreferred => Mode.type.HasFlagCustom(DrmModeType.DRM_MODE_TYPE_PREFERRED);
+        public bool IsPreferred => Mode.type.HasAllFlags(DrmModeType.DRM_MODE_TYPE_PREFERRED);
 
         public string Name { get; }
     }

+ 16 - 0
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@@ -23,9 +23,11 @@ namespace Avalonia.Skia
         private readonly Vector _dpi;
         private readonly Stack<PaintWrapper> _maskStack = new Stack<PaintWrapper>();
         private readonly Stack<double> _opacityStack = new Stack<double>();
+        private readonly Stack<BitmapBlendingMode> _blendingModeStack = new Stack<BitmapBlendingMode>();
         private readonly Matrix? _postTransform;
         private readonly IVisualBrushRenderer _visualBrushRenderer;
         private double _currentOpacity = 1.0f;
+        private BitmapBlendingMode _currentBlendingMode = BitmapBlendingMode.SourceOver;
         private readonly bool _canTextUseLcdRendering;
         private Matrix _currentTransform;
         private bool _disposed;
@@ -145,6 +147,7 @@ namespace Avalonia.Skia
                 })
             {
                 paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality();
+                paint.BlendMode = _currentBlendingMode.ToSKBlendMode();
 
                 drawableImage.Draw(this, s, d, paint);
             }
@@ -508,6 +511,19 @@ namespace Avalonia.Skia
             Canvas.Restore();
         }
 
+        /// <inheritdoc />
+        public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
+        {
+            _blendingModeStack.Push(_currentBlendingMode);
+            _currentBlendingMode = blendingMode;
+        }
+
+        /// <inheritdoc />
+        public void PopBitmapBlendMode()
+        {
+            _currentBlendingMode = _blendingModeStack.Pop();
+        }
+
         public void Custom(ICustomDrawOperation custom) => custom.Render(this);
 
         /// <inheritdoc />

+ 91 - 11
src/Skia/Avalonia.Skia/GeometryImpl.cs

@@ -11,9 +11,36 @@ namespace Avalonia.Skia
     internal abstract class GeometryImpl : IGeometryImpl
     {
         private PathCache _pathCache;
-        
+        private SKPathMeasure _pathMeasureCache;
+
+        private SKPathMeasure CachedPathMeasure
+        {
+            get
+            {
+                if (_pathMeasureCache is null)
+                {
+                    _pathMeasureCache = new SKPathMeasure(EffectivePath);
+                }
+
+                return _pathMeasureCache;
+            }
+        }
+
         /// <inheritdoc />
         public abstract Rect Bounds { get; }
+
+        /// <inheritdoc />
+        public double ContourLength
+        {
+            get
+            {
+                if (EffectivePath is null)
+                    return 0;
+
+                return (double)CachedPathMeasure?.Length;
+            }
+        }
+
         public abstract SKPath EffectivePath { get; }
 
         /// <inheritdoc />
@@ -30,12 +57,12 @@ namespace Avalonia.Skia
             // Usually this function is being called with same stroke width per path, so this saves a lot of Skia traffic.
 
             var strokeWidth = (float)(pen?.Thickness ?? 0);
-            
+
             if (!_pathCache.HasCacheFor(strokeWidth))
             {
                 UpdatePathCache(strokeWidth);
             }
-            
+
             return PathContainsCore(_pathCache.CachedStrokePath, point);
         }
 
@@ -58,7 +85,7 @@ namespace Avalonia.Skia
                 {
                     paint.IsStroke = true;
                     paint.StrokeWidth = strokeWidth;
-                    
+
                     paint.GetFillPath(EffectivePath, strokePath);
 
                     _pathCache.Cache(strokePath, strokeWidth, strokePath.TightBounds.ToAvaloniaRect());
@@ -74,13 +101,13 @@ namespace Avalonia.Skia
         /// <returns>True, if point is contained in a path.</returns>
         private static bool PathContainsCore(SKPath path, Point point)
         {
-           return path.Contains((float)point.X, (float)point.Y);
+            return path.Contains((float)point.X, (float)point.Y);
         }
 
         /// <inheritdoc />
         public IGeometryImpl Intersect(IGeometryImpl geometry)
         {
-            var result = EffectivePath.Op(((GeometryImpl) geometry).EffectivePath, SKPathOp.Intersect);
+            var result = EffectivePath.Op(((GeometryImpl)geometry).EffectivePath, SKPathOp.Intersect);
 
             return result == null ? null : new StreamGeometryImpl(result);
         }
@@ -89,21 +116,74 @@ namespace Avalonia.Skia
         public Rect GetRenderBounds(IPen pen)
         {
             var strokeWidth = (float)(pen?.Thickness ?? 0);
-            
+
             if (!_pathCache.HasCacheFor(strokeWidth))
             {
                 UpdatePathCache(strokeWidth);
             }
-            
+
             return _pathCache.CachedGeometryRenderBounds;
         }
-        
+
         /// <inheritdoc />
         public ITransformedGeometryImpl WithTransform(Matrix transform)
         {
             return new TransformedGeometryImpl(this, transform);
         }
 
+        /// <inheritdoc />
+        public bool TryGetPointAtDistance(double distance, out Point point)
+        {
+            if (EffectivePath is null)
+            {
+                point = new Point();
+                return false;
+            }
+
+            var res = CachedPathMeasure.GetPosition((float)distance, out var skPoint);
+            point = new Point(skPoint.X, skPoint.Y);
+            return res;
+        }
+
+        /// <inheritdoc />
+        public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent)
+        {
+            if (EffectivePath is null)
+            {
+                point = new Point();
+                tangent = new Point();
+                return false;
+            }
+
+            var res = CachedPathMeasure.GetPositionAndTangent((float)distance, out var skPoint, out var skTangent);
+            point = new Point(skPoint.X, skPoint.Y);
+            tangent = new Point(skTangent.X, skTangent.Y);
+            return res;
+        }
+
+        public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure,
+            out IGeometryImpl segmentGeometry)
+        {
+            if (EffectivePath is null)
+            {
+                segmentGeometry = null;
+                return false;
+            }
+
+            segmentGeometry = null;
+
+            var _skPathSegment = new SKPath();
+
+            var res = CachedPathMeasure.GetSegment((float)startDistance, (float)stopDistance, _skPathSegment, startOnBeginFigure);
+
+            if (res)
+            {
+                segmentGeometry = new StreamGeometryImpl(_skPathSegment);
+            }
+
+            return res;
+        }
+
         /// <summary>
         /// Invalidate all caches. Call after chaining path contents.
         /// </summary>
@@ -115,12 +195,12 @@ namespace Avalonia.Skia
         private struct PathCache
         {
             private float _cachedStrokeWidth;
-            
+
             /// <summary>
             /// Tolerance for two stroke widths to be deemed equal
             /// </summary>
             public const float Tolerance = float.Epsilon;
-            
+
             /// <summary>
             /// Cached contour path.
             /// </summary>

+ 33 - 0
src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs

@@ -25,6 +25,39 @@ namespace Avalonia.Skia
             }
         }
 
+        public static SKBlendMode ToSKBlendMode(this BitmapBlendingMode blendingMode)
+        {
+            switch (blendingMode)
+            {
+                case BitmapBlendingMode.SourceOver:
+                    return SKBlendMode.SrcOver;
+                case BitmapBlendingMode.Source:
+                    return SKBlendMode.Src;
+                case BitmapBlendingMode.SourceIn:
+                    return SKBlendMode.SrcIn;
+                case BitmapBlendingMode.SourceOut:
+                    return SKBlendMode.SrcOut;
+                case BitmapBlendingMode.SourceAtop:
+                    return SKBlendMode.SrcATop;
+                case BitmapBlendingMode.Destination:
+                    return SKBlendMode.Dst;
+                case BitmapBlendingMode.DestinationIn:
+                    return SKBlendMode.DstIn;
+                case BitmapBlendingMode.DestinationOut:
+                    return SKBlendMode.DstOut;
+                case BitmapBlendingMode.DestinationOver:
+                    return SKBlendMode.DstOver;
+                case BitmapBlendingMode.DestinationAtop:
+                    return SKBlendMode.DstATop;
+                case BitmapBlendingMode.Xor:
+                    return SKBlendMode.Xor;
+                case BitmapBlendingMode.Plus:
+                    return SKBlendMode.Plus;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(blendingMode), blendingMode, null);
+            }
+        }
+
         public static SKPoint ToSKPoint(this Point p)
         {
             return new SKPoint((float)p.X, (float)p.Y);

+ 43 - 1
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@@ -5,6 +5,7 @@ using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.Rendering.SceneGraph;
 using Avalonia.Utilities;
+using Avalonia.Visuals.Media.Imaging;
 using SharpDX;
 using SharpDX.Direct2D1;
 using SharpDX.Mathematics.Interop;
@@ -121,7 +122,9 @@ namespace Avalonia.Direct2D1.Media
             using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
             {
                 var interpolationMode = GetInterpolationMode(bitmapInterpolationMode);
-
+                
+                // TODO: How to implement CompositeMode here?
+                
                 _deviceContext.DrawBitmap(
                     d2d.Value,
                     destRect.ToSharpDX(),
@@ -149,6 +152,35 @@ namespace Avalonia.Direct2D1.Media
             }
         }
 
+        public static CompositeMode GetCompositeMode(BitmapBlendingMode blendingMode)
+        {
+            switch (blendingMode)
+            {  
+                case BitmapBlendingMode.SourceIn:
+                    return CompositeMode.SourceIn;
+                case BitmapBlendingMode.SourceOut:
+                    return CompositeMode.SourceOut;
+                case BitmapBlendingMode.SourceOver:
+                    return CompositeMode.SourceOver;
+                case BitmapBlendingMode.SourceAtop:
+                    return CompositeMode.SourceAtop; 
+                case BitmapBlendingMode.DestinationIn:
+                    return CompositeMode.DestinationIn;
+                case BitmapBlendingMode.DestinationOut:
+                    return CompositeMode.DestinationOut;
+                case BitmapBlendingMode.DestinationOver:
+                    return CompositeMode.DestinationOver;
+                case BitmapBlendingMode.DestinationAtop:
+                    return CompositeMode.DestinationAtop;
+                case BitmapBlendingMode.Xor:
+                    return CompositeMode.Xor;
+                case BitmapBlendingMode.Plus:
+                    return CompositeMode.Plus;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(blendingMode), blendingMode, null);
+            }
+        }
+
         /// <summary>
         /// Draws a bitmap image.
         /// </summary>
@@ -525,6 +557,16 @@ namespace Avalonia.Direct2D1.Media
             PopLayer();
         }
 
+        public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
+        {
+            // TODO: Stubs for now
+        }
+
+        public void PopBitmapBlendMode()
+        {
+            // TODO: Stubs for now
+        }
+
         public void PushOpacityMask(IBrush mask, Rect bounds)
         {
             var parameters = new LayerParameters

+ 33 - 0
src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs

@@ -1,3 +1,4 @@
+using Avalonia.Logging;
 using Avalonia.Platform;
 using SharpDX.Direct2D1;
 
@@ -8,6 +9,8 @@ namespace Avalonia.Direct2D1.Media
     /// </summary>
     public abstract class GeometryImpl : IGeometryImpl
     {
+        private const float ContourApproximation = 0.0001f;
+
         public GeometryImpl(Geometry geometry)
         {
             Geometry = geometry;
@@ -16,6 +19,9 @@ namespace Avalonia.Direct2D1.Media
         /// <inheritdoc/>
         public Rect Bounds => Geometry.GetWidenedBounds(0).ToAvalonia();
 
+        /// <inheritdoc />
+        public double ContourLength => Geometry.ComputeLength(null, ContourApproximation);
+
         public Geometry Geometry { get; }
 
         /// <inheritdoc/>
@@ -57,6 +63,33 @@ namespace Avalonia.Direct2D1.Media
                     transform.ToDirect2D()),
                 this);
         }
+        
+        /// <inheritdoc />
+        public bool TryGetPointAtDistance(double distance, out Point point)
+        {
+            Geometry.ComputePointAtLength((float)distance, ContourApproximation, out var tangentVector);
+            point = new Point(tangentVector.X, tangentVector.Y);
+            return true;
+        }
+        
+        /// <inheritdoc />
+        public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent)
+        {
+            // Direct2D doesnt have this sadly.
+            Logger.TryGet(LogEventLevel.Warning, LogArea.Visual)?.Log(this, "TryGetPointAndTangentAtDistance is not available in Direct2D.");
+            point = new Point();
+            tangent = new Point();
+            return false;
+        }
+
+        public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry)
+        {
+            // Direct2D doesnt have this too sadly.
+            Logger.TryGet(LogEventLevel.Warning, LogArea.Visual)?.Log(this, "TryGetSegment is not available in Direct2D.");
+
+            segmentGeometry = null;
+            return false;
+        }
 
         protected virtual Geometry GetSourceGeometry() => Geometry;
     }

+ 4 - 4
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@@ -147,13 +147,13 @@ namespace Avalonia.Win32.Interop.Wpf
         {
             var state = Keyboard.Modifiers;
             var rv = default(RawInputModifiers);
-            if (state.HasFlagCustom(ModifierKeys.Windows))
+            if (state.HasAllFlags(ModifierKeys.Windows))
                 rv |= RawInputModifiers.Meta;
-            if (state.HasFlagCustom(ModifierKeys.Alt))
+            if (state.HasAllFlags(ModifierKeys.Alt))
                 rv |= RawInputModifiers.Alt;
-            if (state.HasFlagCustom(ModifierKeys.Control))
+            if (state.HasAllFlags(ModifierKeys.Control))
                 rv |= RawInputModifiers.Control;
-            if (state.HasFlagCustom(ModifierKeys.Shift))
+            if (state.HasAllFlags(ModifierKeys.Shift))
                 rv |= RawInputModifiers.Shift;
             if (e != null)
             {

+ 3 - 3
src/Windows/Avalonia.Win32/DataObject.cs

@@ -181,7 +181,7 @@ namespace Avalonia.Win32
                 ole.GetData(ref format, out medium);
                 return;
             }
-            if(!format.tymed.HasFlagCustom(TYMED.TYMED_HGLOBAL))
+            if(!format.tymed.HasAllFlags(TYMED.TYMED_HGLOBAL))
                 Marshal.ThrowExceptionForHR(DV_E_TYMED);
 
             if (format.dwAspect != DVASPECT.DVASPECT_CONTENT)
@@ -205,7 +205,7 @@ namespace Avalonia.Win32
                 return;
             }
 
-            if (medium.tymed != TYMED.TYMED_HGLOBAL || !format.tymed.HasFlagCustom(TYMED.TYMED_HGLOBAL))
+            if (medium.tymed != TYMED.TYMED_HGLOBAL || !format.tymed.HasAllFlags(TYMED.TYMED_HGLOBAL))
                 Marshal.ThrowExceptionForHR(DV_E_TYMED);
 
             if (format.dwAspect != DVASPECT.DVASPECT_CONTENT)
@@ -228,7 +228,7 @@ namespace Avalonia.Win32
                 return ole.QueryGetData(ref format);
             if (format.dwAspect != DVASPECT.DVASPECT_CONTENT)
                 return DV_E_DVASPECT;
-            if (!format.tymed.HasFlagCustom(TYMED.TYMED_HGLOBAL))
+            if (!format.tymed.HasAllFlags(TYMED.TYMED_HGLOBAL))
                 return DV_E_TYMED;
 
             string dataFormat = ClipboardFormats.GetFormat(format.cfFormat);

+ 12 - 12
src/Windows/Avalonia.Win32/OleDropTarget.cs

@@ -24,11 +24,11 @@ namespace Avalonia.Win32
         public static DropEffect ConvertDropEffect(DragDropEffects operation)
         {
             DropEffect result = DropEffect.None;
-            if (operation.HasFlagCustom(DragDropEffects.Copy))
+            if (operation.HasAllFlags(DragDropEffects.Copy))
                 result |= DropEffect.Copy;
-            if (operation.HasFlagCustom(DragDropEffects.Move))
+            if (operation.HasAllFlags(DragDropEffects.Move))
                 result |= DropEffect.Move;
-            if (operation.HasFlagCustom(DragDropEffects.Link))
+            if (operation.HasAllFlags(DragDropEffects.Link))
                 result |= DropEffect.Link;
             return result;
         }
@@ -36,11 +36,11 @@ namespace Avalonia.Win32
         public static DragDropEffects ConvertDropEffect(DropEffect effect)
         {
             DragDropEffects result = DragDropEffects.None;
-            if (effect.HasFlagCustom(DropEffect.Copy))
+            if (effect.HasAllFlags(DropEffect.Copy))
                 result |= DragDropEffects.Copy;
-            if (effect.HasFlagCustom(DropEffect.Move))
+            if (effect.HasAllFlags(DropEffect.Move))
                 result |= DragDropEffects.Move;
-            if (effect.HasFlagCustom(DropEffect.Link))
+            if (effect.HasAllFlags(DropEffect.Link))
                 result |= DragDropEffects.Link;
             return result;
         }
@@ -50,17 +50,17 @@ namespace Avalonia.Win32
             var modifiers = RawInputModifiers.None;
             var state = (UnmanagedMethods.ModifierKeys)grfKeyState;
 
-            if (state.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_LBUTTON))
+            if (state.HasAllFlags(UnmanagedMethods.ModifierKeys.MK_LBUTTON))
                 modifiers |= RawInputModifiers.LeftMouseButton;
-            if (state.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_MBUTTON))
+            if (state.HasAllFlags(UnmanagedMethods.ModifierKeys.MK_MBUTTON))
                 modifiers |= RawInputModifiers.MiddleMouseButton;
-            if (state.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_RBUTTON))
+            if (state.HasAllFlags(UnmanagedMethods.ModifierKeys.MK_RBUTTON))
                 modifiers |= RawInputModifiers.RightMouseButton;
-            if (state.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_SHIFT))
+            if (state.HasAllFlags(UnmanagedMethods.ModifierKeys.MK_SHIFT))
                 modifiers |= RawInputModifiers.Shift;
-            if (state.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_CONTROL))
+            if (state.HasAllFlags(UnmanagedMethods.ModifierKeys.MK_CONTROL))
                 modifiers |= RawInputModifiers.Control;
-            if (state.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_ALT))
+            if (state.HasAllFlags(UnmanagedMethods.ModifierKeys.MK_ALT))
                 modifiers |= RawInputModifiers.Alt;
             return modifiers;
         }

+ 7 - 7
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@@ -310,9 +310,9 @@ namespace Avalonia.Win32
                             {
                                 Input?.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time,
                                     _owner,
-                                    touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_UP) ?
+                                    touchInput.Flags.HasAllFlags(TouchInputFlags.TOUCHEVENTF_UP) ?
                                         RawPointerEventType.TouchEnd :
-                                        touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_DOWN) ?
+                                        touchInput.Flags.HasAllFlags(TouchInputFlags.TOUCHEVENTF_DOWN) ?
                                             RawPointerEventType.TouchBegin :
                                             RawPointerEventType.TouchUpdate,
                                     PointToClient(new PixelPoint(touchInput.X / 100, touchInput.Y / 100)),
@@ -521,27 +521,27 @@ namespace Avalonia.Win32
             var keys = (ModifierKeys)ToInt32(wParam);
             var modifiers = WindowsKeyboardDevice.Instance.Modifiers;
 
-            if (keys.HasFlagCustom(ModifierKeys.MK_LBUTTON))
+            if (keys.HasAllFlags(ModifierKeys.MK_LBUTTON))
             {
                 modifiers |= RawInputModifiers.LeftMouseButton;
             }
 
-            if (keys.HasFlagCustom(ModifierKeys.MK_RBUTTON))
+            if (keys.HasAllFlags(ModifierKeys.MK_RBUTTON))
             {
                 modifiers |= RawInputModifiers.RightMouseButton;
             }
 
-            if (keys.HasFlagCustom(ModifierKeys.MK_MBUTTON))
+            if (keys.HasAllFlags(ModifierKeys.MK_MBUTTON))
             {
                 modifiers |= RawInputModifiers.MiddleMouseButton;
             }
 
-            if (keys.HasFlagCustom(ModifierKeys.MK_XBUTTON1))
+            if (keys.HasAllFlags(ModifierKeys.MK_XBUTTON1))
             {
                 modifiers |= RawInputModifiers.XButton1MouseButton;
             }
 
-            if (keys.HasFlagCustom(ModifierKeys.MK_XBUTTON2))
+            if (keys.HasAllFlags(ModifierKeys.MK_XBUTTON2))
             {
                 modifiers |= RawInputModifiers.XButton2MouseButton;
             }

+ 2 - 2
src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs

@@ -23,13 +23,13 @@ namespace Avalonia.Win32
             AdjustWindowRectEx(ref rcFrame, (uint)(WindowStyles.WS_OVERLAPPEDWINDOW & ~WindowStyles.WS_CAPTION), false, 0);
 
             var borderThickness = new RECT();
-            if (GetStyle().HasFlagCustom(WindowStyles.WS_THICKFRAME))
+            if (GetStyle().HasAllFlags(WindowStyles.WS_THICKFRAME))
             {
                 AdjustWindowRectEx(ref borderThickness, (uint)(GetStyle()), false, 0);
                 borderThickness.left *= -1;
                 borderThickness.top *= -1;
             }
-            else if (GetStyle().HasFlagCustom(WindowStyles.WS_BORDER))
+            else if (GetStyle().HasAllFlags(WindowStyles.WS_BORDER))
             {
                 borderThickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 };
             }

+ 52 - 20
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -85,6 +85,7 @@ namespace Avalonia.Win32
         private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default;
         private bool _isCloseRequested;
         private bool _shown;
+        private bool _hiddenWindowIsParent;
 
         public WindowImpl()
         {
@@ -483,8 +484,8 @@ namespace Avalonia.Win32
                     IntPtr.Zero,
                     0,
                     0,
-                    requestedClientWidth + (windowRect.Width - clientRect.Width),
-                    requestedClientHeight + (windowRect.Height - clientRect.Height),
+                    requestedClientWidth + (_isClientAreaExtended ? 0 : windowRect.Width - clientRect.Width),
+                    requestedClientHeight + (_isClientAreaExtended ? 0 : windowRect.Height - clientRect.Height),
                     SetWindowPosFlags.SWP_RESIZE);
             }
         }
@@ -571,8 +572,7 @@ namespace Avalonia.Win32
 
         public virtual void Show(bool activate)
         {
-            SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, _parent != null ? _parent._hwnd : IntPtr.Zero);
-
+            SetParent(_parent);
             ShowWindow(_showWindowState, activate);
         }
 
@@ -581,7 +581,16 @@ namespace Avalonia.Win32
         public void SetParent(IWindowImpl parent)
         {
             _parent = (WindowImpl)parent;
-            SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, _parent._hwnd);
+            
+            var parentHwnd = _parent?._hwnd ?? IntPtr.Zero;
+
+            if (parentHwnd == IntPtr.Zero && !_windowProperties.ShowInTaskbar)
+            {
+                parentHwnd = OffscreenParentWindow.Handle;
+                _hiddenWindowIsParent = true;
+            }
+
+            SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, parentHwnd);
         }
 
         public void SetEnabled(bool enable) => EnableWindow(_hwnd, enable);
@@ -838,7 +847,7 @@ namespace Avalonia.Win32
             borderCaptionThickness.left *= -1;
             borderCaptionThickness.top *= -1;
 
-            bool wantsTitleBar = _extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.SystemChrome) || _extendTitleBarHint == -1;
+            bool wantsTitleBar = _extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) || _extendTitleBarHint == -1;
 
             if (!wantsTitleBar)
             {
@@ -855,7 +864,7 @@ namespace Avalonia.Win32
                 borderCaptionThickness.top = (int)(_extendTitleBarHint * RenderScaling);                
             }
 
-            margins.cyTopHeight = _extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.PreferSystemChrome) ? borderCaptionThickness.top : 1;
+            margins.cyTopHeight = _extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.PreferSystemChrome) ? borderCaptionThickness.top : 1;
 
             if (WindowState == WindowState.Maximized)
             {
@@ -883,20 +892,19 @@ namespace Avalonia.Win32
                 _isClientAreaExtended = false;
                 return;
             }
-
-            GetWindowRect(_hwnd, out var rcClient);
+            GetClientRect(_hwnd, out var rcClient);
+            GetWindowRect(_hwnd, out var rcWindow);
 
             // Inform the application of the frame change.
             SetWindowPos(_hwnd,
-                         IntPtr.Zero,
-                         rcClient.left, rcClient.top,
-                         rcClient.Width, rcClient.Height,
-                         SetWindowPosFlags.SWP_FRAMECHANGED);
+                IntPtr.Zero,
+                rcWindow.left, rcWindow.top,
+                rcClient.Width, rcClient.Height,
+                SetWindowPosFlags.SWP_FRAMECHANGED);
 
             if (_isClientAreaExtended && WindowState != WindowState.FullScreen)
             {
                 var margins = UpdateExtendMargins();
-
                 DwmExtendFrameIntoClientArea(_hwnd, ref margins);
             }
             else
@@ -906,10 +914,12 @@ namespace Avalonia.Win32
 
                 _offScreenMargin = new Thickness();
                 _extendedMargins = new Thickness();
+                
+                Resize(new Size(rcWindow.Width/ RenderScaling, rcWindow.Height / RenderScaling));
             }
 
-            if(!_isClientAreaExtended || (_extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.SystemChrome) &&
-                !_extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.PreferSystemChrome)))
+            if(!_isClientAreaExtended || (_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) &&
+                !_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.PreferSystemChrome)))
             {
                 EnableCloseButton(_hwnd);
             }
@@ -1094,16 +1104,38 @@ namespace Avalonia.Win32
                 if (newProperties.ShowInTaskbar)
                 {
                     exStyle |= WindowStyles.WS_EX_APPWINDOW;
+
+                    if (_hiddenWindowIsParent)
+                    {
+                        // Can't enable the taskbar icon by clearing the parent window unless the window
+                        // is hidden. Hide the window and show it again with the same activation state
+                        // when we've finished. Interestingly it seems to work fine the other way.
+                        var shown = IsWindowVisible(_hwnd);
+                        var activated = GetActiveWindow() == _hwnd;
+
+                        if (shown)
+                            Hide();
+
+                        _hiddenWindowIsParent = false;
+                        SetParent(null);
+
+                        if (shown)
+                            Show(activated);
+                    }
                 }
                 else
                 {
+                    // To hide a non-owned window's taskbar icon we need to parent it to a hidden window.
+                    if (_parent is null)
+                    {
+                        SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, OffscreenParentWindow.Handle);
+                        _hiddenWindowIsParent = true;
+                    }
+
                     exStyle &= ~WindowStyles.WS_EX_APPWINDOW;
                 }
 
                 SetExtendedStyle(exStyle);
-
-                // TODO: To hide non-owned window from taskbar we need to parent it to a hidden window.
-                // Otherwise it will still show in the taskbar.
             }
 
             WindowStyles style;
@@ -1253,7 +1285,7 @@ namespace Avalonia.Win32
         public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
         
         /// <inheritdoc/>
-        public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.PreferSystemChrome);
+        public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.PreferSystemChrome);
 
         /// <inheritdoc/>
         public Thickness ExtendedMargins => _extendedMargins;

+ 21 - 0
tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs

@@ -30,6 +30,8 @@ namespace Avalonia.UnitTests
         public IGeometryImpl SourceGeometry { get; }
 
         public Rect Bounds => _context.CalculateBounds();
+        
+        public double ContourLength { get; }
 
         public Matrix Transform { get; }
 
@@ -69,6 +71,25 @@ namespace Avalonia.UnitTests
             return new MockStreamGeometryImpl(transform, _context);
         }
 
+        public bool TryGetPointAtDistance(double distance, out Point point)
+        {
+            point = new Point();
+            return false;
+        }
+
+        public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent)
+        {
+            point = new Point();
+            tangent = new Point();
+            return false;
+        }
+
+        public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry)
+        {
+            segmentGeometry = null;
+            return false;
+        }
+
         class MockStreamGeometryContext : IStreamGeometryContextImpl
         {
             private List<Point> points = new List<Point>();

+ 10 - 0
tests/Avalonia.Visuals.UnitTests/VectorTests.cs

@@ -105,5 +105,15 @@ namespace Avalonia.Visuals.UnitTests
 
             Assert.Equal(expected, Vector.Multiply(vector, 2));
         }
+
+        [Fact]
+        public void Scale_Vector_Should_Be_Commutative()
+        {
+            var vector = new Vector(10, 2);
+
+            var expected = vector * 2;
+
+            Assert.Equal(expected, 2 * vector);
+        }
     }
 }

+ 17 - 0
tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

@@ -112,6 +112,8 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
                 }
             }
 
+            public double ContourLength { get; }
+
             public IStreamGeometryImpl Clone()
             {
                 return this;
@@ -151,6 +153,21 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
                 throw new NotImplementedException();
             }
 
+            public bool TryGetPointAtDistance(double distance, out Point point)
+            {
+                throw new NotImplementedException();
+            }
+
+            public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent)
+            {
+                throw new NotImplementedException();
+            }
+
+            public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry)
+            {
+                throw new NotImplementedException();
+            }
+
             class MockStreamGeometryContext : IStreamGeometryContextImpl
             {
                 private List<Point> points = new List<Point>();