Browse Source

Merge branch 'master' into adirh3/mica-backdrop-brush

pull/6574/head
Lighto 5 years ago
committed by GitHub
parent
commit
4dec69da11
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      Documentation/build.md
  2. 16
      packages/Avalonia/AvaloniaBuildTasks.targets
  3. 1
      src/Avalonia.Base/Metadata/TemplateContent.cs
  4. 15
      src/Avalonia.Controls/Design.cs
  5. 10
      src/Avalonia.Controls/Flyouts/FlyoutBase.cs
  6. 98
      src/Avalonia.Controls/ItemsSourceView.cs
  7. 5
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  8. 19
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  9. 1
      src/Avalonia.Controls/Primitives/Popup.cs
  10. 9
      src/Avalonia.Controls/Templates/IControlTemplate.cs
  11. 20
      src/Avalonia.Controls/Templates/TemplateResult.cs
  12. 12
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  13. 4
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs
  14. 4
      src/Avalonia.Dialogs/ManagedFileChooser.cs
  15. 2
      src/Avalonia.Dialogs/ManagedFileChooserSources.cs
  16. 2
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  17. 8
      src/Avalonia.Input/Gestures.cs
  18. 2
      src/Avalonia.Input/MouseDevice.cs
  19. 1
      src/Avalonia.Themes.Default/Expander.xaml
  20. 2
      src/Avalonia.Themes.Fluent/Controls/NotificationCard.xaml
  21. 6
      src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml
  22. 4
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  23. 170
      src/Avalonia.Visuals/Media/CombinedGeometry.cs
  24. 2
      src/Avalonia.Visuals/Media/FormattedText.cs
  25. 37
      src/Avalonia.Visuals/Media/GeometryCollection.cs
  26. 80
      src/Avalonia.Visuals/Media/GeometryGroup.cs
  27. 10
      src/Avalonia.Visuals/Media/PathMarkupParser.cs
  28. 52
      src/Avalonia.Visuals/Media/RotateTransform.cs
  29. 17
      src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
  30. 37
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  31. 7
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  32. 7
      src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs
  33. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  34. 2
      src/Avalonia.Visuals/Vector.cs
  35. 2
      src/Avalonia.X11/X11CursorFactory.cs
  36. 8
      src/Avalonia.X11/X11Window.cs
  37. 9
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  38. 7
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  39. 14
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatformOptions.cs
  40. 46
      src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs
  41. 70
      src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevBackBuffer.cs
  42. 7
      src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
  43. 3
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  44. 7
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  45. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
  46. 12
      src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
  47. 12
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  48. 6
      src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs
  49. 35
      src/Skia/Avalonia.Skia/CombinedGeometryImpl.cs
  50. 36
      src/Skia/Avalonia.Skia/GeometryGroupImpl.cs
  51. 10
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  52. 2
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  53. 36
      src/Windows/Avalonia.Direct2D1/Media/CombinedGeometryImpl.cs
  54. 33
      src/Windows/Avalonia.Direct2D1/Media/GeometryGroupImpl.cs
  55. 25
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  56. 6
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  57. 10
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  58. 63
      tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs
  59. 62
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/GenericTemplateTests.cs
  60. 89
      tests/Avalonia.RenderTests/Media/CombinedGeometryTests.cs
  61. 95
      tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs
  62. 10
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  63. 26
      tests/Avalonia.Visuals.UnitTests/Media/GeometryGroupTests.cs
  64. 23
      tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs
  65. 10
      tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
  66. BIN
      tests/TestFiles/Direct2D1/Media/CombinedGeometry/Geometry1_Transform.expected.png
  67. BIN
      tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Exclude.expected.png
  68. BIN
      tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Intersect.expected.png
  69. BIN
      tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Union.expected.png
  70. BIN
      tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Xor.expected.png
  71. BIN
      tests/TestFiles/Direct2D1/Media/GeometryGroup/Child_Transform.expected.png
  72. BIN
      tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_Stroke_EvenOdd.expected.png
  73. BIN
      tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_Stroke_NonZero.expected.png
  74. BIN
      tests/TestFiles/Skia/Media/CombinedGeometry/Geometry1_Transform.expected.png
  75. BIN
      tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Exclude.expected.png
  76. BIN
      tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Intersect.expected.png
  77. BIN
      tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Union.expected.png
  78. BIN
      tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Xor.expected.png
  79. BIN
      tests/TestFiles/Skia/Media/GeometryGroup/Child_Transform.expected.png
  80. BIN
      tests/TestFiles/Skia/Media/GeometryGroup/FillRule_Stroke_EvenOdd.expected.png
  81. BIN
      tests/TestFiles/Skia/Media/GeometryGroup/FillRule_Stroke_NonZero.expected.png

1
Documentation/build.md

@ -6,6 +6,7 @@ Avalonia requires at least Visual Studio 2019 and .NET Core SDK 3.1 to build on
```
git clone https://github.com/AvaloniaUI/Avalonia.git
cd Avalonia
git submodule update --init
```

16
packages/Avalonia/AvaloniaBuildTasks.targets

@ -42,12 +42,24 @@
</Target>
<PropertyGroup>
<BuildAvaloniaResourcesDependsOn>$(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences</BuildAvaloniaResourcesDependsOn>
<BuildAvaloniaResourcesDependsOn>$(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences;_GenerateAvaloniaResourcesDependencyCache</BuildAvaloniaResourcesDependsOn>
</PropertyGroup>
<Target Name="_GenerateAvaloniaResourcesDependencyCache" BeforeTargets="GenerateAvaloniaResources">
<ItemGroup>
<CustomAdditionalGenerateAvaloniaResourcesInputs Include="$(IntermediateOutputPath)/Avalonia/Resources.Inputs.cache" />
</ItemGroup>
<Hash ItemsToHash="@(AvaloniaResource);@(AvaloniaXaml);$(MSBuildAllProjects)">
<Output TaskParameter="HashResult" PropertyName="AvaloniaResourcesDependencyHash" />
</Hash>
<WriteLinesToFile Overwrite="true" File="$(IntermediateOutputPath)/Avalonia/Resources.Inputs.cache" Lines="$(AvaloniaResourcesDependencyHash)" WriteOnlyWhenDifferent="True" />
</Target>
<Target Name="GenerateAvaloniaResources"
BeforeTargets="CoreCompile;CoreResGen"
Inputs="@(AvaloniaResource);@(AvaloniaXaml);$(MSBuildAllProjects)"
Inputs="@(AvaloniaResource);@(AvaloniaXaml);@(CustomAdditionalGenerateAvaloniaResourcesInputs);$(MSBuildAllProjects)"
Outputs="$(AvaloniaResourcesTemporaryFilePath)"
DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)">
<ItemGroup>

1
src/Avalonia.Base/Metadata/TemplateContent.cs

@ -8,5 +8,6 @@ namespace Avalonia.Metadata
[AttributeUsage(AttributeTargets.Property)]
public class TemplateContentAttribute : Attribute
{
public Type TemplateResultType { get; set; }
}
}

15
src/Avalonia.Controls/Design.cs

@ -60,6 +60,19 @@ namespace Avalonia.Controls
return target.GetValue(PreviewWithProperty);
}
public static readonly AttachedProperty<IStyle> DesignStyleProperty = AvaloniaProperty
.RegisterAttached<Control, IStyle>("DesignStyle", typeof(Design));
public static void SetDesignStyle(Control control, IStyle value)
{
control.SetValue(DesignStyleProperty, value);
}
public static IStyle GetDesignStyle(Control control)
{
return control.GetValue(DesignStyleProperty);
}
public static void ApplyDesignModeProperties(Control target, Control source)
{
if (source.IsSet(WidthProperty))
@ -68,6 +81,8 @@ namespace Avalonia.Controls
target.Height = source.GetValue(HeightProperty);
if (source.IsSet(DataContextProperty))
target.DataContext = source.GetValue(DataContextProperty);
if (source.IsSet(DesignStyleProperty))
target.Styles.Add(source.GetValue(DesignStyleProperty));
}
}
}

10
src/Avalonia.Controls/Flyouts/FlyoutBase.cs

@ -215,11 +215,6 @@ namespace Avalonia.Controls.Primitives
}
}
if (CancelOpening())
{
return false;
}
if (Popup.Parent != null && Popup.Parent != placementTarget)
{
((ISetLogicalParent)Popup).SetParent(null);
@ -236,6 +231,11 @@ namespace Avalonia.Controls.Primitives
Popup.Child = CreatePresenter();
}
if (CancelOpening())
{
return false;
}
PositionPopup(showAtPointer);
IsOpen = Popup.IsOpen = true;
OnOpened();

98
src/Avalonia.Controls/ItemsSourceView.cs

