Browse Source

Merge branch 'master' into Flyouts

pull/5682/head
Dan Walmsley 5 years ago
committed by GitHub
parent
commit
b581b9c2e8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      readme.md
  2. 3
      samples/RenderDemo/MainWindow.xaml
  3. 89
      samples/RenderDemo/Pages/PathMeasurementPage.cs
  4. 4
      src/Avalonia.Base/Data/BindingValue.cs
  5. 54
      src/Avalonia.Base/EnumExtensions.cs
  6. 4
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  7. 2
      src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
  8. 2
      src/Avalonia.Controls/ComboBox.cs
  9. 16
      src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs
  10. 10
      src/Avalonia.Controls/Grid.cs
  11. 8
      src/Avalonia.Controls/ListBox.cs
  12. 12
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  13. 12
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  14. 36
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
  15. 12
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  16. 2
      src/Avalonia.Controls/ProgressBar.cs
  17. 4
      src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs
  18. 4
      src/Avalonia.Controls/TextBox.cs
  19. 10
      src/Avalonia.Controls/TreeView.cs
  20. 8
      src/Avalonia.Controls/Window.cs
  21. 16
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs
  22. 18
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs
  23. 8
      src/Avalonia.FreeDesktop/DBusMenuExporter.cs
  24. 4
      src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs
  25. 32
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  26. 2
      src/Avalonia.Input/AccessKeyHandler.cs
  27. 14
      src/Avalonia.Input/Gestures.cs
  28. 8
      src/Avalonia.Input/KeyGesture.cs
  29. 4
      src/Avalonia.Input/PointerEventArgs.cs
  30. 14
      src/Avalonia.Input/PointerPoint.cs
  31. 18
      src/Avalonia.Input/TappedEventArgs.cs
  32. 6
      src/Avalonia.Interactivity/EventRoute.cs
  33. 4
      src/Avalonia.Interactivity/Interactive.cs
  34. 9
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  35. 13
      src/Avalonia.Visuals/Media/DrawingContext.cs
  36. 57
      src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs
  37. 11
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  38. 39
      src/Avalonia.Visuals/Platform/IGeometryImpl.cs
  39. 68
      src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs
  40. 30
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  41. 20
      src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
  42. 9
      src/Avalonia.Visuals/Vector.cs
  43. 4
      src/Avalonia.X11/X11Window.Ime.cs
  44. 18
      src/Avalonia.X11/X11Window.cs
  45. 10
      src/Avalonia.X11/XI2Manager.cs
  46. 2
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs
  47. 16
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  48. 102
      src/Skia/Avalonia.Skia/GeometryImpl.cs
  49. 33
      src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
  50. 44
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  51. 33
      src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
  52. 8
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  53. 6
      src/Windows/Avalonia.Win32/DataObject.cs
  54. 24
      src/Windows/Avalonia.Win32/OleDropTarget.cs
  55. 14
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  56. 4
      src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs
  57. 72
      src/Windows/Avalonia.Win32/WindowImpl.cs
  58. 21
      tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
  59. 10
      tests/Avalonia.Visuals.UnitTests/VectorTests.cs
  60. 17
      tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

2
readme.md

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

3
samples/RenderDemo/MainWindow.xaml

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

89
samples/RenderDemo/Pages/PathMeasurementPage.cs

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

4
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 /// Gets a value indicating whether the binding value represents either a binding or data
/// validation error. /// validation error.
/// </summary> /// </summary>
public bool HasError => Type.HasFlagCustom(BindingValueType.HasError); public bool HasError => Type.HasAllFlags(BindingValueType.HasError);
/// <summary> /// <summary>
/// Gets a value indicating whether the binding value has a value. /// Gets a value indicating whether the binding value has a value.
/// </summary> /// </summary>
public bool HasValue => Type.HasFlagCustom(BindingValueType.HasValue); public bool HasValue => Type.HasAllFlags(BindingValueType.HasValue);
/// <summary> /// <summary>
/// Gets the type of the binding value. /// Gets the type of the binding value.

54
src/Avalonia.Base/EnumExtensions.cs

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

