diff --git a/readme.md b/readme.md
index f73bdffaeb..67b706f428 100644
--- a/readme.md
+++ b/readme.md
@@ -2,8 +2,6 @@
[](https://www.nuget.org/packages/Avalonia) [](https://www.nuget.org/packages/Avalonia) [](https://www.myget.org/gallery/avalonia-ci) 
-
-
## 📖 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();