@ -32,8 +32,8 @@ namespace Avalonia.Controls
/// </summary>
public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty<object>());
private protected readonly IList _inner;
private INotifyCollectionChanged? _notifyCollectionChanged;
private IList? _inner;
private NotifyCollectionChangedEventHandler? _collectionChanged;
/// <summary>
/// Initializes a new instance of the ItemsSourceView class for the specified data source.
@ -42,27 +42,22 @@ namespace Avalonia.Controls
public ItemsSourceView(IEnumerable source)
{
source = source ?? throw new ArgumentNullException(nameof(source));
if (source is IList list)
{
_inner = list;
}
else if (source is IEnumerable<object> objectEnumerable)
_inner = source switch
{
_inner = new List<object>(objectEnumerable);
}
else
{
_inner = new List<object>(source.Cast<object>());
}
ListenToCollectionChanges();
ItemsSourceView _ => throw new ArgumentException("Cannot wrap an existing ItemsSourceView.", nameof(source)),
IList list => list,
INotifyCollectionChanged _ => throw new ArgumentException(
"Collection implements INotifyCollectionChanged by not IList.",
nameof(source)),
IEnumerable<object> iObj => new List<object>(iObj),
_ => new List<object>(source.Cast<object>())
};
}
/// <summary>
/// Gets the number of items in the collection.
/// </summary>
public int Count => _inner.Count;
public int Count => Inner.Count;
/// <summary>
/// Gets a value that indicates whether the items source can provide a unique key for each item.
@ -72,6 +67,19 @@ namespace Avalonia.Controls
/// </remarks>
public bool HasKeyIndexMapping => false;
/// <summary>
/// Gets the inner collection.
/// </summary>
public IList Inner
{
get
{
if (_inner is null)
ThrowDisposed();
return _inner!;
}
}
/// <summary>
/// Retrieves the item at the specified index.
/// </summary>
@ -82,15 +90,38 @@ namespace Avalonia.Controls
/// <summary>
/// Occurs when the collection has changed to indicate the reason for the change and which items changed.
/// </summary>
public event NotifyCollectionChangedEventHandler? CollectionChanged;
public event NotifyCollectionChangedEventHandler? CollectionChanged
{
add
{
if (_collectionChanged is null && Inner is INotifyCollectionChanged incc)
{
incc.CollectionChanged += OnCollectionChanged;
}
_collectionChanged += value;
}
remove
{
_collectionChanged -= value;
if (_collectionChanged is null && Inner is INotifyCollectionChanged incc)
{
incc.CollectionChanged -= OnCollectionChanged;
}
}
}
/// <inheritdoc/>
public void Dispose()
{
if (_notifyCollectionChanged != null)
if (_inner is INotifyCollectionChanged incc)
{
_notifyCollectionChanged.CollectionChanged -= OnCollectionChanged;
incc.CollectionChanged -= OnCollectionChanged;
}
_inner = null;
}
/// <summary>
@ -98,9 +129,9 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The item.</returns>
public object? GetAt(int index) => _inner[index];
public object? GetAt(int index) => Inner[index];
public int IndexOf(object? item) => _inner.IndexOf(item);
public int IndexOf(object? item) => Inner.IndexOf(item);
public static ItemsSourceView GetOrCreate(IEnumerable? items)
{
@ -146,7 +177,7 @@ namespace Avalonia.Controls
internal void AddListener(ICollectionChangedListener listener)
{
if (_inner is INotifyCollectionChanged incc)
if (Inner is INotifyCollectionChanged incc)
{
CollectionChangedEventManager.Instance.AddListener(incc, listener);
}
@ -154,7 +185,7 @@ namespace Avalonia.Controls
internal void RemoveListener(ICollectionChangedListener listener)
{
if (_inner is INotifyCollectionChanged incc)
if (Inner is INotifyCollectionChanged incc)
{
CollectionChangedEventManager.Instance.RemoveListener(incc, listener);
}
@ -162,22 +193,15 @@ namespace Avalonia.Controls
protected void OnItemsSourceChanged(NotifyCollectionChangedEventArgs args)
{
CollectionChanged?.Invoke(this, args);
}
private void ListenToCollectionChanges()
{
if (_inner is INotifyCollectionChanged incc)
{
incc.CollectionChanged += OnCollectionChanged;
_notifyCollectionChanged = incc;
}
_collectionChanged?.Invoke(this, args);
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnItemsSourceChanged(e);
}
private void ThrowDisposed() => throw new ObjectDisposedException(nameof(ItemsSourceView));
}
public class ItemsSourceView<T> : ItemsSourceView, IReadOnlyList<T>
@ -216,10 +240,10 @@ namespace Avalonia.Controls
/// <param name="index">The index.</param>
/// <returns>The item.</returns>
[return: MaybeNull]
public new T GetAt(int index) => (T)_inner[index];
public new T GetAt(int index) => (T)Inner[index];
public IEnumerator<T> GetEnumerator() => _inner.Cast<T>().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();
public IEnumerator<T> GetEnumerator() => Inner.Cast<T>().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator();
public static new ItemsSourceView<T> GetOrCreate(IEnumerable? items)
{

5
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -275,7 +275,7 @@ namespace Avalonia.Controls.Platform
return;
}
if (item.HasSubMenu)
if (item.HasSubMenu && item.IsEffectivelyEnabled)
{
Open(item, true);
}
@ -303,7 +303,8 @@ namespace Avalonia.Controls.Platform
{
item.Parent.SelectedItem.Close();
SelectItemAndAncestors(item);
Open(item, false);
if (item.HasSubMenu)
Open(item, false);
}
else
{

19
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -60,9 +60,6 @@ namespace Avalonia.Controls.Presenters
o => o.Viewport,
(o, v) => o.Viewport = v);
// Arbitrary chosen value, probably need to ask ILogicalScrollable
private const int LogicalScrollItemSize = 50;
private bool _canHorizontallyScroll;
private bool _canVerticallyScroll;
private bool _arranging;
@ -351,7 +348,8 @@ namespace Avalonia.Controls.Presenters
if (Extent.Height > Viewport.Height || Extent.Width > Viewport.Width)
{
var scrollable = Child as ILogicalScrollable;
bool isLogical = scrollable?.IsLogicalScrollEnabled == true;
var isLogical = scrollable?.IsLogicalScrollEnabled == true;
var logicalScrollItemSize = new Vector(1, 1);
double x = Offset.X;
double y = Offset.Y;
@ -361,13 +359,18 @@ namespace Avalonia.Controls.Presenters
_activeLogicalGestureScrolls?.TryGetValue(e.Id, out delta);
delta += e.Delta;
if (isLogical && scrollable is object)
{
logicalScrollItemSize = Bounds.Size / scrollable.Viewport;
}
if (Extent.Height > Viewport.Height)
{
double dy;
if (isLogical)
{
var logicalUnits = delta.Y / LogicalScrollItemSize;
delta = delta.WithY(delta.Y - logicalUnits * LogicalScrollItemSize);
var logicalUnits = delta.Y / logicalScrollItemSize.Y;
delta = delta.WithY(delta.Y - logicalUnits * logicalScrollItemSize.Y);
dy = logicalUnits * scrollable!.ScrollSize.Height;
}
else
@ -384,8 +387,8 @@ namespace Avalonia.Controls.Presenters
double dx;
if (isLogical)
{
var logicalUnits = delta.X / LogicalScrollItemSize;
delta = delta.WithX(delta.X - logicalUnits * LogicalScrollItemSize);
var logicalUnits = delta.X / logicalScrollItemSize.X;
delta = delta.WithX(delta.X - logicalUnits * logicalScrollItemSize.X);
dx = logicalUnits * scrollable!.ScrollSize.Width;
}
else

1
src/Avalonia.Controls/Primitives/Popup.cs

@ -53,6 +53,7 @@ namespace Avalonia.Controls.Primitives
AvaloniaProperty.Register<Popup, PopupPositionerConstraintAdjustment>(
nameof(PlacementConstraintAdjustment),
PopupPositionerConstraintAdjustment.FlipX | PopupPositionerConstraintAdjustment.FlipY |
PopupPositionerConstraintAdjustment.SlideX | PopupPositionerConstraintAdjustment.SlideY |
PopupPositionerConstraintAdjustment.ResizeX | PopupPositionerConstraintAdjustment.ResizeY);
/// <summary>

9
src/Avalonia.Controls/Templates/IControlTemplate.cs

@ -1,3 +1,4 @@
using System;
using Avalonia.Controls.Primitives;
using Avalonia.Styling;
@ -10,18 +11,16 @@ namespace Avalonia.Controls.Templates
{
}
public class ControlTemplateResult
public class ControlTemplateResult : TemplateResult<IControl>
{
public IControl Control { get; }
public INameScope NameScope { get; }
public ControlTemplateResult(IControl control, INameScope nameScope)
public ControlTemplateResult(IControl control, INameScope nameScope) : base(control, nameScope)
{
Control = control;
NameScope = nameScope;
}
public void Deconstruct(out IControl control, out INameScope scope)
public new void Deconstruct(out IControl control, out INameScope scope)
{
control = Control;
scope = NameScope;

20
src/Avalonia.Controls/Templates/TemplateResult.cs

@ -0,0 +1,20 @@
namespace Avalonia.Controls.Templates
{
public class TemplateResult<T>
{
public T Result { get; }
public INameScope NameScope { get; }
public TemplateResult(T result, INameScope nameScope)
{
Result = result;
NameScope = nameScope;
}
public void Deconstruct(out T result, out INameScope scope)
{
result = Result;
scope = NameScope;
}
}
}

12
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@ -160,13 +160,19 @@ namespace Avalonia.Diagnostics.Views
return;
}
var root = Root;
if (root is null)
{
return;
}
switch (e.Modifiers)
{
case RawInputModifiers.Control | RawInputModifiers.Shift:
{
IControl? control = null;
foreach (var popupRoot in GetPopupRoots(Root))
foreach (var popupRoot in GetPopupRoots(root))
{
control = GetHoveredControl(popupRoot);
@ -176,7 +182,7 @@ namespace Avalonia.Diagnostics.Views
}
}
control ??= GetHoveredControl(Root);
control ??= GetHoveredControl(root);
if (control != null)
{
@ -190,7 +196,7 @@ namespace Avalonia.Diagnostics.Views
{
vm.FreezePopups = !vm.FreezePopups;
foreach (var popupRoot in GetPopupRoots(Root))
foreach (var popupRoot in GetPopupRoots(root))
{
if (popupRoot.Parent is Popup popup)
{

4
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs

@ -30,13 +30,13 @@ namespace Avalonia.Dialogs
}
else
{
using (Process process = Process.Start(new ProcessStartInfo
using Process process = Process.Start(new ProcessStartInfo
{
FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? url : "open",
Arguments = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"{url}" : "",
CreateNoWindow = true,
UseShellExecute = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
}));
});
}
}

4
src/Avalonia.Dialogs/ManagedFileChooser.cs

@ -1,13 +1,11 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
namespace Avalonia.Dialogs
{
@ -35,7 +33,9 @@ namespace Avalonia.Dialogs
if (_quickLinksRoot != null)
{
var isQuickLink = _quickLinksRoot.IsLogicalAncestorOf(e.Source as Control);
#pragma warning disable CS0618 // Type or member is obsolete
if (e.ClickCount == 2 || isQuickLink)
#pragma warning restore CS0618 // Type or member is obsolete
{
if (model.ItemType == ManagedFileChooserItemType.File)
{

2
src/Avalonia.Dialogs/ManagedFileChooserSources.cs

@ -67,7 +67,7 @@ namespace Avalonia.Dialogs
{
Directory.GetFiles(x.VolumePath);
}
catch (Exception _)
catch (Exception)
{
return null;
}

2
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -47,6 +47,8 @@ namespace Avalonia.Headless
}
public IStreamGeometryImpl CreateStreamGeometry() => new HeadlessStreamingGeometryStub();
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<Geometry> children) => throw new NotImplementedException();
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => throw new NotImplementedException();
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) => new HeadlessRenderTarget();

8
src/Avalonia.Input/Gestures.cs

@ -81,17 +81,21 @@ namespace Avalonia.Input
var e = (PointerPressedEventArgs)ev;
var visual = (IVisual)ev.Source;
if (e.ClickCount <= 1)
#pragma warning disable CS0618 // Type or member is obsolete
var clickCount = e.ClickCount;
#pragma warning restore CS0618 // Type or member is obsolete
if (clickCount <= 1)
{
s_lastPress = new WeakReference<IInteractive>(ev.Source);
}
else if (s_lastPress != null && e.ClickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
else if (s_lastPress != null && clickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{
if (s_lastPress.TryGetTarget(out var target) && target == e.Source)
{
e.Source.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e));
}
}
}
}

2
src/Avalonia.Input/MouseDevice.cs

@ -75,7 +75,9 @@ namespace Avalonia.Input
throw new InvalidOperationException("Control is not attached to visual tree.");
}
#pragma warning disable CS0618 // Type or member is obsolete
var rootPoint = relativeTo.VisualRoot.PointToClient(Position);
#pragma warning restore CS0618 // Type or member is obsolete
var transform = relativeTo.VisualRoot.TransformToVisual(relativeTo);
return rootPoint * transform!.Value;
}

1
src/Avalonia.Themes.Default/Expander.xaml

@ -101,6 +101,7 @@
Grid.Column="1"
Background="Transparent"
Content="{TemplateBinding Content}"
ContentTemplate="{Binding $parent[Expander].HeaderTemplate}"
VerticalAlignment="Center"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"

2
src/Avalonia.Themes.Fluent/Controls/NotificationCard.xaml

@ -17,7 +17,7 @@
<Setter Property="Template">
<ControlTemplate>
<LayoutTransformControl Name="PART_LayoutTransformControl" UseRenderTransform="True">
<Border CornerRadius="{DynamicResource ControlCornerRadius}" BoxShadow="0 6 8 0 #4F000000" Margin="5 5 5 10">
<Border CornerRadius="{TemplateBinding CornerRadius}" BoxShadow="0 6 8 0 #4F000000" Margin="5 5 5 10">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"

6
src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml

@ -23,11 +23,11 @@
<Border x:Name="ProgressBarRoot" ClipToBounds="True" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}">
<Panel>
<Panel x:Name="DeterminateRoot">
<Border CornerRadius="{DynamicResource ControlCornerRadius}" x:Name="PART_Indicator" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
<Border CornerRadius="{TemplateBinding CornerRadius}" x:Name="PART_Indicator" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
</Panel>
<Panel x:Name="IndeterminateRoot">
<Border x:Name="IndeterminateProgressBarIndicator" CornerRadius="{DynamicResource ControlCornerRadius}" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
<Border x:Name="IndeterminateProgressBarIndicator2" CornerRadius="{DynamicResource ControlCornerRadius}" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
<Border x:Name="IndeterminateProgressBarIndicator" CornerRadius="{TemplateBinding CornerRadius}" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
<Border x:Name="IndeterminateProgressBarIndicator2" CornerRadius="{TemplateBinding CornerRadius}" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
</Panel>
</Panel>
</Border>

4
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -67,6 +67,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avaloni
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.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGeometryImpl Avalonia.Platform.IPlatformRenderInterface.CreateCombinedGeometry(Avalonia.Media.GeometryCombineMode, Avalonia.Media.Geometry, Avalonia.Media.Geometry)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGeometryImpl Avalonia.Platform.IPlatformRenderInterface.CreateGeometryGroup(Avalonia.Media.FillRule, System.Collections.Generic.IReadOnlyList<Avalonia.Media.Geometry>)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' does not exist in the implementation but it does exist in the contract.
@ -74,4 +76,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWr
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
Total Issues: 75
Total Issues: 77

170
src/Avalonia.Visuals/Media/CombinedGeometry.cs

@ -0,0 +1,170 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Media
{
public enum GeometryCombineMode
{
/// <summary>
/// The two regions are combined by taking the union of both. The resulting geometry is
/// geometry A + geometry B.
/// </summary>
Union,
/// <summary>
/// The two regions are combined by taking their intersection. The new area consists of the
/// overlapping region between the two geometries.
/// </summary>
Intersect,
/// <summary>
/// The two regions are combined by taking the area that exists in the first region but not
/// the second and the area that exists in the second region but not the first. The new
/// region consists of (A-B) + (B-A), where A and B are geometries.
/// </summary>
Xor,
/// <summary>
/// The second region is excluded from the first. Given two geometries, A and B, the area of
/// geometry B is removed from the area of geometry A, producing a region that is A-B.
/// </summary>
Exclude,
}
/// <summary>
/// Represents a 2-D geometric shape defined by the combination of two Geometry objects.
/// </summary>
public class CombinedGeometry : Geometry
{
/// <summary>
/// Defines the <see cref="Geometry1"/> property.
/// </summary>
public static readonly StyledProperty<Geometry?> Geometry1Property =
AvaloniaProperty.Register<CombinedGeometry, Geometry?>(nameof(Geometry1));
/// <summary>
/// Defines the <see cref="Geometry2"/> property.
/// </summary>
public static readonly StyledProperty<Geometry?> Geometry2Property =
AvaloniaProperty.Register<CombinedGeometry, Geometry?>(nameof(Geometry2));
/// <summary>
/// Defines the <see cref="GeometryCombineMode"/> property.
/// </summary>
public static readonly StyledProperty<GeometryCombineMode> GeometryCombineModeProperty =
AvaloniaProperty.Register<CombinedGeometry, GeometryCombineMode>(nameof(GeometryCombineMode));
/// <summary>
/// Initializes a new instance of the <see cref="CombinedGeometry"/> class.
/// </summary>
public CombinedGeometry()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CombinedGeometry"/> class with the
/// specified <see cref="Geometry"/> objects.
/// </summary>
/// <param name="geometry1">The first geometry to combine.</param>
/// <param name="geometry2">The second geometry to combine.</param>
public CombinedGeometry(Geometry geometry1, Geometry geometry2)
{
Geometry1 = geometry1;
Geometry2 = geometry2;
}
/// <summary>
/// Initializes a new instance of the <see cref="CombinedGeometry"/> class with the
/// specified <see cref="Geometry"/> objects and <see cref="GeometryCombineMode"/>.
/// </summary>
/// <param name="combineMode">The method by which geometry1 and geometry2 are combined.</param>
/// <param name="geometry1">The first geometry to combine.</param>
/// <param name="geometry2">The second geometry to combine.</param>
public CombinedGeometry(GeometryCombineMode combineMode, Geometry? geometry1, Geometry? geometry2)
{
Geometry1 = geometry1;
Geometry2 = geometry2;
GeometryCombineMode = combineMode;
}
/// <summary>
/// Initializes a new instance of the <see cref="CombinedGeometry"/> class with the
/// specified <see cref="Geometry"/> objects, <see cref="GeometryCombineMode"/> and
/// <see cref="Transform"/>.
/// </summary>
/// <param name="combineMode">The method by which geometry1 and geometry2 are combined.</param>
/// <param name="geometry1">The first geometry to combine.</param>
/// <param name="geometry2">The second geometry to combine.</param>
/// <param name="transform">The transform applied to the geometry.</param>
public CombinedGeometry(
GeometryCombineMode combineMode,
Geometry? geometry1,
Geometry? geometry2,
Transform? transform)
{
Geometry1 = geometry1;
Geometry2 = geometry2;
GeometryCombineMode = combineMode;
Transform = transform;
}
/// <summary>
/// Gets or sets the first <see cref="Geometry"/> object of this
/// <see cref="CombinedGeometry"/> object.
/// </summary>
public Geometry? Geometry1
{
get => GetValue(Geometry1Property);
set => SetValue(Geometry1Property, value);
}
/// <summary>
/// Gets or sets the second <see cref="Geometry"/> object of this
/// <see cref="CombinedGeometry"/> object.
/// </summary>
public Geometry? Geometry2
{
get => GetValue(Geometry2Property);
set => SetValue(Geometry2Property, value);
}
/// <summary>
/// Gets or sets the method by which the two geometries (specified by the
/// <see cref="Geometry1"/> and <see cref="Geometry2"/> properties) are combined. The
/// default value is <see cref="GeometryCombineMode.Union"/>.
/// </summary>
public GeometryCombineMode GeometryCombineMode
{
get => GetValue(GeometryCombineModeProperty);
set => SetValue(GeometryCombineModeProperty, value);
}
public override Geometry Clone()
{
return new CombinedGeometry(GeometryCombineMode, Geometry1, Geometry2, Transform);
}
protected override IGeometryImpl? CreateDefiningGeometry()
{
var g1 = Geometry1;
var g2 = Geometry2;
if (g1 is object && g2 is object)
{
var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return factory.CreateCombinedGeometry(GeometryCombineMode, g1, g2);
}
else if (GeometryCombineMode == GeometryCombineMode.Intersect)
return null;
else if (g1 is object)
return g1.PlatformImpl;
else if (g2 is object)
return g2.PlatformImpl;
else
return null;
}
}
}

2
src/Avalonia.Visuals/Media/FormattedText.cs

@ -200,7 +200,7 @@ namespace Avalonia.Media
private void Set<T>(ref T field, T value)
{
if (field != null && field.Equals(value))
if (EqualityComparer<T>.Default.Equals(field, value))
{
return;
}

37
src/Avalonia.Visuals/Media/GeometryCollection.cs

@ -0,0 +1,37 @@
using System.Collections;
using System.Collections.Generic;
using Avalonia.Animation;
#nullable enable
namespace Avalonia.Media
{
public class GeometryCollection : Animatable, IList<Geometry>, IReadOnlyList<Geometry>
{
private List<Geometry> _inner;
public GeometryCollection() => _inner = new List<Geometry>();
public GeometryCollection(IEnumerable<Geometry> collection) => _inner = new List<Geometry>(collection);
public GeometryCollection(int capacity) => _inner = new List<Geometry>(capacity);
public Geometry this[int index]
{
get => _inner[index];
set => _inner[index] = value;
}
public int Count => _inner.Count;
public bool IsReadOnly => false;
public void Add(Geometry item) => _inner.Add(item);
public void Clear() => _inner.Clear();
public bool Contains(Geometry item) => _inner.Contains(item);
public void CopyTo(Geometry[] array, int arrayIndex) => _inner.CopyTo(array, arrayIndex);
public IEnumerator<Geometry> GetEnumerator() => _inner.GetEnumerator();
public int IndexOf(Geometry item) => _inner.IndexOf(item);
public void Insert(int index, Geometry item) => _inner.Insert(index, item);
public bool Remove(Geometry item) => _inner.Remove(item);
public void RemoveAt(int index) => _inner.RemoveAt(index);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

80
src/Avalonia.Visuals/Media/GeometryGroup.cs

@ -0,0 +1,80 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Metadata;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Media
{
/// <summary>
/// Represents a composite geometry, composed of other <see cref="Geometry"/> objects.
/// </summary>
public class GeometryGroup : Geometry
{
public static readonly DirectProperty<GeometryGroup, GeometryCollection?> ChildrenProperty =
AvaloniaProperty.RegisterDirect<GeometryGroup, GeometryCollection?> (
nameof(Children),
o => o.Children,
(o, v) => o.Children = v);
public static readonly StyledProperty<FillRule> FillRuleProperty =
AvaloniaProperty.Register<GeometryGroup, FillRule>(nameof(FillRule));
private GeometryCollection? _children;
private bool _childrenSet;
/// <summary>
/// Gets or sets the collection that contains the child geometries.
/// </summary>
[Content]
public GeometryCollection? Children
{
get => _children ??= (!_childrenSet ? new GeometryCollection() : null);
set
{
SetAndRaise(ChildrenProperty, ref _children, value);
_childrenSet = true;
}
}
/// <summary>
/// Gets or sets how the intersecting areas of the objects contained in this
/// <see cref="GeometryGroup"/> are combined. The default is <see cref="FillRule.EvenOdd"/>.
/// </summary>
public FillRule FillRule
{
get => GetValue(FillRuleProperty);
set => SetValue(FillRuleProperty, value);
}
public override Geometry Clone()
{
var result = new GeometryGroup { FillRule = FillRule, Transform = Transform };
if (_children?.Count > 0)
result.Children = new GeometryCollection(_children);
return result;
}
protected override IGeometryImpl? CreateDefiningGeometry()
{
if (_children?.Count > 0)
{
var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return factory.CreateGeometryGroup(FillRule, _children);
}
return null;
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);
if (change.Property == ChildrenProperty || change.Property == FillRuleProperty)
{
InvalidateGeometry();
}
}
}
}

10
src/Avalonia.Visuals/Media/PathMarkupParser.cs

@ -496,12 +496,18 @@ namespace Avalonia.Media
private bool ReadBool(ref ReadOnlySpan<char> span)
{
if (!ReadArgument(ref span, out var boolValue) || boolValue.Length != 1)
span = SkipWhitespace(span);
if (span.IsEmpty)
{
throw new InvalidDataException("Invalid bool rule.");
}
switch (boolValue[0])
var c = span[0];
span = span.Slice(1);
switch (c)
{
case '0':
return false;

52
src/Avalonia.Visuals/Media/RotateTransform.cs

@ -14,6 +14,18 @@ namespace Avalonia.Media
public static readonly StyledProperty<double> AngleProperty =
AvaloniaProperty.Register<RotateTransform, double>(nameof(Angle));
/// <summary>
/// Defines the <see cref="CenterX"/> property.
/// </summary>
public static readonly StyledProperty<double> CenterXProperty =
AvaloniaProperty.Register<RotateTransform, double>(nameof(CenterX));
/// <summary>
/// Defines the <see cref="CenterY"/> property.
/// </summary>
public static readonly StyledProperty<double> CenterYProperty =
AvaloniaProperty.Register<RotateTransform, double>(nameof(CenterY));
/// <summary>
/// Initializes a new instance of the <see cref="RotateTransform"/> class.
/// </summary>
@ -32,18 +44,52 @@ namespace Avalonia.Media
Angle = angle;
}
/// <summary>
/// Initializes a new instance of the <see cref="RotateTransform"/> class.
/// </summary>
/// <param name="angle">The angle, in degrees.</param>
/// <param name="centerX">The x-coordinate of the center point for the rotation.</param>
/// <param name="centerY">The y-coordinate of the center point for the rotation.</param>
public RotateTransform(double angle, double centerX, double centerY)
: this()
{
Angle = angle;
CenterX = centerX;
CenterY = centerY;
}
/// <summary>
/// Gets or sets the angle of rotation, in degrees.
/// </summary>
public double Angle
{
get { return GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
get => GetValue(AngleProperty);
set => SetValue(AngleProperty, value);
}
/// <summary>
/// Gets or sets the x-coordinate of the rotation center point. The default is 0.
/// </summary>
public double CenterX
{
get => GetValue(CenterXProperty);
set => SetValue(CenterXProperty, value);
}
/// <summary>
/// Gets or sets the y-coordinate of the rotation center point. The default is 0.
/// </summary>
public double CenterY
{
get => GetValue(CenterYProperty);
set => SetValue(CenterYProperty, value);
}
/// <summary>
/// Gets the transform's <see cref="Matrix"/>.
/// </summary>
public override Matrix Value => Matrix.CreateRotation(Matrix.ToRadians(Angle));
public override Matrix Value => Matrix.CreateTranslation(-CenterX, -CenterY) *
Matrix.CreateRotation(Matrix.ToRadians(Angle)) *
Matrix.CreateTranslation(CenterX, CenterY);
}
}

17
src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs

@ -59,6 +59,23 @@ namespace Avalonia.Platform
/// <returns>An <see cref="IStreamGeometryImpl"/>.</returns>
IStreamGeometryImpl CreateStreamGeometry();
/// <summary>
/// Creates a geometry group implementation.
/// </summary>
/// <param name="fillRule">The fill rule.</param>
/// <param name="children">The geometries to group.</param>
/// <returns>A combined geometry.</returns>
IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<Geometry> children);
/// <summary>
/// Creates a geometry group implementation.
/// </summary>
/// <param name="combineMode">The combine mode</param>
/// <param name="g1">The first geometry.</param>
/// <param name="g2">The second geometry.</param>
/// <returns>A combined geometry.</returns>
IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2);
/// <summary>
/// Creates a renderer.
/// </summary>

37
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -35,6 +35,8 @@ namespace Avalonia.Rendering
private IRef<IDrawOperation> _currentDraw;
private readonly IDeferredRendererLock _lock;
private readonly object _sceneLock = new object();
private readonly object _startStopLock = new object();
private readonly object _renderLoopIsRenderingLock = new object();
private readonly Action _updateSceneIfNeededDelegate;
/// <summary>
@ -139,6 +141,8 @@ namespace Avalonia.Rendering
}
Stop();
// Wait for any in-progress rendering to complete
lock(_renderLoopIsRenderingLock){}
DisposeRenderTarget();
}
@ -233,20 +237,26 @@ namespace Avalonia.Rendering
/// <inheritdoc/>
public void Start()
{
if (!_running && _renderLoop != null)
lock (_startStopLock)
{
_renderLoop.Add(this);
_running = true;
if (!_running && _renderLoop != null)
{
_renderLoop.Add(this);
_running = true;
}
}
}
/// <inheritdoc/>
public void Stop()
{
if (_running && _renderLoop != null)
lock (_startStopLock)
{
_renderLoop.Remove(this);
_running = false;
if (_running && _renderLoop != null)
{
_renderLoop.Remove(this);
_running = false;
}
}
}
@ -255,18 +265,27 @@ namespace Avalonia.Rendering
void IRenderLoopTask.Update(TimeSpan time) => UpdateScene();
void IRenderLoopTask.Render() => Render(false);
void IRenderLoopTask.Render()
{
lock (_renderLoopIsRenderingLock)
{
lock(_startStopLock)
if(!_running)
return;
Render(false);
}
}
/// <inheritdoc/>
Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush)
{
return (_currentDraw.Item as BrushDrawOperation)?.ChildScenes?[brush.Visual]?.Size ?? Size.Empty;
return (_currentDraw?.Item as BrushDrawOperation)?.ChildScenes?[brush.Visual]?.Size ?? Size.Empty;
}
/// <inheritdoc/>
void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush)
{
var childScene = (_currentDraw.Item as BrushDrawOperation)?.ChildScenes?[brush.Visual];
var childScene = (_currentDraw?.Item as BrushDrawOperation)?.ChildScenes?[brush.Visual];
if (childScene != null)
{

7
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -289,11 +289,14 @@ namespace Avalonia.Rendering
using (context.PushPostTransform(m))
using (context.PushOpacity(opacity))
using (clipToBounds
? visual is IVisualWithRoundRectClip roundClipVisual
using (clipToBounds
#pragma warning disable CS0618 // Type or member is obsolete
? visual is IVisualWithRoundRectClip roundClipVisual
? context.PushClip(new RoundedRect(bounds, roundClipVisual.ClipToBoundsRadius))
: context.PushClip(bounds)
: default(DrawingContext.PushedState))
#pragma warning restore CS0618 // Type or member is obsolete
using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default(DrawingContext.PushedState))
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default(DrawingContext.PushedState))
using (context.PushTransformContainer())

7
src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs

@ -17,7 +17,12 @@ namespace Avalonia.Rendering.SceneGraph
public override bool HitTest(Point p)
{
return Custom.HitTest(p * Transform);
if (Transform.HasInverse)
{
return Custom.HitTest(p * Transform.Invert());
}
return false;
}
public override void Render(IDrawingContextImpl context)

4
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@ -164,10 +164,12 @@ namespace Avalonia.Rendering.SceneGraph
var visual = node.Visual;
var opacity = visual.Opacity;
var clipToBounds = visual.ClipToBounds;
#pragma warning disable CS0618 // Type or member is obsolete
var clipToBoundsRadius = visual is IVisualWithRoundRectClip roundRectClip ?
roundRectClip.ClipToBoundsRadius :
default;
#pragma warning restore CS0618 // Type or member is obsolete
var bounds = new Rect(visual.Bounds.Size);
var contextImpl = (DeferredDrawingContextImpl)context.PlatformImpl;

2
src/Avalonia.Visuals/Vector.cs

@ -175,7 +175,7 @@ namespace Avalonia
MathUtilities.AreClose(_y, other._y);
}
public override bool Equals(object obj) => obj is Vector other && Equals(other);
public override bool Equals(object? obj) => obj is Vector other && Equals(other);
public override int GetHashCode()
{

2
src/Avalonia.X11/X11CursorFactory.cs

@ -25,7 +25,7 @@ namespace Avalonia.X11
{
{StandardCursorType.Arrow, CursorFontShape.XC_top_left_arrow},
{StandardCursorType.Cross, CursorFontShape.XC_cross},
{StandardCursorType.Hand, CursorFontShape.XC_hand1},
{StandardCursorType.Hand, CursorFontShape.XC_hand2},
{StandardCursorType.Help, CursorFontShape.XC_question_arrow},
{StandardCursorType.Ibeam, CursorFontShape.XC_xterm},
{StandardCursorType.No, CursorFontShape.XC_X_cursor},

8
src/Avalonia.X11/X11Window.cs

@ -192,11 +192,6 @@ namespace Avalonia.X11
if (platform.Options.UseDBusMenu)
NativeMenuExporter = DBusMenuExporter.TryCreate(_handle);
NativeControlHost = new X11NativeControlHost(_platform, this);
DispatcherTimer.Run(() =>
{
Paint?.Invoke(default);
return _handle != IntPtr.Zero;
}, TimeSpan.FromMilliseconds(100));
InitializeIme();
}
@ -805,13 +800,14 @@ namespace Avalonia.X11
if (_handle != IntPtr.Zero)
{
XDestroyWindow(_x11.Display, _handle);
_platform.Windows.Remove(_handle);
_platform.XI2?.OnWindowDestroyed(_handle);
var handle = _handle;
_handle = IntPtr.Zero;
Closed?.Invoke();
_mouse.Dispose();
_touch.Dispose();
XDestroyWindow(_x11.Display, handle);
}
if (_useRenderWindow && _renderHandle != IntPtr.Zero)

9
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -30,10 +30,9 @@ namespace Avalonia.LinuxFramebuffer
public IRenderer CreateRenderer(IRenderRoot root)
{
return new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>())
{
};
var factory = AvaloniaLocator.Current.GetService<IRendererFactory>();
var renderLoop = AvaloniaLocator.Current.GetService<IRenderLoop>();
return factory?.Create(root, renderLoop) ?? new DeferredRenderer(root, renderLoop);
}
public void Dispose()
@ -41,7 +40,7 @@ namespace Avalonia.LinuxFramebuffer
throw new NotSupportedException();
}
public void Invalidate(Rect rect)
{
}

