diff --git a/readme.md b/readme.md index f73bdffaeb..67b706f428 100644 --- a/readme.md +++ b/readme.md @@ -2,8 +2,6 @@
[![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) -Avalonia - ## 📖 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. diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml index 93fbe5e412..aa165d13f7 100644 --- a/samples/RenderDemo/MainWindow.xaml +++ b/samples/RenderDemo/MainWindow.xaml @@ -57,6 +57,9 @@ + + + diff --git a/samples/RenderDemo/Pages/PathMeasurementPage.cs b/samples/RenderDemo/Pages/PathMeasurementPage.cs new file mode 100644 index 0000000000..212377deae --- /dev/null +++ b/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(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); + } + } +} diff --git a/src/Avalonia.Base/Data/BindingValue.cs b/src/Avalonia.Base/Data/BindingValue.cs index 6e3c9ae67b..e79980518f 100644 --- a/src/Avalonia.Base/Data/BindingValue.cs +++ b/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. /// - public bool HasError => Type.HasFlagCustom(BindingValueType.HasError); + public bool HasError => Type.HasAllFlags(BindingValueType.HasError); /// /// Gets a value indicating whether the binding value has a value. /// - public bool HasValue => Type.HasFlagCustom(BindingValueType.HasValue); + public bool HasValue => Type.HasAllFlags(BindingValueType.HasValue); /// /// Gets the type of the binding value. diff --git a/src/Avalonia.Base/EnumExtensions.cs b/src/Avalonia.Base/EnumExtensions.cs index bc1f8d36a9..19eb42a700 100644 --- a/src/Avalonia.Base/EnumExtensions.cs +++ b/src/Avalonia.Base/EnumExtensions.cs @@ -9,31 +9,67 @@ namespace Avalonia public static class EnumExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe bool HasFlagCustom(this T value, T flag) where T : unmanaged, Enum + [Obsolete("This method is obsolete. Use HasAllFlags instead.")] + public static bool HasFlagCustom(this T value, T flag) where T : unmanaged, Enum + => value.HasAllFlags(flag); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe bool HasAllFlags(this T value, T flags) where T : unmanaged, Enum + { + if (sizeof(T) == 1) + { + var byteValue = Unsafe.As(ref value); + var byteFlags = Unsafe.As(ref flags); + return (byteValue & byteFlags) == byteFlags; + } + else if (sizeof(T) == 2) + { + var shortValue = Unsafe.As(ref value); + var shortFlags = Unsafe.As(ref flags); + return (shortValue & shortFlags) == shortFlags; + } + else if (sizeof(T) == 4) + { + var intValue = Unsafe.As(ref value); + var intFlags = Unsafe.As(ref flags); + return (intValue & intFlags) == intFlags; + } + else if (sizeof(T) == 8) + { + var longValue = Unsafe.As(ref value); + var longFlags = Unsafe.As(ref flags); + return (longValue & longFlags) == longFlags; + } + else + throw new NotSupportedException("Enum with size of " + Unsafe.SizeOf() + " are not supported"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe bool HasAnyFlag(this T value, T flags) where T : unmanaged, Enum { if (sizeof(T) == 1) { var byteValue = Unsafe.As(ref value); - var byteFlag = Unsafe.As(ref flag); - return (byteValue & byteFlag) == byteFlag; + var byteFlags = Unsafe.As(ref flags); + return (byteValue & byteFlags) != 0; } else if (sizeof(T) == 2) { var shortValue = Unsafe.As(ref value); - var shortFlag = Unsafe.As(ref flag); - return (shortValue & shortFlag) == shortFlag; + var shortFlags = Unsafe.As(ref flags); + return (shortValue & shortFlags) != 0; } else if (sizeof(T) == 4) { var intValue = Unsafe.As(ref value); - var intFlag = Unsafe.As(ref flag); - return (intValue & intFlag) == intFlag; + var intFlags = Unsafe.As(ref flags); + return (intValue & intFlags) != 0; } else if (sizeof(T) == 8) { var longValue = Unsafe.As(ref value); - var longFlag = Unsafe.As(ref flag); - return (longValue & longFlag) == longFlag; + var longFlags = Unsafe.As(ref flags); + return (longValue & longFlags) != 0; } else throw new NotSupportedException("Enum with size of " + Unsafe.SizeOf() + " are not supported"); diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs index 097731bc60..9f2308a062 100644 --- a/src/Avalonia.Base/Utilities/TypeUtilities.cs +++ b/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()) { diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs index 7c81c90d82..5b1e43b8f4 100644 --- a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs +++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs @@ -2595,7 +2595,7 @@ namespace Avalonia.Collections /// Whether the specified flag is set private bool CheckFlag(CollectionViewFlags flags) { - return _flags.HasFlagCustom(flags); + return _flags.HasAllFlags(flags); } /// diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index c5af5ffa7a..f8728af87f 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/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; diff --git a/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs b/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs index 603a470a93..2af8dba479 100644 --- a/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs +++ b/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('⌘'); } diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 66266c3b61..105a2744e5 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -2355,7 +2355,7 @@ namespace Avalonia.Controls /// 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); } /// diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index b6b3cc786c..f4185650bb 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/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); } } diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs index 7e1a7378c7..414c523e0d 100644 --- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs +++ b/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); diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs index 545034239f..0f68f5d258 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs +++ b/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; } diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs index 80b32b5ef8..dd839a0e9b 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs +++ b/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; diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 19824a71f0..34d3347434 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -321,7 +321,7 @@ namespace Avalonia.Controls.Primitives /// /// Gets a value indicating whether is set. /// - protected bool AlwaysSelected => SelectionMode.HasFlagCustom(SelectionMode.AlwaysSelected); + protected bool AlwaysSelected => SelectionMode.HasAllFlags(SelectionMode.AlwaysSelected); /// 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(); - _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), }; } diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 161f09d9b6..9ab5a73af0 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -138,6 +138,8 @@ namespace Avalonia.Controls static ProgressBar() { ValueProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); + MinimumProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); + MaximumProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); IsIndeterminateProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); } diff --git a/src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs b/src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs index 4eadced423..733b045e1b 100644 --- a/src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs +++ b/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); diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index f2dadb3248..158f3ac886 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -601,7 +601,7 @@ namespace Avalonia.Controls var keymap = AvaloniaLocator.Current.GetService(); bool Match(List 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: diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index c8150cc200..3afbbd944c 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/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) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 98f4cadc13..273ed790c3 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/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)) { diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs index 8239b3f35d..31a061571f 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs @@ -76,13 +76,13 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx protected override async Task 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 { diff --git a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs index 74f54267d0..a73de9dae8 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs +++ b/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 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) diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 9e635e01f1..b5e35db969 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -223,13 +223,13 @@ namespace Avalonia.FreeDesktop return null; var lst = new List(); 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() }; diff --git a/src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs b/src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs index fb61f25cea..18c149ce2e 100644 --- a/src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs +++ b/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); diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 6a78f4c6e7..62cac378d7 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/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) { diff --git a/src/Avalonia.Input/AccessKeyHandler.cs b/src/Avalonia.Input/AccessKeyHandler.cs index 731a409090..5c4af68d79 100644 --- a/src/Avalonia.Input/AccessKeyHandler.cs +++ b/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. diff --git a/src/Avalonia.Input/Gestures.cs b/src/Avalonia.Input/Gestures.cs index 1be2595ebe..60b6b9e947 100644 --- a/src/Avalonia.Input/Gestures.cs +++ b/src/Avalonia.Input/Gestures.cs @@ -24,7 +24,7 @@ namespace Avalonia.Input public static readonly RoutedEvent ScrollGestureEvent = RoutedEvent.Register( "ScrollGesture", RoutingStrategies.Bubble, typeof(Gestures)); - + public static readonly RoutedEvent ScrollGestureEndedEvent = RoutedEvent.Register( "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)); + } } } } diff --git a/src/Avalonia.Input/KeyGesture.cs b/src/Avalonia.Input/KeyGesture.cs index e155666631..0adbe73263 100644 --- a/src/Avalonia.Input/KeyGesture.cs +++ b/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"); diff --git a/src/Avalonia.Input/PointerEventArgs.cs b/src/Avalonia.Input/PointerEventArgs.cs index 451f80b1df..ba39f7ca8e 100644 --- a/src/Avalonia.Input/PointerEventArgs.cs +++ b/src/Avalonia.Input/PointerEventArgs.cs @@ -107,7 +107,9 @@ namespace Avalonia.Input None, Left, Right, - Middle + Middle, + XButton1, + XButton2 } public class PointerPressedEventArgs : PointerEventArgs diff --git a/src/Avalonia.Input/PointerPoint.cs b/src/Avalonia.Input/PointerPoint.cs index a316e0d964..9f8285a8e1 100644 --- a/src/Avalonia.Input/PointerPoint.cs +++ b/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; } } diff --git a/src/Avalonia.Input/TappedEventArgs.cs b/src/Avalonia.Input/TappedEventArgs.cs new file mode 100644 index 0000000000..02add509cd --- /dev/null +++ b/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); + } +} diff --git a/src/Avalonia.Interactivity/EventRoute.cs b/src/Avalonia.Interactivity/EventRoute.cs index 85ba33d7ba..8f826835da 100644 --- a/src/Avalonia.Interactivity/EventRoute.cs +++ b/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) diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index bb7f0a8401..580704bb19 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/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; diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt new file mode 100644 index 0000000000..805d1955ea --- /dev/null +++ b/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 diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index ae4c927ae2..4e3dc8699c 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -121,12 +121,23 @@ namespace Avalonia.Media /// The stroke pen. /// The geometry. public void DrawGeometry(IBrush brush, IPen pen, Geometry geometry) + { + DrawGeometry(brush, pen, geometry.PlatformImpl); + } + + /// + /// Draws a geometry. + /// + /// The fill brush. + /// The stroke pen. + /// The geometry. + public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) { Contract.Requires(geometry != null); if (brush != null || PenIsVisible(pen)) { - PlatformImpl.DrawGeometry(brush, pen, geometry.PlatformImpl); + PlatformImpl.DrawGeometry(brush, pen, geometry); } } diff --git a/src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs b/src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs new file mode 100644 index 0000000000..473b43dab3 --- /dev/null +++ b/src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs @@ -0,0 +1,57 @@ +namespace Avalonia.Visuals.Media.Imaging +{ + /// + /// Controls the way the bitmaps are drawn together. + /// + public enum BitmapBlendingMode + { + /// + /// Source is placed over the destination. + /// + SourceOver, + /// + /// Only the source will be present. + /// + Source, + /// + /// Only the destination will be present. + /// + Destination, + /// + /// Destination is placed over the source. + /// + DestinationOver, + /// + /// The source that overlaps the destination, replaces the destination. + /// + SourceIn, + /// + /// Destination which overlaps the source, replaces the source. + /// + DestinationIn, + /// + /// Source is placed, where it falls outside of the destination. + /// + SourceOut, + /// + /// Destination is placed, where it falls outside of the source. + /// + DestinationOut, + /// + /// Source which overlaps the destination, replaces the destination. + /// + SourceAtop, + /// + /// Destination which overlaps the source replaces the source. + /// + DestinationAtop, + /// + /// The non-overlapping regions of source and destination are combined. + /// + Xor, + /// + /// Display the sum of the source image and destination image. + /// + Plus, + } +} diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index d6e88a7507..39d4066e55 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -148,6 +148,17 @@ namespace Avalonia.Platform /// Pops the latest pushed geometry clip. /// void PopGeometryClip(); + + /// + /// Pushes a bitmap blending value. + /// + /// The bitmap blending mode. + void PushBitmapBlendMode(BitmapBlendingMode blendingMode); + + /// + /// Pops the latest pushed bitmap blending value. + /// + void PopBitmapBlendMode(); /// /// Adds a custom draw operation diff --git a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs index 7490ad912a..79e125c5cb 100644 --- a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs +++ b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs @@ -11,6 +11,12 @@ namespace Avalonia.Platform /// Gets the geometry's bounding rectangle. /// Rect Bounds { get; } + + /// + /// Gets the geometry's total length as if all its contours are placed + /// in a straight line. + /// + double ContourLength { get; } /// /// Gets the geometry's bounding rectangle with the specified pen. @@ -47,5 +53,38 @@ namespace Avalonia.Platform /// The transform. /// The cloned geometry. ITransformedGeometryImpl WithTransform(Matrix transform); + + /// + /// Attempts to get the corresponding point at the + /// specified distance + /// + /// The contour distance to get from. + /// The point in the specified distance. + /// If there's valid point at the specified distance. + bool TryGetPointAtDistance(double distance, out Point point); + + /// + /// Attempts to get the corresponding point and + /// tangent from the specified distance along the + /// contour of the geometry. + /// + /// The contour distance to get from. + /// The point in the specified distance. + /// The tangent in the specified distance. + /// If there's valid point and tangent at the specified distance. + bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent); + + /// + /// Attempts to get the corresponding path segment + /// given by the two distances specified. + /// Imagine it like snipping a part of the current + /// geometry. + /// + /// The contour distance to start snipping from. + /// The contour distance to stop snipping to. + /// If ture, the resulting snipped path will start with a BeginFigure call. + /// The resulting snipped path. + /// If the snipping operation is successful. + bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry); } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs new file mode 100644 index 0000000000..0a5c1f8db6 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs @@ -0,0 +1,68 @@ +using Avalonia.Platform; +using Avalonia.Visuals.Media.Imaging; + +namespace Avalonia.Rendering.SceneGraph +{ + /// + /// A node in the scene graph which represents an bitmap blending mode push or pop. + /// + internal class BitmapBlendModeNode : IDrawOperation + { + /// + /// Initializes a new instance of the class that represents an + /// push. + /// + /// The to push. + public BitmapBlendModeNode(BitmapBlendingMode bitmapBlend) + { + BlendingMode = bitmapBlend; + } + + /// + /// Initializes a new instance of the class that represents an + /// pop. + /// + public BitmapBlendModeNode() + { + } + + /// + public Rect Bounds => Rect.Empty; + + /// + /// Gets the BitmapBlend to be pushed or null if the operation represents a pop. + /// + public BitmapBlendingMode? BlendingMode { get; } + + /// + public bool HitTest(Point p) => false; + + /// + /// Determines if this draw operation equals another. + /// + /// The opacity of the other draw operation. + /// True if the draw operations are the same, otherwise false. + /// + /// The properties of the other draw operation are passed in as arguments to prevent + /// allocation of a not-yet-constructed draw operation object. + /// + public bool Equals(BitmapBlendingMode? blendingMode) => BlendingMode == blendingMode; + + /// + public void Render(IDrawingContextImpl context) + { + if (BlendingMode.HasValue) + { + context.PushBitmapBlendMode(BlendingMode.Value); + } + else + { + context.PopBitmapBlendMode(); + } + } + + public void Dispose() + { + } + } +} diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 28f426266d..e6092574c5 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -253,6 +253,21 @@ namespace Avalonia.Rendering.SceneGraph } } + /// + public void PopBitmapBlendMode() + { + var next = NextDrawAs(); + + if (next == null || !next.Item.Equals(null)) + { + Add(new BitmapBlendModeNode()); + } + else + { + ++_drawOperationindex; + } + } + /// public void PopOpacity() { @@ -358,6 +373,21 @@ namespace Avalonia.Rendering.SceneGraph } } + /// + public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) + { + var next = NextDrawAs(); + + if (next == null || !next.Item.Equals(blendingMode)) + { + Add(new BitmapBlendModeNode(blendingMode)); + } + else + { + ++_drawOperationindex; + } + } + public readonly struct UpdateState : IDisposable { public UpdateState( diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs index c9052c6ef2..d3da19d8c9 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs @@ -67,6 +67,14 @@ namespace Avalonia.Rendering.SceneGraph /// The scaling mode. /// public BitmapInterpolationMode BitmapInterpolationMode { get; } + + /// + /// The bitmap blending mode. + /// + /// + /// The blending mode. + /// + public BitmapBlendingMode BitmapBlendingMode { get; } /// /// Determines if this draw operation equals another. @@ -85,12 +93,12 @@ namespace Avalonia.Rendering.SceneGraph public bool Equals(Matrix transform, IRef 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; } /// diff --git a/src/Avalonia.Visuals/Vector.cs b/src/Avalonia.Visuals/Vector.cs index 1b9f5c67d5..79c4202be4 100644 --- a/src/Avalonia.Visuals/Vector.cs +++ b/src/Avalonia.Visuals/Vector.cs @@ -82,6 +82,15 @@ namespace Avalonia public static Vector operator *(Vector vector, double scale) => Multiply(vector, scale); + /// + /// Scales a vector. + /// + /// The vector. + /// The scaling factor. + /// The scaled vector. + public static Vector operator *(double scale, Vector vector) + => Multiply(vector, scale); + /// /// Scales a vector. /// diff --git a/src/Avalonia.X11/X11Window.Ime.cs b/src/Avalonia.X11/X11Window.Ime.cs index ac626f5825..0bd1bc8a89 100644 --- a/src/Avalonia.X11/X11Window.Ime.cs +++ b/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(); diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 8f3f412578..5ac4c4c9d0 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/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; } diff --git a/src/Avalonia.X11/XI2Manager.cs b/src/Avalonia.X11/XI2Manager.cs index 2874c517a9..7bf1df41b6 100644 --- a/src/Avalonia.X11/XI2Manager.cs +++ b/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); } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs index 34cc261187..e218da5f41 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs +++ b/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; } } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 2a79a4bb50..b7d5d3ec59 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -23,9 +23,11 @@ namespace Avalonia.Skia private readonly Vector _dpi; private readonly Stack _maskStack = new Stack(); private readonly Stack _opacityStack = new Stack(); + private readonly Stack _blendingModeStack = new Stack(); 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(); } + /// + public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) + { + _blendingModeStack.Push(_currentBlendingMode); + _currentBlendingMode = blendingMode; + } + + /// + public void PopBitmapBlendMode() + { + _currentBlendingMode = _blendingModeStack.Pop(); + } + public void Custom(ICustomDrawOperation custom) => custom.Render(this); /// diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 879b18742e..19dbcf9713 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/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; + } + } + /// public abstract Rect Bounds { get; } + + /// + public double ContourLength + { + get + { + if (EffectivePath is null) + return 0; + + return (double)CachedPathMeasure?.Length; + } + } + public abstract SKPath EffectivePath { get; } /// @@ -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 /// True, if point is contained in a path. 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); } /// 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; } - + /// public ITransformedGeometryImpl WithTransform(Matrix transform) { return new TransformedGeometryImpl(this, transform); } + /// + 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; + } + + /// + 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; + } + /// /// Invalidate all caches. Call after chaining path contents. /// @@ -115,12 +195,12 @@ namespace Avalonia.Skia private struct PathCache { private float _cachedStrokeWidth; - + /// /// Tolerance for two stroke widths to be deemed equal /// public const float Tolerance = float.Epsilon; - + /// /// Cached contour path. /// diff --git a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs index bb3dbbfadc..75b4231640 100644 --- a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs +++ b/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); diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 47a19aad8c..af35934785 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/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); + } + } + /// /// Draws a bitmap image. /// @@ -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 diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs index d04e2b3110..ec88347a17 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs +++ b/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 /// 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 /// public Rect Bounds => Geometry.GetWidenedBounds(0).ToAvalonia(); + /// + public double ContourLength => Geometry.ComputeLength(null, ContourApproximation); + public Geometry Geometry { get; } /// @@ -57,6 +63,33 @@ namespace Avalonia.Direct2D1.Media transform.ToDirect2D()), this); } + + /// + 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; + } + + /// + 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; } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 7d86116f38..fe1d625efb 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/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) { diff --git a/src/Windows/Avalonia.Win32/DataObject.cs b/src/Windows/Avalonia.Win32/DataObject.cs index 5f02796d30..3066403186 100644 --- a/src/Windows/Avalonia.Win32/DataObject.cs +++ b/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); diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index d038e341b8..d8cb52a914 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/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; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index e7d16f731c..8e755a33bc 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/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; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs index 0a8648aa9a..d886b67241 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs +++ b/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 }; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index a42dd5fc07..082aca1109 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/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 ExtendClientAreaToDecorationsChanged { get; set; } /// - public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.PreferSystemChrome); + public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.PreferSystemChrome); /// public Thickness ExtendedMargins => _extendedMargins; diff --git a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs index 4fa3fbf523..864e2efbaf 100644 --- a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs +++ b/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 points = new List(); diff --git a/tests/Avalonia.Visuals.UnitTests/VectorTests.cs b/tests/Avalonia.Visuals.UnitTests/VectorTests.cs index 0e72cd7c7f..f9a9e59436 100644 --- a/tests/Avalonia.Visuals.UnitTests/VectorTests.cs +++ b/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); + } } } diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 02236e4107..6d0683e699 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/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 points = new List();