4
src/Avalonia.Base/Utilities/TypeUtilities.cs

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

2
src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs

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

2
src/Avalonia.Controls/ComboBox.cs

@ -206,7 +206,7 @@ namespace Avalonia.Controls
return; return;
if (e.Key == Key.F4 || 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; IsDropDownOpen = !IsDropDownOpen;
e.Handled = true; e.Handled = true;

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

10
src/Avalonia.Controls/Grid.cs

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

8
src/Avalonia.Controls/ListBox.cs

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

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

12
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

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

36
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs

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

12
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

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

2
src/Avalonia.Controls/ProgressBar.cs

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

4
src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs

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

4
src/Avalonia.Controls/TextBox.cs

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

10
src/Avalonia.Controls/TreeView.cs

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

8
src/Avalonia.Controls/Window.cs

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

16
src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs

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

18
src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs

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

8
src/Avalonia.FreeDesktop/DBusMenuExporter.cs

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

4
src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs

@ -33,11 +33,11 @@ namespace Avalonia.Headless.Vnc
{ {
Window?.MouseMove(pt); Window?.MouseMove(pt);
foreach (var btn in CheckedButtons) 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); Window?.MouseUp(pt, TranslateButton(btn), modifiers);
foreach (var btn in CheckedButtons) 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); Window?.MouseDown(pt, TranslateButton(btn), modifiers);
_previousButtons = buttons; _previousButtons = buttons;
}, DispatcherPriority.Input); }, DispatcherPriority.Input);

32
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -104,6 +104,9 @@ namespace Avalonia.Headless
} }
public Rect Bounds { get; set; } public Rect Bounds { get; set; }
public double ContourLength { get; } = 0;
public virtual bool FillContains(Point point) => Bounds.Contains(point); public virtual bool FillContains(Point point) => Bounds.Contains(point);
public Rect GetRenderBounds(IPen pen) public Rect GetRenderBounds(IPen pen)
@ -126,6 +129,25 @@ namespace Avalonia.Headless
public ITransformedGeometryImpl WithTransform(Matrix transform) => public ITransformedGeometryImpl WithTransform(Matrix transform) =>
new HeadlessTransformedGeometryStub(this, 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 class HeadlessTransformedGeometryStub : HeadlessGeometryStub, ITransformedGeometryImpl
@ -359,6 +381,16 @@ namespace Avalonia.Headless
} }
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
}
public void PopBitmapBlendMode()
{
}
public void Custom(ICustomDrawOperation custom) public void Custom(ICustomDrawOperation custom)
{ {

2
src/Avalonia.Input/AccessKeyHandler.cs

@ -177,7 +177,7 @@ namespace Avalonia.Input
{ {
bool menuIsOpen = MainMenu?.IsOpen == true; 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, // 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. // find all controls who have registered that access key.

14
src/Avalonia.Input/Gestures.cs

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

8
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"); s.Append("Ctrl");
} }
if (KeyModifiers.HasFlagCustom(KeyModifiers.Shift)) if (KeyModifiers.HasAllFlags(KeyModifiers.Shift))
{ {
Plus(s); Plus(s);
s.Append("Shift"); s.Append("Shift");
} }
if (KeyModifiers.HasFlagCustom(KeyModifiers.Alt)) if (KeyModifiers.HasAllFlags(KeyModifiers.Alt))
{ {
Plus(s); Plus(s);
s.Append("Alt"); s.Append("Alt");
} }
if (KeyModifiers.HasFlagCustom(KeyModifiers.Meta)) if (KeyModifiers.HasAllFlags(KeyModifiers.Meta))
{ {
Plus(s); Plus(s);
s.Append("Cmd"); s.Append("Cmd");

4
src/Avalonia.Input/PointerEventArgs.cs

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

14
src/Avalonia.Input/PointerPoint.cs

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

18
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);
}
}

6
src/Avalonia.Interactivity/EventRoute.cs

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

4
src/Avalonia.Interactivity/Interactive.cs

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

9
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

13
src/Avalonia.Visuals/Media/DrawingContext.cs

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

57
src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs

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

11
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

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

39
src/Avalonia.Visuals/Platform/IGeometryImpl.cs

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

68
src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs

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

30
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

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

20
src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs

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

9
src/Avalonia.Visuals/Vector.cs

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

4
src/Avalonia.X11/X11Window.Ime.cs

@ -96,14 +96,14 @@ namespace Avalonia.X11
void HandleKeyEvent(ref XEvent ev) 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 // 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(); var key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 1 : 0).ToInt32();
// Manually switch the Shift index for the keypad, // Manually switch the Shift index for the keypad,
// there should be a proper way to do this // 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.Num_Lock && key <= X11Key.KP_9)
key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 0 : 1).ToInt32(); key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 0 : 1).ToInt32();