7
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -37,16 +37,17 @@ namespace Avalonia.LinuxFramebuffer
Threading = new InternalPlatformThreadingInterface();
if (_fb is IGlOutputBackend gl)
AvaloniaLocator.CurrentMutable.Bind<IPlatformOpenGlInterface>().ToConstant(gl.PlatformOpenGlInterface);
var opts = AvaloniaLocator.Current.GetService<LinuxFramebufferPlatformOptions>();
AvaloniaLocator.CurrentMutable
.Bind<IPlatformThreadingInterface>().ToConstant(Threading)
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(opts?.Fps ?? 60))
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<ICursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(new KeyboardDevice())
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
}

14
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatformOptions.cs

@ -0,0 +1,14 @@
namespace Avalonia.LinuxFramebuffer
{
/// <summary>
/// Platform-specific options which apply to the Linux framebuffer.
/// </summary>
public class LinuxFramebufferPlatformOptions
{
/// <summary>
/// Gets or sets the number of frames per second at which the renderer should run.
/// Default 60.
/// </summary>
public int Fps { get; set; } = 60;
}
}

46
src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs

@ -1,46 +0,0 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Platform;
namespace Avalonia.LinuxFramebuffer
{
unsafe class LockedFramebuffer : ILockedFramebuffer
{
private readonly int _fb;
private readonly fb_fix_screeninfo _fixedInfo;
private fb_var_screeninfo _varInfo;
private readonly IntPtr _address;
public LockedFramebuffer(int fb, fb_fix_screeninfo fixedInfo, fb_var_screeninfo varInfo, IntPtr address, Vector dpi)
{
_fb = fb;
_fixedInfo = fixedInfo;
_varInfo = varInfo;
_address = address;
Dpi = dpi;
//Use double buffering to avoid flicker
Address = Marshal.AllocHGlobal(RowBytes * Size.Height);
}
void VSync()
{
NativeUnsafeMethods.ioctl(_fb, FbIoCtl.FBIO_WAITFORVSYNC, null);
}
public void Dispose()
{
VSync();
NativeUnsafeMethods.memcpy(_address, Address, new IntPtr(RowBytes * Size.Height));
Marshal.FreeHGlobal(Address);
Address = IntPtr.Zero;
}
public IntPtr Address { get; private set; }
public PixelSize Size => new PixelSize((int)_varInfo.xres, (int) _varInfo.yres);
public int RowBytes => (int) _fixedInfo.line_length;
public Vector Dpi { get; }
public PixelFormat Format => _varInfo.bits_per_pixel == 16 ? PixelFormat.Rgb565 : _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888;
}
}

