diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs
index bfd633c947..b59fd7abde 100644
--- a/src/Avalonia.Controls/AutoCompleteBox.cs
+++ b/src/Avalonia.Controls/AutoCompleteBox.cs
@@ -1405,8 +1405,11 @@ namespace Avalonia.Controls
break;
case Key.Enter:
- OnAdapterSelectionComplete(this, new RoutedEventArgs());
- e.Handled = true;
+ if (IsDropDownOpen)
+ {
+ OnAdapterSelectionComplete(this, new RoutedEventArgs());
+ e.Handled = true;
+ }
break;
default:
diff --git a/src/Avalonia.Controls/Generators/ItemContainerInfo.cs b/src/Avalonia.Controls/Generators/ItemContainerInfo.cs
index 023b108061..31d9a5c02e 100644
--- a/src/Avalonia.Controls/Generators/ItemContainerInfo.cs
+++ b/src/Avalonia.Controls/Generators/ItemContainerInfo.cs
@@ -37,6 +37,6 @@ namespace Avalonia.Controls.Generators
///
/// Gets the index of the item in the collection.
///
- public int Index { get; internal set; }
+ public int Index { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Native/DoubleClickHelper.cs b/src/Avalonia.Native/DoubleClickHelper.cs
new file mode 100644
index 0000000000..7618d6976a
--- /dev/null
+++ b/src/Avalonia.Native/DoubleClickHelper.cs
@@ -0,0 +1,31 @@
+using Avalonia.Platform;
+
+namespace Avalonia.Native
+{
+ internal class DoubleClickHelper
+ {
+ private int _clickCount;
+ private Rect _lastClickRect;
+ private ulong _lastClickTime;
+
+ public bool IsDoubleClick(
+ ulong timestamp,
+ Point p)
+ {
+ var settings = AvaloniaLocator.Current.GetService();
+ var doubleClickTime = settings.DoubleClickTime.TotalMilliseconds;
+
+ if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime)
+ {
+ _clickCount = 0;
+ }
+
+ ++_clickCount;
+ _lastClickTime = timestamp;
+ _lastClickRect = new Rect(p, new Size())
+ .Inflate(new Thickness(settings.DoubleClickSize.Width / 2, settings.DoubleClickSize.Height / 2));
+
+ return _clickCount == 2;
+ }
+ }
+}
diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs
index f60e83efe3..f3b60f07be 100644
--- a/src/Avalonia.Native/WindowImpl.cs
+++ b/src/Avalonia.Native/WindowImpl.cs
@@ -1,4 +1,5 @@
using System;
+using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
@@ -17,6 +18,8 @@ namespace Avalonia.Native
private readonly AvaloniaNativePlatformOpenGlInterface _glFeature;
IAvnWindow _native;
private double _extendTitleBarHeight = -1;
+ private DoubleClickHelper _doubleClickHelper;
+
internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
AvaloniaNativePlatformOpenGlInterface glFeature) : base(opts, glFeature)
@@ -24,6 +27,8 @@ namespace Avalonia.Native
_factory = factory;
_opts = opts;
_glFeature = glFeature;
+ _doubleClickHelper = new DoubleClickHelper();
+
using (var e = new WindowEvents(this))
{
var context = _opts.UseGpu ? glFeature?.MainContext : null;
@@ -118,7 +123,22 @@ namespace Avalonia.Native
if(visual == null)
{
- _native.BeginMoveDrag();
+ if (_doubleClickHelper.IsDoubleClick(e.Timestamp, e.Position))
+ {
+ // TOGGLE WINDOW STATE.
+ if (WindowState == WindowState.Maximized || WindowState == WindowState.FullScreen)
+ {
+ WindowState = WindowState.Normal;
+ }
+ else
+ {
+ WindowState = WindowState.Maximized;
+ }
+ }
+ else
+ {
+ _native.BeginMoveDrag();
+ }
}
}
}
diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs
index 2ccfd43f03..8136f843df 100644
--- a/src/Avalonia.Visuals/Matrix.cs
+++ b/src/Avalonia.Visuals/Matrix.cs
@@ -282,25 +282,44 @@ namespace Avalonia
}
///
- /// Inverts the Matrix.
+ /// Attempts to invert the Matrix.
///
- /// The inverted matrix.
- public Matrix Invert()
+ /// The inverted matrix or when matrix is not invertible.
+ public bool TryInvert(out Matrix inverted)
{
double d = GetDeterminant();
if (MathUtilities.IsZero(d))
{
- throw new InvalidOperationException("Transform is not invertible.");
+ inverted = default;
+
+ return false;
}
- return new Matrix(
+ inverted = new Matrix(
_m22 / d,
-_m12 / d,
-_m21 / d,
_m11 / d,
((_m21 * _m32) - (_m22 * _m31)) / d,
((_m12 * _m31) - (_m11 * _m32)) / d);
+
+ return true;
+ }
+
+ ///
+ /// Inverts the Matrix.
+ ///
+ /// Matrix is not invertible.
+ /// The inverted matrix.
+ public Matrix Invert()
+ {
+ if (!TryInvert(out Matrix inverted))
+ {
+ throw new InvalidOperationException("Transform is not invertible.");
+ }
+
+ return inverted;
}
///
diff --git a/src/Avalonia.Visuals/VisualExtensions.cs b/src/Avalonia.Visuals/VisualExtensions.cs
index 6079e5941f..e6523a1469 100644
--- a/src/Avalonia.Visuals/VisualExtensions.cs
+++ b/src/Avalonia.Visuals/VisualExtensions.cs
@@ -50,7 +50,13 @@ namespace Avalonia
{
var thisOffset = GetOffsetFrom(common, from);
var thatOffset = GetOffsetFrom(common, to);
- return -thatOffset * thisOffset;
+
+ if (!thatOffset.TryInvert(out var thatOffsetInverted))
+ {
+ return null;
+ }
+
+ return thatOffsetInverted * thisOffset;
}
return null;
diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
index 72700fb8fd..6d0be9f64d 100644
--- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
+++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
@@ -3,6 +3,8 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading;
+
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Media;
using Avalonia.OpenGL;
@@ -166,12 +168,13 @@ namespace Avalonia.Skia
LinearMetrics = true
};
- private static readonly SKTextBlobBuilder s_textBlobBuilder = new SKTextBlobBuilder();
+ private static readonly ThreadLocal s_textBlobBuilderThreadLocal = new ThreadLocal(() => new SKTextBlobBuilder());
///
public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun, out double width)
{
var count = glyphRun.GlyphIndices.Length;
+ var textBlobBuilder = s_textBlobBuilderThreadLocal.Value;
var glyphTypeface = (GlyphTypefaceImpl)glyphRun.GlyphTypeface.PlatformImpl;
@@ -191,15 +194,15 @@ namespace Avalonia.Skia
{
if (glyphTypeface.IsFixedPitch)
{
- s_textBlobBuilder.AddRun(glyphRun.GlyphIndices.Buffer.Span, s_font);
+ textBlobBuilder.AddRun(glyphRun.GlyphIndices.Buffer.Span, s_font);
- textBlob = s_textBlobBuilder.Build();
+ textBlob = textBlobBuilder.Build();
width = glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[0]) * scale * glyphRun.GlyphIndices.Length;
}
else
{
- var buffer = s_textBlobBuilder.AllocateHorizontalRun(s_font, count, 0);
+ var buffer = textBlobBuilder.AllocateHorizontalRun(s_font, count, 0);
var positions = buffer.GetPositionSpan();
@@ -219,12 +222,12 @@ namespace Avalonia.Skia
buffer.SetGlyphs(glyphRun.GlyphIndices.Buffer.Span);
- textBlob = s_textBlobBuilder.Build();
+ textBlob = textBlobBuilder.Build();
}
}
else
{
- var buffer = s_textBlobBuilder.AllocatePositionedRun(s_font, count);
+ var buffer = textBlobBuilder.AllocatePositionedRun(s_font, count);
var glyphPositions = buffer.GetPositionSpan();
@@ -250,7 +253,7 @@ namespace Avalonia.Skia
width = currentX;
- textBlob = s_textBlobBuilder.Build();
+ textBlob = textBlobBuilder.Build();
}
return new GlyphRunImpl(textBlob);
diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTests.cs b/tests/Avalonia.Visuals.UnitTests/VisualTests.cs
index 447a68aa69..38131fbfca 100644
--- a/tests/Avalonia.Visuals.UnitTests/VisualTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/VisualTests.cs
@@ -235,6 +235,25 @@ namespace Avalonia.Visuals.UnitTests
Assert.Equal(new Point(100, 100), point);
}
+ [Fact]
+ public void TransformToVisual_With_NonInvertible_RenderTransform_Should_Work()
+ {
+ var child = new Decorator
+ {
+ Width = 100,
+ Height = 100,
+ RenderTransform = new ScaleTransform() { ScaleX = 0, ScaleY = 0 }
+ };
+ var root = new TestRoot() { Child = child, Width = 400, Height = 400 };
+
+ root.Measure(Size.Infinity);
+ root.Arrange(new Rect(new Point(), root.DesiredSize));
+
+ var tr = root.TransformToVisual(child);
+
+ Assert.Null(tr);
+ }
+
[Fact]
public void Should_Not_Log_Binding_Error_When_Not_Attached_To_Logical_Tree()
{