18
src/Avalonia.X11/X11Window.cs

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

10
src/Avalonia.X11/XI2Manager.cs

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

2
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 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; } public string Name { get; }
} }

16
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

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

102
src/Skia/Avalonia.Skia/GeometryImpl.cs

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

33
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) public static SKPoint ToSKPoint(this Point p)
{ {
return new SKPoint((float)p.X, (float)p.Y); return new SKPoint((float)p.X, (float)p.Y);

44
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

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

33
src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs

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

8
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

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

6
src/Windows/Avalonia.Win32/DataObject.cs

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

24
src/Windows/Avalonia.Win32/OleDropTarget.cs

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

14
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

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

4
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); AdjustWindowRectEx(ref rcFrame, (uint)(WindowStyles.WS_OVERLAPPEDWINDOW & ~WindowStyles.WS_CAPTION), false, 0);
var borderThickness = new RECT(); var borderThickness = new RECT();
if (GetStyle().HasFlagCustom(WindowStyles.WS_THICKFRAME)) if (GetStyle().HasAllFlags(WindowStyles.WS_THICKFRAME))
{ {
AdjustWindowRectEx(ref borderThickness, (uint)(GetStyle()), false, 0); AdjustWindowRectEx(ref borderThickness, (uint)(GetStyle()), false, 0);
borderThickness.left *= -1; borderThickness.left *= -1;
borderThickness.top *= -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 }; borderThickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 };
} }