70
src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevBackBuffer.cs

@ -0,0 +1,70 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia.Platform;
namespace Avalonia.LinuxFramebuffer.Output
{
internal unsafe class FbDevBackBuffer : IDisposable
{
private readonly int _fb;
private readonly fb_fix_screeninfo _fixedInfo;
private readonly fb_var_screeninfo _varInfo;
private readonly IntPtr _targetAddress;
private readonly object _lock = new object();
public FbDevBackBuffer(int fb, fb_fix_screeninfo fixedInfo, fb_var_screeninfo varInfo, IntPtr targetAddress)
{
_fb = fb;
_fixedInfo = fixedInfo;
_varInfo = varInfo;
_targetAddress = targetAddress;
Address = Marshal.AllocHGlobal(RowBytes * Size.Height);
}
public void Dispose()
{
if (Address != IntPtr.Zero)
{
Marshal.FreeHGlobal(Address);
Address = IntPtr.Zero;
}
}
public ILockedFramebuffer Lock(Vector dpi)
{
Monitor.Enter(_lock);
try
{
return new LockedFramebuffer(Address,
new PixelSize((int)_varInfo.xres, (int)_varInfo.yres),
(int)_fixedInfo.line_length, dpi,
_varInfo.bits_per_pixel == 16 ? PixelFormat.Rgb565
: _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888
: PixelFormat.Bgra8888,
() =>
{
try
{
NativeUnsafeMethods.ioctl(_fb, FbIoCtl.FBIO_WAITFORVSYNC, null);
NativeUnsafeMethods.memcpy(_targetAddress, Address, new IntPtr(RowBytes * Size.Height));
}
finally
{
Monitor.Exit(_lock);
}
});
}
catch
{
Monitor.Exit(_lock);
throw;
}
}
public IntPtr Address { get; private set; }
public PixelSize Size => new PixelSize((int)_varInfo.xres, (int) _varInfo.yres);
public int RowBytes => (int) _fixedInfo.line_length;
}
}

