diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 721a0415f4..e67fa14c57 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -31,6 +31,8 @@ jobs:
condition: not(canceled())
- job: macOS
+ variables:
+ SolutionDir: '$(Build.SourcesDirectory)'
pool:
vmImage: 'macOS-10.14'
steps:
@@ -97,6 +99,8 @@ jobs:
- job: Windows
pool:
vmImage: 'windows-2019'
+ variables:
+ SolutionDir: '$(Build.SourcesDirectory)'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.401'
diff --git a/build/Assets/Icon.png b/build/Assets/Icon.png
new file mode 100644
index 0000000000..41a2a618fb
Binary files /dev/null and b/build/Assets/Icon.png differ
diff --git a/build/SharedVersion.props b/build/SharedVersion.props
index a909855ace..d1e9f7b751 100644
--- a/build/SharedVersion.props
+++ b/build/SharedVersion.props
@@ -10,7 +10,7 @@
CS1591
latest
MIT
- https://avatars2.githubusercontent.com/u/14075148?s=200
+ Icon.png
Avalonia is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), MacOS and with experimental support for Android and iOS.
avalonia;avaloniaui;mvvm;rx;reactive extensions;android;ios;mac;forms;wpf;net;netstandard;net461;uwp;xamarin
https://github.com/AvaloniaUI/Avalonia/releases
@@ -18,4 +18,8 @@
$(MSBuildThisFileDirectory)\avalonia.snk
True
+
+
+
+
diff --git a/build/SourceLink.props b/build/SourceLink.props
index 0c9b6a34f8..e27727c9e8 100644
--- a/build/SourceLink.props
+++ b/build/SourceLink.props
@@ -1,5 +1,5 @@
-
+
-
\ No newline at end of file
+
diff --git a/dirs.proj b/dirs.proj
index bf32abef72..594f2c22d3 100644
--- a/dirs.proj
+++ b/dirs.proj
@@ -21,6 +21,7 @@
+
diff --git a/samples/BindingDemo/App.xaml.cs b/samples/BindingDemo/App.xaml.cs
index 13875aeb21..5c38ab8305 100644
--- a/samples/BindingDemo/App.xaml.cs
+++ b/samples/BindingDemo/App.xaml.cs
@@ -1,6 +1,5 @@
-using System;
using Avalonia;
-using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
@@ -13,13 +12,20 @@ namespace BindingDemo
AvaloniaXamlLoader.Load(this);
}
- private static void Main()
+ public override void OnFrameworkInitializationCompleted()
{
- AppBuilder.Configure()
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ desktop.MainWindow = new MainWindow();
+ base.OnFrameworkInitializationCompleted();
+ }
+
+ public static int Main(string[] args)
+ => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
+
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
.UsePlatformDetect()
.UseReactiveUI()
- .LogToDebug()
- .Start();
- }
+ .LogToDebug();
}
}
diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs
index b2df1953f5..e0cc8cc904 100644
--- a/samples/ControlCatalog.Desktop/Program.cs
+++ b/samples/ControlCatalog.Desktop/Program.cs
@@ -10,12 +10,8 @@ namespace ControlCatalog
internal class Program
{
[STAThread]
- static void Main(string[] args)
- {
- // TODO: Make this work with GTK/Skia/Cairo depending on command-line args
- // again.
- BuildAvaloniaApp().Start();
- }
+ public static int Main(string[] args)
+ => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
///
/// This method is needed for IDE previewer infrastructure
diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
index 28b0257eda..2752703e21 100644
--- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
+++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
@@ -11,7 +11,6 @@
-
diff --git a/samples/Previewer/App.xaml.cs b/samples/Previewer/App.xaml.cs
index fffa987a27..ab83d45cd3 100644
--- a/samples/Previewer/App.xaml.cs
+++ b/samples/Previewer/App.xaml.cs
@@ -1,4 +1,5 @@
using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace Previewer
@@ -9,6 +10,13 @@ namespace Previewer
{
AvaloniaXamlLoader.Load(this);
}
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ desktop.MainWindow = new MainWindow();
+ base.OnFrameworkInitializationCompleted();
+ }
}
-}
\ No newline at end of file
+}
diff --git a/samples/Previewer/Program.cs b/samples/Previewer/Program.cs
index 48363e27f2..b12b93974a 100644
--- a/samples/Previewer/Program.cs
+++ b/samples/Previewer/Program.cs
@@ -1,13 +1,14 @@
-using System;
-using Avalonia;
+using Avalonia;
namespace Previewer
{
class Program
{
- static void Main(string[] args)
- {
- AppBuilder.Configure().UsePlatformDetect().Start();
- }
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UsePlatformDetect();
+
+ public static int Main(string[] args)
+ => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
-}
\ No newline at end of file
+}
diff --git a/samples/RenderDemo/App.xaml.cs b/samples/RenderDemo/App.xaml.cs
index 340ccdae19..2247fd7c5a 100644
--- a/samples/RenderDemo/App.xaml.cs
+++ b/samples/RenderDemo/App.xaml.cs
@@ -1,4 +1,5 @@
using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
@@ -11,9 +12,17 @@ namespace RenderDemo
AvaloniaXamlLoader.Load(this);
}
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ desktop.MainWindow = new MainWindow();
+ base.OnFrameworkInitializationCompleted();
+ }
+
// TODO: Make this work with GTK/Skia/Cairo depending on command-line args
// again.
- static void Main(string[] args) => BuildAvaloniaApp().Start();
+ static void Main(string[] args)
+ => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
// App configuration, used by the entry point and previewer
static AppBuilder BuildAvaloniaApp()
diff --git a/samples/RenderDemo/Pages/GlyphRunPage.xaml b/samples/RenderDemo/Pages/GlyphRunPage.xaml
index fb3e318a0e..c2914e8847 100644
--- a/samples/RenderDemo/Pages/GlyphRunPage.xaml
+++ b/samples/RenderDemo/Pages/GlyphRunPage.xaml
@@ -6,9 +6,9 @@
x:Class="RenderDemo.Pages.GlyphRunPage">
-
-
+
diff --git a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
index ddee880288..857358f6b2 100644
--- a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
+++ b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
@@ -9,7 +9,7 @@ namespace RenderDemo.Pages
{
public class GlyphRunPage : UserControl
{
- private DrawingPresenter _drawingPresenter;
+ private Image _imageControl;
private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private readonly Random _rand = new Random();
private ushort[] _glyphIndices = new ushort[1];
@@ -25,7 +25,8 @@ namespace RenderDemo.Pages
{
AvaloniaXamlLoader.Load(this);
- _drawingPresenter = this.FindControl("drawingPresenter");
+ _imageControl = this.FindControl("imageControl");
+ _imageControl.Source = new DrawingImage();
DispatcherTimer.Run(() =>
{
@@ -73,7 +74,7 @@ namespace RenderDemo.Pages
drawingGroup.Children.Add(geometryDrawing);
- _drawingPresenter.Drawing = drawingGroup;
+ (_imageControl.Source as DrawingImage).Drawing = drawingGroup;
}
}
}
diff --git a/samples/VirtualizationDemo/App.xaml.cs b/samples/VirtualizationDemo/App.xaml.cs
index 5990dd282c..81b80c1f40 100644
--- a/samples/VirtualizationDemo/App.xaml.cs
+++ b/samples/VirtualizationDemo/App.xaml.cs
@@ -1,4 +1,5 @@
using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace VirtualizationDemo
@@ -9,5 +10,12 @@ namespace VirtualizationDemo
{
AvaloniaXamlLoader.Load(this);
}
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ desktop.MainWindow = new MainWindow();
+ base.OnFrameworkInitializationCompleted();
+ }
}
}
diff --git a/samples/VirtualizationDemo/Program.cs b/samples/VirtualizationDemo/Program.cs
index 93ea5e1b88..c23bfd7ad9 100644
--- a/samples/VirtualizationDemo/Program.cs
+++ b/samples/VirtualizationDemo/Program.cs
@@ -1,19 +1,17 @@
-using System;
-using Avalonia;
-using Avalonia.Controls;
+using Avalonia;
using Avalonia.ReactiveUI;
namespace VirtualizationDemo
{
class Program
{
- static void Main(string[] args)
- {
- AppBuilder.Configure()
- .UsePlatformDetect()
- .UseReactiveUI()
- .LogToDebug()
- .Start();
- }
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UsePlatformDetect()
+ .UseReactiveUI()
+ .LogToDebug();
+
+ public static int Main(string[] args)
+ => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
}
diff --git a/samples/interop/Direct3DInteropSample/App.paml.cs b/samples/interop/Direct3DInteropSample/App.paml.cs
index 1b6d5fd39c..29365decfe 100644
--- a/samples/interop/Direct3DInteropSample/App.paml.cs
+++ b/samples/interop/Direct3DInteropSample/App.paml.cs
@@ -1,4 +1,5 @@
using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace Direct3DInteropSample
@@ -9,5 +10,12 @@ namespace Direct3DInteropSample
{
AvaloniaXamlLoader.Load(this);
}
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ desktop.MainWindow = new MainWindow();
+ base.OnFrameworkInitializationCompleted();
+ }
}
}
diff --git a/samples/interop/Direct3DInteropSample/Program.cs b/samples/interop/Direct3DInteropSample/Program.cs
index 21302fa68a..bf8e76d7e4 100644
--- a/samples/interop/Direct3DInteropSample/Program.cs
+++ b/samples/interop/Direct3DInteropSample/Program.cs
@@ -1,19 +1,16 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Avalonia;
+using Avalonia;
namespace Direct3DInteropSample
{
class Program
{
- static void Main(string[] args)
- {
- AppBuilder.Configure()
- .With(new Win32PlatformOptions {UseDeferredRendering = false})
- .UseWin32().UseDirect2D1().Start();
- }
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .With(new Win32PlatformOptions { UseDeferredRendering = false })
+ .UseWin32()
+ .UseDirect2D1();
+
+ public static int Main(string[] args)
+ => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
}
diff --git a/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj b/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
index cc831ef8ae..0e0e71fbb2 100644
--- a/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
+++ b/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
@@ -9,7 +9,6 @@
-
diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs
index 23c4acdf6c..92ddd4e736 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs
@@ -665,6 +665,7 @@ namespace Avalonia.Controls
///
/// The data item represented by the row that contains the intended cell.
///
+ /// When the method returns, contains the applied binding.
///
/// A new editing element that is bound to the column's property value.
///
diff --git a/src/Avalonia.Controls/IScrollAnchorProvider.cs b/src/Avalonia.Controls/IScrollAnchorProvider.cs
index 93f3a0abb8..7ba02e99ea 100644
--- a/src/Avalonia.Controls/IScrollAnchorProvider.cs
+++ b/src/Avalonia.Controls/IScrollAnchorProvider.cs
@@ -1,4 +1,6 @@
-namespace Avalonia.Controls
+#nullable enable
+
+namespace Avalonia.Controls
{
///
/// Specifies a contract for a scrolling control that supports scroll anchoring.
@@ -8,7 +10,7 @@
///
/// The currently chosen anchor element to use for scroll anchoring.
///
- IControl CurrentAnchor { get; }
+ IControl? CurrentAnchor { get; }
///
/// Registers a control as a potential scroll anchor candidate.
diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs
index ccb92dc497..b7eeb065da 100644
--- a/src/Avalonia.Controls/Panel.cs
+++ b/src/Avalonia.Controls/Panel.cs
@@ -137,6 +137,11 @@ namespace Avalonia.Controls
throw new NotSupportedException();
}
+ InvalidateMeasureOnChildrenChanged();
+ }
+
+ private protected virtual void InvalidateMeasureOnChildrenChanged()
+ {
InvalidateMeasure();
}
diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
index 3fac440c40..bdc68bee7e 100644
--- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
+++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
@@ -512,6 +512,14 @@ namespace Avalonia.Controls.Presenters
var generator = Owner.ItemContainerGenerator;
var newOffset = -1.0;
+ if (!panel.IsMeasureValid && panel.PreviousMeasure.HasValue)
+ {
+ //before any kind of scrolling we need to make sure panel measure is valid
+ //or we risk get panel into not valid state
+ //we make a preemptive quick measure so scrolling is valid
+ panel.Measure(panel.PreviousMeasure.Value);
+ }
+
if (index >= 0 && index < ItemCount)
{
if (index <= FirstIndex)
diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
index 5fcb14c858..b0b52812b9 100644
--- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
@@ -7,6 +7,8 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.VisualTree;
+#nullable enable
+
namespace Avalonia.Controls.Presenters
{
///
@@ -14,6 +16,8 @@ namespace Avalonia.Controls.Presenters
///
public class ScrollContentPresenter : ContentPresenter, IPresenter, IScrollable, IScrollAnchorProvider
{
+ private const double EdgeDetectionTolerance = 0.1;
+
///
/// Defines the property.
///
@@ -64,11 +68,13 @@ namespace Avalonia.Controls.Presenters
private bool _arranging;
private Size _extent;
private Vector _offset;
- private IDisposable _logicalScrollSubscription;
+ private IDisposable? _logicalScrollSubscription;
private Size _viewport;
- private Dictionary _activeLogicalGestureScrolls;
- private List _anchorCandidates;
- private (IControl control, Rect bounds) _anchor;
+ private Dictionary? _activeLogicalGestureScrolls;
+ private List? _anchorCandidates;
+ private IControl? _anchorElement;
+ private Rect _anchorElementBounds;
+ private bool _isAnchorElementDirty;
///
/// Initializes static members of the class.
@@ -90,8 +96,6 @@ namespace Avalonia.Controls.Presenters
this.GetObservable(ChildProperty).Subscribe(UpdateScrollableSubscription);
}
- internal event EventHandler PreArrange;
-
///
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
///
@@ -138,7 +142,14 @@ namespace Avalonia.Controls.Presenters
}
///
- IControl IScrollAnchorProvider.CurrentAnchor => _anchor.control;
+ IControl? IScrollAnchorProvider.CurrentAnchor
+ {
+ get
+ {
+ EnsureAnchorElementSelection();
+ return _anchorElement;
+ }
+ }
///
/// Attempts to bring a portion of the target visual into view by scrolling the content.
@@ -215,16 +226,18 @@ namespace Avalonia.Controls.Presenters
_anchorCandidates ??= new List();
_anchorCandidates.Add(element);
+ _isAnchorElementDirty = true;
}
///
void IScrollAnchorProvider.UnregisterAnchorCandidate(IControl element)
{
_anchorCandidates?.Remove(element);
+ _isAnchorElementDirty = true;
- if (_anchor.control == element)
+ if (_anchorElement == element)
{
- _anchor = default;
+ _anchorElement = null;
}
}
@@ -247,11 +260,6 @@ namespace Avalonia.Controls.Presenters
///
protected override Size ArrangeOverride(Size finalSize)
{
- PreArrange?.Invoke(this, new VectorEventArgs
- {
- Vector = new Vector(finalSize.Width, finalSize.Height),
- });
-
if (_logicalScrollSubscription != null || Child == null)
{
return base.ArrangeOverride(finalSize);
@@ -271,59 +279,69 @@ namespace Avalonia.Controls.Presenters
// If we have an anchor and its position relative to Child has changed during the
// arrange then that change wasn't just due to scrolling (as scrolling doesn't adjust
// relative positions within Child).
- if (_anchor.control != null &&
- TranslateBounds(_anchor.control, Child, out var updatedBounds) &&
- updatedBounds.Position != _anchor.bounds.Position)
+ if (_anchorElement != null &&
+ TranslateBounds(_anchorElement, Child, out var updatedBounds) &&
+ updatedBounds.Position != _anchorElementBounds.Position)
{
- var offset = updatedBounds.Position - _anchor.bounds.Position;
+ var offset = updatedBounds.Position - _anchorElementBounds.Position;
return offset;
}
return default;
}
- // Calculate the new anchor element.
- _anchor = CalculateCurrentAnchor();
+ var isAnchoring = Offset.X >= EdgeDetectionTolerance || Offset.Y >= EdgeDetectionTolerance;
- // Do the arrange.
- ArrangeOverrideImpl(size, -Offset);
+ if (isAnchoring)
+ {
+ // Calculate the new anchor element if necessary.
+ EnsureAnchorElementSelection();
- // If the anchor moved during the arrange, we need to adjust the offset and do another arrange.
- var anchorShift = TrackAnchor();
+ // Do the arrange.
+ ArrangeOverrideImpl(size, -Offset);
- if (anchorShift != default)
- {
- var newOffset = Offset + anchorShift;
- var newExtent = Extent;
- var maxOffset = new Vector(Extent.Width - Viewport.Width, Extent.Height - Viewport.Height);
+ // If the anchor moved during the arrange, we need to adjust the offset and do another arrange.
+ var anchorShift = TrackAnchor();
- if (newOffset.X > maxOffset.X)
+ if (anchorShift != default)
{
- newExtent = newExtent.WithWidth(newOffset.X + Viewport.Width);
- }
+ var newOffset = Offset + anchorShift;
+ var newExtent = Extent;
+ var maxOffset = new Vector(Extent.Width - Viewport.Width, Extent.Height - Viewport.Height);
- if (newOffset.Y > maxOffset.Y)
- {
- newExtent = newExtent.WithHeight(newOffset.Y + Viewport.Height);
- }
+ if (newOffset.X > maxOffset.X)
+ {
+ newExtent = newExtent.WithWidth(newOffset.X + Viewport.Width);
+ }
- Extent = newExtent;
+ if (newOffset.Y > maxOffset.Y)
+ {
+ newExtent = newExtent.WithHeight(newOffset.Y + Viewport.Height);
+ }
- try
- {
- _arranging = true;
- Offset = newOffset;
- }
- finally
- {
- _arranging = false;
+ Extent = newExtent;
+
+ try
+ {
+ _arranging = true;
+ Offset = newOffset;
+ }
+ finally
+ {
+ _arranging = false;
+ }
+
+ ArrangeOverrideImpl(size, -Offset);
}
-
+ }
+ else
+ {
ArrangeOverrideImpl(size, -Offset);
}
Viewport = finalSize;
Extent = Child.Bounds.Size.Inflate(Child.Margin);
+ _isAnchorElementDirty = true;
return finalSize;
}
@@ -350,7 +368,7 @@ namespace Avalonia.Controls.Presenters
{
var logicalUnits = delta.Y / LogicalScrollItemSize;
delta = delta.WithY(delta.Y - logicalUnits * LogicalScrollItemSize);
- dy = logicalUnits * scrollable.ScrollSize.Height;
+ dy = logicalUnits * scrollable!.ScrollSize.Height;
}
else
dy = delta.Y;
@@ -368,7 +386,7 @@ namespace Avalonia.Controls.Presenters
{
var logicalUnits = delta.X / LogicalScrollItemSize;
delta = delta.WithX(delta.X - logicalUnits * LogicalScrollItemSize);
- dx = logicalUnits * scrollable.ScrollSize.Width;
+ dx = logicalUnits * scrollable!.ScrollSize.Width;
}
else
dx = delta.X;
@@ -405,7 +423,7 @@ namespace Avalonia.Controls.Presenters
if (Extent.Height > Viewport.Height)
{
- double height = isLogical ? scrollable.ScrollSize.Height : 50;
+ double height = isLogical ? scrollable!.ScrollSize.Height : 50;
y += -e.Delta.Y * height;
y = Math.Max(y, 0);
y = Math.Min(y, Extent.Height - Viewport.Height);
@@ -413,7 +431,7 @@ namespace Avalonia.Controls.Presenters
if (Extent.Width > Viewport.Width)
{
- double width = isLogical ? scrollable.ScrollSize.Width : 50;
+ double width = isLogical ? scrollable!.ScrollSize.Width : 50;
x += -e.Delta.X * width;
x = Math.Max(x, 0);
x = Math.Min(x, Extent.Width - Viewport.Width);
@@ -441,7 +459,7 @@ namespace Avalonia.Controls.Presenters
private void ChildChanged(AvaloniaPropertyChangedEventArgs e)
{
- UpdateScrollableSubscription((IControl)e.NewValue);
+ UpdateScrollableSubscription((IControl?)e.NewValue);
if (e.OldValue != null)
{
@@ -449,7 +467,7 @@ namespace Avalonia.Controls.Presenters
}
}
- private void UpdateScrollableSubscription(IControl child)
+ private void UpdateScrollableSubscription(IControl? child)
{
var scrollable = child as ILogicalScrollable;
@@ -498,13 +516,17 @@ namespace Avalonia.Controls.Presenters
}
}
- private (IControl, Rect) CalculateCurrentAnchor()
+ private void EnsureAnchorElementSelection()
{
- if (_anchorCandidates == null)
+ if (!_isAnchorElementDirty || _anchorCandidates is null)
{
- return default;
+ return;
}
+ _anchorElement = null;
+ _anchorElementBounds = default;
+ _isAnchorElementDirty = false;
+
var bestCandidate = default(IControl);
var bestCandidateDistance = double.MaxValue;
@@ -531,10 +553,9 @@ namespace Avalonia.Controls.Presenters
// bounds aren't relative to the ScrollContentPresenter itself, if they change
// then we know it wasn't just due to scrolling.
var unscrolledBounds = TranslateBounds(bestCandidate, Child);
- return (bestCandidate, unscrolledBounds);
+ _anchorElement = bestCandidate;
+ _anchorElementBounds = unscrolledBounds;
}
-
- return default;
}
private bool GetViewportBounds(IControl element, out Rect bounds)
diff --git a/src/Avalonia.Controls/Primitives/IPopupHost.cs b/src/Avalonia.Controls/Primitives/IPopupHost.cs
index 82a49c4189..ab81fe869e 100644
--- a/src/Avalonia.Controls/Primitives/IPopupHost.cs
+++ b/src/Avalonia.Controls/Primitives/IPopupHost.cs
@@ -47,6 +47,7 @@ namespace Avalonia.Controls.Primitives
/// The offset, in device-independent pixels.
/// The anchor point.
/// The popup gravity.
+ /// Defines how a popup position will be adjusted if the unadjusted position would result in the popup being partly constrained.
///
/// The anchor rect. If null, the bounds of will be used.
///
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index 4317d795f1..280f46be9f 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -354,19 +354,15 @@ namespace Avalonia.Controls.Primitives
///
/// The control that raised the event.
/// The container or null if the event did not originate in a container.
- protected IControl? GetContainerFromEventSource(IInteractive eventSource)
+ protected IControl? GetContainerFromEventSource(IInteractive? eventSource)
{
- var parent = (IVisual)eventSource;
-
- while (parent != null)
+ for (var current = eventSource as IVisual; current != null; current = current.VisualParent)
{
- if (parent is IControl control && control.LogicalParent == this
- && ItemContainerGenerator?.IndexFromContainer(control) != -1)
+ if (current is IControl control && control.LogicalParent == this &&
+ ItemContainerGenerator?.IndexFromContainer(control) != -1)
{
return control;
}
-
- parent = parent.VisualParent;
}
return null;
@@ -670,7 +666,7 @@ namespace Avalonia.Controls.Primitives
/// false.
///
protected bool UpdateSelectionFromEventSource(
- IInteractive eventSource,
+ IInteractive? eventSource,
bool select = true,
bool rangeModifier = false,
bool toggleModifier = false,
@@ -794,18 +790,13 @@ namespace Avalonia.Controls.Primitives
/// The event.
private void ContainerSelectionChanged(RoutedEventArgs e)
{
- if (!_ignoreContainerSelectionChanged)
+ if (!_ignoreContainerSelectionChanged &&
+ e.Source is IControl control &&
+ e.Source is ISelectable selectable &&
+ control.LogicalParent == this &&
+ ItemContainerGenerator?.IndexFromContainer(control) != -1)
{
- var control = e.Source as IControl;
- var selectable = e.Source as ISelectable;
-
- if (control != null &&
- selectable != null &&
- control.LogicalParent == this &&
- ItemContainerGenerator?.IndexFromContainer(control) != -1)
- {
- UpdateSelection(control, selectable.IsSelected);
- }
+ UpdateSelection(control, selectable.IsSelected);
}
if (e.Source != this)
@@ -824,12 +815,11 @@ namespace Avalonia.Controls.Primitives
{
try
{
- var selectable = container as ISelectable;
bool result;
_ignoreContainerSelectionChanged = true;
- if (selectable != null)
+ if (container is ISelectable selectable)
{
result = selectable.IsSelected;
selectable.IsSelected = selected;
diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
index 40f1b8dbb9..fb2da09e73 100644
--- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
+++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
@@ -267,6 +267,11 @@ namespace Avalonia.Controls
return result;
}
+ private protected override void InvalidateMeasureOnChildrenChanged()
+ {
+ // Don't invalidate measure when children change.
+ }
+
protected override Size MeasureOverride(Size availableSize)
{
if (_isLayoutInProgress)
@@ -364,6 +369,12 @@ namespace Avalonia.Controls
{
var newBounds = element.Bounds;
virtInfo.ArrangeBounds = newBounds;
+
+ if (!virtInfo.IsRegisteredAsAnchorCandidate)
+ {
+ _viewportManager.RegisterScrollAnchorCandidate(element);
+ virtInfo.IsRegisteredAsAnchorCandidate = true;
+ }
}
}
@@ -515,11 +526,14 @@ namespace Avalonia.Controls
return element;
}
- internal void OnElementPrepared(IControl element, int index)
+ internal void OnElementPrepared(IControl element, VirtualizationInfo virtInfo)
{
- _viewportManager.OnElementPrepared(element);
+ _viewportManager.OnElementPrepared(element, virtInfo);
+
if (ElementPrepared != null)
{
+ var index = virtInfo.Index;
+
if (_elementPreparedArgs == null)
{
_elementPreparedArgs = new ItemsRepeaterElementPreparedEventArgs(element, index);
diff --git a/src/Avalonia.Controls/Repeater/ViewManager.cs b/src/Avalonia.Controls/Repeater/ViewManager.cs
index 416b1e2824..cf2066b373 100644
--- a/src/Avalonia.Controls/Repeater/ViewManager.cs
+++ b/src/Avalonia.Controls/Repeater/ViewManager.cs
@@ -661,7 +661,7 @@ namespace Avalonia.Controls
children.Add(element);
}
- repeater.OnElementPrepared(element, index);
+ repeater.OnElementPrepared(element, virtInfo);
// Update realized indices
_firstRealizedElementIndexHeldByLayout = Math.Min(_firstRealizedElementIndexHeldByLayout, index);
diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls/Repeater/ViewportManager.cs
index bdb0fa3270..6e24408aa9 100644
--- a/src/Avalonia.Controls/Repeater/ViewportManager.cs
+++ b/src/Avalonia.Controls/Repeater/ViewportManager.cs
@@ -240,9 +240,14 @@ namespace Avalonia.Controls
}
}
- public void OnElementPrepared(IControl element)
+ public void OnElementPrepared(IControl element, VirtualizationInfo virtInfo)
{
- _scroller?.RegisterAnchorCandidate(element);
+ // WinUI registers the element as an anchor candidate here, but I feel that's in error:
+ // at this point the element has not yet been positioned by the arrange pass so it will
+ // have its previous position, meaning that when the arrange pass moves it into its new
+ // position, an incorrect scroll anchoring will occur. Instead signal that it's not yet
+ // registered as a scroll anchor candidate.
+ virtInfo.IsRegisteredAsAnchorCandidate = false;
}
public void OnElementCleared(IControl element)
@@ -373,6 +378,11 @@ namespace Avalonia.Controls
}
}
+ public void RegisterScrollAnchorCandidate(IControl element)
+ {
+ _scroller?.RegisterAnchorCandidate(element);
+ }
+
private IControl GetImmediateChildOfRepeater(IControl descendant)
{
var targetChild = descendant;
diff --git a/src/Avalonia.Controls/Repeater/VirtualizationInfo.cs b/src/Avalonia.Controls/Repeater/VirtualizationInfo.cs
index 7a639419c1..f8cfde609e 100644
--- a/src/Avalonia.Controls/Repeater/VirtualizationInfo.cs
+++ b/src/Avalonia.Controls/Repeater/VirtualizationInfo.cs
@@ -38,6 +38,7 @@ namespace Avalonia.Controls
public bool IsInUniqueIdResetPool => Owner == ElementOwner.UniqueIdResetPool;
public bool MustClearDataContext { get; set; }
public bool KeepAlive { get; set; }
+ public bool IsRegisteredAsAnchorCandidate { get; set; }
public ElementOwner Owner { get; private set; } = ElementOwner.ElementFactory;
public string UniqueId { get; private set; }
diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs
index 7d1525afc4..0b7595ec9a 100644
--- a/src/Avalonia.Controls/Shapes/Shape.cs
+++ b/src/Avalonia.Controls/Shapes/Shape.cs
@@ -62,7 +62,6 @@ namespace Avalonia.Controls.Shapes
private Matrix _transform = Matrix.Identity;
private Geometry? _definingGeometry;
private Geometry? _renderedGeometry;
- private bool _calculateTransformOnArrange;
static Shape()
{
@@ -248,52 +247,21 @@ namespace Avalonia.Controls.Shapes
protected override Size MeasureOverride(Size availableSize)
{
- bool deferCalculateTransform;
- switch (Stretch)
+ if (DefiningGeometry is null)
{
- case Stretch.Fill:
- case Stretch.UniformToFill:
- deferCalculateTransform = double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height);
- break;
- case Stretch.Uniform:
- deferCalculateTransform = double.IsInfinity(availableSize.Width) && double.IsInfinity(availableSize.Height);
- break;
- case Stretch.None:
- default:
- deferCalculateTransform = false;
- break;
+ return default;
}
- if (deferCalculateTransform)
- {
- _calculateTransformOnArrange = true;
- return DefiningGeometry?.Bounds.Size ?? Size.Empty;
- }
- else
- {
- _calculateTransformOnArrange = false;
- return CalculateShapeSizeAndSetTransform(availableSize);
- }
+ return CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch).size;
}
protected override Size ArrangeOverride(Size finalSize)
- {
- if (_calculateTransformOnArrange)
- {
- _calculateTransformOnArrange = false;
- CalculateShapeSizeAndSetTransform(finalSize);
- }
-
- return finalSize;
- }
-
- private Size CalculateShapeSizeAndSetTransform(Size availableSize)
{
if (DefiningGeometry != null)
{
// This should probably use GetRenderBounds(strokeThickness) but then the calculations
// will multiply the stroke thickness as well, which isn't correct.
- var (size, transform) = CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch);
+ var (_, transform) = CalculateSizeAndTransform(finalSize, DefiningGeometry.Bounds, Stretch);
if (_transform != transform)
{
@@ -301,13 +269,13 @@ namespace Avalonia.Controls.Shapes
_renderedGeometry = null;
}
- return size;
+ return finalSize;
}
return Size.Empty;
}
- internal static (Size, Matrix) CalculateSizeAndTransform(Size availableSize, Rect shapeBounds, Stretch Stretch)
+ internal static (Size size, Matrix transform) CalculateSizeAndTransform(Size availableSize, Rect shapeBounds, Stretch Stretch)
{
Size shapeSize = new Size(shapeBounds.Right, shapeBounds.Bottom);
Matrix translate = Matrix.Identity;
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index 317b6d3f2e..54574a7e1c 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -451,7 +451,7 @@ namespace Avalonia.Controls
///
/// The dialog result.
///
- /// When the window is shown with the
+ /// When the window is shown with the
/// or method, the
/// resulting task will produce the value when the window
/// is closed.
diff --git a/src/Avalonia.Input/KeyGesture.cs b/src/Avalonia.Input/KeyGesture.cs
index aa6fcc8bff..e155666631 100644
--- a/src/Avalonia.Input/KeyGesture.cs
+++ b/src/Avalonia.Input/KeyGesture.cs
@@ -144,7 +144,10 @@ namespace Avalonia.Input
return s.ToString();
}
- public bool Matches(KeyEventArgs keyEvent) => ResolveNumPadOperationKey(keyEvent.Key) == Key && keyEvent.KeyModifiers == KeyModifiers;
+ public bool Matches(KeyEventArgs keyEvent) =>
+ keyEvent != null &&
+ keyEvent.KeyModifiers == KeyModifiers &&
+ ResolveNumPadOperationKey(keyEvent.Key) == ResolveNumPadOperationKey(Key);
// TODO: Move that to external key parser
private static Key ParseKey(string key)
diff --git a/src/Avalonia.Layout/StackLayout.cs b/src/Avalonia.Layout/StackLayout.cs
index 909c7bc7eb..4a93c8344f 100644
--- a/src/Avalonia.Layout/StackLayout.cs
+++ b/src/Avalonia.Layout/StackLayout.cs
@@ -249,8 +249,8 @@ namespace Avalonia.Layout
realizationWindowOffsetInExtent + _orientation.MajorSize(realizationRect) >= 0 && realizationWindowOffsetInExtent <= majorSize)
{
anchorIndex = (int) (realizationWindowOffsetInExtent / averageElementSize);
- offset = anchorIndex* averageElementSize + _orientation.MajorStart(lastExtent);
anchorIndex = Math.Max(0, Math.Min(itemsCount - 1, anchorIndex));
+ offset = anchorIndex* averageElementSize + _orientation.MajorStart(lastExtent);
}
}
diff --git a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
index ca303211cd..5a0c57b333 100644
--- a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
+++ b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
@@ -99,7 +99,6 @@ namespace Avalonia.Media.Imaging
/// Initializes a new instance of the class.
///
/// The pixel format.
- /// The alpha format.
/// The pointer to the source bytes.
/// The size of the bitmap in device pixels.
/// The DPI of the bitmap.
diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
index cfe2cf979a..d6e88a7507 100644
--- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
+++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
@@ -69,7 +69,7 @@ namespace Avalonia.Platform
/// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible.
///
void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect,
- BoxShadows boxShadow = default);
+ BoxShadows boxShadows = default);
///
/// Draws text.
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs
index 336d11e3fd..8bd079d070 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs
@@ -13,10 +13,9 @@ namespace Avalonia.Rendering.SceneGraph
///
/// Initializes a new instance of the class.
///
- /// The transform.
+ /// The transform.
+ ///
/// The rectangle to draw.
- /// The box shadow parameters
- /// Child scenes for drawing visual brushes.
public ExperimentalAcrylicNode(
Matrix transform,
IExperimentalAcrylicMaterial material,
@@ -44,7 +43,7 @@ namespace Avalonia.Rendering.SceneGraph
/// Determines if this draw operation equals another.
///
/// The transform of the other draw operation.
- /// The fill of the other draw operation.
+ /// The fill of the other draw operation.
/// The rectangle of the other draw operation.
/// True if the draw operations are the same, otherwise false.
///
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
index 8a19679c77..508ca0ad18 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
@@ -63,7 +63,6 @@ namespace Avalonia.Rendering.SceneGraph
/// The fill of the other draw operation.
/// The stroke of the other draw operation.
/// The geometry of the other draw operation.
- /// The box shadow parameters
/// True if the draw operations are the same, otherwise false.
///
/// The properties of the other draw operation are passed in as arguments to prevent
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
index ec1a7753b1..d0c4566485 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
@@ -73,7 +73,7 @@ namespace Avalonia.Rendering.SceneGraph
/// The fill of the other draw operation.
/// The stroke of the other draw operation.
/// The rectangle of the other draw operation.
- /// The box shadow parameters of the other draw operation
+ /// The box shadow parameters of the other draw operation
/// True if the draw operations are the same, otherwise false.
///
/// The properties of the other draw operation are passed in as arguments to prevent
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs
index 787a2e4cb8..9c476b1b63 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs
@@ -113,22 +113,22 @@ namespace Avalonia.LinuxFramebuffer.Output
[StructLayout(LayoutKind.Sequential)]
public struct drmModeConnector {
public uint connector_id;
- public uint encoder_id; /**< Encoder currently connected to */
+ public uint encoder_id; // Encoder currently connected to
public uint connector_type;
public uint connector_type_id;
public DrmModeConnection connection;
- public uint mmWidth, mmHeight; /**< HxW in millimeters */
+ public uint mmWidth, mmHeight; // HxW in millimeters
public DrmModeSubPixel subpixel;
public int count_modes;
public drmModeModeInfo* modes;
public int count_props;
- public uint *props; /**< List of property ids */
- public ulong *prop_values; /**< List of property values */
+ public uint *props; // List of property ids
+ public ulong *prop_values; // List of property values
public int count_encoders;
- public uint *encoders; /**< List of encoder ids */
+ public uint *encoders; //List of encoder ids
}
[StructLayout(LayoutKind.Sequential)]
@@ -143,14 +143,14 @@ namespace Avalonia.LinuxFramebuffer.Output
[StructLayout(LayoutKind.Sequential)]
public struct drmModeCrtc {
public uint crtc_id;
- public uint buffer_id; /**< FB id to connect to 0 = disconnect */
+ public uint buffer_id; // FB id to connect to 0 = disconnect
- public uint x, y; /**< Position on the framebuffer */
+ public uint x, y; // Position on the framebuffer
public uint width, height;
public int mode_valid;
public drmModeModeInfo mode;
- public int gamma_size; /**< Number of gamma stops */
+ public int gamma_size; // Number of gamma stops
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs
index 4569970d01..4df07bcdd8 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs
@@ -3,7 +3,6 @@ using System.IO;
using System.Reflection;
using System.Text;
using Avalonia.Markup.Xaml.XamlIl;
-// ReSharper disable CheckNamespace
namespace Avalonia.Markup.Xaml
{
@@ -13,10 +12,10 @@ namespace Avalonia.Markup.Xaml
/// Loads XAML from a string.
///
/// The string containing the XAML.
- /// Default assembly for clr-namespace:
- ///
- /// The optional instance into which the XAML should be loaded.
- ///
+ /// Default assembly for clr-namespace:.
+ /// The optional instance into which the XAML should be loaded.
+ /// The URI of the XAML being loaded.
+ /// Indicates whether the XAML is being loaded in design mode.
/// The loaded object.
public static object Load(string xaml, Assembly localAssembly = null, object rootInstance = null, Uri uri = null, bool designMode = false)
{
@@ -28,13 +27,35 @@ namespace Avalonia.Markup.Xaml
}
}
+ ///
+ /// Loads XAML from a stream.
+ ///
+ /// The stream containing the XAML.
+ /// Default assembly for clr-namespace:
+ /// The optional instance into which the XAML should be loaded.
+ /// The URI of the XAML being loaded.
+ /// Indicates whether the XAML is being loaded in design mode.
+ /// The loaded object.
public static object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null,
bool designMode = false)
=> AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, designMode);
+ ///
+ /// Parse XAML from a string.
+ ///
+ /// The string containing the XAML.
+ /// Default assembly for clr-namespace:.
+ /// The loaded object.
public static object Parse(string xaml, Assembly localAssembly = null)
=> Load(xaml, localAssembly);
+ ///
+ /// Parse XAML from a string.
+ ///
+ /// The type of the returned object.
+ /// >The string containing the XAML.
+ /// >Default assembly for clr-namespace:.
+ /// The loaded object.
public static T Parse(string xaml, Assembly localAssembly = null)
=> (T)Parse(xaml, localAssembly);
diff --git a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
index 5571bd890d..ceccc481f9 100644
--- a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
+++ b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
@@ -1,6 +1,5 @@
using System;
using System.Runtime.InteropServices;
-using Avalonia.Media;
using Avalonia.Platform;
using HarfBuzzSharp;
using SkiaSharp;
@@ -24,40 +23,33 @@ namespace Avalonia.Skia
Font.SetFunctionsOpenType();
- Font.GetScale(out var xScale, out _);
+ DesignEmHeight = (short)Typeface.UnitsPerEm;
- DesignEmHeight = (short)xScale;
+ var metrics = Typeface.ToFont().Metrics;
- if (!Font.TryGetHorizontalFontExtents(out var fontExtents))
- {
- Font.TryGetVerticalFontExtents(out fontExtents);
- }
+ const double defaultFontRenderingEmSize = 12.0;
- Ascent = -fontExtents.Ascender;
+ Ascent = (int)(metrics.Ascent / defaultFontRenderingEmSize * Typeface.UnitsPerEm);
- Descent = -fontExtents.Descender;
+ Descent = (int)(metrics.Descent / defaultFontRenderingEmSize * Typeface.UnitsPerEm);
- LineGap = fontExtents.LineGap;
+ LineGap = (int)(metrics.Leading / defaultFontRenderingEmSize * Typeface.UnitsPerEm);
- if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition))
- {
- UnderlinePosition = underlinePosition;
- }
+ UnderlinePosition = metrics.UnderlinePosition != null ?
+ (int)(metrics.UnderlinePosition / defaultFontRenderingEmSize * Typeface.UnitsPerEm) :
+ 0;
- if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness))
- {
- UnderlineThickness = underlineThickness;
- }
+ UnderlineThickness = metrics.UnderlineThickness != null ?
+ (int)(metrics.UnderlineThickness / defaultFontRenderingEmSize * Typeface.UnitsPerEm) :
+ 0;
- if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition))
- {
- StrikethroughPosition = strikethroughPosition;
- }
+ StrikethroughPosition = metrics.StrikeoutPosition != null ?
+ (int)(metrics.StrikeoutPosition / defaultFontRenderingEmSize * Typeface.UnitsPerEm) :
+ 0;
- if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness))
- {
- StrikethroughThickness = strikethroughThickness;
- }
+ StrikethroughThickness = metrics.StrikeoutThickness != null ?
+ (int)(metrics.StrikeoutThickness / defaultFontRenderingEmSize * Typeface.UnitsPerEm) :
+ 0;
IsFixedPitch = Typeface.IsFixedPitch;
}
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index ae927d44a5..6ae27870e8 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
@@ -238,22 +238,46 @@ namespace Avalonia.Direct2D1
width = 0;
- for (var i = 0; i < glyphCount; i++)
+ var scale = (float)(glyphRun.FontRenderingEmSize / glyphTypeface.DesignEmHeight);
+
+ if (glyphRun.GlyphAdvances.IsEmpty)
+ {
+ for (var i = 0; i < glyphCount; i++)
+ {
+ var advance = glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale;
+
+ run.Advances[i] = advance;
+
+ width += advance;
+ }
+ }
+ else
+ {
+ for (var i = 0; i < glyphCount; i++)
+ {
+ var advance = (float)glyphRun.GlyphAdvances[i];
+
+ run.Advances[i] = advance;
+
+ width += advance;
+ }
+ }
+
+ if (glyphRun.GlyphOffsets.IsEmpty)
{
- run.Advances[i] = (float)glyphRun.GlyphAdvances[i];
- width += run.Advances[i];
+ return new GlyphRunImpl(run);
}
run.Offsets = new GlyphOffset[glyphCount];
for (var i = 0; i < glyphCount; i++)
{
- var offset = glyphRun.GlyphOffsets[i];
+ var (x, y) = glyphRun.GlyphOffsets[i];
run.Offsets[i] = new GlyphOffset
{
- AdvanceOffset = (float)offset.X,
- AscenderOffset = (float)offset.Y
+ AdvanceOffset = (float)x,
+ AscenderOffset = (float)y
};
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index 59292d605c..ace658654d 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -323,7 +323,6 @@ namespace Avalonia.Direct2D1.Media
///
/// The foreground.
/// The glyph run.
- ///
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
using (var brush = CreateBrush(foreground, glyphRun.Size))
diff --git a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
index 254b5684a4..20b09a9aac 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
@@ -1,6 +1,6 @@
-using System.Globalization;
+using System;
+using System.Globalization;
using Avalonia.Media;
-using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
using Avalonia.Utilities;
@@ -15,51 +15,9 @@ namespace Avalonia.Direct2D1.Media
{
using (var buffer = new Buffer())
{
- buffer.ContentType = ContentType.Unicode;
+ FillBuffer(buffer, text);
- var breakCharPosition = text.Length - 1;
-
- var codepoint = Codepoint.ReadAt(text, breakCharPosition, out var count);
-
- if (codepoint.IsBreakChar)
- {
- var breakCharCount = 1;
-
- if (text.Length > 1)
- {
- var previousCodepoint = Codepoint.ReadAt(text, breakCharPosition - count, out _);
-
- if (codepoint == '\r' && previousCodepoint == '\n'
- || codepoint == '\n' && previousCodepoint == '\r')
- {
- breakCharCount = 2;
- }
- }
-
- if (breakCharPosition != text.Start)
- {
- buffer.AddUtf16(text.Buffer.Span.Slice(0, text.Length - breakCharCount));
- }
-
- var cluster = buffer.GlyphInfos.Length > 0 ?
- buffer.GlyphInfos[buffer.Length - 1].Cluster + 1 :
- (uint)text.Start;
-
- switch (breakCharCount)
- {
- case 1:
- buffer.Add('\u200C', cluster);
- break;
- case 2:
- buffer.Add('\u200C', cluster);
- buffer.Add('\u200D', cluster);
- break;
- }
- }
- else
- {
- buffer.AddUtf16(text.Buffer.Span);
- }
+ buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
buffer.GuessSegmentProperties();
@@ -67,44 +25,38 @@ namespace Avalonia.Direct2D1.Media
var font = ((GlyphTypefaceImpl)glyphTypeface.PlatformImpl).Font;
- buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
-
font.Shape(buffer);
font.GetScale(out var scaleX, out _);
var textScale = fontRenderingEmSize / scaleX;
- var len = buffer.Length;
+ var bufferLength = buffer.Length;
- var info = buffer.GetGlyphInfoSpan();
+ var glyphInfos = buffer.GetGlyphInfoSpan();
- var pos = buffer.GetGlyphPositionSpan();
+ var glyphPositions = buffer.GetGlyphPositionSpan();
- var glyphIndices = new ushort[len];
+ var glyphIndices = new ushort[bufferLength];
- var clusters = new ushort[len];
+ var clusters = new ushort[bufferLength];
- var glyphAdvances = new double[len];
+ double[] glyphAdvances = null;
- var glyphOffsets = new Vector[len];
+ Vector[] glyphOffsets = null;
- for (var i = 0; i < len; i++)
+ for (var i = 0; i < bufferLength; i++)
{
- glyphIndices[i] = (ushort)info[i].Codepoint;
-
- clusters[i] = (ushort)(text.Start + info[i].Cluster);
-
- var advanceX = pos[i].XAdvance * textScale;
- // Depends on direction of layout
- //var advanceY = pos[i].YAdvance * textScale;
+ glyphIndices[i] = (ushort)glyphInfos[i].Codepoint;
- glyphAdvances[i] = advanceX;
+ clusters[i] = (ushort)glyphInfos[i].Cluster;
- var offsetX = pos[i].XOffset * textScale;
- var offsetY = pos[i].YOffset * textScale;
+ if (!glyphTypeface.IsFixedPitch)
+ {
+ SetAdvance(glyphPositions, i, textScale, ref glyphAdvances);
+ }
- glyphOffsets[i] = new Vector(offsetX, offsetY);
+ SetOffset(glyphPositions, i, textScale, ref glyphOffsets);
}
return new GlyphRun(glyphTypeface, fontRenderingEmSize,
@@ -115,5 +67,79 @@ namespace Avalonia.Direct2D1.Media
new ReadOnlySlice(clusters));
}
}
+
+ private static void FillBuffer(Buffer buffer, ReadOnlySlice text)
+ {
+ buffer.ContentType = ContentType.Unicode;
+
+ var i = 0;
+
+ while (i < text.Length)
+ {
+ var codepoint = Codepoint.ReadAt(text, i, out var count);
+
+ var cluster = (uint)(text.Start + i);
+
+ if (codepoint.IsBreakChar)
+ {
+ if (i + 1 < text.Length)
+ {
+ var nextCodepoint = Codepoint.ReadAt(text, i + 1, out _);
+
+ if (nextCodepoint == '\r' && codepoint == '\n' || nextCodepoint == '\n' && codepoint == '\r')
+ {
+ count++;
+
+ buffer.Add('\u200C', cluster);
+
+ buffer.Add('\u200D', cluster);
+ }
+ else
+ {
+ buffer.Add('\u200C', cluster);
+ }
+ }
+ else
+ {
+ buffer.Add('\u200C', cluster);
+ }
+ }
+ else
+ {
+ buffer.Add(codepoint, cluster);
+ }
+
+ i += count;
+ }
+ }
+
+ private static void SetOffset(ReadOnlySpan glyphPositions, int index, double textScale,
+ ref Vector[] offsetBuffer)
+ {
+ var position = glyphPositions[index];
+
+ if (position.XOffset == 0 && position.YOffset == 0)
+ {
+ return;
+ }
+
+ offsetBuffer ??= new Vector[glyphPositions.Length];
+
+ var offsetX = position.XOffset * textScale;
+
+ var offsetY = position.YOffset * textScale;
+
+ offsetBuffer[index] = new Vector(offsetX, offsetY);
+ }
+
+ private static void SetAdvance(ReadOnlySpan glyphPositions, int index, double textScale,
+ ref double[] advanceBuffer)
+ {
+ advanceBuffer ??= new double[glyphPositions.Length];
+
+ // Depends on direction of layout
+ // advanceBuffer[index] = buffer.GlyphPositions[index].YAdvance * textScale;
+ advanceBuffer[index] = glyphPositions[index].XAdvance * textScale;
+ }
}
}
diff --git a/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj
index 0d0dd98a77..0e11ee3b92 100644
--- a/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj
+++ b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj
@@ -2,7 +2,7 @@
net461
true
- true
+ true
true
true
Avalonia.Win32.Interoperability
diff --git a/src/Windows/Avalonia.Win32/Interop/TaskBarList.cs b/src/Windows/Avalonia.Win32/Interop/TaskBarList.cs
index 1b01ebbe7f..88b907aeec 100644
--- a/src/Windows/Avalonia.Win32/Interop/TaskBarList.cs
+++ b/src/Windows/Avalonia.Win32/Interop/TaskBarList.cs
@@ -13,6 +13,7 @@ namespace Avalonia.Win32.Interop
///
/// Ported from https://github.com/chromium/chromium/blob/master/ui/views/win/fullscreen_handler.cc
///
+ /// The window handle.
/// Fullscreen state.
public static unsafe void MarkFullscreen(IntPtr hwnd, bool fullscreen)
{
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
index 01576500e7..370e2a2a4b 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
@@ -343,16 +343,21 @@ namespace Avalonia.Win32
case WindowsMessage.WM_PAINT:
{
- using (_rendererLock.Lock())
+ using (_rendererLock.Lock())
+ {
+ if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero)
{
- Paint?.Invoke(default);
+ var f = RenderScaling;
+ var r = ps.rcPaint;
+ Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f,
+ (r.bottom - r.top) / f));
+ EndPaint(_hwnd, ref ps);
}
-
- ValidateRect(hWnd, IntPtr.Zero);
-
- return IntPtr.Zero;
}
+ return IntPtr.Zero;
+ }
+
case WindowsMessage.WM_SIZE:
{
using (_rendererLock.Lock())
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs
index a3b7574369..68bd40da79 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs
@@ -1,5 +1,4 @@
using System;
-using System.Diagnostics;
using Avalonia.Controls;
using Avalonia.Input;
using static Avalonia.Win32.Interop.UnmanagedMethods;
@@ -11,70 +10,78 @@ namespace Avalonia.Win32
public partial class WindowImpl
{
// Hit test the frame for resizing and moving.
- HitTestValues HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam)
+ private HitTestValues HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam)
{
- // Get the point coordinates for the hit test.
+ // Get the point coordinates for the hit test (screen space).
var ptMouse = PointFromLParam(lParam);
- // Get the window rectangle.
+ // Get the window rectangle.
GetWindowRect(hWnd, out var rcWindow);
// Get the frame rectangle, adjusted for the style without a caption.
- RECT rcFrame = new RECT();
+ var rcFrame = new RECT();
AdjustWindowRectEx(ref rcFrame, (uint)(WindowStyles.WS_OVERLAPPEDWINDOW & ~WindowStyles.WS_CAPTION), false, 0);
- RECT border_thickness = new RECT();
+ var borderThickness = new RECT();
if (GetStyle().HasFlag(WindowStyles.WS_THICKFRAME))
{
- AdjustWindowRectEx(ref border_thickness, (uint)(GetStyle()), false, 0);
- border_thickness.left *= -1;
- border_thickness.top *= -1;
+ AdjustWindowRectEx(ref borderThickness, (uint)(GetStyle()), false, 0);
+ borderThickness.left *= -1;
+ borderThickness.top *= -1;
}
else if (GetStyle().HasFlag(WindowStyles.WS_BORDER))
{
- border_thickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 };
+ borderThickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 };
}
if (_extendTitleBarHint >= 0)
{
- border_thickness.top = (int)(_extendedMargins.Top * RenderScaling);
+ borderThickness.top = (int)(_extendedMargins.Top * RenderScaling);
}
// Determine if the hit test is for resizing. Default middle (1,1).
ushort uRow = 1;
ushort uCol = 1;
- bool fOnResizeBorder = false;
+ bool onResizeBorder = false;
- // Determine if the point is at the top or bottom of the window.
- if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + border_thickness.top)
+ // Determine if the point is at the left or right of the window.
+ if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + borderThickness.left)
{
- fOnResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top));
- uRow = 0;
+ uCol = 0; // left side
}
- else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - border_thickness.bottom)
+ else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - borderThickness.right)
{
- uRow = 2;
+ uCol = 2; // right side
}
- // Determine if the point is at the left or right of the window.
- if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + border_thickness.left)
+ // Determine if the point is at the top or bottom of the window.
+ if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + borderThickness.top)
{
- uCol = 0; // left side
+ onResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top));
+
+ // Two cases where we have a valid row 0 hit test:
+ // - window resize border (top resize border hit)
+ // - area below resize border that is actual titlebar (caption hit).
+ if (onResizeBorder || uCol == 1)
+ {
+ uRow = 0;
+ }
}
- else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - border_thickness.right)
+ else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - borderThickness.bottom)
{
- uCol = 2; // right side
+ uRow = 2;
}
- // Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT)
- HitTestValues[][] hitTests = new[]
+ ReadOnlySpan hitZones = stackalloc HitTestValues[]
{
- new []{ HitTestValues.HTTOPLEFT, fOnResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION, HitTestValues.HTTOPRIGHT },
- new []{ HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT },
- new []{ HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT },
+ HitTestValues.HTTOPLEFT, onResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION,
+ HitTestValues.HTTOPRIGHT, HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT,
+ HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT
};
- return hitTests[uRow][uCol];
+ var zoneIndex = uRow * 3 + uCol;
+
+ return hitZones[zoneIndex];
}
protected virtual IntPtr CustomCaptionProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref bool callDwp)
diff --git a/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs b/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs
index 784f40fe1f..7633a761a3 100644
--- a/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs
+++ b/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs
@@ -113,7 +113,7 @@ namespace Avalonia.Animation.UnitTests
It.IsAny(),
1.0,
0.5));
- target.ResetCalls();
+ target.Invocations.Clear();
control.SetValue(Visual.OpacityProperty, 0.8, BindingPriority.StyleTrigger);
@@ -135,7 +135,7 @@ namespace Avalonia.Animation.UnitTests
target.Setup(x => x.Apply(control, It.IsAny(), 1.0, 0.5)).Returns(sub.Object);
control.Opacity = 0.5;
- sub.ResetCalls();
+ sub.Invocations.Clear();
control.Opacity = 0.4;
sub.Verify(x => x.Dispose());
@@ -158,7 +158,7 @@ namespace Avalonia.Animation.UnitTests
control.Opacity = 0.5;
Assert.Equal(0.9, control.Opacity);
- target.ResetCalls();
+ target.Invocations.Clear();
control.Opacity = 0.4;
@@ -182,7 +182,7 @@ namespace Avalonia.Animation.UnitTests
It.IsAny(),
1.0,
0.5));
- target.ResetCalls();
+ target.Invocations.Clear();
var root = (TestRoot)control.Parent;
root.Child = null;
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Metadata.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Metadata.cs
index 161911dfd5..2edb3deff0 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Metadata.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Metadata.cs
@@ -67,7 +67,7 @@ namespace Avalonia.Base.UnitTests
public static readonly DirectProperty DirectProperty =
AvaloniaProperty.RegisterDirect("Styled", o => o.Direct, unsetValue: "foo");
- private string _direct;
+ private string _direct = default;
public string Direct
{
@@ -92,7 +92,7 @@ namespace Avalonia.Base.UnitTests
public static readonly DirectProperty DirectProperty =
Class1.DirectProperty.AddOwner(o => o.Direct, unsetValue: "baz");
- private string _direct;
+ private string _direct = default;
static Class3()
{
diff --git a/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs b/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs
index 7f118a2c1d..b1b2d3d8f2 100644
--- a/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs
@@ -6,10 +6,12 @@ namespace Avalonia.Base.UnitTests.Collections
{
public class AvaloniaListExtenionsTests
{
+#pragma warning disable CS0618 // Type or member is obsolete
[Fact]
public void CreateDerivedList_Creates_Initial_Items()
{
var source = new AvaloniaList(new[] { 0, 1, 2, 3 });
+
var target = source.CreateDerivedList(x => new Wrapper(x));
var result = target.Select(x => x.Value).ToList();
@@ -137,6 +139,8 @@ namespace Avalonia.Base.UnitTests.Collections
Assert.Equal(source, result);
}
+#pragma warning restore CS0618 // Type or member is obsolete
+
private class Wrapper
{
diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs
index 1e1f4bd4b2..339cf8a334 100644
--- a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs
@@ -356,7 +356,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
}
[Fact]
- public async Task Null_Value_Should_Use_TargetNullValue()
+ public void Null_Value_Should_Use_TargetNullValue()
{
var data = new Class1 { StringValue = "foo" };
diff --git a/tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs b/tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs
index efa81dcc1b..f522acf9ce 100644
--- a/tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs
@@ -1,12 +1,11 @@
+using System;
+using System.ComponentModel;
using System.Globalization;
-using Avalonia.Controls;
-using Avalonia.Data;
-using Xunit;
using System.Windows.Input;
-using System;
+using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Layout;
-using System.ComponentModel;
+using Xunit;
namespace Avalonia.Base.UnitTests.Data.Converters
{
@@ -251,6 +250,11 @@ namespace Avalonia.Base.UnitTests.Data.Converters
{
return obj is CustomType other && this.Value == other.Value;
}
+
+ public override int GetHashCode()
+ {
+ return 8399587^Value.GetHashCode();
+ }
}
private class CustomTypeConverter : TypeConverter
diff --git a/tests/Avalonia.Benchmarks/NullRenderer.cs b/tests/Avalonia.Benchmarks/NullRenderer.cs
index 9a756aaf0b..c1701b5d57 100644
--- a/tests/Avalonia.Benchmarks/NullRenderer.cs
+++ b/tests/Avalonia.Benchmarks/NullRenderer.cs
@@ -9,8 +9,9 @@ namespace Avalonia.Benchmarks
{
public bool DrawFps { get; set; }
public bool DrawDirtyRects { get; set; }
+#pragma warning disable CS0067
public event EventHandler SceneInvalidated;
-
+#pragma warning restore CS0067
public void AddDirty(IVisual visual)
{
}
diff --git a/tests/Avalonia.Benchmarks/NullThreadingPlatform.cs b/tests/Avalonia.Benchmarks/NullThreadingPlatform.cs
index ba84b5afcc..bb469a6b33 100644
--- a/tests/Avalonia.Benchmarks/NullThreadingPlatform.cs
+++ b/tests/Avalonia.Benchmarks/NullThreadingPlatform.cs
@@ -23,6 +23,9 @@ namespace Avalonia.Benchmarks
public bool CurrentThreadIsLoopThread => true;
+#pragma warning disable CS0067
public event Action Signaled;
+#pragma warning restore CS0067
+
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/BorderTests.cs b/tests/Avalonia.Controls.UnitTests/BorderTests.cs
index 6e2599c9fd..ab33eaaff9 100644
--- a/tests/Avalonia.Controls.UnitTests/BorderTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/BorderTests.cs
@@ -54,7 +54,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
var renderer = Mock.Get(root.Renderer);
- renderer.ResetCalls();
+ renderer.Invocations.Clear();
((SolidColorBrush)target.Background).Color = Colors.Green;
diff --git a/tests/Avalonia.Controls.UnitTests/CanvasTests.cs b/tests/Avalonia.Controls.UnitTests/CanvasTests.cs
index da1698330f..11a349f53e 100644
--- a/tests/Avalonia.Controls.UnitTests/CanvasTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/CanvasTests.cs
@@ -1,5 +1,6 @@
using System;
using Avalonia.Controls.Shapes;
+using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests
@@ -9,6 +10,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Left_Property_Should_Work()
{
+ using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
Rectangle rect;
var target = new Canvas
{
@@ -34,6 +37,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Top_Property_Should_Work()
{
+ using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
Rectangle rect;
var target = new Canvas
{
@@ -59,6 +64,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Right_Property_Should_Work()
{
+ using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
Rectangle rect;
var target = new Canvas
{
@@ -84,6 +91,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Bottom_Property_Should_Work()
{
+ using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
Rectangle rect;
var target = new Canvas
{
diff --git a/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs b/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs
index 13c946b549..60139c2881 100644
--- a/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs
@@ -1,5 +1,6 @@
using Avalonia.Controls.Shapes;
using Avalonia.Media;
+using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests
@@ -171,6 +172,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Should_Generate_RotateTransform_90_degrees()
{
+ using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(
100,
25,
@@ -193,6 +196,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Should_Generate_RotateTransform_minus_90_degrees()
{
+ using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(
100,
25,
@@ -215,6 +220,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Should_Generate_ScaleTransform_x2()
{
+ using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(
100,
50,
@@ -236,6 +243,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Should_Generate_SkewTransform_45_degrees()
{
+ using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(
100,
100,
@@ -258,6 +267,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Should_Generate_SkewTransform_minus_45_degrees()
{
+ using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(
100,
100,
@@ -279,6 +290,8 @@ namespace Avalonia.Controls.UnitTests
private static void TransformMeasureSizeTest(Size size, Transform transform, Size expectedSize)
{
+ using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(
size.Width,
size.Height,
@@ -292,6 +305,8 @@ namespace Avalonia.Controls.UnitTests
private static void TransformRootBoundsTest(Size size, Transform transform, Rect expectedBounds)
{
+ using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+
LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(size.Width, size.Height, transform);
Rect outBounds = lt.TransformRoot.Bounds;
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
index 2e2ccf7326..145fce4fed 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
@@ -407,6 +407,53 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(1, raised);
}
+ [Fact]
+ public void Adding_And_Selecting_Item_With_AutoScrollToSelectedItem_Should_NotHide_FirstItem()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var items = new AvaloniaList();
+
+ var wnd = new Window() { Width = 100, Height = 100, IsVisible = true };
+
+ var target = new ListBox()
+ {
+ VerticalAlignment = Layout.VerticalAlignment.Top,
+ AutoScrollToSelectedItem = true,
+ Width = 50,
+ VirtualizationMode = ItemVirtualizationMode.Simple,
+ ItemTemplate = new FuncDataTemplate