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() {