7
src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs

@ -14,6 +14,7 @@ namespace Avalonia.LinuxFramebuffer
private fb_var_screeninfo _varInfo;
private IntPtr _mappedLength;
private IntPtr _mappedAddress;
private FbDevBackBuffer _backBuffer;
public double Scaling { get; set; }
/// <summary>
@ -146,7 +147,9 @@ namespace Avalonia.LinuxFramebuffer
{
if (_fd <= 0)
throw new ObjectDisposedException("LinuxFramebuffer");
return new LockedFramebuffer(_fd, _fixedInfo, _varInfo, _mappedAddress, new Vector(96, 96) * Scaling);
return (_backBuffer ??=
new FbDevBackBuffer(_fd, _fixedInfo, _varInfo, _mappedAddress))
.Lock(new Vector(96, 96) * Scaling);
}
@ -165,6 +168,8 @@ namespace Avalonia.LinuxFramebuffer
public void Dispose()
{
_backBuffer?.Dispose();
_backBuffer = null;
ReleaseUnmanagedResources();
GC.SuppressFinalize(this);
}

3
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -14,7 +14,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
class AvaloniaXamlIlCompiler : XamlILCompiler
{
private readonly TransformerConfiguration _configuration;
private readonly IXamlType _contextType;
private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer;
private readonly AvaloniaBindingExtensionTransformer _bindingTransformer;
@ -22,8 +21,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
private AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings)
: base(configuration, emitMappings, true)
{
_configuration = configuration;
void InsertAfter<T>(params IXamlAstTransformer[] t)
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T) + 1, t);