72
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -85,6 +85,7 @@ namespace Avalonia.Win32
private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default; private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default;
private bool _isCloseRequested; private bool _isCloseRequested;
private bool _shown; private bool _shown;
private bool _hiddenWindowIsParent;
public WindowImpl() public WindowImpl()
{ {
@ -483,8 +484,8 @@ namespace Avalonia.Win32
IntPtr.Zero, IntPtr.Zero,
0, 0,
0, 0,
requestedClientWidth + (windowRect.Width - clientRect.Width), requestedClientWidth + (_isClientAreaExtended ? 0 : windowRect.Width - clientRect.Width),
requestedClientHeight + (windowRect.Height - clientRect.Height), requestedClientHeight + (_isClientAreaExtended ? 0 : windowRect.Height - clientRect.Height),
SetWindowPosFlags.SWP_RESIZE); SetWindowPosFlags.SWP_RESIZE);
} }
} }
@ -571,8 +572,7 @@ namespace Avalonia.Win32
public virtual void Show(bool activate) public virtual void Show(bool activate)
{ {
SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, _parent != null ? _parent._hwnd : IntPtr.Zero); SetParent(_parent);
ShowWindow(_showWindowState, activate); ShowWindow(_showWindowState, activate);
} }
@ -581,7 +581,16 @@ namespace Avalonia.Win32
public void SetParent(IWindowImpl parent) public void SetParent(IWindowImpl parent)
{ {
_parent = (WindowImpl)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); public void SetEnabled(bool enable) => EnableWindow(_hwnd, enable);
@ -838,7 +847,7 @@ namespace Avalonia.Win32
borderCaptionThickness.left *= -1; borderCaptionThickness.left *= -1;
borderCaptionThickness.top *= -1; borderCaptionThickness.top *= -1;
bool wantsTitleBar = _extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.SystemChrome) || _extendTitleBarHint == -1; bool wantsTitleBar = _extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) || _extendTitleBarHint == -1;
if (!wantsTitleBar) if (!wantsTitleBar)
{ {
@ -855,7 +864,7 @@ namespace Avalonia.Win32
borderCaptionThickness.top = (int)(_extendTitleBarHint * RenderScaling); 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) if (WindowState == WindowState.Maximized)
{ {
@ -883,20 +892,19 @@ namespace Avalonia.Win32
_isClientAreaExtended = false; _isClientAreaExtended = false;
return; return;
} }
GetClientRect(_hwnd, out var rcClient);
GetWindowRect(_hwnd, out var rcClient); GetWindowRect(_hwnd, out var rcWindow);
// Inform the application of the frame change. // Inform the application of the frame change.
SetWindowPos(_hwnd, SetWindowPos(_hwnd,
IntPtr.Zero, IntPtr.Zero,
rcClient.left, rcClient.top, rcWindow.left, rcWindow.top,
rcClient.Width, rcClient.Height, rcClient.Width, rcClient.Height,
SetWindowPosFlags.SWP_FRAMECHANGED); SetWindowPosFlags.SWP_FRAMECHANGED);
if (_isClientAreaExtended && WindowState != WindowState.FullScreen) if (_isClientAreaExtended && WindowState != WindowState.FullScreen)
{ {
var margins = UpdateExtendMargins(); var margins = UpdateExtendMargins();
DwmExtendFrameIntoClientArea(_hwnd, ref margins); DwmExtendFrameIntoClientArea(_hwnd, ref margins);
} }
else else
@ -906,10 +914,12 @@ namespace Avalonia.Win32
_offScreenMargin = new Thickness(); _offScreenMargin = new Thickness();
_extendedMargins = new Thickness(); _extendedMargins = new Thickness();
Resize(new Size(rcWindow.Width/ RenderScaling, rcWindow.Height / RenderScaling));
} }
if(!_isClientAreaExtended || (_extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.SystemChrome) && if(!_isClientAreaExtended || (_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) &&
!_extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.PreferSystemChrome))) !_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.PreferSystemChrome)))
{ {
EnableCloseButton(_hwnd); EnableCloseButton(_hwnd);
} }
@ -1094,16 +1104,38 @@ namespace Avalonia.Win32
if (newProperties.ShowInTaskbar) if (newProperties.ShowInTaskbar)
{ {
exStyle |= WindowStyles.WS_EX_APPWINDOW; 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 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; exStyle &= ~WindowStyles.WS_EX_APPWINDOW;
} }
SetExtendedStyle(exStyle); 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; WindowStyles style;
@ -1253,7 +1285,7 @@ namespace Avalonia.Win32
public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; } public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.PreferSystemChrome); public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.PreferSystemChrome);
/// <inheritdoc/> /// <inheritdoc/>
public Thickness ExtendedMargins => _extendedMargins; public Thickness ExtendedMargins => _extendedMargins;

21
tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs

@ -30,6 +30,8 @@ namespace Avalonia.UnitTests
public IGeometryImpl SourceGeometry { get; } public IGeometryImpl SourceGeometry { get; }
public Rect Bounds => _context.CalculateBounds(); public Rect Bounds => _context.CalculateBounds();
public double ContourLength { get; }
public Matrix Transform { get; } public Matrix Transform { get; }
@ -69,6 +71,25 @@ namespace Avalonia.UnitTests
return new MockStreamGeometryImpl(transform, _context); 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 class MockStreamGeometryContext : IStreamGeometryContextImpl
{ {
private List<Point> points = new List<Point>(); private List<Point> points = new List<Point>();

10
tests/Avalonia.Visuals.UnitTests/VectorTests.cs

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

17
tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

@ -112,6 +112,8 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
} }
} }
public double ContourLength { get; }
public IStreamGeometryImpl Clone() public IStreamGeometryImpl Clone()
{ {
return this; return this;
@ -151,6 +153,21 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
throw new NotImplementedException(); 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 class MockStreamGeometryContext : IStreamGeometryContextImpl
{ {
private List<Point> points = new List<Point>(); private List<Point> points = new List<Point>();

Loading…
Cancel
Save