7
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs

@ -49,8 +49,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
XmlNamespaceInfoProvider =
typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlXmlNamespaceInfoProvider"),
DeferredContentPropertyAttributes = {typeSystem.GetType("Avalonia.Metadata.TemplateContentAttribute")},
DeferredContentExecutorCustomizationDefaultTypeParameter = typeSystem.GetType("Avalonia.Controls.IControl"),
DeferredContentExecutorCustomizationTypeParameterDeferredContentAttributePropertyNames = new List<string>
{
"TemplateResultType"
},
DeferredContentExecutorCustomization =
runtimeHelpers.FindMethod(m => m.Name == "DeferredTransformationFactoryV1"),
runtimeHelpers.FindMethod(m => m.Name == "DeferredTransformationFactoryV2"),
UsableDuringInitializationAttributes =
{
typeSystem.GetType("Avalonia.Metadata.UsableDuringInitializationAttribute"),

2
src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github

@ -1 +1 @@
Subproject commit f4ac681b91a9dc7a7a095d1050a683de23d86b72
Subproject commit 8e20d65eb5f1efbae08e49b18f39bfdce32df7b3

12
src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs

@ -7,6 +7,7 @@ namespace Avalonia.Markup.Xaml.Templates
public static class TemplateContent
{
public static ControlTemplateResult Load(object templateContent)
{
if (templateContent is Func<IServiceProvider, object> direct)
{
@ -20,5 +21,16 @@ namespace Avalonia.Markup.Xaml.Templates
throw new ArgumentException(nameof(templateContent));
}
public static TemplateResult<T> Load<T>(object templateContent)
{
if (templateContent is Func<IServiceProvider, object> direct)
return (TemplateResult<T>)direct(null);
if (templateContent is null)
return null;
throw new ArgumentException(nameof(templateContent));
}
}
}

12
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@ -15,6 +15,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
public static Func<IServiceProvider, object> DeferredTransformationFactoryV1(Func<IServiceProvider, object> builder,
IServiceProvider provider)
{
return DeferredTransformationFactoryV2<IControl>(builder, provider);
}
public static Func<IServiceProvider, object> DeferredTransformationFactoryV2<T>(Func<IServiceProvider, object> builder,
IServiceProvider provider)
{
var resourceNodes = provider.GetService<IAvaloniaXamlIlParentStackProvider>().Parents
.OfType<IResourceNode>().ToList();
@ -25,7 +31,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
var scope = parentScope != null ? new ChildNameScope(parentScope) : (INameScope)new NameScope();
var obj = builder(new DeferredParentServiceProvider(sp, resourceNodes, rootObject, scope));
scope.Complete();
return new ControlTemplateResult((IControl)obj, scope);
if(typeof(T) == typeof(IControl))
return new ControlTemplateResult((IControl)obj, scope);
return new TemplateResult<T>((T)obj, scope);
};
}

6
src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs

@ -184,6 +184,9 @@ namespace Avalonia.Markup.Parsers
}
// Don't need to override GetHashCode as the ISyntax objects will not be stored in a hash; the
// only reason they have overridden Equals methods is for unit testing.
#pragma warning disable CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode()
public class PropertySyntax : ISyntax
{
public string Name { get; set; } = string.Empty;
@ -205,7 +208,7 @@ namespace Avalonia.Markup.Parsers
&& other.TypeName == TypeName
&& other.TypeNamespace == TypeNamespace;
}
public class ChildTraversalSyntax : ISyntax
{
public static ChildTraversalSyntax Instance { get; } = new ChildTraversalSyntax();
@ -231,5 +234,6 @@ namespace Avalonia.Markup.Parsers
&& other.TypeName == TypeName
&& other.TypeNamespace == TypeNamespace;
}
#pragma warning restore CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode()
}
}

35
src/Skia/Avalonia.Skia/CombinedGeometryImpl.cs

@ -0,0 +1,35 @@
using System.Collections.Generic;
using Avalonia.Media;
using SkiaSharp;
#nullable enable
namespace Avalonia.Skia
{
/// <summary>
/// A Skia implementation of a <see cref="Avalonia.Media.GeometryGroup"/>.
/// </summary>
internal class CombinedGeometryImpl : GeometryImpl
{
public CombinedGeometryImpl(GeometryCombineMode combineMode, Geometry g1, Geometry g2)
{
var path1 = ((GeometryImpl)g1.PlatformImpl).EffectivePath;
var path2 = ((GeometryImpl)g2.PlatformImpl).EffectivePath;
var op = combineMode switch
{
GeometryCombineMode.Intersect => SKPathOp.Intersect,
GeometryCombineMode.Xor => SKPathOp.Xor,
GeometryCombineMode.Exclude => SKPathOp.Difference,
_ => SKPathOp.Union,
};
var path = path1.Op(path2, op);
EffectivePath = path;
Bounds = path.Bounds.ToAvaloniaRect();
}
public override Rect Bounds { get; }
public override SKPath EffectivePath { get; }
}
}

36
src/Skia/Avalonia.Skia/GeometryGroupImpl.cs

@ -0,0 +1,36 @@
using System.Collections.Generic;
using Avalonia.Media;
using SkiaSharp;
#nullable enable
namespace Avalonia.Skia
{
/// <summary>
/// A Skia implementation of a <see cref="Avalonia.Media.GeometryGroup"/>.
/// </summary>
internal class GeometryGroupImpl : GeometryImpl
{
public GeometryGroupImpl(FillRule fillRule, IReadOnlyList<Geometry> children)
{
var path = new SKPath
{
FillType = fillRule == FillRule.NonZero ? SKPathFillType.Winding : SKPathFillType.EvenOdd,
};
var count = children.Count;
for (var i = 0; i < count; ++i)
{
if (children[i]?.PlatformImpl is GeometryImpl child)
path.AddPath(child.EffectivePath);
}
EffectivePath = path;
Bounds = path.Bounds.ToAvaloniaRect();
}
public override Rect Bounds { get; }
public override SKPath EffectivePath { get; }
}
}

10
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -62,6 +62,16 @@ namespace Avalonia.Skia
return new StreamGeometryImpl();
}
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<Geometry> children)
{
return new GeometryGroupImpl(fillRule, children);
}
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2)
{
return new CombinedGeometryImpl(combineMode, g1, g2);
}
/// <inheritdoc />
public IBitmapImpl LoadBitmap(string fileName)
{

2
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -175,6 +175,8 @@ namespace Avalonia.Direct2D1
public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2);
public IGeometryImpl CreateRectangleGeometry(Rect rect) => new RectangleGeometryImpl(rect);
public IStreamGeometryImpl CreateStreamGeometry() => new StreamGeometryImpl();
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<Geometry> children) => new GeometryGroupImpl(fillRule, children);
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => new CombinedGeometryImpl(combineMode, g1, g2);
/// <inheritdoc />
public IBitmapImpl LoadBitmap(string fileName)

36
src/Windows/Avalonia.Direct2D1/Media/CombinedGeometryImpl.cs

@ -0,0 +1,36 @@
using SharpDX.Direct2D1;
using AM = Avalonia.Media;
namespace Avalonia.Direct2D1.Media
{
/// <summary>
/// A Direct2D implementation of a <see cref="Avalonia.Media.CombinedGeometry"/>.
/// </summary>
internal class CombinedGeometryImpl : GeometryImpl
{
/// <summary>
/// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
/// </summary>
public CombinedGeometryImpl(
AM.GeometryCombineMode combineMode,
AM.Geometry geometry1,
AM.Geometry geometry2)
: base(CreateGeometry(combineMode, geometry1, geometry2))
{
}
private static Geometry CreateGeometry(
AM.GeometryCombineMode combineMode,
AM.Geometry geometry1,
AM.Geometry geometry2)
{
var g1 = ((GeometryImpl)geometry1.PlatformImpl).Geometry;
var g2 = ((GeometryImpl)geometry2.PlatformImpl).Geometry;
var dest = new PathGeometry(Direct2D1Platform.Direct2D1Factory);
using var sink = dest.Open();
g1.Combine(g2, (CombineMode)combineMode, sink);
sink.Close();
return dest;
}
}
}

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

@ -0,0 +1,33 @@
using System.Collections.Generic;
using SharpDX.Direct2D1;
using AM = Avalonia.Media;
namespace Avalonia.Direct2D1.Media
{
/// <summary>
/// A Direct2D implementation of a <see cref="Avalonia.Media.GeometryGroup"/>.
/// </summary>
internal class GeometryGroupImpl : GeometryImpl
{
/// <summary>
/// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
/// </summary>
public GeometryGroupImpl(AM.FillRule fillRule, IReadOnlyList<AM.Geometry> geometry)
: base(CreateGeometry(fillRule, geometry))
{
}
private static Geometry CreateGeometry(AM.FillRule fillRule, IReadOnlyList<AM.Geometry> children)
{
var count = children.Count;
var c = new Geometry[count];
for (var i = 0; i < count; ++i)
{
c[i] = ((GeometryImpl)children[i].PlatformImpl).Geometry;
}
return new GeometryGroup(Direct2D1Platform.Direct2D1Factory, (FillMode)fillRule, c);
}
}
}

25
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -808,6 +808,31 @@ namespace Avalonia.Win32.Interop
MNC_SELECT = 3
}
public enum SysCommands
{
SC_SIZE = 0xF000,
SC_MOVE = 0xF010,
SC_MINIMIZE = 0xF020,
SC_MAXIMIZE = 0xF030,
SC_NEXTWINDOW = 0xF040,
SC_PREVWINDOW = 0xF050,
SC_CLOSE = 0xF060,
SC_VSCROLL = 0xF070,
SC_HSCROLL = 0xF080,
SC_MOUSEMENU = 0xF090,
SC_KEYMENU = 0xF100,
SC_ARRANGE = 0xF110,
SC_RESTORE = 0xF120,
SC_TASKLIST = 0xF130,
SC_SCREENSAVE = 0xF140,
SC_HOTKEY = 0xF150,
SC_DEFAULT = 0xF160,
SC_MONITORPOWER = 0xF170,
SC_CONTEXTHELP = 0xF180,
SC_SEPARATOR = 0xF00F,
SCF_ISSECURE = 0x00000001,
}
[StructLayout(LayoutKind.Sequential)]
public struct RGBQUAD
{

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

@ -123,6 +123,12 @@ namespace Avalonia.Win32
break;
}
case WindowsMessage.WM_SYSCOMMAND:
// Disable system handling of Alt/F10 menu keys.
if ((SysCommands)wParam == SysCommands.SC_KEYMENU && HighWord(ToInt32(lParam)) <= 0)
return IntPtr.Zero;
break;
case WindowsMessage.WM_MENUCHAR:
{
// mute the system beep

10
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@ -36,6 +36,16 @@ namespace Avalonia.Benchmarks
return new MockStreamGeometryImpl();
}
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<Geometry> children)
{
throw new NotImplementedException();
}
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2)
{
throw new NotImplementedException();
}
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
throw new NotImplementedException();

63
tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs

@ -0,0 +1,63 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using Avalonia.Collections;
using Avalonia.Diagnostics;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class ItemsSourceViewTests
{
[Fact]
public void Only_Subscribes_To_Source_CollectionChanged_When_CollectionChanged_Subscribed()
{
var source = new AvaloniaList<string>();
var target = new ItemsSourceView<string>(source);
var debug = (INotifyCollectionChangedDebug)source;
Assert.Null(debug.GetCollectionChangedSubscribers());
void Handler(object sender, NotifyCollectionChangedEventArgs e) { }
target.CollectionChanged += Handler;
Assert.NotNull(debug.GetCollectionChangedSubscribers());
Assert.Equal(1, debug.GetCollectionChangedSubscribers().Length);
target.CollectionChanged -= Handler;
Assert.Null(debug.GetCollectionChangedSubscribers());
}
[Fact]
public void Cannot_Wrap_An_ItemsSourceView_In_Another()
{
var source = new ItemsSourceView<string>(new string[0]);
Assert.Throws<ArgumentException>(() => new ItemsSourceView<string>(source));
}
[Fact]
public void Cannot_Create_ItemsSourceView_With_Collection_That_Implements_INCC_But_Not_List()
{
var source = new InvalidCollection();
Assert.Throws<ArgumentException>(() => new ItemsSourceView<string>(source));
}
private class InvalidCollection : INotifyCollectionChanged, IEnumerable<string>
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
public IEnumerator<string> GetEnumerator()
{
yield break;
}
IEnumerator IEnumerable.GetEnumerator()
{
yield break;
}
}
}
}

62
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/GenericTemplateTests.cs

@ -0,0 +1,62 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Metadata;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
public class SampleTemplatedObject : StyledElement
{
[Content] public List<SampleTemplatedObject> Content { get; set; } = new List<SampleTemplatedObject>();
public string Foo { get; set; }
}
public class SampleTemplatedObjectTemplate
{
[Content]
[TemplateContent(TemplateResultType = typeof(SampleTemplatedObject))]
public object Content { get; set; }
}
public class SampleTemplatedObjectContainer
{
public SampleTemplatedObjectTemplate Template { get; set; }
}
public class GenericTemplateTests
{
[Fact]
public void DataTemplate_Can_Be_Empty()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<s:SampleTemplatedObjectContainer xmlns='https://github.com/avaloniaui'
xmlns:sys='clr-namespace:System;assembly=netstandard'
xmlns:s='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<s:SampleTemplatedObjectContainer.Template>
<s:SampleTemplatedObjectTemplate>
<s:SampleTemplatedObject x:Name='root'>
<s:SampleTemplatedObject x:Name='child1' Foo='foo' />
<s:SampleTemplatedObject x:Name='child2' Foo='bar' />
</s:SampleTemplatedObject>
</s:SampleTemplatedObjectTemplate>
</s:SampleTemplatedObjectContainer.Template>
</s:SampleTemplatedObjectContainer>";
var container =
(SampleTemplatedObjectContainer)AvaloniaRuntimeXamlLoader.Load(xaml,
typeof(GenericTemplateTests).Assembly);
var res = TemplateContent.Load<SampleTemplatedObject>(container.Template.Content);
Assert.Equal(res.Result, res.NameScope.Find("root"));
Assert.Equal(res.Result.Content[0], res.NameScope.Find("child1"));
Assert.Equal(res.Result.Content[1], res.NameScope.Find("child2"));
Assert.Equal("foo", res.Result.Content[0].Foo);
Assert.Equal("bar", res.Result.Content[1].Foo);
}
}
}
}

89
tests/Avalonia.RenderTests/Media/CombinedGeometryTests.cs

@ -0,0 +1,89 @@
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Xunit;
#if AVALONIA_SKIA
namespace Avalonia.Skia.RenderTests
#else
namespace Avalonia.Direct2D1.RenderTests.Media
#endif
{
public class CombinedGeometryTests : TestBase
{
public CombinedGeometryTests()
: base(@"Media\CombinedGeometry")
{
}
[Theory]
[InlineData(Avalonia.Media.GeometryCombineMode.Union)]
[InlineData(Avalonia.Media.GeometryCombineMode.Intersect)]
[InlineData(Avalonia.Media.GeometryCombineMode.Xor)]
[InlineData(Avalonia.Media.GeometryCombineMode.Exclude)]
public async Task GeometryCombineMode(GeometryCombineMode mode)
{
var target = new Border
{
Width = 200,
Height = 200,
Background = Brushes.White,
Child = new Path
{
Data = new CombinedGeometry
{
GeometryCombineMode = mode,
Geometry1 = new RectangleGeometry(new Rect(25, 25, 100, 100)),
Geometry2 = new EllipseGeometry
{
Center = new Point(125, 125),
RadiusX = 50,
RadiusY = 50,
}
},
Fill = Brushes.Blue,
Stroke = Brushes.Red,
StrokeThickness = 1,
}
};
var testName = $"{nameof(GeometryCombineMode)}_{mode}";
await RenderToFile(target, testName);
CompareImages(testName);
}
[Fact]
public async Task Geometry1_Transform()
{
var target = new Border
{
Width = 200,
Height = 200,
Background = Brushes.White,
Child = new Path
{
Data = new CombinedGeometry
{
Geometry1 = new RectangleGeometry(new Rect(25, 25, 100, 100))
{
Transform = new RotateTransform(45, 75, 75)
},
Geometry2 = new EllipseGeometry
{
Center = new Point(125, 125),
RadiusX = 50,
RadiusY = 50,
}
},
Fill = Brushes.Blue,
Stroke = Brushes.Red,
StrokeThickness = 1,
}
};
await RenderToFile(target);
CompareImages();
}
}
}

95
tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs

@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Xunit;
#if AVALONIA_SKIA
namespace Avalonia.Skia.RenderTests
#else
namespace Avalonia.Direct2D1.RenderTests.Media
#endif
{
public class GeometryGroupTests : TestBase
{
public GeometryGroupTests()
: base(@"Media\GeometryGroup")
{
}
[Theory]
[InlineData(FillRule.EvenOdd)]
[InlineData(FillRule.NonZero)]
public async Task FillRule_Stroke(FillRule fillRule)
{
var target = new Border
{
Width = 200,
Height = 200,
Background = Brushes.White,
Child = new Path
{
Data = new GeometryGroup
{
FillRule = fillRule,
Children =
{
new RectangleGeometry(new Rect(25, 25, 100, 100)),
new EllipseGeometry
{
Center = new Point(125, 125),
RadiusX = 50,
RadiusY = 50,
},
}
},
Fill = Brushes.Blue,
Stroke = Brushes.Red,
StrokeThickness = 1,
}
};
var testName = $"{nameof(FillRule_Stroke)}_{fillRule}";
await RenderToFile(target, testName);
CompareImages(testName);
}
[Fact]
public async Task Child_Transform()
{
var target = new Border
{
Width = 200,
Height = 200,
Background = Brushes.White,
Child = new Path
{
Data = new GeometryGroup
{
Children =
{
new RectangleGeometry(new Rect(25, 25, 100, 100))
{
Transform = new RotateTransform(45, 75, 75)
},
new EllipseGeometry
{
Center = new Point(125, 125),
RadiusX = 50,
RadiusY = 50,
},
}
},
Fill = Brushes.Blue,
Stroke = Brushes.Red,
StrokeThickness = 1,
}
};
await RenderToFile(target);
CompareImages();
}
}
}

10
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@ -52,6 +52,16 @@ namespace Avalonia.UnitTests
return new MockStreamGeometryImpl();
}
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<Geometry> children)
{
return Mock.Of<IGeometryImpl>();
}
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2)
{
return Mock.Of<IGeometryImpl>();
}
public IWriteableBitmapImpl CreateWriteableBitmap(
PixelSize size,
Vector dpi,

26
tests/Avalonia.Visuals.UnitTests/Media/GeometryGroupTests.cs

@ -0,0 +1,26 @@
using Avalonia.Media;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Media
{
public class GeometryGroupTests
{
[Fact]
public void Children_Should_Have_Initial_Collection()
{
var target = new GeometryGroup();
Assert.NotNull(target.Children);
}
[Fact]
public void Children_Can_Be_Set_To_Null()
{
var target = new GeometryGroup();
target.Children = null;
Assert.Null(target.Children);
}
}
}

23
tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs

@ -297,5 +297,28 @@ namespace Avalonia.Visuals.UnitTests.Media
Assert.Equal(new Point(20, 20), figure.StartPoint);
}
}
[Fact]
public void Should_Parse_Flags_Without_Separator()
{
var pathGeometry = new PathGeometry();
using (var context = new PathGeometryContext(pathGeometry))
using (var parser = new PathMarkupParser(context))
{
parser.Parse("a.898.898 0 01.27.188");
var figure = pathGeometry.Figures[0];
var segments = figure.Segments;
Assert.NotNull(segments);
Assert.Equal(1, segments.Count);
var arcSegment = segments[0];
Assert.IsType<ArcSegment>(arcSegment);
}
}
}
}

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

@ -37,6 +37,16 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
return new MockStreamGeometry();
}
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<Geometry> children)
{
throw new NotImplementedException();
}
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(Stream stream)
{
throw new NotImplementedException();

BIN
tests/TestFiles/Direct2D1/Media/CombinedGeometry/Geometry1_Transform.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Exclude.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Intersect.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Union.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Xor.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
tests/TestFiles/Direct2D1/Media/GeometryGroup/Child_Transform.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_Stroke_EvenOdd.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_Stroke_NonZero.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
tests/TestFiles/Skia/Media/CombinedGeometry/Geometry1_Transform.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Exclude.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Intersect.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Union.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Xor.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
tests/TestFiles/Skia/Media/GeometryGroup/Child_Transform.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
tests/TestFiles/Skia/Media/GeometryGroup/FillRule_Stroke_EvenOdd.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
tests/TestFiles/Skia/Media/GeometryGroup/FillRule_Stroke_NonZero.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Loading…
Cancel
Save